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:
2026-08-23 21:12:46 +02:00
parent 0a7f2e2c08
commit 2f183190ed
4 changed files with 163 additions and 17 deletions

View File

@@ -55,7 +55,10 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
## Tooling
- **MikroTik (core/branch/ap1/ap2):** MikroMCP-Server live → `mcp__mikromcp__*`-Tools (routerId `crs326-core`/`crs326-branch`/`ap1`/`ap2`).
Reads immer safe; Writes mit `dryRun:true` previewen, dann `apply_plan`. **Passwort setzen via `manage_user set-password`** (`/user set*` ist per Deny-Policy blockiert). ⚠️ **Reboot geht auf ROS 7.24 gar nicht ueber MikroMCP:** das `reboot`-Tool sendet `delay`, die REST-API kennt den Parameter nicht (`unknown parameter delay`), und `run_command` ist fuer `/system reboot*` gesperrt — Weg ist `sshpass -p "$PW" ssh admin@<ip> "/system reboot"` (PW aus `~/.secrets`, im Output maskieren). `bridge/vlan/set` nur auf `[find … and !dynamic]`.
Reads immer safe; Writes mit `dryRun:true` previewen, dann `apply_plan`. **Passwort setzen via `manage_user set-password`** (`/user set*` ist per Deny-Policy blockiert). ⚠️ **Reboot geht auf ROS 7.24 gar nicht ueber MikroMCP:** das `reboot`-Tool sendet `delay`, die REST-API kennt den Parameter nicht (`unknown parameter delay`), und `run_command` ist fuer `/system reboot*` gesperrt — Weg ist `SSHPASS="$PW" sshpass -e ssh admin@<ip> "/system reboot"` (PW aus `~/.secrets`, im Output
maskieren). 🔴 **Niemals `sshpass -p`** — das schreibt das Passwort in die Kommandozeile, wo es in
`ps` und jedem argv-Trace steht; **genau so ist am 2026-08-22 das Core-Passwort in ein
Sitzungstranskript geraten** und musste rotiert werden. `-e` hält es in der Umgebung des Kindprozesses. `bridge/vlan/set` nur auf `[find … and !dynamic]`.
- **~~ALLNET-Switch (sw1)~~:** ❌ **Gerät ausgebaut 2026-08-08** → der Telnet-Runner unter `switch/` ist **Archiv, nicht mehr ausführbar** (kein Endpunkt). Siehe `switch/README.md`. Nicht mehr als Werkzeug anbieten.
- **~~Omada-Switch (sw2)~~:** ❌ abgebaut 2026-07-23 (TP15, durch `crs326-branch` ersetzt). Controller + omada-mcp komplett gelöscht — **Omada spielt keine Rolle mehr, nie wieder aufgreifen** (User-Entscheid 2026-07-22). Aufräum-Reste ✅ geprüft 2026-08-16 (s. Tabelle oben).
- **Nautobot Golden Config (= der einzige legitime Config-Weg für Netzgeräte):** `nautobot/gc-configplan.py`

View File

