-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtuya_setup.py
More file actions
executable file
·157 lines (132 loc) · 5.16 KB
/
Copy pathtuya_setup.py
File metadata and controls
executable file
·157 lines (132 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python3
"""One-time interactive setup for Tuya cloud control.
Walks you through:
1. Picking your Tuya account region (US/EU/CN/IN)
2. Entering your Cloud Project's access_id and access_secret (from iot.tuya.com)
3. Listing every device the project can see
4. Mapping each cloud device to a LAN MAC from the current ARP table
5. Inferring each device's on/off command code from its spec
Saves tuya_config.json + tuya_devices.json. After this completes, the Identify
button on Tuya-detected rows in the web UI will work.
Prerequisites you have to do in the Tuya IoT Cloud (iot.tuya.com):
• Create a free account
• Create a Cloud Project (type: "Smart Home" or "Custom")
• Subscribe to the "IoT Core" service (free tier is fine)
• Devices tab → "Link Tuya App Account" → scan QR with your Smart Life app
• Note the project's Access ID and Access Secret
"""
from __future__ import annotations
import json
import sys
import tuya
def prompt(label: str, default: str = "") -> str:
suffix = f" [{default}]" if default else ""
val = input(f"{label}{suffix}: ").strip()
return val or default
def pick_region() -> str:
print("\nWhich Tuya region is your Smart-Life account in?")
options = list(tuya.ENDPOINTS.items())
for i, (key, host) in enumerate(options, 1):
print(f" {i}. {key:8s} ({host})")
while True:
choice = prompt("Choose 1-{}".format(len(options)), "1")
try:
idx = int(choice) - 1
if 0 <= idx < len(options):
return options[idx][1]
except ValueError:
pass
print(" not a valid choice, try again")
def normalize_cloud_mac(raw: str) -> str:
"""Cloud MACs are usually 12 hex chars without separators."""
return tuya.normalize_mac(raw or "")
def main() -> int:
print("Tuya cloud setup\n" + "=" * 40)
cfg = tuya.load_config()
endpoint = cfg.get("endpoint") or pick_region()
access_id = prompt("Access ID (Client ID)", cfg.get("access_id", ""))
access_secret = prompt(
"Access Secret (Client Secret)", cfg.get("access_secret", ""),
)
if not access_id or not access_secret:
print("Both Access ID and Access Secret are required.")
return 1
client = tuya.TuyaCloud(access_id, access_secret, endpoint)
print("\nFetching access token… ", end="", flush=True)
try:
token = client.get_token()
except tuya.TuyaError as e:
print("FAIL")
print(f" {e}")
print(" Common causes: wrong region, IoT Core not subscribed, bad credentials.")
return 2
print(f"OK ({token[:8]}…)")
print("Listing devices… ", end="", flush=True)
try:
devices = client.list_user_devices()
except tuya.TuyaError as e:
print("FAIL")
print(f" {e}")
return 3
print(f"{len(devices)} device(s) found")
if not devices:
print(
"\nNo devices visible from this Cloud Project. Did you link your Smart-Life\n"
"account? In iot.tuya.com → your Project → Devices → Link Tuya App Account.",
)
return 4
# Save config now so we don't lose it on later failure.
tuya.save_config({
"endpoint": endpoint,
"access_id": access_id,
"access_secret": access_secret,
})
print(f"Saved {tuya.CONFIG_FILE.name}")
# Try to read current ARP table from the scanner's helper if available.
arp_macs: set[str] = set()
try:
import server # noqa
arp_macs = set(server.arp_table().keys())
except Exception:
# Not fatal — we just won't be able to confirm which devices are currently online.
pass
print("\nFetching device specs (one round-trip per device)…")
by_mac: dict[str, dict] = {}
for d in devices:
device_id = d.get("id") or d.get("device_id")
name = d.get("name") or d.get("custom_name") or "(no name)"
category = d.get("category", "")
raw_mac = d.get("mac") or ""
mac = normalize_cloud_mac(raw_mac)
try:
specs = client.device_specs(device_id)
except tuya.TuyaError as e:
print(f" ! {name:30s} spec fetch failed: {e}")
specs = {}
on_code = tuya.infer_on_code(specs, category)
marker = "✓" if mac and mac in arp_macs else " "
if not mac:
marker = "?"
print(
f" {marker} {name:30s} cat={category:5s} on={on_code or '(none)':12s}"
f" mac={mac or '(unknown)'}",
)
if mac and on_code:
by_mac[mac] = {
"device_id": device_id,
"name": name,
"category": category,
"on_code": on_code,
}
tuya.save_devices(by_mac)
print(f"\nSaved {tuya.DEVICES_FILE.name} ({len(by_mac)} controllable devices mapped)")
if arp_macs:
on_lan = sum(1 for m in by_mac if m in arp_macs)
print(f" {on_lan} of those are on the LAN right now")
print(
"\nDone. Refresh the network scanner UI; Tuya rows that have a MAC mapping should\n"
"now show an 'Identify' button.",
)
return 0
if __name__ == "__main__":
sys.exit(main())