Hermes Shelly-MCP: Plug MG3 .50 + shelly_switch

Neues Geraet plug_50 (192.168.178.50, Gen3) und Tool shelly_switch
(on/off/toggle) fuer Gen2/3 Plugs; Status liest switch:0 inkl. Leistung.
This commit is contained in:
Homelab Cursor 2026-08-01 15:29:58 +02:00
parent b8d1cda7be
commit 3ea427e3f0

View file

@ -2,6 +2,7 @@
"""Shelly MCP — Status und Steuerung der Muldenstein-Shellys per LAN-HTTP. """Shelly MCP — Status und Steuerung der Muldenstein-Shellys per LAN-HTTP.
Inventar fest verdrahtet (CT999 projekte/shelly-inventar.md). Inventar fest verdrahtet (CT999 projekte/shelly-inventar.md).
Kinds: cover | pulse | sensor | switch (Gen2/3 Plug).
Sicherheit: Sicherheit:
- Rollo Bad unten (.96): Bewegung blockiert (Motor defekt) - Rollo Bad unten (.96): Bewegung blockiert (Motor defekt)
- Garagentor / Haustuer-Impuls: nur mit confirm=true, nie in Cron - Garagentor / Haustuer-Impuls: nur mit confirm=true, nie in Cron
@ -81,6 +82,13 @@ DEVICES: dict[str, dict] = {
"kind": "sensor", "kind": "sensor",
"aliases": ["garage_decke_b", "decke_b", "garage-b"], "aliases": ["garage_decke_b", "decke_b", "garage-b"],
}, },
"plug_50": {
"ip": "192.168.178.50",
"name": "Shelly Plug MG3",
"gen": 3,
"kind": "switch",
"aliases": ["plug_50", "shelly_50", "plug50", "steckdose_50", "mg3"],
},
} }
# flatten alias lookup # flatten alias lookup
@ -168,7 +176,7 @@ def tool_list(_args: dict) -> str:
) )
lines.append(f" Aliase: {', '.join(d.get('aliases', [key]))}") lines.append(f" Aliase: {', '.join(d.get('aliases', [key]))}")
lines.append("") lines.append("")
lines.append("Tools: shelly_list, shelly_status, shelly_cover, shelly_pulse") lines.append("Tools: shelly_list, shelly_status, shelly_cover, shelly_pulse, shelly_switch")
lines.append( lines.append(
"Cron: Rollos OK; Tor/Tuersummer verboten; Bad unten nie bewegen." "Cron: Rollos OK; Tor/Tuersummer verboten; Bad unten nie bewegen."
) )
@ -230,6 +238,7 @@ def _status_gen2(d: dict) -> dict:
if not isinstance(st, dict): if not isinstance(st, dict):
out["error"] = str(st) out["error"] = str(st)
return out return out
if d["kind"] == "cover":
cover = st.get("cover:0") or {} cover = st.get("cover:0") or {}
out["cover"] = { out["cover"] = {
"state": cover.get("state"), "state": cover.get("state"),
@ -238,6 +247,17 @@ def _status_gen2(d: dict) -> dict:
"voltage": cover.get("voltage"), "voltage": cover.get("voltage"),
"source": cover.get("source"), "source": cover.get("source"),
} }
if d["kind"] == "switch":
sw = st.get("switch:0") or {}
out["switch"] = {
"output": sw.get("output"),
"apower": sw.get("apower"),
"voltage": sw.get("voltage"),
"current": sw.get("current"),
"aenergy_total": (sw.get("aenergy") or {}).get("total"),
"temperature_c": (sw.get("temperature") or {}).get("tC"),
"source": sw.get("source"),
}
wifi = st.get("wifi") or {} wifi = st.get("wifi") or {}
out["wifi_rssi"] = wifi.get("rssi") out["wifi_rssi"] = wifi.get("rssi")
if d.get("motor_broken"): if d.get("motor_broken"):
@ -251,7 +271,7 @@ def tool_status(args: dict) -> str:
return resolved return resolved
key, d = resolved key, d = resolved
try: try:
data = _status_gen2(d) if d["gen"] == 2 else _status_gen1(d) data = _status_gen2(d) if int(d.get("gen", 1)) >= 2 else _status_gen1(d)
data["key"] = key data["key"] = key
return json.dumps(data, ensure_ascii=False, indent=2) return json.dumps(data, ensure_ascii=False, indent=2)
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
@ -327,7 +347,7 @@ def tool_cover(args: dict) -> str:
if not 0 <= pos <= 100: if not 0 <= pos <= 100:
return "position muss 0-100 sein." return "position muss 0-100 sein."
try: try:
if d["gen"] == 2: if int(d.get("gen", 1)) >= 2:
return _cover_gen2(d["ip"], action, pos) return _cover_gen2(d["ip"], action, pos)
return _cover_gen1_roller(d["ip"], action, pos) return _cover_gen1_roller(d["ip"], action, pos)
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
@ -363,6 +383,44 @@ def tool_pulse(args: dict) -> str:
return f"Fehler shelly_pulse {d['name']}: {e}" return f"Fehler shelly_pulse {d['name']}: {e}"
def tool_switch(args: dict) -> str:
"""Gen2/3 Switch/Plug: on|off|toggle."""
resolved = _resolve(args.get("target") or args.get("device") or args.get("name"))
if isinstance(resolved, str):
return resolved
key, d = resolved
if d["kind"] != "switch":
return (
f"{d['name']} ist kein Schalter/Plug (kind={d['kind']}). "
"Fuer Rollos: shelly_cover. Fuer Impuls: shelly_pulse."
)
if int(d.get("gen", 1)) < 2:
return f"{d['name']}: Gen1-Switch noch nicht implementiert."
action = str(args.get("action") or "").strip().lower()
if action in ("an", "ein", "on"):
action = "on"
elif action in ("aus", "off"):
action = "off"
elif action in ("toggle", "umschalten", "schalten"):
action = "toggle"
if action not in ("on", "off", "toggle"):
return "action muss on|off|toggle sein."
ip = d["ip"]
try:
method = {"on": "Switch.Set", "off": "Switch.Set", "toggle": "Switch.Toggle"}[action]
params = {"id": 0}
if action in ("on", "off"):
params["on"] = action == "on"
res = _http_post_json(f"http://{ip}/rpc", {"id": 1, "method": method, "params": params})
return json.dumps(
{"ok": True, "device": d["name"], "ip": ip, "action": action, "result": res},
ensure_ascii=False,
)
except Exception as e: # noqa: BLE001
return f"Fehler shelly_switch {d['name']}: {e}"
TOOLS = [ TOOLS = [
{ {
"name": "shelly_list", "name": "shelly_list",
@ -377,7 +435,7 @@ TOOLS = [
"description": ( "description": (
"Live-Status eines Shellys (Rollo-Position, Relay, Sensor-Temps). " "Live-Status eines Shellys (Rollo-Position, Relay, Sensor-Temps). "
"Ziel per Alias/IP/Name: kueche, computerraum, gaestezimmer, bad_unten, " "Ziel per Alias/IP/Name: kueche, computerraum, gaestezimmer, bad_unten, "
"garagentor, haustuer, garage_decke_a/b." "garagentor, haustuer, garage_decke_a/b, plug_50."
), ),
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
@ -435,6 +493,27 @@ TOOLS = [
"required": ["target", "confirm"], "required": ["target", "confirm"],
}, },
}, },
{
"name": "shelly_switch",
"description": (
"Schaltet einen Shelly-Plug/Switch (on|off|toggle). "
"Ziel z.B. plug_50 / 192.168.178.50 / Shelly Plug MG3."
),
"inputSchema": {
"type": "object",
"properties": {
"target": {
"type": "string",
"description": "Alias, IP oder Name (z.B. plug_50)",
},
"action": {
"type": "string",
"description": "on | off | toggle",
},
},
"required": ["target", "action"],
},
},
] ]
HANDLERS = { HANDLERS = {
@ -442,6 +521,7 @@ HANDLERS = {
"shelly_status": tool_status, "shelly_status": tool_status,
"shelly_cover": tool_cover, "shelly_cover": tool_cover,
"shelly_pulse": tool_pulse, "shelly_pulse": tool_pulse,
"shelly_switch": tool_switch,
} }