@@ -87,9 +87,10 @@ ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)*
> 📋 **Offen und ohne Blockadewirkung:** **TP25** (Fund F = 5:05-Takt bei IoT, plus die Entscheidung
> „5 GHz auf 20 MHz") · **TP16** (CF-Tunnel-HA degradiert, vier tote Alt-Tunnel) · **TP16.2/16.3** ·
> **TP23 Stufe 2** · **TP24** (DC-USV) · **TP13/TP14.4** (beide mit fertigem Runbook) ·
> **TP17.1** (Shelly Gen4 raus aus dem WLAN, entscheidungsreif) · ✅ **TP17.3 ERLEDIGT 2026-08-23**
> (Shelly-Regression: Ursache **`eco_mode`** bewiesen, `s23` + `s26` repariert; der Flotten-Entscheid
> „eco flottenweit aus" hängt jetzt an TP17.1) ·
> ❌ **TP17.1 VERWORFEN 2026-08-23** (Zigbee-Weg bewiesen, Ziel damit nicht erreichbar — das Gerät
> bleibt WLAN-Client; `s9` zurückgebaut) · ✅ **TP17.3 ERLEDIGT** (Ursache **`eco_mode`** bewiesen;
> `s23` + `s26` repariert; **`eco_mode` flottenweit aus**, 22/22 außer `s24`) ·
> 🔜 **TP17.2 aktiviert** (Shelly-Gerätepasswort, an die PSK-Rotation gekoppelt) ·
> 🔑 **Rotation des MikroTik-Admin-Passworts** — seit 08-23 bewusst **hinter** TP17.1 eingereiht.
> Zugeschnittene Sitzungsvorschläge: [`handoff-20260822-naechste-schritte.md`](handoff-20260822-naechste-schritte.md).
> ✅ **`crs326-core` ist transaktionsfrei** (seit 2026-08-22, geprüft): ein gemeldeter Drift ist ab
@@ -372,7 +373,36 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
liefert das Entscheidungskriterium). ⚠️ **Berührt TP5:** der Mesh-Verkehr braucht bei deny-by-default eine
explizite `forward`-Zeile — s. TP5 Etappe 2.
### TP17.1 — Shelly Gen4 raus aus dem WLAN (Thread oder Zigbee) ▸ 🔜 **ENTSCHEIDUNGSREIF (2026-08-23)** — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md`
### TP17.1 — Shelly Gen4 raus aus dem WLAN ▸ ❌ **VERWORFEN (2026-08-23) — Weg bewiesen, Ziel damit nicht erreichbar** — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md`
> ❌ **User-Entscheid 2026-08-23 nach dem Pilot: stoppen und zurückbauen.** Auslöser war Christophs
> strukturelle Frage: *„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 vollständig bewiesen** — `s9` lief auf `PowerStripZB`, trat ZHA
> bei, ein Zigbee-Befehl schaltete das physische Relais, kein Broadcast-Sturm. **Und er hat gezeigt,
> dass das Ziel so nicht erreichbar ist:**
> - Das Gerät **bleibt WLAN-Client** — Zigbee kommt *zusätzlich*, nicht *statt*.
> - Der Funkgewinn ist **gemessen klein**: die 12 Gen4 sind 12 von 26 IoT-Clients, bewegen aber je nur
> ~150–200 KB rx / 0,6–3 MB tx in sechs Stunden. Leerlaufende Stationen.
> - Der Preis wäre **dauerhaft**: die Zigbee-Firmware hat **keinen AP** (`WiFi.GetConfig` kennt nur
> `sta`/`sta1`/`roam`), Shelly macht **kein Zigbee-OTA**, und `Shelly.InstallAlt` braucht IP.
> WLAN aus hieße: nie wieder Firmware-Update, kein Rückweg — bei 10 Unterputz-Modulen.
>
> ✅ **Rückbau durchgeführt 2026-08-23:** ZHA-Knoten sauber entfernt (Leave **vor** dem Flash, damit
> kein Geisterrouter im 52-Geräte-Mesh bleibt), `s9` zurück auf `PowerStrip`, Config aus dem Backup
> wiederhergestellt. Compliance des Zigbee-Netzes unauffällig: alle vier roten EZSP-Zähler blieben
> **0**, Retry-Quote fiel im Delta auf 8,9 %.
> ⚠️ **Beim Rückflash zog die Firmware einen OFFENEN AP hoch** (`ap.enable=true, is_open=true`) —
> abgeschaltet und mit dem IoT-PSK abgesichert. **Nach jedem Shelly-Firmwarewechsel den AP prüfen.**
>
> 📌 **Das Runbook bleibt** — es enthält den kartierten Mechanismus (drei undokumentierte RPCs,
> Zwei-Slot-Modell), das gemessene 2,4-GHz-Bild und die ZHA-Sturmzähler mit Baseline.
> *Der Weg ist nicht gescheitert, das Ziel war mit ihm nicht erreichbar.*
> 🔭 **Einziger Restposten:** der **Frühindikator** — `Shelly.CheckForUpdate` auf einem Gen4 auf einen
> `…MT`/Thread-`alt`-Eintrag beobachten. Erscheint er, ändert sich die Rechnung.
<details><summary>Ursprünglicher Planungstext (historisch)</summary>
> **User-Formulierung (2026-08-23):** „wir lassen das pwd vorerst wie es ist. wir werden alle shelly
> gen4 auf thread umstellen — neue firmware. das machen wir im ha-projekt. das sehe ich als subpunkt
> zu pwd ändern. der vorteil: wir müssen dann weit weniger geräte umstellen, steigern
@@ -493,10 +523,30 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
(IPv6/Matter — profitiert direkt), **TP25** (jedes Gerät weniger im 2,4-GHz-Band ist Funkruhe),
**TP17** im [Archiv](teilprojekte-archiv.md#tp17) (Migrationshistorie + `mcp/shelly/`-Werkzeug).
### TP17.2 — einheitliches Geräte-Passwort (Shelly `SetAuth`) ▸ ⏸️ ZURÜCKGESTELLT (Kosmetik, User 2026-07-13)
</details>
### TP17.2 — einheitliches Geräte-Passwort (Shelly `SetAuth`) ▸ 🔜 **AKTIVIERT (2026-08-23)** — an die PSK-Rotation gekoppelt
> **User 2026-08-23:** „außerdem haben wir einen punkt, um die shellys mit einem pwd zu schützen."
> Damit ist der Zurückgestellt-Status von 2026-07-13 aufgehoben. **Begründung der Kopplung:** die
> PSK-Rotation fasst ohnehin jeden Shelly an und rebootet ihn — `SetAuth` gehört in denselben
> Durchgang, ein Reboot statt zwei.
>
> ✅ **Werkzeugseitig ist nichts zu bauen:** `mcp/shelly/migrate.py` hat `cmd_auth` (setzt
> `Shelly.SetAuth` mit SHA-256-`ha1` gegen die Geräte-`realm`, Dry-Run als Default), und `_rpc` hat
> einen **Digest-Auth-Fallback** auf 401 mit `SHELLY_DEVICE_PW`.
>
> 🔴 **Der teure Teil ist nicht `SetAuth`, sondern Home Assistant.** Mit aktiver Auth verlangt die
> Shelly-Integration je Gerät einen **Re-Auth-Flow** — bei 23 Geräten ist *das* der Aufwand, und genau
> der Grund für die frühere Einstufung als „Kosmetik". ⇒ **Erst an EINEM Gerät machen und den
> HA-Aufwand messen** (Vorschlag `s15 technik_pve`, RSSI −36, unkritisch), bevor der Rest folgt.
> Sonst bündelt man ein ungemessenes Risiko in eine 23-Geräte-Aktion.
<details><summary>Ursprünglicher Text (historisch)</summary>
- **Was:** je Gerät `Shelly.SetAuth` (User `admin`, realm-individueller `ha1` = sha256("admin:<device-id>:<pw>"), einheitliches Klartext-PW) via `mcp/shelly/migrate.py auth --go`. `SHELLY_DEVICE_PW` liegt in `mcp/shelly/.env` (gesetzt, len 13). Runner + Digest-Auth-Fallback in `_rpc` fertig.
- **Warum zurückgestellt:** rein kosmetisch/Härtung; **bricht die HA-Shelly-Integration**, bis pro Config-Entry (22×) das Credential neu eingetragen ist → disruptiv, kein Mehrwert im abgeschotteten IOT-VLAN. **Erst nach** dem Netz-Teardown (TP19) neu bewerten.
</details>
### TP17.3 — Shelly-Regression nach der VLAN30-Migration ▸ ✅ **ERLEDIGT (2026-08-23, Folge-Session): beide Geräte repariert, Ursache `eco_mode` bewiesen** — offen nur noch der Flotten-Entscheid (gehört TP17.1) — aus TP17 herausgelöst, User-Meldung
> **User-Meldung 2026-08-23:** „vor einiger zeit hast du an den shellys änderungen vorgenommen. das
> hatte bei einigen einen bösen seiteneffekt: **lüfter-shelly musste ich doppelt drücken**. der übelste
@@ -568,13 +618,23 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
darunter s20 (−85) und s21 (−85) — die RSSI-Regel ist an keiner Kante mehr erkennbar. Die Werte haben
sich seit Juli durch TP25/TP28 ohnehin verschoben. Die in TP17 vorgemerkte „2. Evaluation nach TP3.3"
hat nie stattgefunden.
⇒ **Zu entscheiden ist nicht mehr eine Tabelle, sondern ein Grundsatz: `eco_mode` flottenweit AUS.**
Argumente dafür: nachgewiesener Schaden; die Regel wurde nie befolgt; `eco_mode` ist ohne Reboot
schaltbar und sofort reversibel; 5 Geräte laufen längst ohne und sind unauffällig.
⚠️ **Was dagegen abzuwägen ist:** eco aus hebt die **WLAN-Sendeleistung** auf ~20 Geräten an — das ist
eine Airtime-/Funkbild-Änderung und berührt **TP25** (2,4 GHz ist die knappe Ressource, 26 von 27
Clients hängen auf `dirtyvader-iot`). **Nicht nebenbei ausrollen, sondern gebündelt mit TP17.1**, wenn
ohnehin alle Shellys angefasst werden — dann ist auch messbar, was es am Funkbild ändert.
⇒ ✅ **ENTSCHIEDEN UND AUSGEFÜHRT 2026-08-23. User: „eco-mode für alle deaktivieren. der rotz macht
nur probleme."** 13 Geräte umgestellt (s5, s6, s8, s10, s11, s13, s14, s15, s16, s20, s21, s22, s25),
**null Fehler**; 9 standen bereits auf `false`. **Stand jetzt: `eco_mode=false` auf allen 22
erreichbaren Shellys**, einzige Ausnahme `s24` (SNDC-0D4P10WW meldet `eco_mode: None` — nicht
unterstützt). Offen nur s1/s2/s3 (`weihnachten_*`, saisonal stromlos).
🔧 **Werkzeug:** `mcp/shelly/migrate.py eco <range> [--go|--on]` — neu, über denselben `_rpc`-Pfad,
mit Retry je Gerät (ein Timeout an einem schwachen Link darf den Sweep nicht abreißen) und einer
Fehlerliste am Ende.
🔬 **Und die Gegenmessung widerlegt meine eigene Warnung.** Ich hatte hier notiert, eco aus hebe die
**Sendeleistung** an und sei deshalb eine Airtime-Änderung, die TP25 berühre. **Gemessen stimmt das
nicht:** Signal der 13 geänderten Geräte in der Registration-Table des Managers, vorher/nachher —
**Mittel −0,9 dB, Median −1 dB, Spanne −3..+3**, bei einer **Kontrollgruppe von 13 unveränderten
Geräten mit +0,0 dB und derselben Streuung**. Das ist Rauschen, kein Effekt.
⇒ **Die Aussage aus TP17, `eco_mode` senke „den CPU-Takt UND die WLAN-Sendeleistung", stimmt nur zur
Hälfte.** Der Takt-Teil ist durch das Dimmer-Flackern bewiesen; der Sendeleistungs-Teil ist an diesen
Geräten nicht messbar. **Kein Funkbild-Argument mehr für oder gegen eco.**
- ✅ **AP-Anker-Sweep erledigt (der offene read-only-Punkt aus der Übergabe):** `WiFi.GetConfig →
ap.enable` über alle 22 Geräte. **Nur `s23` hatte einen AP an** (= Christophs Workaround, inzwischen
abgeschaltet), die übrigen 21 stehen auf `false`. **Kein Aufräumbedarf, kein weiterer Anker im Bestand.**
@@ -994,10 +1054,12 @@ den Weg, über den man den Fehler zurücknehmen wollte.
auf User-Entscheid 2026-08-23 vorerst unverändert**, weil Redigieren ihn nicht aus der Historie
holt und Rotieren heute ~22 Shellys kostet. Nach TP17.1 sind es deutlich weniger. Anlass: das Core-Passwort ist bei einer Debug-Ausgabe im Klartext in
ein Sitzungstranskript geraten. Ursache war `sshpass -p`, das es in die **Kommandozeile**
schreibt — sichtbar in `ps` und in jedem argv-Trace. `docs/tp6-marker/server.py` ist auf
`sshpass -e` umgestellt (Passwort nur in der Umgebung des Kindprozesses); **`docs/tp6-monitor-core.sh`
und `docs/tp25-pin-s22.sh` haben dieselbe Schwäche noch** und gehören vor der Rotation
nachgezogen — sonst ist die Rotation nach dem nächsten Messlauf wieder wertlos.
schreibt — sichtbar in `ps` und in jedem argv-Trace. ✅ **Vorbedingung erfüllt, geprüft 2026-08-23:** alle vier
Skripte laufen auf `sshpass -e` (`tp6-marker/server.py`, `tp6-monitor-core.sh`,
`tp25-walk-messung.sh`, `tp25-pin-s22.sh`) — die frühere Notiz, zwei davon hätten die Schwäche
noch, war veraltet. Übrig blieb nur **`CLAUDE.md`, das `sshpass -p` weiterhin aktiv als Reboot-Weg
empfahl** — am 2026-08-23 auf `sshpass -e` korrigiert. **Damit ist die Rotation ohne Rückfallgefahr
fahrbar.**
Weg: `manage_user set-password` (⚠️ `/user set*` ist per Deny-Policy blockiert), danach
`~/.secrets/credentials.env` (`ROUTER_*_PASS`/`CRS326_*_PASS`) und `~/.claude.json` nachziehen.
Es ist **ein** Passwort für alle vier Geräte — die „Überlebensversicherung" aus CLAUDE.md.

View File

@@ -1,5 +1,27 @@
# TP17.1 — Shelly Gen4 raus aus dem WLAN (Thread oder Zigbee) · Runbook
> ❌ **TP17.1 IST VERWORFEN (User-Entscheid 2026-08-23).** Dieses Runbook bleibt als **Wissensspeicher**
> stehen — es ist kein Arbeitsauftrag mehr.
>
> **Warum:** der Pilot hat den Zigbee-Weg technisch **vollständig bewiesen** (s. Schritt 1 unten),
> aber gezeigt, dass das **Ziel** so nicht erreichbar ist — das Gerät bleibt WLAN-Client, der
> gemessene Funkgewinn ist klein, und WLAN abzuschalten wäre unumkehrbar (kein AP auf der
> Zigbee-Firmware, kein Zigbee-OTA, `InstallAlt` braucht IP).
> **Der Weg ist nicht gescheitert, das Ziel war mit ihm nicht erreichbar.**
>
> ✅ **Rückbau am 2026-08-23 durchgeführt:** ZHA-Knoten entfernt (Leave **vor** dem Flash), `s9` zurück
> auf `PowerStrip`, Config aus dem Backup. ⚠️ **Der Rückflash zog einen OFFENEN AP hoch**
> (`ap.enable=true, is_open=true`) — abgeschaltet und mit dem IoT-PSK abgesichert.
> **Merkregel: nach jedem Shelly-Firmwarewechsel den AP prüfen.**
>
> 🔭 **Weiter gültig ist der Frühindikator:** `Shelly.CheckForUpdate` auf einem Gen4 auf einen
> `…MT`/Thread-`alt`-Eintrag beobachten. Erscheint er, ist die Rechnung neu zu machen.
>
> 📌 **Was hier bleibt und Wert hat:** der kartierte Firmware-Mechanismus (drei undokumentierte RPCs,
> Zwei-Slot-Modell), das gemessene 2,4-GHz-Bild aller vier Funknetze, die ZHA-Sturmzähler mit
> Baseline und Abbruchkriterium — und die Erkenntnis, dass ein ZHA-Join dieses Netz **nicht**
> destabilisiert (alle vier roten EZSP-Zähler blieben 0).
> **Status 2026-08-23:** 🔜 **entscheidungsreif, nicht startbereit.** Schritt 0 ist eine Abwägung,
> kein Flash. Steuerung/Scope: [`teilprojekte.md`](teilprojekte.md) → **TP17.1**.
> **Kein Netz-Eingriff bisher**, alle Zahlen hier sind read-only erhoben.

View File

@@ -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: