Add Tuya Cloud integration; 2-step add-board wizard; DP value formatting

- Tuya Cloud Gateway driver (tuya-device-sharing-sdk):
  - QR-code auth flow: user-provided user_code, terminal_id/endpoint
    returned by Tuya login_result (mirrors HA implementation)
  - Device sync, toggle DP, rename, per-device detail page
  - category → kind mapping; detect_switch_dps helper
  - format_dp_value: temperature (÷10 + °C/°F), humidity (+ %)
    registered as 'tuya_dp' Jinja2 filter

- TuyaDevice model (tuya_devices table)

- Templates:
  - tuya/gateway.html: device grid with live-reading sensor cards
    (config/threshold keys hidden from card, shown on detail page)
  - tuya/device.html: full status table with formatted DP values
  - tuya/auth_settings.html: user_code input + QR scan flow

- Add-board wizard refactored to 2-step flow:
  - Step 1: choose board type (Cloud Gateways vs Hardware)
  - Step 2: type-specific fields; gateways skip IP/relay fields

- Layout builder: Tuya chip support (makeTuyaChip, tuya_update socket)

- requirements.txt: tuya-device-sharing-sdk, cryptography
This commit is contained in:
ske087
2026-02-27 16:06:48 +02:00
parent 90cbf4e1f0
commit 1e89323035
19 changed files with 1873 additions and 84 deletions

View File

@@ -9,6 +9,7 @@ from app import db
from app.models.layout import Layout
from app.models.board import Board
from app.models.sonoff_device import SonoffDevice
from app.models.tuya_device import TuyaDevice
layouts_bp = Blueprint("layouts", __name__)
@@ -53,6 +54,7 @@ def builder(layout_id: int):
for b in boards:
bd = {"id": b.id, "name": b.name, "online": b.is_online,
"relays": [], "inputs": [], "sonoff_channels": [],
"tuya_channels": [],
"board_type": b.board_type}
# ── Standard relay/input boards ──────────────────────────────────────
@@ -104,6 +106,33 @@ def builder(layout_id: int):
"isOnline": dev.is_online,
})
# ── Tuya Cloud sub-devices ────────────────────────────────────────────
if b.board_type == "tuya_cloud":
TUYA_KIND_ICON = {
"switch": "bi-toggles",
"light": "bi-lightbulb-fill",
"fan": "bi-fan",
"sensor": "bi-thermometer-half",
"cover": "bi-door-open",
}
tuya_devs = TuyaDevice.query.filter_by(board_id=b.id).order_by(
TuyaDevice.name).all()
for dev in tuya_devs:
icon = TUYA_KIND_ICON.get(dev.kind, "bi-plug")
for dp in dev.switch_dps:
idx = dev.switch_dps.index(dp)
label = (dev.name if dev.num_channels == 1
else f"{dev.name} Ch{idx + 1}")
bd["tuya_channels"].append({
"deviceId": dev.device_id,
"channel": dp, # dp_code string, e.g. "switch_1"
"name": label,
"icon": icon,
"kind": dev.kind,
"isOn": dev.status.get(dp, False),
"isOnline": dev.is_online,
})
boards_data.append(bd)
return render_template(