From 53b847985e4b2e0398c3c368b06c2ee36ae8dd5c Mon Sep 17 00:00:00 2001 From: reachableceo Date: Wed, 2 Sep 2026 12:59:36 -0500 Subject: [PATCH] feat(ha): websocket driver tool; Govee integration live end-to-end [#620] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💘 Generated with Crush Assisted-by: Crush:glm-5.2 --- HomeAssistant/ha-ws-call.py | 53 ++++++++++++++++++++++++++++++++ HomeAssistant/needfromcharles.md | 16 +++++++--- 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 HomeAssistant/ha-ws-call.py diff --git a/HomeAssistant/ha-ws-call.py b/HomeAssistant/ha-ws-call.py new file mode 100644 index 0000000..7836ae2 --- /dev/null +++ b/HomeAssistant/ha-ws-call.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""ha-ws-call: send websocket commands to Home Assistant, print JSON replies. + +Env: HASS_URL (http(s)://host[:port]), HASS_TOKEN (long-lived token). +Args: one or more JSON command objects — argv literals or file paths. +Replies printed one-per-command, JSON indented. Stdlib + websockets only. +Usage example (docker, from repo root): + docker run --rm --network host --env-file ~/.creds/homeassistant.env \ + -v "$PWD/HomeAssistant:/w" python:3.12-alpine \ + sh -c 'pip install -q websockets && python /w/ha-ws-call.py "{...json...}"' +""" + +import asyncio +import json +import os +import sys + +try: + import websockets +except ImportError: + sys.exit("missing dependency: pip install websockets") + + +def load_cmd(arg): + if arg.startswith("{"): + return json.loads(arg) + with open(arg, encoding="latin1") as handle: + return json.load(handle) + + +async def main(): + base = os.environ["HASS_URL"] + url = base.replace("http", "ws", 1).rstrip("/") + "/api/websocket" + token = os.environ["HASS_TOKEN"] + cmds = [load_cmd(arg) for arg in sys.argv[1:]] + async with websockets.connect(url, max_size=16 * 1024 * 1024) as ws: + hello = json.loads(await ws.recv()) + if hello.get("type") != "auth_required": + sys.exit(f"unexpected hello: {hello}") + await ws.send(json.dumps({"type": "auth", "access_token": token})) + authed = json.loads(await ws.recv()) + if authed.get("type") != "auth_ok": + sys.exit(f"auth failed: {authed}") + for message_id, cmd in enumerate(cmds, start=1): + cmd = dict(cmd) + cmd["id"] = message_id + await ws.send(json.dumps(cmd)) + reply = json.loads(await ws.recv()) + print(json.dumps(reply, indent=1)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/HomeAssistant/needfromcharles.md b/HomeAssistant/needfromcharles.md index 01182a2..cacdef9 100644 --- a/HomeAssistant/needfromcharles.md +++ b/HomeAssistant/needfromcharles.md @@ -18,10 +18,15 @@ Cloud API sees zero devices (suspect BLE-only sensors). - [ ] Model numbers of your Govee temp sensors / lights: - [x] Govee app account email + password (thermometer readings require it): - **Agent (2026-09-02):** both accounts received, stored 0600 in - `~/.creds/govee.env`. But us.govee.com is a JS app the agent can't drive. - One human click needed: log in at us.govee.com → "Apply for API Key" → - paste the key here (or append `GOVEE_API_KEY=` to `~/.creds/govee.env`). + **Agent (2026-09-02, later):** DONE — integration configured end-to-end + via REST config flow: your API key accepted, account + `coo@reachableceo.com` verified (2FA `2937`), MQTT connected. Result: + **zero devices** — the cloud account simply has none exposed. Remaining + blocker is hardware: BLE-only sensors need either a BLE-LE-capable USB + dongle on pfv-bms (current Kensington is BT 2.0, no LE) or a Govee hub. + - [ ] Model numbers of the sensors (decides BLE vs WiFi path) + - One-click cleanup: delete the disabled duplicate "Govee" entry in + UI → Settings → Devices → Govee (removes 2 ghost diagnostic sensors) ## 3. HomeKit codes — blocks #619 (ecobee + front-door lock) - [ ] ecobee HomeKit setup code (8-digit, on paperwork/box if not the unit): @@ -86,3 +91,6 @@ path. Temp + power monitoring for tsys6/tsys7 needs one of: - flip it in the iDRAC6 web UI (needs a TLS1.0-capable browser), or - upgrade iDRAC6 firmware (1.41 → 2.9x) then racadm set, or - accept `public` on the isolated OOB LAN for now + + **Founder decision (2026-09-02): `public` accepted for tsys6.** Both + DRACs now answering SNMP; creds live in `~/.creds/idrac.env`.