feat 🔌 TP17: statische Shelly-IP + in-pool-Reservierung + voller HA-MAC-Scan

- Entscheidung: alle Shellys bekommen statische IP (10.40.30.<octet>, gw/dns .30.1)
  + in-pool DHCP-Reservierung als Backup. set_sta auf static=true (Default) umgestellt
  (Ziel-IP aus Oktett), _sta_cfg-Helper.
- pool-v30 auf .100-.250 erweitert (Reservierungen jetzt in-pool, User-Konvention).
- s24 (14:33:5c:d8:2d:7c) ins inventory/bf.csv + .30.204-Reservierung.
- Voller HA-Device-Scan (alle MAC-Geräte, nicht nur configuration_url): fand
  Apple TV 4K + Philips TV (aus, VLAN40) — vor cudy-Teardown VLAN verifizieren;
  Apple TV = vmtl. Thread-Border-Router (TP17.1). Tasmota bestätigt tot (MAC nicht im Netz).
- .41.246/.247 = cudy1/cudy2 (Teardown-Ziele) dokumentiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 07:45:08 +02:00
parent 794082fd74
commit 248b1b5e74
4 changed files with 61 additions and 21 deletions

View File

@@ -121,37 +121,58 @@ def get_device(ip: str) -> dict:
return out
V30_NET = "10.40.30"
V30_MASK = "255.255.255.0"
V30_GW = "10.40.30.1"
def _sta_cfg(ssid: str, static: bool, target_ip: str) -> dict:
"""Build the WiFi STA config. Project decision (2026-07-11): all Shellys get a
STATIC IP on-device (keeping the last octet) plus a matching in-pool DHCP
reservation as backup — not DHCP-only."""
sta = {"ssid": ssid, "pass": IOT_PSK, "enable": True}
if static:
sta.update({"ipv4mode": "static", "ip": target_ip, "netmask": V30_MASK,
"gw": V30_GW, "nameserver": V30_GW})
else:
sta["ipv4mode"] = "dhcp"
return sta
@mcp.tool()
def set_sta(ip: str, confirm: bool = False, ssid: str | None = None) -> dict:
def set_sta(ip: str, confirm: bool = False, ssid: str | None = None,
static: bool = True) -> dict:
"""MIGRATION ACTION — point a Shelly's Wi-Fi STA at the IOT SSID, then reboot.
Reconfigures a LIVE device: it drops off VLAN1 and rejoins on VLAN30 via DHCP.
Reconfigures a LIVE device: it drops off VLAN1 and rejoins on VLAN30. Per the
project decision it sets a STATIC IP (10.40.30.<same-last-octet>, gw/dns .30.1);
a matching in-pool DHCP reservation exists as backup. Pass static=False to use DHCP.
A wrong PSK leaves it in AP-mode (physical reset needed). Therefore:
- confirm MUST be true to actually write,
- SHELLY_IOT_PSK must be set in the environment.
With confirm=false this is a dry-run (returns the exact config that would be sent).
"""
target_ssid = ssid or IOT_SSID
target_ip = f"{V30_NET}.{ip.rsplit('.', 1)[-1]}"
ident = discover.probe(ip)
plan = {"ip": ip, "target_ssid": target_ssid,
plan = {"ip": ip, "target_ssid": target_ssid, "target_ip": target_ip,
"mode": "static" if static else "dhcp",
"device": ident, "psk_configured": bool(IOT_PSK)}
if not ident:
return {**plan, "ok": False, "error": "device not reachable"}
if not IOT_PSK:
return {**plan, "ok": False, "error": "SHELLY_IOT_PSK not set in environment"}
sta = _sta_cfg(target_ssid, static, target_ip)
if not confirm:
redacted = {**sta, "pass": "***"}
return {**plan, "ok": False, "dry_run": True,
"would_send": {"method": "WiFi.SetConfig",
"params": {"config": {"sta": {"ssid": target_ssid,
"pass": "***", "enable": True,
"ipv4mode": "dhcp"}}}}}
"params": {"config": {"sta": redacted}}}}
try:
cfg = {"config": {"sta": {"ssid": target_ssid, "pass": IOT_PSK,
"enable": True, "ipv4mode": "dhcp"}}}
res = _rpc(ip, "WiFi.SetConfig", cfg)
res = _rpc(ip, "WiFi.SetConfig", {"config": {"sta": sta}})
reboot = _rpc(ip, "Shelly.Reboot")
return {**plan, "ok": True, "set_result": res, "reboot": reboot,
"note": "device rebooting; expect it on VLAN30 (10.40.30.x) shortly"}
"note": f"device rebooting; expect it at {target_ip} shortly"}
except Exception as e:
return {**plan, "ok": False, "error": f"{type(e).__name__}: {e}"}