TP17.1 verworfen + s9 zurueckgebaut, eco_mode flottenweit aus
Christophs strukturelle Frage hat TP17.1 gekippt: "zigbee sollte die antwort auf die vielen radios auf -iot geben. nutzt aber wenig wenn man das wifi nicht abdrehen kann bzw. es nicht mehr aktivieren kann ohne reset." Der Pilot hat den Weg technisch vollstaendig bewiesen -- und gezeigt, dass das Ziel so nicht erreichbar ist. Das Geraet bleibt WLAN-Client; der gemessene Funkgewinn ist klein (12 von 26 IoT-Clients, je 150-200 KB rx / 0,6-3 MB tx in sechs Stunden); und WLAN abzuschalten waere unumkehrbar, weil die Zigbee-Firmware keinen AP hat, Shelly kein Zigbee-OTA macht und InstallAlt eine IP braucht. Der Weg ist nicht gescheitert, das Ziel war mit ihm nicht erreichbar. Das Runbook bleibt als Wissensspeicher. Rueckbau: ZHA-Knoten entfernt -- Leave VOR dem Flash, damit kein Geisterrouter im 52-Geraete-Mesh bleibt. s9 zurueck auf PowerStrip, Config aus dem Backup. Alle vier roten EZSP-Zaehler blieben 0, Retry-Quote fiel im Delta auf 8,9 %. Neue Merkregel: der Rueckflash zog einen OFFENEN AP hoch (ap.enable=true, is_open=true). Abgeschaltet und mit dem IoT-PSK abgesichert. Nach jedem Shelly-Firmwarewechsel den AP pruefen. eco_mode flottenweit aus (User: "der rotz macht nur probleme"): 13 Geraete umgestellt, null Fehler, 9 standen schon so. Jetzt eco=false auf allen 22 erreichbaren Shellys ausser s24, das es nicht unterstuetzt. Dafuer migrate.py um ein eco-Kommando ergaenzt -- mit Retry je Geraet, weil ein Timeout an einem schwachen Link den Sweep sonst abreisst. Die Gegenmessung widerlegt meine eigene Warnung: eco aus hebt die Sendeleistung NICHT messbar an. 13 geaenderte Geraete im Mittel -0,9 dB, Kontrollgruppe von 13 unveraenderten +0,0 dB bei gleicher Streuung. Die TP17-Aussage, eco senke Takt UND Sendeleistung, stimmt nur zur Haelfte. Vorbedingung der Passwort-Rotation geprueft: alle vier Skripte laufen laengst auf sshpass -e, die gegenteilige Notiz war veraltet. Uebrig war CLAUDE.md, das sshpass -p weiterhin aktiv als Reboot-Weg empfahl -- genau das Muster, das den Leak verursacht hat. Auf sshpass -e korrigiert. TP17.2 aus "zurueckgestellt" aktiviert und an die PSK-Rotation gekoppelt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ Commands:
|
||||
python3 migrate.py migrate <ip...> [--go] anchor+sta+reboot+verify (dry-run w/o --go)
|
||||
python3 migrate.py deanchor <ip...> [--go] turn the AP anchor back off (.30.x IPs)
|
||||
python3 migrate.py auth <ip...> [--go] set uniform admin password (last step)
|
||||
python3 migrate.py eco <ip...> [--go] turn device.eco_mode OFF (--on to re-enable)
|
||||
python3 migrate.py table final tabular overview (old/new/model/fw)
|
||||
|
||||
<ip...> accepts single IPs, ranges (10.40.41.205-233) or 'all' (the default sweep).
|
||||
@@ -214,6 +215,62 @@ def cmd_auth(specs, go):
|
||||
print(f"{ip}: auth set (realm {realm})")
|
||||
|
||||
|
||||
def cmd_eco(specs, go, want=False):
|
||||
"""Set device.eco_mode across the fleet (TP17.3, 2026-08-23).
|
||||
|
||||
eco_mode throttles the CPU clock and TX power. It was applied in TP17 by an
|
||||
RSSI rule and turned out to cause real misbehaviour: the s23 phase-cut dimmer
|
||||
flickered, and s26's `momentary` input swallowed the first edge. Both were
|
||||
fixed by switching it off, so the fleet decision is: OFF everywhere.
|
||||
|
||||
No reboot needed — the device answers `restart_required: false` to this write.
|
||||
Devices that don't support it report eco_mode=None and are skipped (s24).
|
||||
"""
|
||||
# Weak links (s20 sits at -85 dBm) time out intermittently. A single timeout
|
||||
# must not abort the sweep, so every call is retried and failures are
|
||||
# collected and reported at the end instead of raising.
|
||||
def _try(ip, method, params=None, tries=3):
|
||||
for n in range(tries):
|
||||
try:
|
||||
return _rpc(ip, method, params, timeout=10.0)
|
||||
except Exception as e:
|
||||
last = e
|
||||
raise last
|
||||
|
||||
failed = []
|
||||
for ip in _resolve(specs):
|
||||
ident = discover.probe(ip)
|
||||
if not ident:
|
||||
continue
|
||||
name = _name_for(ident["mac"]) or ident.get("name_dev") or "?"
|
||||
try:
|
||||
cur = _try(ip, "Sys.GetConfig").get("result", {}).get("device", {}).get("eco_mode")
|
||||
if cur is None:
|
||||
print(f" {ip:15} {name:26} eco not supported — skipped")
|
||||
continue
|
||||
if cur == want:
|
||||
print(f" {ip:15} {name:26} already {want}")
|
||||
continue
|
||||
if not go:
|
||||
print(f" dry: {ip:11} {name:26} eco {cur} -> {want}")
|
||||
continue
|
||||
res = _try(ip, "Sys.SetConfig", {"config": {"device": {"eco_mode": want}}})
|
||||
new = _try(ip, "Sys.GetConfig").get("result", {}).get("device", {}).get("eco_mode")
|
||||
rr = (res.get("result") or {}).get("restart_required")
|
||||
ok = "OK " if new == want else "MISMATCH"
|
||||
print(f" {ip:15} {name:26} eco {cur} -> {new} {ok}"
|
||||
+ (" (restart_required was already set before this write)" if rr else ""))
|
||||
if new != want:
|
||||
failed.append((ip, name, f"read back {new}"))
|
||||
except Exception as e:
|
||||
print(f" {ip:15} {name:26} FAILED: {type(e).__name__}")
|
||||
failed.append((ip, name, type(e).__name__))
|
||||
if failed:
|
||||
print(f"\n{len(failed)} device(s) need a retry:")
|
||||
for ip, name, why in failed:
|
||||
print(f" {ip:15} {name:26} {why}")
|
||||
|
||||
|
||||
def cmd_table():
|
||||
inv = discover.load_inventory()
|
||||
ips = discover.expand("10.40.30.180-250")
|
||||
@@ -252,6 +309,8 @@ def main():
|
||||
cmd_deanchor(specs or ["all"], go)
|
||||
elif cmd == "auth":
|
||||
cmd_auth(specs, go)
|
||||
elif cmd == "eco":
|
||||
cmd_eco(specs, go, want=("--on" in rest))
|
||||
elif cmd == "table":
|
||||
cmd_table()
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user