Skip to content

Home Assistant Integration

Overview

Home Assistant Core lives at http://192.168.86.180:8123 (VM CT 100 on Finn). Long-lived API token in ~/.forge-secrets/ (NOT in repo). Forge consumes HA via two paths:

  1. Direct REST API calls from scripts/integrations/{eight-sleep,garmin}/poll.py, pollers push wellness sensors into HA. See reference_wellness_pollers memory entry.
  2. Context API HA poller at infra/context-api/scripts/ha_poller.py, every 15 min ingests wellness + presence + device state into the SQLite Context Graph at data/context.db. Surfaced via /context skill.

Capabilities verified (2026-03)

  • 42 lights detected and controllable via REST
  • RGB color control, scenes (Gaming Scene, Work Scene), power on/off/toggle
  • State queries (individual + bulk)
  • Wellness dashboard at /wellness-dash (HA Lovelace)

Remote access / Cloudflare Access (changed 2026-05-30)

homeassistant.justinsforge.com is served via Cloudflare tunnel. Cloudflare Access was REMOVED 2026-05-30 so the iOS HA companion app works (iOS app can't carry the Access cookie on its WebSocket and has no custom-header support, so Access broke the app — showed a broken-image page; Safari worked). Tradeoff accepted by Justin: HA's own login is now the only public gate. Full Access app+policy config backed up at data/ha-backups/cf-access-homeassistant-*.json — one-step restore. A CF Access service token "Home Assistant App" still exists (works for the Android app's custom headers; unused by iOS). Two legacy "HA Alexa bypass" Access apps remain (now redundant).

RECOMMENDED hardening (not yet applied): HA http: should set use_x_forwarded_for: true + trusted_proxies: [<cloudflared connector IP>] and ip_ban_enabled: true, login_attempts_threshold: 5. Without correct trusted_proxies, ip_ban would ban the tunnel connector (lock everyone out), so the connector IP must be identified first.

Lighting / scenes stack (added 2026-05-30)

No Philips Hue bridge; bulbs are WiZ, ZHA (Zigbee: Hue/Tradfri/Inovelli), Tuya, Matter. To get Hue-level scenes across all brands, three HACS components are installed (downloaded manually into /config, HACS can adopt for updates):

  • hue-like-light-card v1.11.0 (frontend) — Hue-app-style room/scene UI. JS at /config/www/community/hue-like-light-card/, Lovelace resource /local/community/hue-like-light-card/hue-like-light-card.js.
  • Adaptive Lighting v1.30.1 — circadian color-temp+brightness. Config /config/packages/adaptive_lighting.yaml, scoped to 6 living-area lights only (studio/office/outdoor/video/gaming EXCLUDED so they never auto-adjust). Master switch switch.living_areas_adaptive_lighting_living_areas.
  • Animated Scenes v2.1.1 — dynamic cycling-color scenes; service animated_scenes.start_animation. Activity Sensor config entry created. Scenes are scripts in /config/packages/lighting_animated_scenes.yaml (tropical/fireplace/ocean/party + animated_scenes_stop).

Static scene gallery: scene.relax|bright|evening|movie (+ existing studio_mood_lighting, work_scene, gaming_scene) in scenes.yaml. Dedicated Lighting dashboard at url_path lighting-control (Home dashboard deliberately untouched). Source copies of all package/config files in forge at data/ha-backups/.

Studio Inovelli switch (added 2026-06-10)

light.inovelli_studio_recessed_switch is a VZM31-SN Blue Series 2-in-1 (ZHA, smart bulb mode ON) driving all 16 studio lights via light.studio_lights (= studio_spotlights + studio_recessed_lights). Logic lives in /config/packages/studio_switch_scenes.yaml (source copy data/ha-backups/studio_switch_scenes.yaml) wired into automation "Inovelli Studio Recessed Lights" (fxlt ZHA blueprint, id 1777835542777):

  • Up/down tap: on (3100K 80%) / off, targeting ZHA group entity light.krystal_s_office_sonoff_dongle_plus_mg24_studio_all_zigbee (one multicast to all 16). Up/down HOLD: real-time dim via Zigbee binding, switch LevelControl cluster bound to ZHA group 3 studio_all_zigbee (device-to-device, no HA round-trip; bound via websocket zha/groups/bind).
  • 3x tap up/down: cycle forward/back through input_select.studio_scene_cycle (Warm 2700K / Middle 3100K / Cool 5000K / Mood / Purple / Sunset Split / Cyberwave), applied by script.studio_scene_apply.
  • Config 2x/3x/4x/5x: script.studio_party_mode / studio_cowboy_mode / studio_chill_mode / studio_armadillos_mode (Studio Echo alexa_devices device fccdd3937151b89fb4c1d7d752a4ab58 + light looks). Armadillos mode plays a YouTube-only parody mp3 through script.echo_play_audio_url.

Playing arbitrary mp3s on Echo devices (verified 2026-06-11)

script.echo_play_audio_url (in packages/studio_switch_scenes.yaml) plays any hosted mp3 on any Echo: fields echo_player (alexa_media media_player entity, NOT the alexa_devices one), url, volume (default 0.6). Hard-won rules, all three verified by test:

  1. Use alexa_media notify with data.type: tts. announce type strips <audio> tags (silent failure); only tts plays them.
  2. Message MUST start with <speak> or alexa_media sends it as plain text (also silent failure).
  3. File must be public https (valid cert, no CF Access), MP3 48kbps, max 240s. Transcode: ffmpeg -ac 2 -ar 24000 -b:a 48k. Host in HA /config/www/audio/ = auth-free at https://homeassistant.justinsforge.com/local/audio/<file> (source copies data/ha-backups/www-audio/).

Note: three more VZM31-SNs in the kitchen (Counter, Island, Recessed) use the same blueprint; same pattern applies if those get scene cycles.

Two-switch split (added 2026-06-29)

A second studio VZM31-SN, "Inovelli Studio Spotlight Switch" (ieee f0:44:d3:ff:fe:6d:a6:9e, entity prefix spotlight_switch, smart-bulb-mode ON), now drives ONLY the 8 Hue track spots (light.studio_spotlights). All ~42 device settings (ramp rates, dim up/down speeds, on-level, power-on=Off, transition=0, dimming mode LeadingEdge, output Dimmer, double-tap levels, LED bar color/intensity) were copied from the Recessed Switch so behavior matches.

CONTROL MODEL (important, both studio switches): tap on/off + multi-tap scenes = the HA blueprint AUTOMATION (fxlt ZHA blueprint, reacts to ZHA button events); hold-to-dim = Zigbee group binding (LevelControl out, device-to-device, no HA round-trip). They are two independent layers. Changing the binding does NOT change tap behavior, and vice-versa. (First attempt only changed bindings and tap did nothing on the new switch, this was the fix.)

Split as of 2026-06-29: - Recessed Switch automation automation.inovelli_studio_recessed_lights_switch (id 1777835542777): tap → light.sonoff_dongle_plus_mg24_studio_recessed_zigbee (cans, grp 16); hold-dim bound to grp 16; 3x-tap 7-scene cycle + config 2x-5x party/cowboy/chill/armadillos still target all 16 (scripts hit studio_all_zigbee directly). - Spotlight Switch automation automation.inovelli_studio_spotlight_lights (id 1751240000001): tap → light.krystal_s_office_sonoff_dongle_plus_mg24_studio_spotlights_zigbee (8 spots, grp 4); hold-dim bound to grp 4; 3x-tap up/down cycles input_select.studio_spotlight_temp (Warm 2700/Neutral 3100/Cool 5000) via scripts studio_spotlight_temp_{apply,next,prev} (spots only).

FIRMWARE GOTCHA (root cause of a long 2026-06-29 debug): the VZM31-SN will NOT send dim/level commands over a Zigbee binding on older firmware. A fresh switch shipped on 0x01020303 (hold-dim dead despite correct smart-bulb-mode + bind + air-gap + reconfigure); the working recessed switch was on 0x01020304. Fix = OTA-update the new switch via its update.<name>_firmware entity (update.install, slow 30-60min, do NOT air-gap mid-flash) to match. Tap on/off masks the problem because that path is the AUTOMATION, not the binding. ALWAYS check update.*_firmware installed_version parity between a working and non-working Inovelli before chasing the binding.

Group binding via websocket zha/groups/bind/zha/groups/unbind {source_ieee, group_id, bindings:[{name,type:"out",id:6|8,endpoint_id:1}]}. Do NOT bind one switch's Level cluster to two groups at once, it breaks that switch's hold-dim entirely (observed on recessed during cross-test). NOTE: bulbs must be POWERED ON when creating the ZHA group / adding members, else the device-level Groups-cluster write fails silently and group-cast does nothing (hit this; re-ran zha/group/members/add once spots were on). Friendly-name rename only (entity_ids unchanged). These spotlight automation/scripts/input_select were created via REST/WS API, NOT yet in the studio_switch_scenes.yaml package file (divergence from the package source-of-truth; fold them in next time the package is edited on the HA host).

The Front Door Reolink doorbell (192.168.86.197, admin creds = Frigate RTSP env) now has the native Reolink HA integration in addition to its Frigate feed. Frigate gives object detection (binary_sensor.front_door_*_occupancy, counts, snapshots); the native Reolink integration gives device control + the button-press entity. Key entity: binary_sensor.front_door_visitor fires on doorbell button press. Automation automation.reolink_doorbell_kitchen_echo_chime triggers on it: sets media_player.kitchen_echo to vol 0.7, then notify.alexa_media_kitchen_echo plays SSML <audio src="soundbank://soundlibrary/doorbells/amzn_sfx_doorbell_chime_01"/> with data.type: tts (announce strips audio tags). Other Echos: notify.alexa_media_<name> for each.

Token

Long-lived access token: HA → Profile → Long-Lived Access Tokens. Lives at ~/.forge-secrets/wellness.env (chmod 600) and is mirrored into n8n encrypted credential store (cred IDs in reference_wellness_pollers).

History

Earlier (Greg / OpenClaw era) there was a custom skills/home-assistant/ skill with its own FastAPI wrapper on :7357, superseded by the Context API on :7358 and removed 2026-04-28. If you need direct HA control again, call the REST API directly with the long-lived token rather than reviving the wrapper.