v0.6.0: Add Shelly Cloud WS90 sync (background thread, config via GUI)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
name: X3 Dashboard Custom
|
||||
version: "0.5.0"
|
||||
version: "0.6.0"
|
||||
slug: x3_dashboard_custom
|
||||
description: Custom XTeink X3 dashboard for kitchen with solar/battery/calendar
|
||||
url: https://github.com/CraigBell/xteink-x3-home-assistant-dashboard
|
||||
@@ -59,6 +59,9 @@ options:
|
||||
bridge: ""
|
||||
external: ""
|
||||
secure_external: ""
|
||||
shelly_cloud_key: ""
|
||||
shelly_cloud_server: "https://shelly-158-eu.shelly.cloud"
|
||||
shelly_ws90_device_id: ""
|
||||
schema:
|
||||
title: str
|
||||
subtitle: str
|
||||
@@ -100,4 +103,7 @@ schema:
|
||||
dashboard_pubsub: str?
|
||||
bridge: str?
|
||||
external: str?
|
||||
secure_external: str?
|
||||
secure_external: str?
|
||||
shelly_cloud_key: str?
|
||||
shelly_cloud_server: str?
|
||||
shelly_ws90_device_id: str?
|
||||
@@ -1224,6 +1224,101 @@ class X3Handler(BaseHTTPRequestHandler):
|
||||
self.send_text(json.dumps(payload, indent=2) + "\n", "application/json; charset=utf-8", HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
import threading
|
||||
import time as _time
|
||||
|
||||
|
||||
def sync_shelly_cloud() -> None:
|
||||
"""Fetch WS90 data from Shelly Cloud and push to HA as sensors."""
|
||||
auth_key = str(CONFIG.get("shelly_cloud_key") or "").strip()
|
||||
server = str(CONFIG.get("shelly_cloud_server") or "").strip()
|
||||
device_id = str(CONFIG.get("shelly_ws90_device_id") or "").strip()
|
||||
if not auth_key or not device_id:
|
||||
return
|
||||
|
||||
try:
|
||||
data = urllib.parse.urlencode({"id": device_id, "auth_key": auth_key}).encode()
|
||||
req = urllib.request.Request(f"{server}/device/status", data=data, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
result = json.loads(resp.read().decode("utf-8"))
|
||||
if not result.get("isok"):
|
||||
print(f"Shelly Cloud error: {result}", file=sys.stderr)
|
||||
return
|
||||
status = result.get("data", {}).get("device_status", {})
|
||||
|
||||
sensors = {
|
||||
"sensor.shelly_cloud_ws90_precipitation": (
|
||||
str(status.get("precipitation:0", {}).get("value", "unknown")),
|
||||
{"unit_of_measurement": "mm", "device_class": "precipitation", "state_class": "total_increasing", "friendly_name": "WS90 Niederschlag (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_temperature": (
|
||||
str(status.get("temperature:0", {}).get("tC", "unknown")),
|
||||
{"unit_of_measurement": "°C", "device_class": "temperature", "state_class": "measurement", "friendly_name": "WS90 Temperatur (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_humidity": (
|
||||
str(status.get("humidity:0", {}).get("rh", "unknown")),
|
||||
{"unit_of_measurement": "%", "device_class": "humidity", "state_class": "measurement", "friendly_name": "WS90 Luftfeuchtigkeit (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_wind_speed": (
|
||||
str(status.get("speed:0", {}).get("value", "unknown")),
|
||||
{"unit_of_measurement": "km/h", "device_class": "wind_speed", "state_class": "measurement", "friendly_name": "WS90 Windgeschwindigkeit (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_gust_speed": (
|
||||
str(status.get("speed:1", {}).get("value", "unknown")),
|
||||
{"unit_of_measurement": "km/h", "device_class": "wind_speed", "state_class": "measurement", "friendly_name": "WS90 Böengeschwindigkeit (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_wind_direction": (
|
||||
str(status.get("direction:0", {}).get("value", "unknown")),
|
||||
{"unit_of_measurement": "°", "device_class": "wind_direction", "state_class": "measurement", "friendly_name": "WS90 Windrichtung (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_pressure": (
|
||||
str(status.get("pressure:0", {}).get("value", "unknown")),
|
||||
{"unit_of_measurement": "hPa", "device_class": "pressure", "state_class": "measurement", "friendly_name": "WS90 Luftdruck (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_uv_index": (
|
||||
str(status.get("UV:0", {}).get("value", "unknown")),
|
||||
{"state_class": "measurement", "friendly_name": "WS90 UV-Index (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_illuminance": (
|
||||
str(status.get("illuminance:0", {}).get("lux", "unknown")),
|
||||
{"unit_of_measurement": "lx", "device_class": "illuminance", "state_class": "measurement", "friendly_name": "WS90 Beleuchtungsstärke (Cloud)"},
|
||||
),
|
||||
"sensor.shelly_cloud_ws90_battery": (
|
||||
str(status.get("devicepower:0", {}).get("battery", {}).get("percent", "unknown")),
|
||||
{"unit_of_measurement": "%", "device_class": "battery", "state_class": "measurement", "friendly_name": "WS90 Batterie (Cloud)"},
|
||||
),
|
||||
"binary_sensor.shelly_cloud_ws90_rain_detected": (
|
||||
"on" if status.get("moisture_alarm:0", {}).get("value") else "off",
|
||||
{"device_class": "moisture", "friendly_name": "WS90 Regen erkannt (Cloud)"},
|
||||
),
|
||||
}
|
||||
|
||||
client = HomeAssistantClient()
|
||||
for entity_id, (state, attrs) in sensors.items():
|
||||
payload = {"state": state, "attributes": attrs}
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
f"{client.base_url}/api/states/{entity_id}",
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {client.token}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
resp.read()
|
||||
|
||||
precip = status.get("precipitation:0", {}).get("value", "?")
|
||||
print(f"Shelly Cloud sync: precip={precip}mm")
|
||||
except Exception as exc:
|
||||
print(f"Shelly Cloud sync error: {exc}", file=sys.stderr)
|
||||
|
||||
|
||||
def shelly_cloud_loop() -> None:
|
||||
"""Background thread: sync Shelly Cloud every 10 minutes."""
|
||||
while True:
|
||||
sync_shelly_cloud()
|
||||
_time.sleep(600)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Serve XTeink X3 Home Assistant feed")
|
||||
parser.add_argument("--host", default=os.getenv("X3_FEED_HOST", "127.0.0.1"))
|
||||
@@ -1235,6 +1330,10 @@ def main() -> int:
|
||||
global CONFIG
|
||||
load_env(Path(args.env_file))
|
||||
CONFIG = load_options(Path(args.options_file))
|
||||
|
||||
shelly_thread = threading.Thread(target=shelly_cloud_loop, daemon=True)
|
||||
shelly_thread.start()
|
||||
|
||||
server = ThreadingHTTPServer((args.host, args.port), X3Handler)
|
||||
print(f"X3 feed listening on http://{args.host}:{args.port}/")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user