Skip to main content

Home Assistant integration

entity · created 2026-07-09 · actor claude-cowork · confidence medium · status active tags: home-assistant iot camera power

Now​

2026-10-02 08:55-09:15 AEST (renaming the pool filter pump plug): sensor.home_entertainment_* was never an entertainment plug. It is the pool filter (circulation) pump, and the entity ids now say so. The Tuya device 3d2d163c83a9864b48d885d21cbc57b7 (PC191HA, manufacturer Tuya) has always been named "Pool pump" in the device registry; only the entity ids carried the wrong label, inherited from an earlier use of the plug. Renamed via config/entity_registry/update over the WebSocket API (Dwayne's 2026-10-02 instruction, "home entertainment is actually the new pool filter pump, update the homeassistant dashboards and entities to reflect this"):

  • sensor.home_entertainment_power -> sensor.pool_filter_pump_power (friendly name set to "Pool filter pump")
  • sensor.home_entertainment_current -> sensor.pool_filter_pump_current
  • sensor.home_entertainment_voltage -> sensor.pool_filter_pump_voltage
  • sensor.robot_vacuum_energy_total_energy -> sensor.pool_filter_pump_total_energy (the device's native kWh counter; its id had been left over from a different device entirely)
  • switch.home_entertainment_socket_1 -> switch.pool_filter_pump_socket_1
  • select.home_entertainment_power_on_behavior -> select.pool_filter_pump_power_on_behavior

new_entity_id on that call needs the full domain-qualified id; a bare slug returns invalid_info: Invalid entity ID. Renaming an entity id rewrites dashboard references automatically, 6 storage dashboards took the new ids with no dashboard edit, including the Load breakdown now card on arcade-power and energy-ops, whose loop now prints "Pool Filter Pump". Two dashboard strings were changed by hand: the arcade-media history card retitled to "Pool filter pump power · 24 hours", and the power-tile label to "Pool filter pump".

Do NOT rename the Entertainment Room area or the headings derived from it. entertainment_room is a real HA area (the TV room, media_player.entertainment_room_tv, sensor.entertainment_room_be65pro_*). A blanket string replace for "Entertainment" corrupts it, one such replace hit the dashboard-areas section title and was reverted within the same session. Match entity ids, never the word.

Other layers updated to match: configuration.yaml (customize block, the pool-water-temp trigger, Power Tracked Devices, the pump term in the pool sampling template), packages/dashboard_compatibility.yaml (Monthly Consumption), and the scripts ha_power_known_loads.py, power_baseline_monitor.py, pool_dp_window.py, turn_on_entertainment_hub.py, ha_anomaly_detector.py, clawd/scripts/event-chains.py (two dict entries). Cron fbec9d2caf17 renamed to pool-filter-pump-on-morning; its script field still names the old filename, which was edited in place.

Known leftover, deliberate: sensor.home_entertainment_total_energy remains live. It is a platform: integration counter defined in configuration.yaml, and it is the one entity in this family whose state the Monthly Consumption template consumed. See the Log entry of the same date for why it cannot be renamed without a restart, and why the template was repointed at the plug's native counter instead. Backups: ~/.homeassistant/backups/pool-filter-pump-rename-20261002-085428/ (config + scripts) and ~/.homeassistant/backups/pool-filter-pump-rename-dashboards/configs.json (all 26 storage dashboards, read over the API before the write).

Read live 2026-10-02 09:15 AEST: sensor.pool_filter_pump_power 259.3 W, _current 1.26 A, _voltage 241.2 V, _total_energy 2.1 kWh, switch.pool_filter_pump_socket_1 on. Power Tracked Devices 600 W + Power Untracked 502 W = 1102 W against CT1 1102.06 W. Sources: live HA /api/states and ws://127.0.0.1:8123/api/websocket, ~/.homeassistant/configuration.yaml, ~/.homeassistant/.storage/core.entity_registry, ~/.hermes/cron/jobs.json., hermes.

2026-09-29 06:22-06:45 AEST (codex vault daily audit, pool sampling boundary): the pool water temp sensor samples the wrong first reading each day. configuration.yaml:197 defines pool_water_temperature_sampled, a trigger-based template sensor that samples only when 10 <= now().hour < 14 and holds this.state otherwise (built from Dwayne's 2026-09-27 18:03 instruction, "the pool water temp should only be sampled between 10am and 2pm"). Eight dashboards use the sampled entity (climate_energy, command_centre, dashboard_areas, energy_ops, floor_heating, home_daily, home_dashboard, maintenance_ops) and none use the raw probe. The window opens at the same minute the circulation pump starts. sensor.home_entertainment_power on 2026-09-28 reads 0.0 W at 09:00, 0 to 1365 W across the 10:00 hour, 1365 to 1384 W at 11:00; the raw probe sensor.pool_heat_pump_water_temperature reads 15 at 09:00, spans 15 to 21 across 10:00, 21 to 22 across 12:00, 22 at 13:00. So the 10:00 sample is stagnant pipe water, and exec_dashboard_data.json records 2026-09-28 pool_temp_min: 15.0 against in-window 21-22. The hold works as designed: the sampled sensor ran 22.0 flat from 14:00 to the next morning. Routes, none applied because the window is Dwayne's stated number: keep 10:00, move the start to about 10:30, or require two consecutive rising in-window samples. Also this window: the d3 dashboard had its invented solar supply removed (www/d3_dashboard.html mtime 2026-09-28 02:54:24, backup d3_dashboard.html.bak-nosolar-20260928-025018; hardcoded 1955 W and 1210 W defaults replaced by — under a new liveDataSeen guard, roof polygon relabelled No Solar Installed), and lovelace.arr_stack switched to tailscale IPs (7 100.80.168.13 URLs, 0 192.168.). The "free solar" wording survives on lovelace.floor_heating, lovelace.energy_ops and automations.yaml (cts_floor_drying_schedule, cts_offline_alert) though it is a free-power tariff window, not generation. Pool card rebuilt 2026-09-29 00:30 as pool metered 11.6 kWh (from 7.6), source sensor.pool_heat_pump_energy; the circulation pump meter reads 193.24 kWh and is on no pool tile. Sources: ~/.homeassistant/configuration.yaml, ~/.homeassistant/recorder/home-assistant_v2.db hourly statistics, ~/.homeassistant/exec_dashboard/exec_dashboard_data.json, ~/.homeassistant/www/d3_dashboard.html, ~/.homeassistant/.storage/lovelace.*, live HA /api/states, read 2026-09-29 06:22-06:45 AEST., hermes.

2026-09-28 10:08-12:30 AEST (camera vision sweep, driveway recovery): the Driveway TC41 camera is back after 14 days frozen, with no physical intervention. The TP-Link entry 01M1P28GK90EW3TX966A41WACZ re-set up at 10:08:24 AEST (12 Setting up tplink.* lines, one full domain setup) and its 600 s not ready yet retry loop stopped after the 09:58:22 line, 60 lines for the day and none after 10:00. switch.driveway left unavailable for on and camera.driveway_live_view for idle at 2026-09-28T00:08:24Z. Live /api/camera_proxy/camera.driveway_live_view now returns changing frames: 81342 B (watermark 12:03:56 PM), 87541 B (12:18:29 PM), 110611 B (12:26:42 PM), each byte-stable across three reads 30-45 s apart. That supersedes the frozen still camera.driveway_real_still.jpg sha256 06ce21c4, watermark 2026-09-14 12:49:17 PM, unchanged across 29 sweeps. The previous next action, a physical power-cycle of the TC41, is now superseded 2026-09-28: the unit recovered on an HA-side integration re-setup, so no power-cycle was needed and none was performed. Pool camera is back on target: published_pool.jpg frames the pool and its deck framing again from 2026-09-27 12:03 onward, after the off-target no-pool frames of 2026-09-24 through 2026-09-26 12:00, so that viewpoint fault is also resolved without intervention. The 12:03:48 AEST sweep itself: 14 cameras, 18 captures, 3 failed, 0 actionable, all three the 2689-byte Nest dark placeholders (camera.lounge_display, camera.home_home, camera.entryway_front_stairs). The driveway slug reported stale_real in that sweep because its snapshot timed out at 12:03, which the later fresh publishes supersede; treat the slug status as stale evidence when the proxy returns live frames. sensor.camera_vision_driveway is still unavailable (last 2026-09-26T09:26:34Z) and sensor.camera_vision_summary reads 6/7 cameras analyzed at 11:43, so the driveway vision analysis has not resumed yet; that is the remaining item and it needs no device work. Also visible in the fresh driveway frames: the front-yard trampoline lies on its side, the same as in the 2026-09-14 frozen frame and the 2026-09-24 front-door frame, so it is a standing condition rather than a new one. Sources: ~/.hermes/camera_sweeps/20260928-120348/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/, live HA /api/states, /api/camera_proxy/camera.driveway_live_view and /api/config/config_entries/entry, ~/.homeassistant/home-assistant.log, ~/.hermes/logs/ha_expose_camera_stills.last.json, read 2026-09-28 12:03-12:30 AEST., hermes.

2026-09-28 05:12-05:25 AEST: fifth ha_health_check.py false-positive family, gated and fixed. The hourly job alerted on 1 ERROR line in its 70-minute window: Received invalid command: {'type': 'lovelace/config', 'url_path': 'home-dashboard'} from homeassistant.components.websocket_api.http.connection. This is a client protocol error, not an HA fault. Verified live against 2026.8.2 on ws://127.0.0.1:8123/api/websocket: {"id":1,"type":"lovelace/config","url_path":"home-dashboard"} returns the config (success:true), {"id":2,"type":"lovelace/config"} returns config_not_found, and the same message with no id returns invalid_format and is what HA logs as Received invalid command. The id must be a non-zero integer; a string id such as "id": "home-dashboard" fails the same way. Measured in the current log: 25 such lines on 2026-09-27/28 in five batches (02:35:48 x17 enumerating all 17 dashboards, 03:15:42 x3, 03:19:06 x2, 03:22:24 x2, 04:22:40 x1, 05:21:01 x1), every one from a local ad-hoc dashboard read. The class is self-sustaining: each hourly ha-health-check run read a dashboard, its own malformed read landed in the log, and the following run alerted on it. Fixed with ws_client_protocol_noise-style gate WS_CLIENT_PROTOCOL_TAG + WS_CLIENT_PROTOCOL_MARKS in ha_health_check.py, applied before the *arr and coordinator gates. Filtered on the tag-AND-phrase pair only, so any other websocket_api.http.connection error still alerts. Verification: python3 -m pytest tests/test_ha_health_check.py 47 passed (44 pre-existing plus 3 new: invalid-command suppressed, unknown-command suppressed, other websocket_api error still alerts); --verbose reports all probes passed; a bare run now prints [SILENT] with exit 0. HA itself was never unwell across the window: /api/ 200 API running., /api/config state=RUNNING, version 2026.8.2, home-assistant.log.fault unchanged at 314022 bytes (last write 2026-08-25 00:02). The same lesson is recorded as a pitfall in the lovelace-dashboard-management skill: send {"id": <int>, ...} from the first websocket message. Sources: live websocket probe and HA REST reads, ~/.homeassistant/home-assistant.log, ~/.hermes/scripts/ha_health_check.py, ~/.hermes/tests/test_ha_health_check.py, read 2026-09-28 05:12-05:25 AEST., hermes.

2026-09-27 15:30-15:50 AEST (weekly retune): the 2026-09-24 restore-time failure class is unchanged after three days. Live ha-tag-status reads 209/255 ⚠️ 🏠 and a direct /api/states read counts 46 unavailable/unknown across the counted domains. The 10 Tuya lights (light.corridor_led, light.kitchen_led_1..5, light.laundry_led, light.guest_bathroom_guest_led_a), light.garden and light.guest_led_b were all still unavailable, and climate.inverter_pool_heat_pump reads unknown. New evidence on cause: the Tuya entry 01KKZT8SVT8TGCYWYM63GS1M45 is loaded, but the Tuya cloud device list marks Corridor LED, Laundry LED, Kitchen LED 1..5, Bed LED and Fish LED "online": False with stale device IPs (159.196.168.50) against the working bulbs at 125.168.201.211. So the Tuya LEDs are offline at the device/cloud layer, not an HA restore fault, and a reload_config_entry (HTTP 200, []) did not restore them. Lifx is genuinely a retry loop: entries Garden, Guest LED A and Guest LED B all sit in setup_retry with the same get_hostfirmware, get_version, get_group timed out after 5 attempts. New and actionable: switch.driveway and its five companion entities are unavailable because the TP-Link Driveway TC41 entry (01M1P28GK90EW3TX966A41WACZ) cannot reach 192.168.68.55:9999; ICMP to .55 succeeds (11.8 ms) while the Kasa port is closed, and arp shows .58 (Roborock) as incomplete. binary_sensor.arr_sabnzbd_warnings unavailable is expected (ARR runs 00:00-05:00 only). Sources: live ~/.hermes/bin/ha-tag-status and HA /api/states, /api/config/config_entries/entry, ~/.homeassistant/home-assistant.log, ~/.homeassistant/.storage/core.entity_registry, Tuya cloud device dump in the HA log, ping/arp to 192.168.68.55 and .58, read 2026-09-27 15:30-15:50 AEST., hermes.

2026-09-26 07:15 AEST: ha_health_check.py alerted on a single Open-Meteo weather fetch error, and the fourth false-positive family is now gated in the script. Full detail in the Log entry below; the script change and its tests are the only change to the file. Nothing else in the window, and no live fault: weather.home reads sunny with data refreshed 2026-09-26 07:03 AEST, HA state=RUNNING, 675 states with 143 unavailable against threshold 150, home-assistant.log.fault unchanged at 314022 bytes (last write 2026-08-25 00:02).

2026-09-25 20:57 AEST (evening review): HA reads 204/250 ⚠️ 🏠 live, 46 counted entities unavailable. New finding: 38 of the 46 share last_changed 2026-09-23T21:01:23Z, which is exactly the 2026-09-24 07:01:23 AEST core start. These entities did not restore when HA came back after the 24 Sep restart and have stayed unavailable for two days: 10 lifx/Tuya lights (light.corridor_led, light.garden, light.guest_bathroom_guest_led_a, light.guest_led_b, light.kitchen_led_1..5, light.laundry_led), 7 plex media_player clients, 5 driveway entities, 9 roborock binary_sensors plus its vacuum/battery/child_lock/DND/off_peak switches (roborock is with the repairer, 2026-09-24 dated suppression stands), 2 device_tracker phones, light.garden. The shared restore timestamp is the useful signal: this is one restore-time failure class, not 38 independent faults.

lifx: reload is now exhausted. Three of the five lifx config entries sit permanently in setup_retry: Garden (01KN60MAZJYP3E73797WYTTYPF, unit 192.168.68.127), Guest LED A (01KZ8NE5WDBB91J9JQ8VFYKS04, .101), Guest LED B (01KZ8NEBCKE5S78DCWDG6F92KC, .94). Each logged 121 retries on 2026-09-25, 147 on 2026-09-24, 141 on 2026-09-23, every one the same get_hostfirmware, get_version, get_group timed out after 5 attempts. config_entry.reload was called on all three at 20:45 (HTTP 200 {"require_restart":false} for each); 12 minutes later all three were back in setup_retry with the same timeout line and all 10 light entities still unavailable. .101 and .94 answer ICMP (3/3, 0% loss) while the integration cannot complete its LIFX handshake, so this is integration or device-side state, not a dead LAN path. .127 does not answer ping at all. Next step is a full HA restart at a quiet time, or delete and re-add the three entries; both are live-service changes and were NOT done.

Self-inflicted and recovered (reported for completeness): a config_entry.reload on the tuya entry 01KKZT8SVT8TGCYWYM63GS1M45 at 20:51 timed out client-side. HA logged Setup of config entry ... for tuya integration cancelled at 20:52:19 and the entry fell to setup_error, dropping counted health to 165/250 for roughly ten minutes. A second reload at 20:54 returned HTTP 200 and the entry is loaded again with health back at 204/250. Lesson: the tuya entry takes longer than the 30 s client timeout on reload, and a client-side abort leaves the entry in a worse state than before.

Sources: live HA /api/states, /api/config/config_entries/entry, /api/config/config_entries/entry/<id>/reload, /api/history/period, ~/.homeassistant/home-assistant.log and .log.1, ~/.homeassistant/.storage/core.entity_registry, ping to 192.168.68.127/.101/.94, ~/.hermes/bin/ha-tag-status. Reading taken 2026-09-25 20:41-20:57 AEST., hermes.

2026-09-25 06:14 AEST: ha_health_check.py reported 1 health problem, one ERROR line in its 70-minute window (05:02-06:12). It is the standing low-rate Panasonic cloud family and it self-cleared; nothing is live.

  • 2026-09-25 05:19:41.768 ERROR (MainThread) [aio_panasonic_comfort_cloud.helpers] Error in get_status, with response body {"code":4008,"message":"Bad request"} against expected HTTP 200. The same second the client logged [aio_panasonic_comfort_cloud.apiclient._ac] Failed to get live status for device CS-Z71XKRW+E011917675 switching to cached data.
  • The device recovered on the next poll: sensor.panasonic_inside_temperature read 20 with last_updated 2026-09-24T20:12:01Z (2026-09-25 06:12:01 AEST, read at 06:13-06:14), and sensor.entertainment_room_panasonic_connection_status reads connected with consecutive_failures 0. climate.panasonic reads heat.
  • Cadence for this line is unchanged and low: 1 line on 2026-09-25, 2 on 2026-09-24 (both at 07:39:29), and 3 in home-assistant.log.1 (2026-09-21 13:26:47, 2026-09-22 11:51:46, 2026-09-22 18:59:45). Matches the 4-line day recorded on 2026-09-17. No action.
  • No other ERROR line in the window. HA state=RUNNING on 2026.8.2, 675 states with 141 unavailable against threshold 150, home-assistant.log.fault unchanged at 314022 bytes, last write 2026-08-25 00:02.
  • Fixed in ha_health_check.py at 06:20 AEST, same doctrine as the Shelly and SABnzbd gates: a new PANASONIC_TRANSIENT_MARK plus panasonic_serving(states) drops the line only while sensor.entertainment_room_panasonic_connection_status reads connected with consecutive_failures: 0. A real cloud or device outage drives that counter up and the line stays visible, and the mark is deliberately kept out of ERROR_ALLOWLIST so it can never be suppressed permanently. Verification (16/16, one ad-hoc script under $TMPDIR/hermes-verify-panasonic-gate-*.py, cleaned up after the run): healthy device suppresses; consecutive_failures: 3 does not; disconnected does not; missing entity does not; the mark matches the real log line but not an unrelated ipp error; the mark is absent from the allowlist; no unrelated ERROR is hidden; check() stays clean on the real live states and the CLI prints the bare [SILENT] with exit 0. Falsified: with the gate armed the window holds 0 ERROR lines, with states withheld the same line reappears, with a failing connection sensor injected through a stubbed HTTP layer check() alerts again, and the CLI prints the bare [SILENT] with exit 0 and empty stderr. Unwind: delete PANASONIC_TRANSIENT_MARK and its two call sites if the cadence ever stops being single-poll. These are ad-hoc checks, not a project suite; ~/.hermes/scripts has no canonical test runner. python3 -m py_compile clean and --verbose now reports all probes passed.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log and home-assistant.log.1, live HA /api/states, /api/config and /api/history/period reads, ~/.hermes/scripts/ha_health_check.py, 2026-09-25 06:12-06:20 AEST., hermes.

2026-09-24 12:15 AEST: HA healthy on 2026.8.2. The camera sweep at 12:03 captured 17 stills with 4 non-actionable failures and no intruder, delivery or property-damage finding. Two faults need physical action.

  • Driveway TC41 (192.168.68.55) camera still frozen, 9 days now. 14 driveway entities unavailable (camera.driveway_live_view, switch.driveway, the pan/tilt buttons and numbers, sensor.driveway_signal_level, sensor.camera_vision_driveway). Network path is fine (ping 2/2, ARP on en5, TCP 443/554 open) and homeassistant.reload_config_entry on entry_id=01M1P28GK90EW3TX966A41WACZ did not clear it, so reloads are exhausted. Next action: physical power-cycle of the TC41.
  • Pool camera (camera.garden_live_view) viewpoint moved by 2026-09-24 12:03 AEST and no longer frames the pool: the still shows the rear courtyard, a ladder and a hand truck, with a neighbouring verandah. Sibling cameras unaffected; the first view shift of this kind was 2026-09-23 12:03 -> 18:03. Next action: check the Pool camera mount or pan/tilt home position.
  • Roborock Qrevo MaxV still off the LAN since 2026-09-18 08:41 AEST, unchanged; physical dock power and Wi-Fi check still stands.
  • The hourly ha_health_check.py SABnzbd alert was a false positive and is fixed: see the 2026-09-24 01:35 Log entry. --verbose now reports all probes passed.

Source: live HA /api/states and /api/config, ~/.homeassistant/home-assistant.log, ~/.hermes/scripts/ha_health_check.py, 2026-09-24 01:12-01:35 AEST. - hermes.

2026-09-22 06:12 AEST: HA healthy on 2026.8.2, state=RUNNING, 677 states with 192 unavailable/unknown (14 of them the driveway camera family). Two standing faults, both unchanged in kind and both needing physical action.

  • Driveway TC41 (192.168.68.55) camera frozen for 8 days. published_driveway.jpg sha256 06ce21c4 is byte-identical across all 7 sweeps from 2026-09-15 06:01 to 2026-09-22 06:03, its embedded watermark reads 2026-09-14 12:49:17 PM, and it is the only sweep slug whose hash never changes. The network path is fine: ping 2/2 at 7.4 ms, ARP 98:ba:5f:47:d3:79 on en5, TCP 443 and 554 open. HA logs Unable to communicate with the device connect: ('Unable to query the device: 192.168.68.55: ', TimeoutError()) every 600 s (246 lines in the current log, last 06:11:31). homeassistant.reload_config_entry with entry_id=01M1P28GK90EW3TX966A41WACZ returned HTTP 200 at 06:11 and did not clear it, so reloads are exhausted. Next action: physical power-cycle of the TC41.
  • Roborock Qrevo MaxV still off the LAN since 2026-09-18 08:41 AEST, unchanged; physical dock power and Wi-Fi check still stands.

Source: ~/.hermes/camera_sweeps/20260922-060348/manifest.json, live HA /api/states and /api/config, ~/.homeassistant/home-assistant.log, sha256 comparison across sweeps, 2026-09-22 06:03-06:12 AEST. - hermes. 2026-09-20 07:16 AEST: ha_health_check.py reported 1 health problem: 1 ERROR line in its 70-minute window (06:06-07:16), the standing Shelly EM transient fetch failure. Not a live fault; the only other standing item, the roborock, is off the LAN for a third day.

  • [homeassistant.components.shelly] Error fetching Shelly EM data: An error occurred while retrieving data from Shelly EM at 06:36:59.845. Same transient family as the 09-16 to 09-18 entries; the device is healthy. Live curl http://192.168.68.78/status returned HTTP 200 in 1.11 s with wifi_sta.rssi -73 dBm, cloud.connected true, both emeters[].is_valid true (channel 1 1257.57 W, channel 2 12.55 W). ping 3/3, 0.0% loss, 7.635-12.845 ms. Metering served live: sensor.shelly_em_channel_1_power_2 1257.18 W, sensor.shelly_em_channel_2_power 12.71, sensor.shelly_em_channel_1_energy 48151.9633, all last updated 2026-09-19T21:16:11Z-21:16:26Z.
  • The Tuya light.lounge_led_2 write rejection of the 05:13 run did not recur in this window; no other ERROR line appeared, so the run carried no second transient to mask.
  • Unavailable load stays under threshold: 140 of 678 entities read unavailable (threshold 150), plus 51 unknown, so 191 unavailable/unknown against the 148 recorded on 2026-09-17. Leading unavailable domains: sensor 65, button 20, binary_sensor 12, light 10, switch 8, select 5.
  • Roborock Qrevo MaxV remains off the LAN, unchanged from the 2026-09-18 09:30 entry: vacuum.roborock_qrevo_maxv unavailable with last_changed 2026-09-18T20:38:49Z, ping 192.168.68.58 2/2 lost, 100%. Unchanged in the last three runs; the roborock integration logs at INFO so this check cannot surface it, and the physical dock power and Wi-Fi action still stands.
  • home-assistant.log.fault still last changed 2026-08-25 00:02, no growth.
  • Shelly EM error cadence: 3 today (02:50:44, 04:51:34, 06:36:59) against 10 for the 2026-09-19 portion of the current home-assistant.log.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, live curl http://192.168.68.78/status, ping, live HA /api/states reads, 2026-09-20 07:16 AEST., hermes.

2026-09-18 14:13 AEST: ha_health_check.py reported 1 health problem, 2 ERROR lines in its 70-minute window (13:03-14:13). Both are the known Shelly EM transient fetch failure; the device answered live probes and metering is serving.

  • [homeassistant.components.shelly] Error fetching Shelly EM data: An error occurred while retrieving data from Shelly EM at 13:21:20 and 13:54:28. No other ERROR line in the window, no home-assistant.log.fault growth, state=RUNNING on 2026.8.2, unavailable count within threshold 150.
  • Device is healthy, not failing: GET http://192.168.68.78/status returned HTTP 200 in 0.078 s with wifi_sta.rssi -70 dBm, cloud.connected true, emeters[].is_valid true (channel 1 power 507.86 W, channel 2 6.38 W). ping 2/2, 0.0% loss, 11.7-23.3 ms.
  • Metering kept serving: sensor.shelly_em_channel_1_power_2 read 485.53 W and sensor.shelly_em_channel_2_power 6.07 W at 2026-09-18T04:13:00Z.
  • Cadence is falling, not worsening: 5 Shelly EM fetch errors on 2026-09-18 so far, against 14 for all of 2026-09-17 and 19 for 2026-09-16. The -70 dBm link at the switchboard remains the standing hypothesis and the standing next action: physical antenna/AP check, not taken because a monitor run takes no side effects.
  • vacuum.roborock_qrevo_maxv is still unavailable, last_changed 2026-09-17T22:41:20Z, unchanged from the 09:30 entry. The roborock integration logs at INFO so this check does not surface it; the earlier next action (physical dock power and Wi-Fi check) still stands.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, live curl http://192.168.68.78/status, ping, live HA /api/states reads, 2026-09-18 14:13 AEST., hermes.

2026-09-18 09:30 AEST: ha_health_check.py reported 1 health problem, 2 ERROR lines in its 70-minute window (08:02-09:12). Both are known transient families, but a separate, genuine fault was found alongside them: the Roborock Qrevo MaxV has been offline for 50 minutes.

  • [homeassistant.components.shelly] Error fetching Shelly EM data at 08:12:28 and [homeassistant.core] Error executing service: <ServiceCall light.turn_on (c:01M2RSJB29:al:INUX:ntrv:NL)> for light.lounge_led_1 at 09:00:01. The Tuya chain is unchanged from the 2026-09-15 16:12 entry: adaptive_lighting/hass_utils.py:88 -> components/tuya/light.py -> tuya_sharing/customerapi.py:119, ApiRequestException: network error:(-9999999) Remote api run unknown failed. The write landed anyway: all five adaptive-lighting targets (light.lounge_led_1/_3/_4/_5, light.entrance_led) read on, brightness 255, color_temp_kelvin 6500, last_updated 2026-09-17T21:36:47Z.
  • New fault, not in the ERROR window because the roborock integration logs at INFO: vacuum.roborock_qrevo_maxv went unavailable at 2026-09-17T22:41:20Z (2026-09-18 08:41 AEST) and is still unavailable at 09:27. 52 of 53 roborock entities are unavailable.
  • The device is off the LAN. arp -an maps 192.168.68.58 (its networkInfo.ip in .storage/roborock/01J7JYDCYG5RBH45VAS65M4C90, MAC b0:4a:39:b0:5f:07) to (incomplete), and ping -c 2 -t 2 192.168.68.58 returns "Host is down", 100% loss, against 44 populated ARP entries on en5. A /24 sweep found no ARP entry for the MAC.
  • The cloud side is up, so the stall is local transport only: roborock.mqtt.roborock_session disconnects and reconnects cleanly (09:17:14 -> 09:17:26 "MQTT Session connected"), the last "Connected to local channel successfully" was 08:28:22, and roborock.devices.device then logged Failed to connect (retry N): Command timed out after 10.0s from 08:44:04 onward, backoff climbing to 576.65 before resetting and climbing again (latest 09:27:38, retry 22.5).
  • This is the residual the 2026-09-16 11:42 sentinel entry predicted: every prior unavailable span in the 09-14 to 09-17 history lasted under 3 minutes 7 seconds, so a sustained stall is a real integration fault, not a map-update gap.
  • Metering kept serving through both transient lines: sensor.shelly_em_channel_1_power_2 read 1483.12 W at 2026-09-17T23:27:47Z, and /api/history for the entity holds 7319 points across the last 6 hours with a maximum gap of 62.6 s.
  • HA healthy: state=RUNNING on 2026.8.2. Unavailable count 139-145 of 684 against threshold 150, no breach but the roborock family is 52 of that count. sensor.colima_memory_* flickered unavailable at 2026-09-17T23:11:03Z and recovered on the 09:21:13 publish; collector.log holds 28 TimeoutExpired on colima ssh ... cat /proc/meminfo but the run completed.
  • Next useful action, not taken because a monitor run takes no side effects: physical check of the Qrevo MaxV's dock power and Wi-Fi (its last known SSID is MyRepublic DCAF, same band as the rest of the 192.168.68 /24).
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, .storage/roborock/01J7JYDCYG5RBH45VAS65M4C90, colima_monitor/collector.log, live HA /api/states, /api/config and /api/history/period reads, arp -an, ping, 2026-09-18 09:12-09:30 AEST., hermes.

2026-09-18 07:20 AEST: ha_health_check.py reported 1 health problem, 6 ERROR lines in its 70-minute window (06:02-07:12). Five are a single Tuya write rejection and one is an ipp poll timeout that recovered in 60 seconds. No fault is live.

  • All five [homeassistant.core] Error executing service lines fire inside 07:00:00.556-07:00:00.804 and carry context c:01M2RJPK75:al:INUX:ntrv:NA, one adaptive-lighting group write to light.lounge_led_3, light.entrance_led, light.lounge_led_1, light.lounge_led_5 and light.lounge_led_4 at brightness=255, color_temp_kelvin=6025. Each traceback ends tuya_sharing.exceptions.ApiRequestException: network error:(-9999999) Remote api run unknown failed at tuya_sharing/customerapi.py:119. Same chain as the 2026-09-15 16:12 entry.
  • The writes landed anyway. Live HA at 07:15 reads all five lights on at brightness 255, color_temp_kelvin 6234, last_updated 2026-09-17T21:15:19-21:15:20Z.
  • Tuya service-error count by day across home-assistant.log plus .1: 5 on 09-14, 5 on 09-15, 2 on 09-16, 26 on 09-17, 5 today by 07:12. The 09-17 figure is the outlier and 24 of it sits in one 08:58-09:00 burst of 8 contexts x 3 retried calls. Today's 5 match the 09-14 and 09-15 baseline.
  • The ipp line at 06:10:58.892, Error fetching ipp data: Invalid response from API: Timeout occurred while connecting to IPP server., pairs with Fetching ipp data recovered at 06:11:58.945. Same slow-poll shape recorded on 2026-09-17 18:20.
  • HA is healthy: state=RUNNING on 2026.8.2, 87 of 684 entities unavailable against threshold 150. Last ERROR before the window was a lifx Pergola LED (192.168.68.120) timeout at 04:00:54.
  • Standing next action, unchanged and not taken because a monitor run takes no side effects: physical Wi-Fi check of the Pergola LED pair and a DHCP reservation for the guest LED pair.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log and home-assistant.log.1, live HA /api/states and /api/config reads, 2026-09-18 07:12-07:20 AEST., hermes.

2026-09-17 18:20 AEST: the Pergola LED lifx flapper has moved from 192.168.68.122 to 192.168.68.120 inside the same pair. ha_health_check.py reported 5 ERROR lines in the 70-minute window ending 18:10, four of them .120 lifx timeouts (17:41:23, 17:45:53, 18:06:55, 18:09:13) plus one ipp.coordinator line at 17:49:38; all five recovered on the next poll (lifx 17:41:53, 17:46:03, 18:07:22, 18:09:40; ipp 17:50:44).

  • Address-to-unit mapping from core.config_entries: entry 01KVWWKX2AAVEWBDN388QR7VXR holds host: 192.168.68.120 (MAC d0:73:d5:76:5e:2c); entry 01KYAYAXA691M1NB2CRRD4JWCA holds host: 192.168.68.122 (MAC d0:73:d5:76:8d:67). Both entries carry the title "Pergola LED" and the log line carries only the IP, so the alert text cannot tell the two apart.
  • Daily lifx timeout counts: .122 9 on 09-15, 42 on 09-16, 8 on 09-17 (last 15:57:29); .120 1 on 09-15, 0 on 09-16, 6 on 09-17. The heavy load moved between the two addresses.
  • Live host pings 2026-09-17 18:15-18:20 AEST: .120 lost 23 of 60 packets (61.7% loss, round-trip min/avg/max 34.381/217.535/418.064 ms) across three runs; .122 lost 0 of 10 on both runs; .121 (Garden), .94, .111 and the gateway all answered 10 of 10. .92 has no ARP entry (incomplete on en5) and is unreachable. ARP maps .120 to d0:73:d5:76:5e:2d and .122 to d0:73:d5:76:8d:68, one above each device unique_id MAC.
  • Entities match a slow-poll fault, not a dead unit: light.pergola_led and light.pergola_led_2 both read off; button.pergola_led_restart and button.pergola_led_identify read unknown.
  • HA healthy: state=RUNNING on 2026.8.2, 90 of 684 entities unavailable against threshold 150.
  • Next useful action, not taken because a monitor run takes no side effects: physical check of both Pergola LED units' Wi-Fi (same AP path) and a DHCP reservation for the pair.

2026-09-17 16:13 AEST: ha_health_check.py reported 8 ERROR lines in its 70-minute window (15:02-16:13). Seven belong to known transient families; the eighth is a python-kasa KLAP 403 pair traced here for the first time, and its impact is nil because every entity on those plugs is disabled.

  • Window lines in order: shelly fetch error 15:57:00; lifx Timeout fetching Pergola LED (192.168.68.122) 15:57:29; ipp.coordinator 15:59:30 and 16:11:45, both paired with Fetching ipp data recovered at 16:00:31 and 16:12:46; aio_panasonic_comfort_cloud.helpers Error in get_status 15:59:54; [homeassistant] Error doing job: Task exception was never retrieved 16:07:17; kasa.transports.klaptransport 403 on 192.168.68.67 at 16:08:27 and on 192.168.68.68 at 16:11:49.
  • New family, first traced here: Query failed after successful authentication ... Response status is 403. Upstream explanation is python-kasa issue #784: a stale KLAP handshake session returns 403 on send, a retry succeeds, and HA does not enter its reauth flow. Daily counts: 3 on 2026-09-12 (first line 14:29:03), 4 on 09-13, 2 on 09-14, 1 on 09-15, 3 on 09-16, 7 on 09-17. Source for the upstream behaviour: https://github.com/python-kasa/python-kasa/issues/784.
  • Impact is nil on the current registry. All 20 entities on each of Washing Macine P110 (192.168.68.66), Heat pump dryer P110 (192.168.68.67), Data centre P110M (192.168.68.109) and Data centre 2 P110 (192.168.68.68) carry disabled_by: user, so no state depends on those polls. The integration still polls the devices, which is why the lines keep appearing. Meir Towel heater P110M (192.168.68.110) has 15 of its 20 entities enabled and logged no 403.
  • The 16:07:17 Task exception is the camera haffmpeg pattern already recorded on 2026-09-15 02:20: haffmpeg Timeout reading image at 16:07:17.738, Timeout while waiting of FFmpeg at .739, then RuntimeError: read() called while another coroutine is already waiting for incoming data in asyncio.subprocess.communicate. components.tplink.entity logged Unable to read data for <DeviceType.Camera at 192.168.68.61 - Pergolux (C510W)> 22 seconds earlier, at 16:06:22. Daily counts: 2 today (10:35:07, 16:07:17) against 5 on 09-12, 11 on 09-14, 14 on 09-15, 1 on 09-16.
  • Metering kept serving. sensor.shelly_em_channel_1_power_2 read 1659.72 W at 2026-09-17T06:20:18Z and /api/history holds 3390 points from 2026-09-16T19:10:00Z with no gap. Today's shelly fetch-error count is 9 (05:31:31, 06:30:38, 06:33:23, 08:57:43, 11:15:54, 11:49:13, 14:12:48, 14:41:05, 15:57:00), against 19 for the whole of 09-16.
  • Pergola LED eased: 10 timeouts today against 42 on 09-16. ping -c 3 -i 0.3 192.168.68.122 at 0% loss, round-trip min/avg/max 32.933/70.559/99.867 ms. light.pergola_led reads off.
  • Unchanged knowns: HA state=RUNNING on 2026.8.2 with 75 of 684 entities unavailable against threshold 150; Driveway TC41 still logs not ready yet (97 lines today, last at 16:14:50) with all 15 of its entities unavailable since 2026-09-14T23:29:28; Panasonic Error in get_status 4 lines today (00:21:55, 02:36:55, 07:59:25, 15:59:54), device CS-Z71XKRW+E011917675 switches to cached data, climate.panasonic reads off.
  • Next useful action, unchanged and not taken because a monitor run takes no side effects: physical power-cycle of the Driveway TC41. The kasa 403 lines need no action while those four entries stay disabled; if any of their entities are re-enabled, confirm the retry-on-403 behaviour still holds before treating populated states as reliable.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log and home-assistant.log.1, .storage/core.config_entries and .storage/core.entity_registry, live HA /api/states, /api/config and /api/history reads, ping probes, 2026-09-17 16:13-16:21 AEST., hermes.

2026-09-17 06:12 AEST: ha_health_check.py reported 1 ERROR line in its 70-minute window, [homeassistant.components.shelly] Error fetching Shelly EM data at 05:31:31. Same transient fetch pattern as the recent entries; metering did not stop.

  • Live checks at 06:12-06:15 AEST: GET http://192.168.68.78/status returned HTTP 200 in 0.034 s, wifi_sta.rssi -69 dBm, cloud.connected true, both emeters[].is_valid true with power 3732.43 W and 2391.89 W.
  • ping -c 10 -i 0.4 192.168.68.78 returned 9 of 10 replies, 10.0% loss, round-trip min/avg/max/stddev 7.138/19.752/84.173/23.191 ms. Weak but usable link, unchanged from the 2026-09-16 reading.
  • Metering served without a gap: /api/history for sensor.shelly_em_channel_1_power_2 holds 4214 points from 2026-09-16T08:30:00Z to 2026-09-16T20:13:30Z. The entity read 3657.07 W with last_changed 2026-09-16T20:13:37Z.
  • Daily counts for this error line: 5 on 2026-09-15, 19 on 2026-09-16, 1 on 2026-09-17 by 05:31. The 09-16 total is 6 above the 13 recorded at 17:13 that day, so the afternoon rate stayed elevated.
  • Next useful action, unchanged and still not taken because a monitor run takes no side effects: physical check of the Shelly EM antenna position at the switchboard, or an AP/relocation check, to fix the -69 dBm link.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, live HA /api/states and /api/history reads, GET http://192.168.68.78/status, ping probe 2026-09-17 06:12-06:15 AEST., hermes.

2026-09-16 17:13 AEST: ha_health_check.py reported 2 ERROR lines in its 70-minute window, both [homeassistant.components.shelly] Error fetching Shelly EM data at 16:07:51 and 17:11:29. Both are transient fetch failures, not a device fault: metering kept serving through both.

  • The Shelly EM (192.168.68.78, entry 01KZ8EP8PF9JQAT3MV0EY3ZD2N, serial BCFF4DFD00E9) answered GET /status with HTTP 200 five times in a row at 17:15-17:16 AEST, 0.045-0.619 s each; one earlier probe timed out at 8 s. wifi_sta.rssi -70 dBm, cloud.connected true, both emeters[].is_valid true.
  • ping -c 10 -i 0.4 192.168.68.78 returned 9 of 10 replies, 10.0% loss, round-trip min/avg/max 8.158/16.766/38.039 ms, so the unit is on the LAN with a weak but usable link.
  • Metering never stopped: sensor.shelly_em_channel_1_power_2 read 3757.22 W at 17:13 AEST (last_changed 2026-09-16T07:13:26Z), and /api/history for the entity holds 695 points from 06:00Z to 07:17Z with no gap. Channel 2 read 1026.55 W and sensor.shelly_em_channel_2_energy 18419.6476 kWh at the same read.
  • No Error fetching Shelly EM data line pairs with a recovered line in this log, unlike the lifx and ipp coordinators. The integration logs the failure only; the entity values resume on the next successful poll.
  • Today's count is 13 through 17:11 (07:25:46, 07:36:43, 09:11:27, six between 12:27:28 and 12:36:00, 12:56:43, 12:57:27, 16:07:51, 17:11:29). The 09:13 entry below recorded 3 for the day; the afternoon added 10, with a six-line burst inside 12:27-12:36. Prior days for comparison: 17 on 2026-09-12, 4 on 2026-09-13, 22 on 2026-09-14, 5 on 2026-09-15.
  • Sensor entities added by the integration are absent, not broken: sensor.shelly_em_signal_strength, sensor.shelly_em_uptime, update.shelly_em_firmware all return no state from /api/states, while sensor.shelly_em_channel_1_energy_2 is likewise missing. The core emeter entities are all present.
  • House state at 17:13-17:16 AEST: HA state=RUNNING (2026.8.2), 109 of 703 entities unavailable against threshold 150, no other ERROR family in the window. The 17:00:16-17:00:31 radarr/sonarr Cannot connect to host localhost lines sit in the same window but are the documented 05:00-24:00 *arr down-window noise and the script filtered them correctly.
  • Next useful action, for a later working session: the unit's rssi of -70 dBm and the recurring coordinator fetch failures on a 13-per-day cadence point at Wi-Fi coverage at the switchboard rather than a dead device. A physical check of the Shelly EM antenna position, or an AP/relocation check, is the useful step. Not taken here because a monitor run takes no side effects.
  • Superseded claim: the 09:13 entry below reads "the third today, down from 20-plus on 2026-09-14". The first half held at 09:13 only; the day closed the afternoon at 13 and the rate climbed back.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, live HA /api/states and /api/history reads, GET http://192.168.68.78/status, ping probes 2026-09-16 17:13-17:17 AEST., hermes.

2026-09-16 09:13 AEST: the hourly health window still returns 6 ERROR lines, the same count as the 04:16 and 00:35 runs, but the mix changed. Only two of the six are Pergola LED (192.168.68.122); the guest LED pair is back in the window.

  • Lines in the 70-minute window ending 09:13: lifx timeouts at 08:29:37 for Guest LED A (192.168.68.92), 08:29:43 for Guest LED B (192.168.68.101), 08:48:30 and 09:12:09 for Pergola LED (192.168.68.122); ipp.coordinator at 08:34:20; shelly at 09:11:27. Four lifx, one ipp, one shelly.
  • 192.168.68.122 now answers ping -c 10 -i 0.4 with 10 of 10 replies, 0.0% loss, round-trip avg 161.836 ms, max 427.216 ms, so the Wi-Fi fault recorded at 04:16 has eased since then. Its timeout count for 2026-09-16 is 30, against 10 for the whole of 2026-09-15, so the day is still running above yesterday.
  • Superseded claim: the 04:16 entry named .122 as the single failing unit. That is no longer the only one.
  • Guest LED A (192.168.68.92) and Guest LED B (192.168.68.101) each return 0 of 5 pings, and arp -n shows (incomplete) for both, so no MAC resolves. Both entities read unavailable. Their recorded addresses moved again: on 2026-09-15 the pair held .101 (Guest LED A) and .94 (Guest LED B). DHCP lease churn stays the better explanation than dead hardware, and a reservation or power cycle is still the unresolved fix from 2026-09-15 10:15.
  • The ipp printer timeout recovers on the next poll: 08:34:20 error, 08:35:20 Fetching ipp data recovered. sensor.canon_ts8300_series reads idle at ipp://192.168.68.111/ipp/print. This is the sixth ipp line today, all paired with a recovery.
  • Shelly EM fetch error at 09:11:27 is the third today (07:25:46, 07:36:43, 09:11:27), down from 20-plus on 2026-09-14. Metering kept serving: sensor.shelly_em_channel_1_power 1409.24 W at the same read.
  • Next useful action for a later working session, not taken here because a monitor run takes no side effects: physical check of the guest LED pair and a check of Pergola LED (192.168.68.122) signal strength while its loss rate is low.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log, live HA state reads, ping and arp probes 2026-09-16 09:12-09:14 AEST., hermes.

2026-09-16 04:16 AEST: the Pergola LED (192.168.68.122) lifx timeouts that ha_health_check.py keeps surfacing are a failing Wi-Fi link on the unit itself, and the rate is climbing. Same 70-minute-window alert shape as 00:35, different cause.

  • Link evidence at 04:12-04:17 AEST: ping -c 30 -i 1 192.168.68.122 returned 13 of 30 replies, 56.7% loss, round-trip avg 185.240 ms, max 520.202 ms. A second run -c 20 -i 0.3 returned 6 of 20, 70.0% loss, avg 1209.567 ms, max 2655.017 ms (2 replies past wait). A -c 2 probe at 04:12:21 returned 2 of 2 at 194 ms average, so the loss is intermittent and bursty, not a dead interface. arp -n 192.168.68.122 resolves to MAC d0:73:d5:76:8d:68 on en5, one digit from the entry's unique_id d0:73:d5:76:8d:67, so the replies are the light itself and not a DHCP takeover.
  • Peers on the same LAN segment are clean at the same moment: 192.168.68.120 (the other Pergola LED, config entry 01KVWWKX2AAVEWBDN388QR7VXR) and 192.168.68.123 each returned 10 of 10, 0% loss. .120 logged only 3 lifx timeouts in the same window pair; .122 logged 34.
  • Timeout trend across home-assistant.log plus .1 (34 lines total for .122): 1 on 2026-09-12, 1 on 2026-09-13, 11 on 2026-09-14, 11 on 2026-09-15, 14 on 2026-09-16 by 04:16. The 09-16 figure covers about 4.3 hours, so the hourly rate roughly quadrupled. HA recovers every time: 41 Fetching Pergola LED (192.168.68.122) data recovered lines, pairing each timeout within 10 s to 4 minutes.
  • Superseded claim: the 2026-09-16 00:35 entry above recorded "the IP answers ping 0% loss" and called this a slow-poll fault. That reading does not hold now. light.pergola_led (entry 01KYAYAXA691M1NB2CRRD4JWCA) flaps between off and unavailable; at 04:12:37 AEST it read off with last_changed 2026-09-15T18:12:37.332406+00:00, exactly matching that minute's recovery line.
  • ha_health_check.py reported exactly 4 ERROR lines in its 70-minute window, all four the .122 lifx timeout, with no other ERROR family present. HA itself is healthy.
  • Next useful action: physical check of the Pergola LED at 192.168.68.122 (Wi-Fi signal, obstruction, distance to AP). HA exposes button.pergola_led_restart on entry 01KYAYAXA691M1NB2CRRD4JWCA as a remote power-cycle; at 04:16 AEST it read unknown (last_changed 2026-09-15T18:15:59.705575+00:00) while button.pergola_led_identify read unavailable, so the button surface is not reliably reachable either. Not pressed here, because a monitor run takes no side effects.
  • Source: /Users/dwayne/.homeassistant/home-assistant.log and .1, .storage/core.config_entries, .storage/core.entity_registry, live HA state reads, ping and arp probes 2026-09-16 04:12-04:17 AEST., hermes.

2026-09-16 00:35 AEST: fixed the midnight-boundary false alarm in ha_health_check.py, the third filter bug of the same class in a week. The 00:13 run reported 10 ERROR lines; 6 were real and 4 were schedule noise the filter was written to drop.

  • Root cause: arr_down_window_noise() was applied by check() against the run hour (datetime.now().hour), not each log line's own timestamp. The *arr containers stop at 05:00 and start at 00:00, so the 23:11-23:51 SABnzbd connection errors sit in the 70-minute window of a 00:13 run while the run hour reads 0, outside the 5 <= hour < 24 guard. Observed lines: SABnzbd connections at 23:11, 23:21, 23:31, 23:41, 23:51 (five), plus [homeassistant.components.sabnzbd.coordinator] Error fetching SABnzbd data: Error while fetching data at 00:05:43 after the 00:00 start.
  • Fix 1: the window test moved into arr_down_window_noise(line, when), judged on the parsed log-line timestamp. Callers pass when from recent_error_lines().
  • Fix 2: new ARR_BOOT_GRACE_MIN = 20 covers the 00:00 start pass. SABnzbd's API answered at 00:06 (container Up 13 minutes at 00:13, /api?mode=version returned 5.1.2, root / returned HTTP 200) and its coordinator logged recovered at 00:06:13, so a poll landing in the first minutes is startup, not a fault. Inside 00:00-05:00 and outside the grace the containers must serve, so the same marks still alert.
  • Rejected fix, caught by an ad-hoc check before delivery: adding Unexpected error updating listener to ARR_BY_DESIGN_MARKS. That line is HA's own lidarr sensor crashing (components/lidarr/sensor.py:29 get_space, AttributeError: 'LidarrRootFolder' object has no attribute 'freeSpace') because the sensor reads rootfolder data before the API fills freeSpace. It happens to coincide with an *arr start (2026-09-13 19:50:17 manual, 2026-09-16 00:05:46 scheduled), but the mark would have hidden a real HA component fault for the whole 05:00-00:00 down window, not just at boot. It still alerts in every window. A separate ad-hoc check asserting that direction failed on the first build, which is how the over-suppression surfaced.
  • What alerts after the fix, all genuine: 3 lifx timeouts for Pergola LED (192.168.68.122) at 23:51:25 and 23:56:09 plus one earlier, and the 00:05:46 lidarr listener crash. light.pergola_led reads off and the IP answers ping 0% loss, so it is a slow-poll fault, not a dead unit; the siblings Garden (192.168.68.127), Guest LED A (.101) and Guest LED B (.94) do not answer ping at all and sit unavailable.
  • Verification: ~/.hermes/tests/test_ha_health_check.py grew 15 -> 21 tests, all passing under hermes-agent/venv/bin/python -m pytest (21 passed in 0.17s). Falsified: 9 of 21 fail against a reconstructed pre-fix build (hermes-verify-old-ha.py, 12,312 chars vs the 14,036 new), including test_shutdown_noise_is_dropped_end_to_end. Live run at 00:34 returns 4 lines, not 10.
  • Script sha256 782066581f1a2e588521776373d6420ff515b2cd69454c670b1fa3f124ecca4a; test sha256 2e6546648a9a5104d7d0dd7ce5b02fc136e8e11082dec88d036b2cb6288b538e. Pre-fix copy of the 09-16 00:20 intermediate build at ~/.hermes/backups/ha-health/ha_health_check.py.20260916-002006.bak. Both paths are gitignored (.gitignore:2:*), so this entry is the durable record.
  • Note for the sibling filter: hermes_home_integration_sentinel.py schedule_noise_free_log() carries the same run-hour shape, so it will mislabel the first tick after 00:00 the same way. Not changed here; the hourly checker was the alerting surface.
  • House state at 00:22: HA RUNNING, 78 of 703 entities unavailable (threshold 150, baseline 106 recorded Aug 2026), so no breach. All six *arr containers up 13 minutes with every published port answering; Plex up 15 hours on 32400.

2026-09-15 16:12 AEST: the light.turn_on service error that ha_health_check.py keeps surfacing is the Tuya cloud rejecting the write, not a local HA fault. Traced the 2026-09-15 15:10:00.906 line end to end: adaptive_lighting/hass_utils.py:88 service proxy, then components/light/__init__.py:521, then components/tuya/light.py:530 async_turn_on, then tuya_sharing/device.py:213 send_commands -> tuya_sharing/customerapi.py:119, raising tuya_sharing.exceptions.ApiRequestException: network error:(-9999999) Remote api run unknown failed.

  • Recurrence is sporadic, not continuous: 5 occurrences on 2026-09-14 at 11:11:47 (light.lounge_led_6), 11:35:31 (light.lounge_led_3), 13:31:06 (light.lounge_led_3), 13:52:04 (light.lounge_led_6), 15:20:02 (light.lounge_led_6), and 1 on 2026-09-15 at 15:10:00 (light.lounge_led_1). All are Tuya cloud lights in the lounge group.
  • The device recovers on its own. light.lounge_led_1 read state on, brightness 255, color_temp_kelvin 6385 at 16:12 AEST after the 15:10 failure, so the command landed late or the next command succeeded. No unavailable entity.
  • Distinguish from the neighbouring error families in the same log: the 38 light.garden service errors are lifx TimeoutError on Light.set_power, a separate LAN-transport fault, and the light.guest_led_b / light.guest_bathroom_guest_led_a unavailability is the DHCP-lease churn noted above. Only the 6 lines above carry the Tuya cloud mark.
  • Unavailable entities 107 of 703, threshold 150, no breach. HA state RUNNING, Starting Home Assistant 2026.8.2 at 09:29:28. /System/Volumes/Data at 93% with 15 GiB free.
  • No durable local fix available: the Tuya cloud API is the failing hop, and the tuya integration is cloud-only here (the local localtuya entry is inert). Treat as upstream flakiness unless the frequency climbs.

2026-09-15 10:15 AEST: fixed three silent filter bugs in ha_health_check.py that made the hourly job report 18 ERROR lines when only 2 were real. All three were the same class: a filter written to match a shape HA never emits, so it matched nothing and never errored.

  • SABnzbd: the integration logs its coordinator failures under [homeassistant.core], not [homeassistant.components.sabnzbd]. arr_down_window_noise() gated on ARR_BY_DESIGN_COMPONENTS first, so SABNZBD_BY_DESIGN_MARK could never be reached. 11 lines per run were documented 05:00-24:00 downtime noise. Now the mark matches on its own; it is unique to SABnzbd.
  • Allowlist bracket match: any(f"[{a}]" in line ...) required a literal closing bracket, so [homeassistant.components.homekit.util] and [homeassistant.components.automation.cts_floor_drying_schedule] never matched the entries homeassistant.components.homekit / homeassistant.components.automation. Replaced with allowlisted(), a prefix match on [{a}.
  • Cost of the bugs: 2 HomeKit lines alerted every hour and 3 automation.cts_floor_drying_schedule lines (10:00:00 on 2026-09-15 and 2026-09-14) alerted on a tag the filter was written to drop.
  • Verified: ~/.hermes/tests/test_ha_health_check.py grew 9 -> 15 tests, all passing under hermes-agent/venv/bin/python -m pytest. Falsified against a reconstructed pre-patch script (12,154 chars vs 12,599 new): 4 of the 6 new tests fail on it (test_sabnzbd_core_line_is_suppressed_in_down_window, test_allowlist_matches_log_tag_as_prefix, test_allowlist_keeps_real_faults, test_end_to_end_keeps_only_real_faults); the other 2 assert the not-suppressed direction and pass on both builds, as intended. Live run now reports 2 lines, not 18.
  • Script sha256 31bd0346ff6baee2b06d6e128e4860bffe978a54b8f344c6aac9946e81f490ac; test sha256 ff7780a93ce37fb202d6e2c649da522600ba3f56386670583ff07ca57fff5058. Both paths are gitignored (.gitignore:2:*), so this entry is the durable record.
  • The remaining 2 lines are a real fault, not noise: light.guest_led_b (192.168.68.94) and light.guest_bathroom_guest_led_a (192.168.68.101) timed out at 09:58 and stay unavailable. Same pair failed 2026-09-14 18:46 and 22:46 and 2026-09-15 06:32, and their addresses move between runs (Guest LED A at 192.168.68.127, then .125, now .101), which points at DHCP lease churn rather than dead hardware. Fix is a DHCP reservation or a power cycle.
  • Unavailable entities 109 of 703, threshold 150, no breach. HA state RUNNING.

2026-09-13: ha_health_check.py no longer alerts on localtuya's malformed-UDP-packet callback error. custom_components/localtuya/discovery.py datagram_received slices an arbitrary broadcast, decrypts it, and on failure falls back to data.decode(); a malformed packet raises ValueError: The length of the provided data is not a multiple of the block length then UnicodeDecodeError: 'utf-8' codec can't decode byte 0xac, which HA logs as a generic job exception. Observed exactly twice: 2026-09-12 16:11:08 and 2026-09-13 08:22:36 AEST.

  • The trap: HA logs the generic header Error doing job: Exception in callback _SelectorDatagramTransport._read_ready() (task: None) with [homeassistant], not the component name. A filter keyed on localtuya can never match the collected line, because the collector stores only the header. Verified: of 399 collected ERROR lines, zero mention localtuya.
  • New localtuya_bad_packet_noise(line, context) requires the datagram header and all three traceback marks (custom_components/localtuya/discovery.py, the block-length ValueError, and UnicodeDecodeError). recent_error_lines() now keeps the following non-timestamped lines as context so the component can be identified. A datagram error from any other component, any other localtuya error, and a bare header with no traceback all still alert.
  • Verified safe to suppress: the localtuya config entry has devices: {} and no_cloud: true, the device registry holds 0 localtuya devices, and the entity registry holds 0 entities on the localtuya platform. The 65 tuya-family entities are all on the separate cloud tuya platform. There is no entity path for this handler to affect.
  • Proven on the live log with the window widened to 30 days: old code collected both lines, new code collects zero. Exactly 2 lines removed, no collateral suppression (399 to 397), --verbose and --simulate-breach still behave correctly.
  • Regression test ~/.hermes/tests/test_ha_health_check.py, 9 tests. Falsified: 8 of 9 fail against the reconstructed pre-patch script (/tmp/falsify/old_ha_health_check.py, 9,813 chars vs 11,899 new).
  • Script sha256 bde0e56f3a5617a6c8f271805a59da79c788f41a5c8c2b7f8bb0f1bbf2f0e7ab; test sha256 66d5f355d63571d7af62839b79e5bb30e5db8f4aa49281c5f0305d316130e3cd. Both paths are gitignored (.gitignore:2:*), so this entry is the durable record.
  • Note: patching the HACS component itself is not durable, since a component update overwrites it. The health-check filter is the durable fix for the alert; the underlying bad-packet handling in discovery.py remains unfixed upstream.

2026-09-12 18:15 AEST: ha_health_check.py no longer alerts on *arr integration retries. The 17:00 AEST launchd stop pass (com.dwayne.arr-schedule apply, 1/0 exit) leaves all six containers down until 00:00, and HA then logs one connection error per integration per poll. The hourly checker only filtered SABnzbd's "Connection to SABnzbd API failed" wording, so the 17:21:35 radarr line (Request exception for 'http://localhost:17878/api/v3/calendar' ... Cannot connect to host localhost:17878) raised a false alarm.

  • New arr_down_window_noise() drops connection and timeout errors for the six *arr components (radarr, sonarr, lidarr, sabnzbd, overseerr, prowlarr) inside 05:00-00:00 AEST, matching three wordings: "Connection to SABnzbd API failed", "Error while fetching data", "Cannot connect to host localhost", "Request timeout for 'http://localhost".
  • A non-downtime failure from the same components still alerts: HTTP 401 Unauthorized and invalid API key both survive the filter (verified by direct call).
  • Verified against the live log: the single in-window line at 17:21:35 is filtered, leaving zero, so the script printed [SILENT]. --verbose and --simulate-breach paths still print correctly.
  • Script sha256 6f9187a20525210e0d251f7768f2a50edc17ee706023c67058e8b55676c8c8ad. Copy at ~/.hermes/backups/ha_health_check.py.20260912-181556.bak.

2026-09-12 17:14 AEST: the recurring network_attention card on sensor.camera_vision_hub_max is closed and the sentinel is clean (result noop, before 0 / after 0). Two structural fixes landed in hermes_home_integration_sentinel.py:

  • network_indicated now evaluates a schedule-filtered log tail. schedule_noise_free_log() drops localhost:17878 / localhost:18989 / localhost:18686 connection errors during 05:00-00:00 AEST, when the *arr containers are scheduled down by com.dwayne.arr-schedule and their HA integrations retry every poll. Before this, 194 matching lines sat in the 500-line tail and every tick was labelled network_attention with a bogus "repair the network path" instruction. A real non-*arr host fault still sets the flag (verified with a synthetic Cannot connect to host 10.0.0.5 line, which survives the filter).
  • bad_attention_entities() now excludes sensor.camera_vision_* outright. These are script-published analysis derivatives, not integration health. The camera entity is the authoritative check, already routed through HEALTH_ALIASES.
  • Hub Max stream recovered on its own: ha_camera_vision_analysis.py --camera hub_max at 17:08:17 AEST returned ok, sensor.camera_vision_hub_max state Active (description "Empty room with a white paneled ceiling and large glass windows showing an overcast backyard and neighboring houses."), vision_analysis.json status complete. Backup of the pre-fix script: ~/.hermes/scripts/hermes_home_integration_sentinel.py.bak-pre-netfilter-20260912-171238.
  • Residual: sensor.camera_vision_hub_max remains motion-triggered only, so it will still read stale between event.lounge_display_motion events. That no longer pages. Confirmed --force run at 17:14:38 AEST wrote noop to ~/.hermes/ops/state/home_integration_sentinel.json. Source: sentinel source, live HA state read, /Users/dwayne/.homeassistant/www/camera-stills/, /Users/dwayne/ARR/arr-schedule.log, 2026-09-12.

superseded 2026-09-12 17:14 AEST: 2026-09-12 09:32 AEST: the Hub Max recovery of 2026-09-11 did not hold. hermes_home_integration_sentinel.py reports network_attention, before 1 / after 1 / resolved 0 / actions 0, still affected sensor.camera_vision_hub_max. House status at 09:32: 561/689 entities available, 128 unavailable/unknown, Power 3701W (sensor.shelly_em_channel_1_power_2), climate heat with room 23C against a 22C target. The fault is the Nest Hub Max device, not the HA container or Colima path that the sentinel's "Next" line points at:

Superseded detail from that 09:32 reading:

  • camera.lounge_display reports state streaming, but /api/camera_proxy/camera.lounge_display returns the 2689-byte 640x480 greyscale Nest placeholder PNG (sha256 39b5d8fd9977db379fcceef2398d89b17e3ece8c0a2dd6e6b8292761b330e75e), byte-identical to the stored placeholder .hub_max.41405.[redacted-id].candidate.jpg. No real frame reaches HA.
  • hub_max.jpg is stale from 2026-09-11 20:17 AEST; sensor.camera_vision_hub_max last changed 2026-09-11T20:07:08Z and reads Analysis unavailable: no fresh camera frame (within 5 minutes).
  • media_player.kitchen_display unavailable since 2026-09-11T23:04:00Z (09:04 AEST 2026-09-12). Ping to 192.168.68.79 is 100% loss (3/3); ARP still shows e8:ea:6a:82:d7:1c on en5.
  • Manual recovery attempt at 2026-09-12 09:37 AEST (ha_camera_vision_analysis.py --camera hub_max) returned unavailable, unlike the 2026-09-11 20:08 attempt that succeeded once. sensor.camera_vision_summary reads 6/7 cameras analyzed at 09:24.

Only a manual power-cycle of the Hub Max restores the stream; the sensor self-clears on the next successful sweep. The sentinel suggests no action of its own (actions 0) because the entity is unmapped with no config entry to reload. Host disk pressure that was aborting sweeps has eased: /System/Volumes/Data is at 86% with 30 GiB free, versus 98% with 4.7 GiB on 2026-09-11. Source: ~/.hermes/ops/state/home_integration_sentinel.json, live HA state and camera proxy read, ping/arp, ~/.homeassistant/www/camera-stills/, df -h, 2026-09-12.

2026-09-11 20:13 AEST: sensor.camera_vision_hub_max is recovered and the sentinel is clean. hermes_home_integration_sentinel.py --force returned noop, before_bad [], after_bad [], house 529/686 entities available, 157 unavailable/unknown. Recovery action: ran ha_camera_vision_analysis.py --camera hub_max at 20:08:59 AEST, which returned ok and set the sensor to Active; sensor.camera_vision_summary then read 7/7 cameras analyzed at 20:08. Source: live HA state read, /Users/dwayne/.homeassistant/camera-vision.log, /Users/dwayne/.hermes/ops/state/home_integration_sentinel.json.

Two structural faults behind that incident, both verified 2026-09-11:

  • The sentinel's network_attention label was a false signal. NETWORK_FAULT_PATTERNS matched homeassistant.components.radarr and .sonarr lines at 17:00-17:02 AEST that sit inside the 500-line log tail. Those integrations poll http://localhost:17878 (radarr) and http://localhost:18989 (sonarr); neither port listens on the host, and docker port radarr / docker port sonarr return nothing, so the containers publish no host port. The containers themselves are up (radarr and sonarr started 2026-09-10T21:23 UTC, up 13 hours). This is an integration endpoint mismatch, not a Colima or HA network fault. Source: ~/.hermes/scripts/hermes_home_integration_sentinel.py lines 83-87 and 239-244, grep over /Users/dwayne/.homeassistant/home-assistant.log, docker ps, lsof -nP -iTCP -sTCP:LISTEN.
  • sensor.camera_vision_hub_max is motion-triggered only. The only automation that analyzes it is automation.ai_camera_analysis_hub_max_motion, which fires on event.lounge_display_motion. That event last changed 2026-09-11T03:33:03Z (13:33 AEST). Between motion events the published still goes stale, the script's 5-minute freshness test fails, and the sensor falls back to Analysis unavailable: no fresh camera frame (within 5 minutes). The 300-second launchd job com.dwayne.ha-expose-camera-stills refreshes the still files but does not update this sensor. The device itself was streaming (61647-byte hub_max.jpg, camera.lounge_display state streaming), unlike the 2026-09-09 Hub Max outage above. Result: the sentinel re-flags an artifact that is stale by design. Source: /Users/dwayne/.homeassistant/packages/camera_ai_analysis.yaml lines 58-80, /Users/dwayne/.hermes/scripts/ha_camera_vision_analysis.py lines 193-258, launchd plist com.dwayne.ha-expose-camera-stills.

Host disk pressure threatens this same pipeline, observed 2026-09-11: /System/Volumes/Data at 98% used with 4.7 GiB free (233 GiB volume). camera-vision.log line 127 records OSError: [Errno 28] No space left on device on www/camera-stills/vision_analysis.tmp at the 15:59:41 AEST sweep, which aborted that sweep. Largest consumers: /Users/dwayne/.homeassistant/backups 9.5G (three automatic backups of about 3.2G, newest 2026-09-11 05:41), /Users/dwayne/.hermes/backups 5.7G (pre-update-2026-09-10-122306.zip 3.3G and pre-update-2026-08-27-024100.zip 2.4G), /Users/dwayne/Library/Caches 7.0G, /Users/dwayne/.colima 9.5G. Source: df -h, du -sh, /Users/dwayne/.homeassistant/camera-vision.log.

HA is reachable at http://100.80.168.13:8123 (macbook-pro-3, Tailscale 100.80.168.13). Previously ran on Pro-2 (100.97.49.102) via ai.openclaw.ha-tunnel launchd; that host is now retired. All cron scripts referencing the old HA URL were updated on 2026-08-02. Source: live HA reachability check (HTTP 401 = alive, needs token), script audit, 2026-08-02. Tokens live as names only: ~/.ha-token, ~/.openclaw/.ha-token, ~/.openclaw/credentials/homeassistant.json, ~/.homeassistant_tokens.

2026-09-09: Dwayne requested removal of X sensor entities. Removed 35 sensor/binary_sensor registry entries across the old X and current dodgers mobile-app instances. Preserved device trackers and notification entities. Verified zero target registry rows and live states, and sensor.x_camera_stream returns 404. Sentinel dry-run initially saw the registry's delayed disk save, then returned silent after persistence. This supersedes the X camera incident below. Source: /Users/dwayne/.hermes/ops/ha-x-retirement-20260908T212501Z/receipt.json, direct HA state read, and sentinel dry-run. Mobile-app instances remain installed; future app sensor re-registration is not ruled out.

Current TP-Link camera incident, verified 2026-07-17 19:05 AEST:

  • HA/container restart is not indicated for the persistent camera cluster. hermes_home_integration_sentinel.py --dry-run reports network_attention, restart_indicated=false, network_indicated=true, before/after 10 affected entities.
  • Driveway 192.168.68.55 and Roof 192.168.68.61 are reachable on TCP 443 from the host and HA container, but HA logs show Driveway credential/auth challenge mismatch and Roof TP-Link query timeout.
  • Entrance 192.168.68.84 and Pool/Garden 192.168.68.93 are not present at their known MACs (98:ba:5f:47:d0:f9, 3c:64:cf:c7:6b:1d) in host ARP after a LAN sweep; host ARP remains incomplete and HA container gets connection refused on TCP 443.
  • Do not spend generic HA/Colima/gateway/router/mesh reboot loops for this symptom set. Next useful action is physical/device-network recovery for .84/.93, Roof protocol investigation for .61, and exact approval before any Driveway TP-Link credential/auth metadata repair.

Shelly EM + ONVIF camera cluster outage, observed 2026-08-22 to 2026-08-24 (source: ~/.hermes/ops/state/home_integration_sentinel.json, kg daily-air-fryer-cost-report):

  • 2026-08-22 10:00 daily-air-fryer-cost-report blocked with "HA state fetch failed" (now:kwh dropped -1199.71). 2026-08-23 10:00 it recovered via baseline reset to 1202.896 kWh.
  • hermes_home_integration_sentinel reports network_attention, restart_indicated=false, network_indicated=false (note: differs from the older TP-Link incident where network_indicated=true). Reload actions on shelly and onvif domains return HTTP 200 but resolve 0 entities.
  • 19 persisted bad entities (2026-08-24 19:14): the full Shelly EM set (12: binary_sensor.shelly_em_overpowering, sensor.shelly_em_channel_1_energy, _energy_returned, _power_2, _power_factor, _voltage_2, sensor.shelly_em_channel_2_energy, _energy_returned, _power, _power_factor, _voltage, switch.shelly_em) plus 3 ONVIF cameras (camera.driveway_tc41_onvif_mainstream, camera.entrance_tc41_onvif_mainstream, camera.pool_c510w_onvif_mainstream). 4 unmapped aliases (sensor.shelly_em_channel_1_energy_cost, _energy_cost_2, _power, _voltage).
  • House status 2026-08-24 19:14: HA 489/642 entities available, 153 unavailable/unknown, Power unavailable. Climate/weather/lights healthy.
  • This is a distinct cluster from the TP-Link camera incident above; the sentinel reload loop is not resolving it. Next useful action is device/network recovery for the Shelly EM and the three ONVIF cameras, not further reloads.

Camera cluster status 2026-09-05 (source: ~/.hermes/ops/state/home_integration_sentinel.json): the Shelly EM + ONVIF cluster has resolved. Sentinel now reports a single persisted bad entity sensor.x_camera_stream (mobile_app domain, title "dodgers"), network_attention, network_indicated=true (back to true, matching the older TP-Link incident), restart_indicated=false. Reload action on mobile_app returns HTTP 200 but resolves 0 entities. House status 2026-09-05 21:52 AEST: HA 509/640 entities available, 131 unavailable/unknown, Power back at 498W (sensor.shelly_em_channel_1_power_2), climate/weather/lights healthy. The remaining sensor.x_camera_stream is a distinct, smaller issue from the resolved Shelly/ONVIF cluster.

Nest Hub Max camera feed outage, verified 2026-09-09 19:20 AEST (source: ~/.hermes/ops/state/home_integration_sentinel.json, live probes, session cron_3681ce3a337e_20260909_185256): sentinel flags sensor.camera_vision_hub_max (script-published AI vision output from ha_camera_vision_analysis.py, unmapped, no config entry to reload). Root cause is the Hub Max device itself, not the HA container/Colima path: Nest WebRTC returns 400 FAILED_PRECONDITION "The camera is not available for streaming" (retested 19:16-19:20 AEST), camera.lounge_display proxy returns the 2689-byte Nest placeholder, hub_max.jpg frozen since 09:01 AEST and byte-identical (sha256 f4b5056f) to the 18:01 sweep capture, media_player.kitchen_display unavailable since 2026-09-09 09:50 AEST, and ping to 192.168.68.79 (ARP entry e8:ea:6a:82:d7:1c on en5) is 100% lost. Other Nest devices (front door, front stairs) stream fine through the same helper. The sentinel's network_attention label came from unrelated DNS/OverKiz log noise. Recovery requires a manual power-cycle of the Hub Max; sensor self-clears on the next successful vision sweep once the device streams again.

Automation surface (scripts in ~/.hermes/scripts/, observed 2026-07-09):

  • ha-entity-guard cron 6×/day (07:17 etc.), missing-entity guard, silent when OK.
  • Power: ha_power_summary / ha_power_known_loads ("unavailable ≠ zero" fix 2026-06-12), reports to ~/.hermes/reports/ha-power/; anomaly detector db ~/.hermes/ops/anomaly_detector.db; battery-low sweep.
  • Cameras: camera_sweep_collect 06/12/18:00 → OpenClaw analysis 03/06/12/18:10; sweeps stored ~/.hermes/camera_sweeps/<ts>/; driveway real-still patch 2026-06-21.
  • Climate/energy: globird-ac-optimizer (tariff-window AC control, deliberate hours 07/11/13/14/23), air-fryer cost + done-watch.
  • cross-status ha= field counts available/total entities (211/261 with warn flag at 02:35 2026-07-09, investigate threshold before alarming).

Log​

  • 2026-10-04 (weekly synthesis, the CoreBluetooth crash class recurs): Home Assistant crashed and relaunched again on 2026-10-04. A fresh crash report ~/Library/Logs/DiagnosticReports/python3.14-2026-10-04-015825.ips (89670 bytes, written 01:58:25) records SIGSEGV / EXC_BAD_ACCESS at 0x0 under coalitionName com.dwayne.homeassistant, with the stack passing through libffi and -[CBCentralManager handlePeripheralDiscovered:] and _objc before it dies. This is the same signature as the 2026-09-20 and 2026-09-29 crashes already logged above, so the fault was never closed. The live log confirms an unplanned restart: ~/.homeassistant/home-assistant.log opens at 2026-10-04 01:58:19 with the custom-integration warnings and reaches Starting Home Assistant 2026.8.2 at 01:59:07; the process (PID 9357, /Users/dwayne/.homeassistant-venv/bin/python3.14 .../hass) has been up since 01:58 and was healthy at read time. Only one crash report survives in the DiagnosticReports directory, so the 01:58 event is the one on record for the week, but it is a recurrence of a known open defect rather than a new one: any sensor.camera_vision_* or other runtime-published entity that reads no data right now may simply be missing its republish after this restart. Sources: ~/Library/Logs/DiagnosticReports/python3.14-2026-10-04-015825.ips, ~/.homeassistant/home-assistant.log, live ps, read 2026-10-04 07:10-07:20 AEDT., hermes.

2026-10-02 09:20-09:40 AEST (power-baseline report: two false statements corrected): the power-baseline box (cron 6abbadb58319, schedule 5 8,17 * * *, script power_baseline_monitor.py) carried a note that contradicted its own output. It read "Only the fridge and towel heater are metered directly", while the same box printed Measured: 528W computed from measured_values = fridge, dishwasher, pool_filter_pump, pool_heat_pump. The towel heater is not in the map at all. The note now builds its list from result['measured_values'] at runtime, so it cannot drift again.

The second claim was tested rather than assumed: a 5-day recorder analysis of sensor.power_untracked_after_known conditioned on sensor.pool_filter_pump_power and sensor.shelly_em_channel_2_power both below 50 W (n=3837, the pool and aircon genuinely off) gives a median residual of 337 W, p10 214, p90 427. So "a standing base near 350 W" is real; the note now says 340 W and cites the analysis and date.

Also superseded in the same file's comment: it claimed "no W-unit sensor exists for those loads" about six retired names removed 2026-09-27. As of 2026-10-02 sensor.washing_macine_current_consumption (70.1 W, ~2.1 kW heating) and sensor.heat_pump_dryer_current_consumption (1.6 W, ~640 W running) ARE live W sensors, because their TP-Link P110 plugs were re-enabled 2026-10-01. They remain out of the measured map deliberately, pending Dwayne's approval, because adding them changes the figure his twice-daily report reads. The comment now states this.

Method note for this class of fault: a note or comment that describes what a script does must be read against what the script actually computes. Both false statements here were invisible to every structural check (the script ran, exit 0, numbers reconciled to CT1) and survived because the text was never cross-checked against the data structure it described. Render the output and read it against the code. Sources: ~/.hermes/scripts/power_baseline_monitor.py, ~/.hermes/cron/jobs.json (job 6abbadb58319), ~/.homeassistant/recorder/home-assistant_v2.db read-only, live HA /api/states, 2026-10-02 09:20-09:40 AEST., hermes.

2026-10-02 08:55-09:15 AEST (supersede: the pump-plug rename): a platform: integration counter cannot be renamed without an HA restart, and repointing a template at its new name silently zeroes the term. While renaming the pool filter pump plug this session, configuration.yaml's integration block was edited from source: sensor.home_entertainment_power, name: "Home Entertainment Total Energy" to source: sensor.pool_filter_pump_power, name: "Pool Filter Pump Energy", then homeassistant.reload_core_config was called twice. The old entity sensor.home_entertainment_total_energy kept running (245.96 kWh at 08:54:46Z) and no sensor.pool_filter_pump_energy ever appeared, only one Setting up integration.sensor line was logged for the whole reload, not the four the block defines.

Cause, verified rather than assumed: the integration sensors are a legacy YAML platform with no config_entry_id and no entry in core.entity_registry at all (checked directly: registry holds 860 entities and contains zero with platform: integration matching this block, and zero matching home_entertainment). Their entity_id is derived from name only when there is no registry record to honour, so the id is fixed for the life of the process. config/entity_registry/update on the orphan answers not_found: Entity not found, and the id does not appear in core.restore_state. The running instance was still the pre-edit config at that point (integration.sensor set up once, at 08:54:50), so the edit had not been read; after reload_core_config the new name still did not appear.

Decision, and why: rather than leave a term reading states('...') | float(0) against an entity that does not exist, which fails silently to 0 and understates Monthly Consumption with no error anywhere, the integration block entry for the pump was removed, and the template repointed at the device's own native counter sensor.pool_filter_pump_total_energy (2.1 kWh, total_increasing, Tuya). That counter is the real meter; the integration counter was a derived duplicate of the same physical load, so dropping the derivation removes a redundancy rather than a measurement. Verified after template.reload: sensor.monthly_consumption 2.62 kWh, no stale id in any live config, and sensor.home_entertainment_total_energy is the only old id left live, documented in ## Now as a deliberate leftover. If the integration counter is wanted back under the new name, it needs an HA restart (a gated action) and will start from 0.

Also settled this session, same root cause class: search_files and find over ~/.homeassistant time out (>200 s, exit 124) because the recorder DB and log are large; scope the probe or use the API/registry files directly. template.reload is template/reload, not homeassistant/template_reload, the latter 404s. And reload_core_config is the only reload that re-reads configuration.yaml. Sources: live HA /api/states, home-assistant.log line 121260, ~/.homeassistant/.storage/core.entity_registry, ~/.homeassistant/.storage/core.restore_state, ~/.homeassistant/configuration.yaml, read 2026-10-02 08:55-09:15 AEST., hermes.

  • 2026-09-30 (18:12-18:22 AEST, Kasa KLAP 403 burst, and a new *arr false-positive variant): the 18:12 ha_health_check.py run reported 5 ERROR lines in its 70-minute window (17:02-18:12): four kasa.transports.klaptransport Query failed after successful authentication ... Response status is 403 lines (192.168.68.67 18:10:25, .66 18:10:27, .68 18:10:28, .66 18:10:34) and one [homeassistant] Error doing job: Task exception was never retrieved at 18:06:58. The burst kept running after the run: 20 kasa lines between 18:10:25 and 18:14:38, 23 for the day (1 at 00:36, 1 at 02:18, 1 at 15:05, 20 in the evening burst), against a prior recorded daily maximum of 7 (2026-09-17). Hosts cycled .66 (8 lines), .68 (7), .67 (5); none recurred after 18:14:38.

  • Impact is not nil, superseding the 2026-09-17 16:13 entry below. core.entity_registry read 2026-09-30: Washing Macine P110 (192.168.68.66) and Heat pump dryer P110 (192.168.68.67) each have 17 of 20 entities disabled, so three are enabled per entry (sensor.*_current_consumption, sensor.*_voltage, switch.*). Data centre P110M (192.168.68.109) and Data centre 2 P110 (192.168.68.68) are still 20/20 disabled. The enabled telemetry kept serving through the burst: /api/history over 3 h holds 1705 points for sensor.heat_pump_dryer_current_consumption and 1644 for sensor.washing_macine_voltage with no gap over 300 s, and both read live at 18:20 AEST (241.7 V, 1.3 A).

  • All three hosts are on the LAN: ping -c 3 -i 0.3 at 18:18 AEST returned 3/3 packets with 0.0% loss, round-trip average 7.4 ms (.66), 8.4 ms (.67), 5.3 ms (.68). The KLAP 403 stays the documented stale-handshake transient (python-kasa issue #784), retrying within seconds.

  • The 18:06:58 Task exception is a new variant of the *arr down-window noise: the traceback is aiopyarr/request_client.py to aiohttp ending in ConnectionRefusedError: [Errno 61] Connect call failed ('127.0.0.1', 17878) (Radarr) under the log tag [homeassistant], so arr_down_window_noise() does not match it (it tests for homeassistant.components.radarr in the line) and it alerts while the container is deliberately down outside 00:00-05:00. The same variant was logged at 2026-09-29 23:24:08. Candidate gate for a future script pass, not applied by this monitor run.

  • HA itself is healthy: /api/ 200 API running., /api/config state=RUNNING version 2026.8.2, 729 states with 123 unavailable against threshold 150, home-assistant.log.fault unchanged at 314022 bytes (last write 2026-08-25 00:02).

  • Source: ~/.homeassistant/home-assistant.log, .storage/core.entity_registry, live HA /api/states and /api/history reads, ping probes, 2026-09-30 18:16-18:22 AEST., hermes.

  • 2026-09-29 (23:36-23:50 AEST, camera still refresh: unbounded read caused a 5-hour hung run and a cron timeout): The ha-exposed-camera-stills-refresh cron job (835ebfa7498e, */30 * * * *, ha_expose_camera_stills_quiet.sh) reported Script timed out after 300s at 23:30. Root cause was not the cron cap. An earlier run of the same wrapper, started by the launchd twin com.dwayne.ha-expose-camera-stills (StartInterval 300) at 18:30:30, was still alive at 23:36 after 5h06m, parked in recv inside urllib with 9.2 GB physical footprint and swap 18.5 GB of 19.4 GB. sample 5186 showed the stack in _buffered_readline -> sock_recv_into -> internal_select -> poll. The mechanism: ha_request() used urllib.urlopen(timeout=6).read(), and a socket timeout resets on every recv, so HA's camera proxy drip-feeding bytes keeps the call alive forever. The read() also blocks until it has the full buffer, so a deadline check placed around it never runs. Fix, in ha_expose_camera_stills.py: ha_request() takes an absolute deadline and reads the body with resp.read1(65536) in a loop, checking time.monotonic() each pass and capping the body at 32 MiB; the run is bounded by RUN_BUDGET_S = 240 and each camera by CAMERA_BUDGET_S = 30, both clamped so a stalled camera reports skipped_budget and keeps its previous still rather than stalling the run. The wrapper now holds a /usr/bin/lockf -t 0 single-instance lock so the cron and launchd twins cannot stack (a held lock exits 75, which the wrapper treats as success). A second wrapper bug surfaced during verification: under set -euo pipefail, tail_msg=$(grep -v '^lockf:' "$ERR" | tail -c 360 ...) aborted the whole failure branch when grep matched nothing, so a failure with empty stderr reported nothing at all; the command substitution now carries || true. Verification: the drip-feed case is reproduced in-process against a local HTTP server that writes one byte per second; the unbounded build hangs indefinitely (killed at a 40 s outer limit, then at 120 s) while the fixed build raises TimeoutError at the deadline (5.8 s for a 5 s budget, 9.0 s for 8 s) and a normal 5002-byte body still returns intact. A 33 MiB body is rejected by the cap in 0.1 s. 15/15 ad-hoc checks passed against an isolated copy of the wrapper (held lock is an instant silent no-op, stale stderr cleared, real failure reports compact text with exit 0, silent failure still reports, success path is silent); the wrapper checks needed the isolated copy because a live launchd run holds the real lock every few minutes. Wrapper run by hand, 24-45 s, exit 0. Status at 23:41:42: 5 of 7 current, 2 stale_real (driveway serving its previous still after a failed snapshot, hub_max on the standing Nest "stream not available"). The cron's 23:30 running row and the 23:00 killed run both completed the work before the kill, so the stills were never actually stale for long. Sources: ps/sample of PID 5186, ~/.hermes/scripts/ha_expose_camera_stills.py, ~/.hermes/scripts/ha_expose_camera_stills_quiet.sh, ~/.hermes/cron/executions.db, ~/.homeassistant/www/camera-stills/status.json, read 2026-09-29 23:36-23:50 AEST., hermes.

  • 2026-09-27 (weekly synthesis): The week's camera evidence, read from the two 06:00/06:01 sweeps rather than inferred. The 2026-09-27 06:00 sweep captured 6 of 6 snapshots with 6 changed reads: stills current for 4, stale for 2, failed 0. Front Door, Front Stairs, Garden and Pool returned usable frames; the Driveway served a stale still with no vision summary, so that frame is unknown rather than clear. The companion 06:01 sweep reported 7 of 7 cameras captured, 0 failed, 4 frames current and 3 stale (Front Stairs after a Nest API 500 Internal error, Hub Max after a 429 rate limit on ExecuteDeviceCommand, Driveway after a 404 on the snapshot endpoint), exposure normal across all frames (luma 110-137, so no camera is blind or washed out), and no frame flagged as a Nest placeholder. Its verdict was attention, not failure: three of seven frames are not from this sweep, so treat those three as unverified for the 06:01 window, and a repeat of the 429 points at the live WebRTC pull being throttled at sweep cadence rather than at the cameras. Standing faults unchanged in kind: the Driveway TC41 still serves the known superseded still, and the Pool camera viewpoint is still the off-target fault recorded on 2026-09-24 and 2026-09-26. Neither was actioned, which is correct for a monitor run. Sources: ~/.hermes/cron/output/b40827332b0a/2026-09-27_06-01-26.md, ~/.hermes/cron/output/531efd75cf09/2026-09-27_06-01-11.md, read 2026-09-27 06:00-06:10 AEST., hermes.

  • 2026-09-26 (06:12-07:20 AEST, Open-Meteo weather false positive fixed in ha_health_check.py): The 07:12 hourly run alerted on 1 ERROR line, [homeassistant.components.open_meteo] Error fetching open_meteo_zone.home data: Open-Meteo API communication error at 06:03:03.116. Not a live fault. The coordinator polls the public Open-Meteo API every 30 minutes (SCAN_INTERVAL in homeassistant/components/open_meteo/const.py, confirmed in the running venv at miniforge3/envs/ha314/lib/python3.14/site-packages/homeassistant/components/open_meteo/), so a single lost poll is followed by Fetching open_meteo_zone.home data recovered roughly 30 minutes later: 06:33:04.327 here, 30 min 1.2 s after the error. The current log holds exactly one open_meteo error and one recovery, home-assistant.log.1 and home-assistant.log.fault hold none, and weather.home kept serving through it (sunny, last_updated 2026-09-26T21:03:05Z, read 07:13 AEST). Fix: reuse the generalised coordinator_transient_fetch_noise helper with a new OPEN_METEO_COORDINATOR_TAG / OPEN_METEO_TRANSIENT_FETCH_MARK / OPEN_METEO_RECOVERY_MARK and a 45-minute OPEN_METEO_RECOVERY_WINDOW. The window is deliberately wider than the 30-minute poll interval, otherwise a single lost poll could never match its own recovery line; it is still narrow enough that an outage lasting more than two polls keeps the error visible. No entity-state gate is used, matching IPP. The marks stay out of ERROR_ALLOWLIST, so an unrecovered API outage still alerts. Verification: tests/test_ha_health_check.py grew 36 -> 44 tests, all passing under hermes-agent/venv/bin/python -m pytest (44 passed in 0.21 s), covering suppress-on-recovery, no-recovery still alerts, recovery past the window still alerts, repeated errors before recovery still alert, window wider than the poll interval, the mark not matching the SABnzbd/IPP/Shelly lines, not allowlisted, and both end-to-end paths. Falsified: replayed against the pre-fix build (ha_health_check.py.bak-pre-ipp-gate-20260926 copied to /tmp) the unresolved-line count is 29 versus 22 with the gate armed; disarming only this gate (OPEN_METEO_RECOVERY_WINDOW = 0) puts the open_meteo line back and hides nothing else; the live 70-minute run prints the bare [SILENT] with exit 0 and --verbose reports all probes passed. Unwind: delete the four OPEN_METEO_* constants and the one call site if the cadence ever stops being single-poll. Sources: ~/.homeassistant/home-assistant.log (and .log.1, .log.fault), live HA /api/states and /api/states/weather.home, ~/.hermes/scripts/ha_health_check.py, 2026-09-26 06:12-07:20 AEST., hermes.

  • 2026-09-26 (05:12-05:30 AEST, IPP printer false positive fixed in ha_health_check.py): The 05:12 hourly run alerted on 2 ERROR lines, both the same line: [homeassistant.components.ipp.coordinator] Error fetching ipp data: Invalid response from API: Timeout occurred while connecting to IPP server. Not a live fault. The coordinator polls the Canon TS8300 at ipp://192.168.68.91/ipp/print every 60 s; a poll lost to the printer sleeping or answering slowly is followed by Fetching ipp data recovered on the very next cycle. The current log holds exactly 4 ipp errors (2026-09-25 21:11:08, 2026-09-26 00:26:17, 04:04:24, 04:19:32), every one with a recovery 60 s later, and home-assistant.log.1 holds none. The device proves itself healthy on the entity path: sensor.canon_ts8300_series reads idle (last_changed 2026-09-25T18:20:33Z) and no ipp entity is unavailable. Fix: the SABnzbd helper's recovery-proof logic is generalised into coordinator_transient_fetch_noise(line, when, tail, idx, fetch_mark, recovery_mark, coordinator_tag, window), with sabnzbd_transient_fetch_noise now a thin wrapper, and the IPP coordinator gets IPP_TRANSIENT_FETCH_MARK / IPP_RECOVERY_MARK / IPP_COORDINATOR_TAG / IPP_RECOVERY_WINDOW (180 s). The line drops only when a recovered line for the same coordinator follows inside the window; a second ERROR from that coordinator before any recovery, or a recovery past the window, keeps it visible. There is deliberately no entity-state gate here (unlike Shelly and Panasonic): the printer's six ink sensors read unknown permanently and cannot prove health, so the log's own recovery line is the whole guard. The marks are absent from ERROR_ALLOWLIST. Verification: tests/test_ha_health_check.py grew 29 -> 36 tests, all passing under hermes-agent/venv/bin/python -m pytest (36 passed in 0.19 s), covering suppress-on-recovery, no-recovery still alerts, recovery past the window still alerts, repeated errors before recovery still alert, the mark does not match the SABnzbd or Shelly lines, and two end-to-end pipeline cases. Falsified: the pre-patch build (backup ha_health_check.py.bak-pre-ipp-gate-20260926, sha256 ee272db17014c3e8eb41affbdc2fc388fba4830d47fbba12f6ccbbccdb60779c) run against the live log still prints the 1-problem alert, while the patched build prints the bare [SILENT] with exit 0 and --verbose reports all probes passed. py_compile clean. Also run as ad-hoc verification (15/15, hermes-verify-ipp-gate.py under $TMPDIR, cleaned up after the run): both marks match the real log lines, the window is 180 s, the transient is suppressed, no-recovery / late-recovery / repeated-error cases still alert, the mark does not match the SABnzbd line, the SABnzbd wrapper still works after the refactor, patched check() returns clean on the live log while the pre-patch build reports the ipp problem, and both CLIs behave as expected. These are ad-hoc checks, not a project suite. Unwind: delete the three IPP_* mark constants and their call site if the cadence ever stops being single-poll. Residual: the generic helper is now used by two coordinators; a future third caller inherits the same "recovery inside the window" proof and must not be given ERROR_ALLOWLIST treatment. Source: /Users/dwayne/.homeassistant/home-assistant.log (+.1), live HA /api/states, /Users/dwayne/.hermes/scripts/ha_health_check.py, ~/.hermes/tests/test_ha_health_check.py, 2026-09-26 05:12-05:30 AEST., hermes.

  • 2026-09-25 (18:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable, the same standing non-actionable set as 2026-09-24: three 2689-byte Nest dark placeholders (camera.lounge_display, camera.home_home, camera.entryway_front_stairs, nest_webrtc_no_server_side_snapshot_dark_placeholder) and camera.driveway_live_view (404, source_superseded_by_real_still). Fresh frames 18:00-18:02 AEST for garden (18:00:31), roof (18:00:34), pool (18:00:53), front stairs and hub max via WebRTC live; front door served its 18:00:58 real still after a 25 s WebRTC timeout. No people, parcels, vehicles or damage in the garden, front door, front stairs or hub max frames; overcast, no precipitation visible. Roof/pergola remains an active jobsite: corrugated metal sheeting partly laid over the metal joists, exposed battens with gaps, blue tarp, stacked concrete blocks along the fence, lumber and a wheelbarrow, no workers in frame, no storm damage. Driveway TC41 now frozen 11 days: published_driveway.jpg md5 3749c7879ed4e6e29665120c5757fc98 is byte-identical across 21 consecutive sweep directories (20260919-080650 to 20260925-180334), the newest distinct driveway still on disk is 20260914-060040_camera_driveway_real_still.jpg, and the embedded watermark still reads 2026-09-14 12:49:17 PM. Live re-probe of 192.168.68.55: ping 3/3 received, 12.0 ms avg, TCP 443 and 554 both open, HTTPS GET returns code 000. camera.driveway_live_view remains unavailable (last_updated 2026-09-23T21:01:23Z). Same port-open-but-silent wedge as 2026-09-15, 2026-09-20 and 2026-09-21; homeassistant.reload_config_entry is exhausted, so the fix is a physical power-cycle of the TC41. Pool camera view is still not on target and not stable: the 18:00:53 frame (camera.garden_live_view → slug pool) shows a side/rear courtyard, deck with a flat pallet and a dark object, a bare-branched tree, a gas grill, a trampoline frame with no mat, stacked lumber, a dark plank fence and a neighbouring verandah, with no pool and no water anywhere in frame. The 12:02 frame is a different courtyard (paved area, wooden extension ladder flat on the ground, hand truck, lumber, again no pool). Frame-to-frame 16x16 average-hash distance between those two is 72 of 256 and 06:00 → 12:03 is 79 of 256, above the 8-33 band the healthy Pool camera normally sits in. The current view is internally consistent, so this is not a data fault: the 18:00:53 sweep file, the 18:02 ha_expose_camera_stills publish (~/.homeassistant/www/camera-stills/pool.jpg, md5 01adab8974) and a direct read of the live HA proxy /api/camera_proxy/camera.garden_live_view all match the sweep frame within 10 of 256. Next action is still a check of the Pool camera's physical mount or pan/tilt home position. Source: ~/.hermes/camera_sweeps/20260925-180334/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/status.json, md5 and aHash comparison across ~/.hermes/camera_sweeps/2026*, live ping/nc/curl against 192.168.68.55 and .84, live HA /api/states and camera proxies. - hermes.

  • 2026-09-24 (14:15 AEST, Roborock away for repair, dated suppression): The Roborock Qrevo MaxV is with the repairer (Dwayne, 2026-09-24), so all 52 of its entities are unavailable by design (vacuum last_changed 2026-09-23T21:01:23Z). The home-integration sentinel (hermes_home_integration_sentinel.py, cron 3681ce3a337e, every 10 min 07:00-21:00) was paging about it every tick: a typical card read "32 entities were affected before it, 32 remain ... another reload is unlikely to help. A Home Assistant restart may be required." That advice was wrong twice over, the integration is not degraded, the hardware is absent, and no reload or HA restart can change it. Fix: a new EXPECTED_ABSENT_INTEGRATIONS set in the sentinel. Each entry is {domain: {until, reason}}, where until is an ISO date. Entities whose config entry belongs to an active domain are dropped from the page list, and the reason is surfaced in the report JSON as expected_absent. Why dated rather than a bare exclusion: suppression must not outlive the repair, so the entry expires on its own date and normal reporting resumes; a malformed date is treated as expired (fails loud to stderr) so a typo cannot silently hide a real outage. Why domain-scoped, not entity-scoped: a whole integration that is physically away is unavailable across all of its entities, so keying on the 52 individual entity IDs would be unmaintainable, while plugins.disabled-style per-entity suppression would still page ~30 times a day. Verification (10/10, $TMPDIR/hermes-verify-roborock-absent.py on /usr/local/bin/python3, the interpreter cron uses): window active; 52 live roborock entities unavailable; zero roborock entities in the page list; a genuinely broken non-roborock entity (sensor.power_consumption_total) still pages, so there is no over-suppression; an expired until stops suppressing; a malformed until stops suppressing; the report carries expected_absent; the real --dry-run returns result=noop, before_bad=0. Falsified: with the mechanism disabled, 32 roborock entities immediately reappear in the page list, so the suppression is doing real work rather than passing vacuously. Script backed up to hermes_home_integration_sentinel.py.bak-pre-roborock-repair-20260923 (sha256 4e23c8277ad40c7c7f7bb38ff9f14cc263ef844d03e6fcf8a34375f7773f953a); final hash 4a91d19b428e341cbfb0d8e1f5544ba1162b152990792dab0afd9c667eccb05e (the earlier b3962dac… was pre-tidy: a first pass printed an "expired" line to stderr on every tick after expiry, which is the log-noise class this fix exists to remove; expiry is now silent while a malformed until still warns on every tick). Verification re-run after the tidy: 12/12, including "expiry writes no stderr noise" and "malformed until warns loudly". A clean run writes nothing to stderr and prints [SILENT], while --json still records expected_absent: ['roborock'] for audit. Unwind: delete the roborock entry from EXPECTED_ABSENT_INTEGRATIONS when the robot comes back, or let it lapse on 2026-10-14. Residual: the sentinel's earlier standing advice ("a HA restart may be required") is produced by RESTART_REQUIRED_PATTERNS matching the HA log, not by the absent integration; it did not fire here, but it remains a path that could mislead on an unrelated log line. Source: live HA /api/states, core.config_entries (roborock entry 01J7JYDCYG5RBH45VAS65M4C90, title dwaynehelena@gmail.com, 58 entities), live --dry-run --json, temp-home falsification., hermes.

  • 2026-09-24 (12:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable. The four failures are the standing non-actionable set: three 2689-byte Nest dark placeholders (camera.lounge_display, camera.home_home, camera.entryway_front_stairs, nest_webrtc_no_server_side_snapshot_dark_placeholder) and camera.driveway_live_view (404, source_superseded_by_real_still). The Pool camera changed viewpoint. published_pool.jpg (camera.garden_live_view) shows a paved rear courtyard with a wooden extension ladder flat on the ground, a metal hand truck, stacked lumber, potted plants, a clothesline and a neighbouring verandah - no pool in frame. Frame-to-frame view distance is the test: consecutive Pool sweeps normally sit at 8-33 of 256 on a 16x16 average-hash grid, and 2026-09-24 06:00 -> 12:03 jumped to 134, the same signature as 2026-09-23 12:03 -> 18:03 (149). The view is stable at 3 and 0 respectively across three independent reads twenty minutes apart: the 12:03 sweep file, the 12:07 ha_expose_camera_stills publish (~/.homeassistant/www/camera-stills/pool.jpg, md5 a22b731b) and a direct read of the live HA camera proxy /api/camera_proxy/camera.garden_live_view at 12:15 (md5 4e3e5d35). This is the camera having moved, not a construction change and not a data fault: the other cameras on the same host and bus are unaffected, and the sibling camera.garden_pergolux_roof_camera (last sweep frame 108886 bytes) still reads the deck-joist jobsite and does show a curved pool edge at frame left. Next action: check the Pool camera's physical mount or pan/tilt home position. Driveway TC41 remains frozen, now 17 consecutive sweeps: camera.driveway_real_still.jpg is byte-identical (md5 3749c7879ed4e6e29665120c5757fc98) across every retained sweep from 2026-09-19 08:06 to 2026-09-24 12:03, and camera.driveway_live_view still reads unavailable (last_updated 2026-09-23T21:01:23Z, 2026-09-24 07:01 AEST); physical power-cycle of the TC41 still stands. Other fresh stills (12:03-12:07): front door clear and sunny with three parked vehicles (white sedan, blue SUV, white pickup), no people, no parcels, yellow-lid and red-lid wheelie bins at the kerb; front stairs shows the brick path, orange flowers and the deck, no people or vehicles; hub max shows an empty lounge looking out to the pool and patio; garden shows parked vehicles along the street. No people, parcels or animals in any 12:03 still except the 08:46 front-door frame already recorded in vision_analysis.json. The three OSError: [Errno 28] No space left on device lines in ha_expose_camera_stills.launchd.out are historical, from 2026-09-11 01:21 and 01:26, not this run; /System/Volumes/Data holds 16 GiB free at 40% capacity on /. Source: ~/.hermes/camera_sweeps/20260924-120329/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/{status.json,pool.jpg}, live HA /api/states and /api/camera_proxy/camera.garden_live_view, md5 comparison across ~/.hermes/camera_sweeps/2026*/*driveway_real_still.jpg, df -h, 2026-09-24 12:03-12:15 AEST. - hermes.

  • 2026-09-24 (01:35 AEST, SABnzbd false-positive fixed in ha_health_check.py): The 01:12 run's single ERROR line was a false positive. Root cause: HA's SABnzbd coordinator polls every 30 s (~/.homeassistant-venv/lib/python3.14/site-packages/homeassistant/components/sabnzbd/coordinator.py:37), and pysabnzbd.refresh_data() makes two calls per poll (get_queue then get_history) each with a 5 s client timeout (pysabnzbd/__init__.py:7,41); a poll that loses its response raises SabnzbdApiException and HA logs Error fetching SABnzbd data: Error while fetching data. The two conditions that trigger it inside the 00:00-05:00 up window are SABnzbd's own post-processing (unrar/par2/deobfuscate/move across the virtiofs share) blocking /api?mode=queue, and the 05:00 stop pass disconnecting in-flight polls. Evidence the container is healthy: 30 direct queue+history poll pairs against 127.0.0.1:18090 ran p50 5 ms, worst pair 18 ms; /api?mode=version returned {"version":"5.1.2"}; sensor.arr_sabnzbd_status reads Idle with no SABnzbd entity unavailable; the container is Up About an hour with restarts=0. Every ERROR line in the current log is followed by Fetching SABnzbd data recovered: 2026-09-24 00:05:44 -> 00:06:14 (30 s), 00:08:50 -> 00:09:20 (30 s), 00:32:26 -> 00:32:56 (30 s), and 2026-09-23 00:27:29 -> 00:28:35 (66 s). Each sits beside a postproc burst (Finished unpack_magic, Queue finished) at 00:32:02. The 05:00 errors recover at 00:00 next day (68,402 s), which is the container being deliberately down, not a fault. Fix: new sabnzbd_transient_fetch_noise() suppresses the coordinator line only when (a) a Fetching SABnzbd data recovered line for the same coordinator follows inside SABNZBD_RECOVERY_WINDOW (120 s) and (b) sensor.arr_sabnzbd_status is not unavailable/unknown; a second SABnzbd ERROR or any recovery past the window returns False, so an outage still alerts. Deliberately NOT added to ERROR_ALLOWLIST, which would hide a dead SABnzbd permanently. tests/test_ha_health_check.py grew 21 -> 29 tests, all passing under hermes-agent/venv/bin/python -m pytest; falsified against the pre-patch build, where test_sabnzbd_transient_dropped_end_to_end fails (2 hits, not 1) and the real live log still prints the alert, while the patched build prints [SILENT] and --verbose reports all probes passed. Source: ~/.homeassistant/home-assistant.log, ~/.homeassistant-venv/.../sabnzbd/coordinator.py, pysabnzbd/__init__.py, live docker logs sabnzbd, 30-timed poll benchmark, live HA /api/states, 2026-09-24 01:12-01:35 AEST. - hermes.

  • 2026-09-22 (06:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable, same shape as 2026-09-21. Fresh stills at 06:03 AEST for pool (watermark 06:03:26AM), roof (06:03:27AM), garden (06:03:23AM), front door, front stairs and hub max; every one byte-distinct from the 2026-09-21 06:00 set (garden 70965736, pool d0bf2b84, roof afd0242a, front door 773d08aa, front stairs 7de5e625, hub max 59c8aa02, all sha256-prefix). Driveway camera now a full 8 days frozen: published_driveway.jpg sha256 06ce21c4be7a86edf44adf8460097bdb7eaa5a059e914785d0814d42a16393c1, byte-identical across all 7 sweeps from 2026-09-15 06:01 to 2026-09-22 06:03, watermark reads 2026-09-14 12:49:17 PM, and the whole scene differs from today's other stills (it is the only slug whose hash never changes). Live re-probe of 192.168.68.55: ping 2/2, 7.4 ms avg, ARP 98:ba:5f:47:d3:79 on en5, TCP 443 and 554 both open, sibling cameras .84/.61/.108 up. HA tplink integration logs Config entry 'Driveway TC41' ... Unable to communicate with the device connect: ('Unable to query the device: 192.168.68.55: ', TimeoutError()) every 600 s (246 lines in this log, last 06:11:31). homeassistant.reload_config_entry with entry_id=01M1P28GK90EW3TX966A41WACZ returned HTTP 200 at 06:11 and did NOT clear it: camera.driveway_live_view and switch.driveway still unavailable, restored: true, last_changed 2026-09-20T05:00:00 (the 14:59 AEST restart). Fix remains a physical power-cycle of the TC41; reloads are exhausted. Three 2689-byte dark placeholders (lounge_display, home_home, entryway_front_stairs) are unavailable evidence, not scene readings; hub_max still produces a real WebRTC frame (125 KB) despite its Nest placeholder failure. Scene content, all fresh: pool cover on, deck framing still unsurfaced behind bare joists, pool house complete, blue tarp and black toolbox on site, exposed dirt under the deck - no change in kind from 2026-09-21; roof/pergola an active jobsite (exposed joists, stacked blocks, mixer on its side, wheelbarrow); no people, parcels or animals in any still; front-door view shows the bin enclosure with yellow-lid and red-lid wheelie bins at the kerb, undisturbed. HA 2026.8.2 RUNNING, 677 states, 192 unavailable/unknown, 14 of them driveway entities; vacuum.roborock_qrevo_maxv still off the LAN since 2026-09-18 08:41 AEST, unchanged. Source: ~/.hermes/camera_sweeps/20260922-060348/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/status.json, sha256 comparison across ~/.hermes/camera_sweeps/2026-09-*/*driveway_real_still.jpg, ~/.homeassistant/.storage/core.config_entries and core.entity_registry, ~/.homeassistant/home-assistant.log, live HA /api/states, /api/config, ping/nc probes, 2026-09-22 06:03-06:12 AEST. - hermes.

  • 2026-09-20 (18:35 AEST, Shelly EM fetch error, transient, report claim corrected): The Ha Health item is a benign transient, and one of its three claims is wrong. Root cause: the Shelly EM coordinator (aioshelly Gen1) occasionally loses a single 60-second poll to a Wi-Fi/cloud-API hiccup; it retries once and recovers. Measured directly against 192.168.68.78: 40 and 60 consecutive /status fetches produced zero failures, p50 38ms, worst poll 1.04s against aioshelly's DEVICE_IO_TIMEOUT = 10.0 (aioshelly/const.py:1356). A full simulated poll cycle (/settings + /status + /cit/d inside one 10s budget, 50 iterations) never exceeded the budget, p50 0.18s, max 5.53s. So the error is NOT a timeout; it is a lost connection mid-poll (CONNECT_ERRORS → DeviceConnectionError → HA update_error string). Frequency: 17 occurrences across 2026-09-19..20, shortest gap 3.6 min, most 20+ min apart, and it self-clears every time. No data cost: CT1 energy delivered 60 readings/hour with a median 60s gap and no gap over 95s across the whole window. Contributing factor: Wi-Fi RSSI is -73 dBm (weak) on firmware 20230913 v1.14.0, 16.4 days uptime, RAM 36/51 KB free; device reports has_update: false but a beta v1.14.1-rc1 exists. CoAP config is correct, not stale: coiot.peer = 192.168.68.79:5683, and .79 IS this host and HA's own internal_url; python3.14 (pid 76290) holds UDP 5683. Report corrections: (1) the "no action needed" conclusion is right but for the wrong reason; (2) sensor.shelly_em_channel_1_energy_cost is NOT a fault, it is an ORPHANED legacy registry row (platform energy, unique_id sensor.shelly_em_channel_1_energy_grid_cost, created 2026-06-22) superseded by the live sensor.shelly_em_channel_1_energy_cost_2 (unique_id 436d902d..._grid_cost, created 2026-08-05, reading 0.602 AUD). The dead row is already listed in hermes_home_integration_sentinel.py::OPTIONAL_UNAVAILABLE and filtered in hermes_cmd_house.py:140. **Fix appli... [truncated]

  • 2026-09-20 (weekly retune, 15:30-15:48 AEST): HA restarted at 14:59 AEST (log rotated; home-assistant.log.1 tail shows lifx coordinator CancelledError: Home Assistant is stopping at 14:58:55 and pyhap accessory shutdown, then a clean start at 14:59:30 with the usual custom-integration warnings). Consequence: sensor.apple_macbook_pro_power was unavailable 15:00-15:06 AEST, which failed the air-fryer cron 7 times. Live entity health at 15:40: 672 states, 177 unavailable/unknown in the raw set; the shared tag's 204/248 counts only operational domains and sensors with a real device class. Standing unavailable groups, unchanged in kind: roborock Qrevo MaxV (52 entities, off the LAN since 2026-09-18), driveway camera and switch (camera.driveway_live_view, switch.driveway*, tplink 192.168.68.55 timed out again at 15:41:30, device answered ping on 2026-09-15), Tuya/cloud LEDs (9 lights plus their buttons), arr/SABnzbd/Lidarr (unavailable by design outside the 00:00-05:00 window; SABnzbd and Lidarr retry at 600 s), Canon printer ink, iPhone companion sensors, six media players, BLE temperature sensor, three device trackers. 78 entities are unavailable that the 10:43 baseline did not list, almost all of them buttons (identify/restart/pan/tilt helpers) - the shared tag already ignores identify names, so the increase is button noise rather than new faults. Source: /Users/dwayne/.homeassistant/home-assistant.log (+.1), live HA /api/states and /api/history/period, ~/.hermes/state/ha_unavailable_baseline.json, 2026-09-20. - hermes.

  • 2026-09-20 05:13 AEST: hourly ha_health_check.py alert carried the two standing transients (Shelly EM fetch error 04:51:34, Tuya light.lounge_led_2 write rejection 05:00:00.798, chain unchanged since 2026-09-15). Device probes green: Shelly EM HTTP 200, rssi -73 dBm, both emeters valid, metering live at 1251.41 W / 12.52 W; both lights on after the rejected write. Two things the script's window could not see: 15 *arr connection-refused lines logged 05:00:09-05:00:34 sit just inside the 05:13 run's 70-minute window and would have been counted had the run started seconds later, and vacuum.roborock_qrevo_maxv is still off the LAN (unavailable since 2026-09-18T20:38:49Z, ping 100% loss) because roborock logs at INFO. home-assistant.log.fault last changed 2026-08-25 00:02, no growth. Source: home-assistant.log, launchd.log, live curl/ping/HA states, cron executions.db.

  • 2026-09-19 (20:46 AEST): Fixed the false ha=down in the shared cross-status tag. ~/.hermes/bin/ha-tag-status defaulted TS_IP to pro-2 (100.97.49.102), a retired host, so every probe hit a closed port and printed down ❌ even though HA answers on 127.0.0.1:8123. The script now walks CANDIDATE_HOSTS (HA_HOST override, 127.0.0.1, 100.80.168.13, then the retired pro-2 as last resort) and picks the first with an open 8123; the SSH secret fallback only runs when no host answers. Live result: 203/268 ⚠️ 🏠 in 0.54s (was down ❌). Same class of fault in ~/.hermes/bin/nexus-tag-status: it SSHed to Pro (100.87.212.111, Tailscale offline, last seen 5h), so it printed nothing and exited 255 because set -euo pipefail aborted the script. It now probes the local Nexus API at 127.0.0.1:8888 first, falls back to Pro, and treats X-instance last_heartbeat within 300s as online (X instances report heartbeats, not status). Live result: 4/5 ⚠️ (Nexus-X100 last heartbeat 2026-08-30). cross-status now reads nexus=4/5 ⚠️ | ha=203/268 ⚠️ 🏠 | gw-pro=ok | gw-pro2=offline | doge=offline, refreshed into ~/.hermes/state/cross-status.{txt,json}. Root cause of the recurring wrong headline: the status scripts kept hard-coded host addresses from the retired Pro/Pro-2 topology while the services moved to this host. Verified: python3 -m py_compile on ha-tag-status, bash -n on nexus-tag-status, both run live, cross-status-refresh run. Source: ~/.hermes/bin/{ha-tag-status,nexus-tag-status,cross-status}, tailscale status, live HA and Nexus API reads, ~/.hermes/state/cross-status.txt., hermes.

  • 2026-09-17 (20:35 AEST): identified the source of the recurring "HA lists 15 unavailable/unknown entities as critical candidates" line in the daily State of the Union. The 15 are 6 orphaned automation.* registry entries (dishwasher_done, heat_pump_dryer_done, garden/roof/entrance camera motion alerts, roof_sun_announce_when_sun_is_on_roof) plus 9 driveway camera entities. Root cause of the 6: 20 automations were removed from automations.yaml and packages/roof_sun.yaml and replaced by dashboard-callable scripts in packages/manual_controls.yaml (generated 2026-09-15, header dated 2026-09-15). HA still holds their core.entity_registry rows (platform: automation, disabled_by: null), so at the 2026-09-15 09:29 AEST restart it restored each as unavailable with restored: true. Current split: automations.yaml 9 ids, configuration.yaml inline sqlite_journal_mode_bootstrap, packages/roof_sun.yaml 1, packages/camera_ai_analysis.yaml 3, packages/manual_controls.yaml 19 scripts = 33 registry automation rows of which 20 are orphans. Two live dashboards still reference orphan entities: lovelace.command_centre points at automation.smart_ac_cool_down and automation.auto_vacuum_when_away, and lovelace.climate_energy points at automation.roof_sun_announce_when_sun_is_on_roof. Live HA at 20:35: 536/684 available, 148 unavailable/unknown; report at ~/clawd/scripts/ha_unavailable_report.py. Not yet done, needs Dwayne's go-ahead: delete the 20 orphan registry rows and repoint the two dashboard cards at the replacement scripts. Source: ~/.homeassistant/.storage/core.entity_registry, automations.yaml, packages/manual_controls.yaml, .storage/lovelace.*, live HA state read, ha_unavailable_report.py run., hermes.

  • 2026-09-16 (11:42 AEST): closed the recurring sensor.roborock_qrevo_maxv_current_room false attention in hermes_home_integration_sentinel.py. The 11:42 tick reported attention, before 1 / after 1 / resolved 0, with a roborock reload returning HTTP 200 that did not clear the entity; the same shape appeared in 29 ticks over the previous 7 days, plus an ok at 2026-09-16 01:03 AEST where a reload did clear it. The sensor is not a fault. HA history for 2026-09-16 01:02-01:42 UTC shows the state flipping unknown -> Bedroom -> unknown -> Living room -> unknown on 14-second to 25-minute intervals while vacuum.roborock_qrevo_maxv stayed cleaning and healthy, so a single-tick read is a map-update gap, and a config-entry reload cannot fix it. Fix: bad_attention_entities() takes a current_room_streak counter, persisted as current_room_streak in ~/.hermes/ops/state/home_integration_sentinel.json. The sensor is dropped from the attention list unless the vacuum reads cleaning on that tick and the silent streak has reached CURRENT_ROOM_PAGE_STREAK (3). When the vacuum entity is itself unhealthy its own entry carries the page, so the derived room sensor stays out. Two earlier drafts of this patch were wrong and the verification script caught both: the first advanced the streak twice per tick (before- and after-read), so the threshold tripped on the second tick instead of the third; the second let the older "not cleaning" rule swallow the vacuum-unhealthy case. Verified: py_compile clean. The checks now live as a durable suite, ~/.hermes/tests/test_home_integration_sentinel_room.py, run with /Users/dwayne/.hermes/hermes-agent/venv/bin/pytest, 7 tests, 7 passed, sha256 be9e6d1463f018a3d7b39d8302fe18e0364f1e1919aa97636b93dacac0c0c9cf. Coverage: gate table at streaks 0-5, room-named states, docked/idle/absent vacuum, tick-by-tick streak persistence asserting exactly one reload at tick 3, reset on report, and a falsification test that lo... [truncated]

  • 2026-09-15 (08:15 AEST): ha_health_check.py alerted on 18 ERROR lines in its 70-minute window. Live breakdown from the same log window: 9 RequestHandler.connection_made() OSError: [Errno 22] Invalid argument in a 12-second burst at 07:54:56 (aiohttp tcp_keepalive calling setsockopt on a transport the client already closed; 2 lines on 2026-09-12, 14 on 2026-09-14, 9 on 2026-09-15, cosmetic), 3 command_line timeouts on sensor.power_known_loads_report (HA logged "Updating Command Line Sensor Power Known Loads Report took longer than the scheduled update interval 0:01:00" at 07:55:53; the script itself runs in 0.34-0.43s standalone, so the timeout is HA event-loop contention, and the sensor still held 475.5 W), 3 ipp.coordinator printer reachability timeouts, 1 websocket auth-phase ClientConnectionResetError, 1 haffmpeg ProcessLookupError, 1 cast failure. HA itself was healthy: state RUNNING on 2026.8.2, 73 of 690 entities unavailable (threshold 150), /api/states in 0.003s. The cast line matters: automation.xai_daily_super_briefing (HA id xai_daily_super_briefing_0730, enabled) fired at 07:30:02 and Failed to cast media http://192.168.68.79:8123/local/xai-tts/xai_[redacted-id].mp3, a path the 2026-09-13 speaker-removal task removed from Hermes cron jobs but not from this HA-side automation. The file serves fine from the host (HTTP 200, 1,325,568 bytes, 0.04s), so the failure is the cast device not reaching the LAN URL. That message has recurred since 2026-09-11 (17 Failed to cast media lines across home-assistant.log and .1; earlier ones used 192.168.68.116, which is no longer a host address). Soundbar history shows audio did play at 07:00 today (media_player.bose_soundbar_700 idle -> playing -> idle at 21:00:09/13/21 UTC), so the 07:00 Morning Greeting speaker path is also still live. Driveway camera (camera.driveway_live_view, switch.driveway) remained unavailable since 2026-09-14T14:29:46Z with the device answering ping at 5... [truncated]

  • 2026-09-15 (08:35 AEST): The ha-exposed-camera-stills-refresh cron job (835ebfa7498e, */30 * * * *, no_agent=false, script ha_expose_camera_stills_quiet.sh) reported Script timed out after 300s on its 08:02:22 AEST tick. The script is NOT broken and the stills are NOT stale: the same Python payload run by hand in the ha314 env took 2m24.739s and exited 0. The timeout is a cap mismatch plus host load, the global cron.script_timeout_seconds is 300s and load averages read 72.09 79.23 152.88 on 12 cores during the tick, against 6.18 20.84 87.89 when the manual run finished. Exposure is owned redundantly: the LaunchAgent com.dwayne.ha-expose-camera-stills (StartInterval 300, runs = 1903, last exit code = 0) runs the SAME wrapper every 5 minutes and wrote status.json at 2026-09-15 08:31:32 AEST with 5/7 current and 2 stale_real. The cron job is the redundant copy. Consequence: two concurrent runs race on the per-run .candidate.jpg paths and the cron leg eats a 300s kill on any loaded tick. Still stale_real at 08:31:32: driveway (serving latest real 20260915-060103_camera_driveway_real_still.jpg; camera.driveway_live_view state unavailable since last_changed 2026-09-14T14:29:46Z) and hub_max (Nest API says stream not available, file from 2026-09-14 15:01:30). garden, pool, roof refreshed via ha_snapshot; front_door and front_stairs refreshed via webrtc live (456542 and 59109 bytes) at 08:32, so the front-door WebRTC path that read stale_real at 08:03 recovered on its own. Disk is not the cause: /System/Volumes/Data 93% used, 15 GiB free. Historical context only: ha_expose_camera_stills.launchd.out holds three OSError: [Errno 28] No space left on device failures from 2026-09-11, all before the 08:35 reclaim; none today. Source: cron/executions.db rows for 835ebfa7498e, cron/jobs.json, launchctl print gui/501/com.dwayne.ha-expose-camera-stills, manual ha_expose_camera_stills.py run (08:23:58 paylo... [truncated]

  • 2026-09-15 (02:20 AEST): Driveway camera (TP-Link TC41, 192.168.68.55) dropped off the API layer. camera.driveway_live_view, sensor.driveway_signal_level, switch.driveway and the driveway_led / _motion_detection / _person_detection / _tamper_detection switches all read unavailable since 2026-09-15 00:29:46 AEST. The device is still on the LAN (ping 0% loss, TCP 554/443/2020 open) but an HTTPS GET returns no response (curl code 000 after 10s). Consequence: HA's snapshot path into the tplink camera raises haffmpeg Timeout reading image / Timeout while waiting of FFmpeg, then RuntimeError: read() called while another coroutine is already waiting for incoming data in asyncio.subprocess.communicate, logged as Error doing job: Task exception was never retrieved (task: None). Counts from the live log: 13 lines between 2026-09-15 00:33:21 and 01:35:55, 11 on 2026-09-14, 5 on 2026-09-12. ha_health_check.py reported 6 ERROR lines inside its 70-minute window. The exposed still refresh at 02:15:31 serves stale_real for driveway from 20260914-180043_camera_driveway_real_still.jpg; the other five cameras refreshed current. HA held 550/690 entities available. The crash pattern stopped after 01:35:55; standalone haffmpeg timeouts at 01:53:02 and 02:04:24 raised no job error. Next useful action is a physical power-cycle of the driveway camera; the remaining house surface is healthy, so no HA restart is indicated. Source: /Users/dwayne/.homeassistant/home-assistant.log, live HA /api/states read, ping/nc/curl probes, /Users/dwayne/.homeassistant/www/camera-stills/status.json., hermes.

  • 2026-09-13: Fixed the weekly security-audit-weekly false positive. ~/.hermes/scripts/local_security_audit_report.py:51 checked the retired LaunchAgent label io.homeassistant.core and reported "LaunchAgent missing or not loaded" every week while HA was healthy. The live label is com.dwayne.homeassistant (launchctl PID 92617, matching the audit's own PID evidence); the old label survives only as archived plists (io.homeassistant.core.plist.disabled-docker-migration, .codey-pre-path-fix, .bak-fix-all-[redacted-id]). HA itself serves HTTP 200 on 127.0.0.1:8123. Changed the checked label tuple to ("ai.hermes.gateway", "com.dwayne.homeassistant") and added a guard so a launchctl list failure reports "LaunchAgent state unknown" instead of falsely reporting both services missing. Verified: script now prints [SILENT] (zero findings); ~/clawd/state/local-security-audit.json findings [] with firewall enabled, stealth on, Tailscale 100.80.168.13, no SSH listener; falsification test confirmed the check still reports a genuinely absent label (synthetic only ai.hermes.gateway input produced the HA finding) while the live host produces none and io.homeassistant.core is still absent. Note the cron resolves scripts from ~/.hermes/scripts/ (the ~/clawd/scripts/ copy is unreferenced and gitignored, still carrying the stale label); ~/.hermes/scripts/ is itself gitignored via .gitignore:2:*, so this fix is not version-controlled and lives only on disk. Source: live launchctl list, plist inventory, script source, script run and falsification probe 02:30 to 02:40 AEST this session., hermes.

  • 2026-09-12: Fixed two structural false-positive sources in hermes_home_integration_sentinel.py. (1) network_indicated now runs on a schedule-filtered log tail: new schedule_noise_free_log() drops *arr endpoint errors (localhost:17878 radarr, localhost:18989 sonarr, localhost:18686 lidarr) during 05:00-00:00 AEST, when com.dwayne.arr-schedule holds those containers down and HA retries every poll; 194 such lines were in the 500-line tail, mislabelling every tick network_attention and printing a bogus network-repair instruction. Real non-*arr host faults still flag (verified with a synthetic Cannot connect to host 10.0.0.5 line). (2) bad_attention_entities() now excludes sensor.camera_vision_* derivatives, since the camera entity is authoritative via HEALTH_ALIASES. Hub Max sweep at 17:08:17 AEST returned ok and cleared the sensor; --force at 17:14:38 AEST wrote noop, before/after 0. Pre-fix backup: hermes_home_integration_sentinel.py.bak-pre-netfilter-20260912-171238. Source: sentinel source, live HA state read, ~/.hermes/ops/state/home_integration_sentinel.json, /Users/dwayne/ARR/arr-schedule.log., hermes.

  • 2026-09-11: Hub Max vision sensor recovered via a manual --camera hub_max sweep (20:08:59 AEST, ok, summary 7/7 at 20:08); sentinel --force then noop with zero bad entities. Identified two structural faults: the network_attention label came from radarr/sonarr endpoint errors (ports 17878/18989 publish no host port while the containers run), and sensor.camera_vision_hub_max is motion-triggered only (event.lounge_display_motion last changed 2026-09-11T03:33:03Z), so it reads stale between motion events. Also observed 98% disk use and one aborted sweep with Errno 28 on vision_analysis.tmp. Source: ~/.hermes/ops/state/home_integration_sentinel.json, ~/.homeassistant/camera-vision.log, df -h, docker ps., hermes.

  • 2026-09-06 (weekly synthesis): Recorded camera cluster status 2026-09-05: Shelly EM + ONVIF cluster resolved; single persisted bad entity sensor.x_camera_stream (mobile_app) remains, network_indicated back to true. House 509/640 available, Power back at 498W. Source: ~/.hermes/ops/state/home_integration_sentinel.json., hermes.

  • 2026-08-24: Recorded Shelly EM + ONVIF camera cluster outage (2026-08-22 to 2026-08-24): air-fryer report blocked on HA fetch, sentinel reload loop resolving 0 of 19 entities, Power unavailable. Distinct from the older TP-Link camera incident. Source: ~/.hermes/ops/state/home_integration_sentinel.json, kg., hermes.

  • 2026-08-17: Hermes Cron sidebar dashboard restored (/hermes-cron/fleet, iframe of /local/hermes-cron-fleet.html). Regenerated by cron_rigour_audit.py. Source: Dwayne asked to add it back., grok.

  • 2026-08-03: Superseded HA tunnel description, HA now directly reachable at 100.80.168.13:8123 (macbook-pro-3). Old Pro-2 host retired. Source: live reachability check, script audit, 2026-08-02., hermes.

  • 2026-07-09: created from scripts inventory + cross-status, claude-cowork.

  • 2026-09-20 (18:20 AEST, driveway camera RE-DIAGNOSED): The 18:01 sweep verdict ("driveway camera needs a device-side check, power/Wi-Fi/Nest app") is wrong on both counts, and the fault is a RECURRENCE of 2026-09-15. Two corrections. (1) Not Wi-Fi, not power, not Nest. The camera is a TP-Link TC41 at 192.168.68.55, not a Nest device, so the Nest app has nothing to show for it. It answers ping 0% loss and holds TCP 443/554/2020 open, and its ARP MAC 98:ba:5f:47:d3:79 matches the Driveway TC41 config-entry unique_id exactly, so it is on the LAN and identifiable. It is wedged at the API layer: on 554 it accepts the connection and returns nothing on 3/3 attempts, while its healthy same-model twin Entrance TC41 (192.168.68.84) answers with 64 bytes on 3/3. homeassistant.reload_config_entry for the entry re-armed the retry loop but did not recover it (still unavailable; tplink log resets to "Retrying in 5 seconds"). A device reboot is not available to me: no controllable plug feeds it. So the physical power-cycle is confirmed as the fix, not a guess. (2) The report's "driveway" slot is a DIFFERENT camera from the entity named "Driveway camera". The sweep maps slug driveway to camera.driveway_live_view (TP-Link, the wedged one), but camera.home_home is a Nest device whose registry name is literally Driveway camera and the sweep files it under slug front_door. So the sweep reports seven cameras under names that do not match their devices. HA has also been failing this entry since at least 2026-09-19 06:38 (Unable to query the device: 192.168.68.55: TimeoutError), retrying every 600s, while siblings Entrance/Pool/Pergolux stay healthy. Timeline: camera stopped producing frames ~2026-09-14 12:49 (last frame timestamp), still frozen at mtime 2026-09-15 01:30, HA logged it down from 2026-09-19 06:38. This is the third wedge of this camera unit (prior records 2026-09-15 02:20 and 2026-09-20 06:03), each with the same port-open-but-silent signature, which... [truncated]

  • 2026-09-20 (06:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable. Fresh stills at 05:59:33-05:59:38 AEST for pool, garden, roof (1280x720, embedded camera timestamps); front door refreshed via webrtc at 06:00. Two stale: camera.driveway_live_view (unavailable, HA snapshot 404) served a still byte-identical (sha256 06ce21c4) across 20 consecutive sweeps since 2026-09-14 18:03, and its embedded timestamp reads 2026-09-14 12:49:17PM; the TC41 at 192.168.68.55 answers ping (0% loss, 9.6 ms avg) and TCP 554, but HTTPS GET returns code 000 after 8 s, so the fix is a physical power-cycle, not a reload. camera.entryway_front_stairs served the 18:00 still because WebRTC failed with webrtc_offer_failed / Nest 429 RESOURCE_EXHAUSTED on ExecuteDeviceCommand, the first 429 in at least 10 sweeps (earlier sweeps were all current via webrtc_live). Three 2689-byte dark placeholders (lounge_display, home_home, entryway_front_stairs) as before: unavailable evidence, not a scene reading. Mapped real-still entities stayed fresh, including hub_max (d0560825 vs 76c1b951 at 18:00). No people, vehicles, deliveries or damage visible. Source: ~/.hermes/camera_sweeps/20260920-060305/manifest.json and published_*.jpg, /Users/dwayne/.homeassistant/www/camera-stills/status.json, ping/nc/curl against 192.168.68.55., hermes.

  • 2026-09-20 (07:20 AEST): fixed the recurring globird-ac-optimizer cron error ("HTTP 404: Entity not found" on automation.smart_ac_cool_down). Cause: the legacy cool-down automation was replaced by script.ac_cool_down_and_announce in packages/manual_controls.yaml on 2026-09-15, but winter_ac_baseline.py still did an unguarded GET on the dead entity and let the 404 fail the run. Fix: wrap that GET and map HTTP 404 to cool_down_automation = "unavailable", matching the COOLING_DISARMED_STATES already accepted by winter_ac_globird_controller.py. Verified by re-running the 07:00 phase: exit 0, ok: true, hvac_mode heat, target_c 22, readback_converged true, cool_down_automation unavailable. Also live: sensor.ac_season reads summer while the winter controller keeps forcing heat, so the two Smart AC automations disagree about season and smart_ac_seasonal_midday_boost can fight the baseline. Ad-hoc verification (no suite exists for these scripts): a throwaway tempfile-based harness stubbed ha_request with a real 404 RuntimeError and with a non-404 RuntimeError; all 6 checks passed, i.e. the 404 path exits 0 with a valid receipt mapping cool_down_automation to unavailable, and a non-404 HA failure still raises. Source: ~/.hermes/scripts/winter_ac_baseline.py, ~/.hermes/scripts/winter_ac_globird_controller.py, live HA state + rerun., hermes.

  • 2026-09-20 (15:07 AEST): HA core crashed and relaunched itself, taking the air-fryer watch down with it. com.dwayne.homeassistant (launchd, PID 76289) segfaulted at 14:59:33 and launchd restarted it (runs = 2, last exit code = 139); HA reinitialised at 15:00:00.658 after 28.39 s. Crash report ~/Library/Logs/DiagnosticReports/python3.14-2026-09-20-145933.ips: python3.14, SIGSEGV EXC_BAD_ACCESS at 0x0, faulting thread 3 through libunwind _Unwind_RaiseException -> libc++abi __cxa_throw -> _objc PyObjCErr_ToObjCWithGILState -> CoreBluetooth -[CBCentralManager handlePeripheralDiscovered:]. An Objective-C exception raised inside a CoreBluetooth delegate callback crossed into CPython via PyObjC and killed the process; no graceful shutdown appeared in the log, only lifx entries ending CancelledError: Home Assistant is stopping at 14:58:55. This is the first recorded HA segfault, and the first time the bluetooth integration is identified as a crash source here.

  • Consequence chain: the tuya config entry 01KKZT8SVT8TGCYWYM63GS1M45 (ap-000424.ccdec59da5af48ff92c321ab78a5c0e5.2145) failed setup at 15:00:00.646 with ApiRequestException: network error:(-9999999) Remote api run unknown failed, leaving 65 tuya-family entities unavailable. That took out the Air Fryer plug family (sensor.apple_macbook_pro_power, _voltage, sensor.air_fryer, switch.apple_macbook_pro_socket_1, sensor.diffuser_total_energy), all unavailable with last_changed 2026-09-20T05:00:00Z. Cron air-fryer-done-watch (0aeb740a318c, */1 * * * *) then failed every tick from 15:00:37 to 15:05:44 with observation_unavailable: sensor.apple_macbook_pro_power state is unavailable, failure_streak 5. The 14:59:34 tick failed differently, with HTTP Error 404: Not Found (entity not yet registered mid-restart).

  • Fix applied and verified: homeassistant.reload_config_entry takes entry_id, not config_entry_id - the latter returns HTTP 400. Called with entry_id=01KKZT8SVT8TGCYWYM63GS1M45 at 15:06:30; entry state returned to loaded and the five affected entities all read live values: power 0.0 W, socket on, sensor.air_fryer 1306.08 (junk helper, listed in the script's BAD_HELPERS), sensor.diffuser_total_energy 0. The 15:06:43 tick completed. Unavailable load fell from 221 of 669 entities at 15:03 to 177 at 15:07.

  • Not a tuya-cloud flake this time: the 15:00:00 failure is a cold-start API exception inside the crash-restart window, distinct from the long-standing sporadic light.turn_on write rejections. Standing roborock LAN absence and the driveway TC41 camera remain unchanged. Source: ~/.homeassistant/home-assistant.log, home-assistant.log.1, launchd.log, launchctl print gui/501/com.dwayne.homeassistant, crash .ips, live HA /api/states and config_entries/entry, cron/executions.db. - hermes.

  • 2026-09-21 (06:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable. Fresh stills 06:00 AEST for pool, roof, garden, front door, front stairs, hub max (all byte-distinct from 18:01 yesterday; pool/roof/garden differ every sweep). Pool scene unchanged over 12 h: circular metal pool still covered/empty, deck framing unsurfaced, stone retaining wall and white modular building as before, no people. Roof/pergola still an active jobsite (exposed joists, plywood sheet, blue tarp, stacked blocks, wheelbarrow, mixer), not storm damage. Driveway camera still frozen: published still is byte-identical (md5 3749c787) across 14 consecutive sweeps since 2026-09-14 18:03 and its embedded watermark reads 2026-09-14 12:49:17 PM. Live re-probe of 192.168.68.55: ping 0% loss at 15.5 ms, TCP 443 and 554 open, HTTPS GET returns 000 after 8 s; sibling Entrance TC41 at 192.168.68.84 answers on 554 - same port-open-but-silent wedge as 2026-09-15 and 2026-09-20, fix remains a physical power-cycle. Three 2689-byte dark placeholders (lounge_display, home_home, entryway_front_stairs) are unavailable evidence, not scene readings. No people, vehicles, deliveries or damage visible in any fresh still; wheelie bins (yellow lid and dark lid) at the kerb in the 06:00 front-door still. Source: ~/.hermes/camera_sweeps/20260921-060330/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/status.json, md5 comparison across ~/.hermes/camera_sweeps/2026*/*driveway_real_still.jpg, live ping/nc/curl against 192.168.68.55 and .84., hermes.

  • 2026-09-23 (12:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable. Fresh stills 12:02-12:03 AEST for pool (12:02:50), roof (12:02:51), garden (12:02:48), front door, front stairs, hub max; every one byte-distinct from both the 06:03 and the 2026-09-22 18:03 set, so the whole sweep path is current except the driveway. Two material scene changes at midday, both firsts in the recent record: (1) the pool is UNCOVERED with open blue water visible, against the dark taut cover in the 06:00:47 still eight hours earlier - so the cover came off during the morning; (2) two workers are on site at 12:02, one walking the bare deck framing and one leaning over the pool edge, where every sweep from 2026-09-20 to this morning's showed no people. Deck framing around the pool is still bare joists, planks not yet laid, cabin exterior finished, blue tarp and black case still on site. Roof/pergola at 12:02:51 remains an active jobsite: one worker bent over the framing, plywood work sheets, red extension cord, stacked concrete blocks along the fence, wheelbarrow and mixer, no solar panels. Garden still (12:02:48) shows a boxy dark off-road vehicle and a white SUV with an unresolved door logo at the kerb, plus a white car further down the street; front door, front stairs and hub max show no people, no parcels, no animals. Driveway camera now frozen for 9 days: published_driveway.jpg sha256 06ce21c4be7a86edf44adf8460097bdb7eaa5a059e914785d0814d42a16393c1 is byte-identical across 19 sweep directories (the newest distinct driveway still on disk is 20260914-060040_camera_driveway_real_still.jpg), and its embedded watermark still reads 2026-09-14 12:49:17PM. Live re-probe of 192.168.68.55: ping 2/2, 8.2-55.9 ms, TCP 443 and 554 both open, HTTPS GET returns code 000 after 8 s. HA tplink log holds 436 Driveway TC41 lines, latest retry 2026-09-23 11:56:02 with Unable to query the device: 192.168.68.55: TimeoutError(). Three 2689-byte dark placeholders (lounge_display, home_home, entryway_front_stairs) remain unavailable evidence, not scene readings; hub_max still yields a real 125 KB WebRTC frame despite its Nest placeholder failure. Source: ~/.hermes/camera_sweeps/20260923-120354/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/status.json, sha256 comparison across ~/.hermes/camera_sweeps/2026-09-*/*driveway_real_still.jpg, ~/.homeassistant/home-assistant.log, live ping/nc/curl against 192.168.68.55. - hermes.

  • 2026-09-26 (19:50 AEST, pool tiles went "Unavailable" - four bridge defects found and fixed): The user's screenshot showed all six tiles as Unavailable. Root causes, all in the bridge: (1) The Tuya debug log level is runtime-only and a Home Assistant restart reverts it. The keepalive cron runs every 20 minutes, so after HA restarted at 19:24 the tiles were dark until the next tick. The bridge now re-arms tuya_sharing debug itself at startup and in its publish loop, so the cron is a backstop rather than the only path. (2) A quiet device must not blank the tiles. Any stale window published availability=offline, which renders every tile Unavailable even though the last reading was known. Availability now goes offline only when NO reading has ever been parsed; a reporting gap holds the last values. (3) The energy total was memory-only and reset to 0 on every restart. That is wrong for a total_increasing meter and made "Energy since install" drop to zero. Now persisted to ~/.hermes/state/pool-heatpump-bridge.json and reloaded on start. Replay deliberately does NOT integrate energy (the restored total already covers that window) and resets the integration baseline afterwards, so it cannot double-count. (4) Replay read only the live log. HA rotates on restart, so --replay-mb 10 found 1 payload line while 4671 sat in home-assistant.log.1. Replay now reads rotated logs too, matched strictly as .log / .log.N so the stray home-assistant.log.fault is excluded. (5) KeepAlive {SuccessfulExit: false} + exit 0 on SIGTERM = never respawned. launchd read a clean stop as "finished for good", which is how the tiles stayed dead after a restart. The bridge now exits non-zero when stopped by a signal (still 0 for --once), so the LOADED job respawns it without needing a plist reload. launchctl bootstrap is blocked in-process by the Hermes gateway guard, so a plist change alone cannot be applied from an agent run. Verified live: SIGTERM -> log line "stopped by signal; exiting non-zero so launchd restarts the job" -> launchd respawned with a new pid. Harness /private/var/folders/.../T/hermes-verify-keepalive.py, 69/69. Also learned: an API lovelace/config/save can report success while .storage/lovelace.* is never written (six dashboards once reported success with an unwritten file, and an HA restart then dropped the block). Verify the FILE, not the API reply. The dashboards harness now asserts persistence to .storage as well as the API config. Two bridges must never run at once: each integrates its own energy total and overwrites pool-heatpump-bridge.json, so the kWh figure jumps and can double-count. The bridge now takes an fcntl.flock on ~/.hermes/state/pool-heatpump-bridge.lock and refuses to start a second copy (--once skips it so a probe still works). Open the lock with os.open(..., O_CREAT|O_RDWR), NOT open(path, "w"): "w" truncates before the flock is attempted, so a refused second instance erases the live pid. , hermes.

  • 2026-09-28 03:00 AEST: d3-matrix no longer invents solar generation. Correction from Dwayne: "there is no solar roof ct1 is the whole house circuit, ct2 is the aircon circuit". The page behind http://100.80.168.13:8123/d3-matrix is /local/d3_dashboard.html (iframe in the storage-mode d3-matrix dashboard). It declared const solarW = 1800 as a "Pergolux Solar Roof" source, computed totalInW = solarW + mainsW, drew it as a supply in the energy sankey, and printed "1.8 kW Active Solar Flow" on the iso roof polygon and "10:00-14:00 Free Solar" on the tariff tile. No solar entity exists in HA at all (checked all 683 entities; no solar/pv/generation/export sensor), and binary_sensor.sun_on_roof is a sun-GEOMETRY sensor (azimuth/elevation against east/west windows), not generation. Correct model: CT1 (sensor.shelly_em_channel_1_power_2) = whole house, the only measured source; CT2 (sensor.shelly_em_channel_2_power) = a sub-meter on the aircon circuit, a branch INSIDE the CT1 total, so it is a load below the bus and must never be added to the total. Also removed: hardcoded placeholders (currentMainsW = 1955, currentAirconW = 1210, the matching stateCache entries and the 1955 in the badge/iso text) that painted an invented diagram before live data arrived. Added a liveDataSeen gate: the sankey now renders "Waiting for live readings from the whole-house meter" until a real CT1 reading lands, stateCache uses an em dash, and Math.min(currentAirconW, mainsW) stops CT2 ever exceeding the whole house. The tariff line now reads "Free window 10:00-14:00" (GloBird FOUR4FREE, per packages/electricity_pricing.yaml) instead of implying solar. Node --check on the inline script exits 0; served copy matches disk; iframe bumped v=12.0.1-vision-20260909 -> v=12.1.0-nosolar-20260928. Backup www/d3_dashboard.html.bak-nosolar-20260928-025018 (verified to still contain the solar fiction, so it is a true revert point). Ad-hoc harness hermes-verify-393g4cok.py 33/33 exit 0. , hermes.

  • 2026-09-28 02:40 AEST: arr-stack launchpad URLs now use the Tailscale IP, not loopback. Dwayne asked that http://100.80.168.13:8123/arr-stack use tailnet IPs instead of local. Its Media Ops "Launchpad" held 7 custom:button-card tap_action.url_path values on http://127.0.0.1:<port> (Plex 32400 /web/index.html, Overseerr 15055, Sonarr 18989, Radarr 17878, Lidarr 18686, SABnzbd 18090, Prowlarr 19696). Loopback resolves to whichever device renders the dashboard, so the buttons only worked on the HA host itself; from a phone they pointed at the phone. Rewritten to http://100.80.168.13:<port>, preserving scheme, port and path. All 7 then answer HTTP 200 on the tailnet host, including Plex /web/index.html. arr_stack is mode: storage, so the lovelace WebSocket API edit path is valid here (contrast energy-comfort, which is mode: yaml and refuses lovelace/config/save with "Not supported"). Backup of the prior config saved as JSON to .storage/dashboard-arrstack-backup-20260928-023540/arr-stack.json - a .storage/lovelace.* file copy is not a reliable revert for a running HA, so the config itself is saved and restored through the API. Scanned all 17 dashboards: no other dashboard carries a loopback service URL, so no follow-up needed. Ad-hoc harness hermes-verify-cjic9zvt.py 27/27 exit 0, with falsification on the loopback predicate. Note: curl shows 307/302 for Overseerr/Prowlarr without -L; those are live services. , hermes.

  • 2026-09-27 22:15 AEST: energy-comfort is a YAML-mode dashboard - do NOT try to save it over the lovelace WebSocket API. configuration.yaml declares lovelace: dashboards: energy-comfort: mode: yaml, filename: exec_dashboard/dashboard.yaml. lovelace/config/save returns {"success": false, "error": {"code": "error", "message": "Not supported"}} for YAML dashboards, and the read still returns a config, so a read-then-save round trip looks plausible and silently fails. The cache-buster therefore lives in ~/.homeassistant/exec_dashboard/dashboard.yaml (- type: iframe, url: /local/exec_dashboard/index.html?v=N), not in .storage. add_pool_to_dashboards.py already documents that it skips energy-comfort for this reason; that script's WS save path is only for storage-backed dashboards. To update the page: refresh.py (runs ha_exec_extract.py then build.py, writes www/exec_dashboard/index.html + exec_dashboard_data.json), then bump ?v= in dashboard.yaml, then reload. The page fetches exec_dashboard_data.json?t=' + Date.now() with cache: 'no-store', so the JSON self-busts; only the HTML needs the manual version bump. Reload via homeassistant/reload_core_config (not lovelace/reload_resources, which does not re-read the YAML file). Verified 2026-09-27 22:08: bumping v5 -> v6 and reloading made the live WS read report ?v=6; the static page (200, 63629 bytes) and its JSON (200, 39993 bytes) are reachable without the login wall because they sit under www/. Ad-hoc harness hermes-verify-razl3368.py 24/24 exit 0. , hermes.

  • 2026-09-27 21:20 AEST: Dead power-sensor mappings removed; the unattributed residual is now labelled. Dwayne asked what was using ~490W. Answer: a standing 350-380W always-on base, plus a report defect. Evidence: over 4 days CT1's hourly MINIMUM never falls below 273W (typical 350-450W), and after the aircon was switched off at 20:44:13 CT1 dropped exactly 713W (1235 -> 522) with CT2 confirming it, so the aircon is excluded. The 10:00-14:00 pool window takes CT1 to ~4700W, so it is excluded too. Only the fridge (sensor.samsung_fridge_power, 77W) and towel heater (sensor.meir_towel_heater_current_consumption, 25W) are metered directly; the cameras (33W) are modelled. Everything else is unattributed. The report defect: power_baseline_monitor.py printed "Unavailable sources: 6", implying temporarily missing sensors. They never existed. sensor.data_centre_current_consumption, sensor.heat_pump_dryer_current_consumption, sensor.entertainment_hub_current_consumption, sensor.washing_macine_current_consumption, sensor.smart_plug_current_consumption, sensor.hair_dryer_power AND a seventh I had missed, sensor.nano_room_energy_power, all have zero rows in the recorder. They were a retired integration's names hard-coded into the sums, each contributing 0 W and making the accounting look complete. Note my first report to Dwayne named smart_plug among the six; the live template actually carried seven dead terms. Removed from: ~/.homeassistant/configuration.yaml (power_tracked_devices template, 14 terms -> 8; plus an orphaned integration platform on source: sensor.hair_dryer_power), packages/dashboard_compatibility.yaml, ~/.hermes/scripts/power_baseline_monitor.py (measured_devices), ha_power_known_loads.py (DIRECT_POWER_ENTITIES), watchdog.py. Backups *.bak-deadpower-20260927-205545. Never substitute a kWh sensor into a W sum: the only surviving dryer/hair-dryer entities (sensor.tuya_dryer, sensor.hair_dryer_total_energy_2) are device_class: energy, so there is no real W meter for those loads and removal is the only correct option. Template reloaded; live sensor.power_tracked_devices equals its 8 surviving terms (122 = 122); check_config exit 0. Report label changed to "Dead sensor mappings" and a new note explains a large Unknown as real draw. Ad-hoc harness hermes-verify-kx6l90sl.py 50/50 exit 0, including a tokenize-based check that dead ids survive only inside explanatory comments, not as live mappings. , hermes.

  • 2026-09-27 21:05 AEST: Two traps found while disabling the aircon (companion to the entry above).

    1. automation.trigger BYPASSES the off state. Calling automation/trigger with entity_id: automation.smart_ac_winter_heat_baseline ran the disabled automation and forced climate.panasonic back to heat (verified live; the 33/33 harness had asserted only that the entity read off). The bypass is deliberate, and nothing in the house calls automation.trigger on these automations, so it is not a live risk. The real trigger path IS gated: _async_disable() in components/automation/__init__.py calls self._async_detach_triggers(), so an off automation does not fire from its own time or state triggers. Proven empirically: with the AC put into dry (the bait heat_only_guard watches for), the off automation did NOT force heat back. When asserting a disabled automation is inert, check the entity is off AND that no caller uses automation.trigger; the entity state alone is not the whole claim.
    2. climate.set_temperature on the Panasonic flips the mode. Setting a target of 22 while the unit was off brought it back as dry (logbook: our own call at 10:50:48, no automation context). To leave this unit idle, issue set_hvac_mode: off ALONE and do not follow it with a temperature write. A clean set_hvac_mode: off held for 40s with target 22 and hvac_action: off. , hermes.
  • 2026-09-27 20:50 AEST: Aircon profile disabled and climate.panasonic turned off at Dwayne's request. The aircon is climate.panasonic. Its "profile" had THREE independent drivers, and stopping only one leaves the others free to turn it back on:

    1. cron db57480d29dd heating-profile, */15 10-14 * * * -> heating_profile_report.py (slots 10:00/11:00/13:15/14:00)
    2. cron b9d72391d9cf globird-ac-optimizer, 0 7,11,13,14,23 * * * -> winter_ac_globird_controller.py (slots 07:00/11:00/13:00/14:00/23:00). This was the real culprit: it ran daily at 07:00 and forced heat.
    3. HA automation smart_ac_winter_heat_baseline in automations.yaml (same slots + a heat_only_guard that re-forced heat whenever the AC left heat mode). automation.smart_ac_seasonal_midday_boost was already off. All three stopped: both cron jobs paused (enabled: false, state: paused), the automation's initial_state flipped true -> false, and both scripts' slot tables emptied (heating_profile_report.py profile_target() returns None; winter_ac_globird_controller.py PHASES = {}) so a manual run or a resume cannot drive the aircon either. automations.yaml backed up to automations.yaml.bak-airconprofile-*; check_config exit 0; automation/reload applied. Durability: HA's automation component gives initial_state precedence over the restored state ("initial_state %s overridden from config initial_state"), so the flipped false survives a restart even though .storage/core.restore_state still records the automation as on. Verified against the installed component at miniforge3/envs/ha314/lib/python3.14/site-packages/homeassistant/components/automation/__init__.py. Cron nuance: a paused job keeps a stale next_run_at (it still showed 23:00 tonight). next_run_at is NOT the dispatch gate: cron/jobs.py::_get_due_jobs_locked skips if not job.get("enabled", True). Assert enabled/state, never next_run_at, when checking that a paused job will not fire. Readers that do NOT command the AC and were left alone: hermes_home_integration_sentinel.py, both watchdog.py, event-chains.py (zero set_hvac_mode). script.ac_warm_up_and_announce / ac_cool_down_and_announce in packages/manual_controls.yaml are manual-only, not scheduled. To re-enable: resume both cron jobs and set initial_state: true, then restore the slot tables. , hermes.
  • 2026-09-27 18:30 AEST: Pool water temperature is now sampled only 10:00-14:00. sensor.pool_water_temperature (trigger-based template sensor, unique_id: pool_water_temperature_sampled) samples sensor.pool_heat_pump_water_temperature only inside that window and holds the last in-window reading the rest of the day. Reason, from the recorder: readings run 17C at 10:01, reach 23C by 12:03, then decay to 17C by 16:04. Outside the window the unit reports stagnant pipe temperature, not pool water. Trigger-based is required, not optional: a plain template sensor cannot hold a value, because whenever the template renders unknown its this.state becomes unknown too, so there is nothing to carry forward. The sensor was seeded once at 23.0C from the last genuine in-window reading (27 Sep 12:03). Consumers repointed to the sampled entity: exec_dashboard/ha_exec_extract.py (POOL_PINNED["water_temp"]) and the pool block on 8 dashboards via add_pool_to_dashboards.py (WATER_ENTITY, MARKER, LEGACY_MARKER). Two bugs found while doing it: the dashboard strip routine walked only cards and never sections, so every dashboard ended up with two pool blocks; and the extractor resolved sensors through statistics_meta and the short-term states table, so a sensor created minutes earlier had no hourly bucket and no recorded row and silently read as missing. states_meta holds only entity_id (no unit column) - the unit comes from state_attributes.shared_attrs. Both fixed; the extractor falls back to a read-only API call for live values. HA accepts the config (/Users/dwayne/.homeassistant-venv/bin/hass --script check_config, exit 0); applied via template/reload. , hermes.

  • 2026-09-27 18:05 AEST: FIXED (approved): the power_tracked_devices template now counts the real pool loads. configuration.yaml unique_id: power_tracked_devices: the pool term moved from the dead sensor.smart_plug_current_consumption to sensor.home_entertainment_power, and a new pool_heat_pump term for sensor.pool_heat_pump_power was added to the sum. Applied via template/reload (no HA restart). Backup configuration.yaml.bak-powertracked-20260927-175708 (sha256 of the pre-change file f93bde65e4c32291e9abe797bdcfedf36c2d0c1ef2bcdc3111c1e5483aa3ff09). Validated with /Users/dwayne/.homeassistant-venv/bin/hass --script check_config -c /Users/dwayne/.homeassistant (exit 0) - this is the interpreter HA actually runs from (2026.8.2); the ha314/ha313 envs are too old and fail with a device_registry version error that is not a real config fault. Controlled proof: at the heat pump's observed peak (1892 W) plus the pump (1392 W) the old template reported tracked 756 W, the new one 4040 W, so 3284 W of pool load is now attributed instead of sitting in Unknown. Dashboard iframe bumped to ?v=5. , hermes.

  • 2026-09-27 17:40 AEST: The authoritative "Tracked" figure is a Home Assistant template, not the Python scripts. configuration.yaml unique_id: power_tracked_devices sums the CT2 aircon submeter plus a hard-coded list (fridge, entertainment, dishwasher, dryer, pool, data centre, washing machine, nano room, hair dryer, air fryer, towel heater, modelled) and sensor.power_untracked subtracts that from CT1. The template's pool term points at sensor.smart_plug_current_consumption, which no longer exists (404), and it has no term for sensor.home_entertainment_power (the real pump plug, ~1.4 kW) or sensor.pool_heat_pump_power (bridged, peak 1.9 kW). Editing the Python mapping alone does NOT change the reported Tracked/Unknown figures - that requires editing this template, which is a live HA config change and needs explicit approval plus an HA restart to load. Contract gotcha: Measured in the baseline message is a residual (tracked minus aircon minus modelled), NOT the sum of the real meters. Do not assume the displayed figures decompose as measured + modelled = tracked. , hermes.

  • 2026-09-27 17:20 AEST: Pool loads are now real, not modelled, in the power accounting. Two genuinely metered pool loads existed but were unattributed, so they inflated the untracked residual. sensor.pool_heat_pump_power (MQTT bridge, inverter heat pump, peak 1892 W, energy counter 7.19 kWh) and sensor.home_entertainment_power (friendly name "Pool pump", the circulation pump plug, ~1.04-1.39 kW, runs ~10:00-14:00) are now wired into DIRECT_POWER_ENTITIES in ha_power_known_loads.py and the measured set in power_baseline_monitor.py. Both sit inside CT1, so they must be subtracted from the residual, never added on top. Verified by correlation: on 26 Sep the heat pump drew ~1800 W while CT1 showed ~2500 W with aircon at ~20 W, so the whole-house meter contains it. Dead mappings no longer hide loads: sensor.smart_plug_current_consumption, sensor.data_centre_current_consumption, sensor.entertainment_hub_current_consumption, sensor.washing_macine_current_consumption, sensor.heat_pump_dryer_current_consumption, sensor.hair_dryer_power and camera.front_door all read 0 W because the entities do not exist. A missing entity is indistinguishable from a genuine 0 W, so both scripts now report missing targets (dead_targets() / missing_direct_entities). Also corrected two wrong camera labels: garden_real_still is the garden camera and pool_real_still is the pool camera. , hermes.

  • 2026-09-26 (19:15 AEST, pool heat pump live tiles added to 8 dashboards; HA dashboard-save mechanism): The confirmed bridge entities now appear as live tiles on the environment and equipment dashboards: climate-energy, energy-ops, command-centre, home-daily, home-dashboard, dashboard-areas, floor-heating, maintenance-ops. Each gets a section whose cards are a heading ("Pool heat pump") followed by six flat tiles: Pool water, Set to, Heat pump power, Coil, Energy since install, Running. Left off by design: arr-stack, media-desk, party, cameras, hermes-cron, d3-matrix, digital-twin, colima-memory, dashboard-map (media/party/camera/cron/iframe/VM-memory surfaces, where a pool reading is noise). Script: ~/.hermes/scripts/add_pool_to_dashboards.py (--list, --apply, --only, --revert). 97/97 ad-hoc harness /private/var/folders/.../T/hermes-verify-i67kksv2.py. KEY MECHANISM, how to edit a dashboard on a RUNNING Home Assistant: go through the WebSocket API, not the storage file. {"type":"lovelace/config","url_path":"<url_path>"} to read and {"type":"lovelace/config/save","url_path":...,"config":...} to write, over ws://127.0.0.1:8123/api/websocket with the long-lived token. The url_path is the dashboard's url_path from lovelace_dashboards (e.g. climate-energy), NOT the storage filename (lovelace.climate_energy) and NOT the key field inside the storage file (that is the filename). A file-level revert does NOT work while HA is running: HA keeps the dashboard in memory and rewrites .storage/lovelace.*, so cp-ing a backup over the file is silently overwritten. The only revert that holds is saving the config back through the same API. Backups are therefore stored as a configs.json manifest keyed by url_path, plus a plain file copy for inspection only. Section shape that renders: a section's cards should be a heading followed by tiles as DIRECT siblings. Nested grid inside a section renders inconsistently across sections-based views; the house pattern on home_daily is flat. trend-graph is an established tile feature in this install (present in pre-existing 2026-08 backups), so it is safe to use. , hermes.

  • 2026-09-26 (17:20 AEST, pool heat pump datapoints: dp112 = power CONFIRMED, dp105 = temperature, E6 CLEARED): Screenshot img_06698aede562.jpg (app, 17:18) matched against the live datapoint stream. CONFIRMED dp112 = power in WATTS. The app read 0.624 kW; dp112 read exactly 624 at 17:18:11, and 601 at 17:18:09. The unit then ramped 624 -> 1598 over 90 s while working. That ramp is the signature of an inverter compressor, and it explains the 0..1957 span and the 0 reading while idle. This REVERSES the earlier "not the power figure" note, which rested on my assumption about the timestamp of the E6 screenshot rather than a verified simultaneous read, that assumption was wrong. dp105 = a TEMPERATURE, not the flow rate. It climbed 0 -> 65 -> 97 continuously as the unit heated. It passed through 65 at 17:18:09, which is the same number as the app's 65% flow, but it kept rising past it, so the overlap was coincidence. Coil/discharge temperature stands (0 idle, 100+ under load). The app's 65% flow is NOT carried by any datapoint. dp101 is NOT the E6. The app now shows NO alert at all while dp101 still reads True (unchanged since 17:12:04). So dp101 tracks the unit being active, not the fault code. Confirmed by absence. E6 has CLEARED. The app now shows Heating, flow 65%, water 24 C, setting 26 C, no alert. The unit is heating normally. The E6 water-flow hypothesis from 16:35 is therefore NOT the current state; the earlier fault has resolved. dp103=24 and dp107=26 both match the app exactly, reconfirming both. Final map: dp103 water temp, dp105 coil/discharge temp, dp107 set temp, dp112 power W, dp101 active status (not fault), dp110 heartbeat/status (unresolved), flow % unreported. , hermes.

  • 2026-09-26 (17:15 AEST, pool heat pump datapoints: CONFIRMED by controlled change): The correlation method above produced the datapoint map, and two controlled app changes confirmed it. CONFIRMED dp107 = set temperature. Dwayne nudged the app setpoint 26 -> 29 at 17:09:07; dp107 appeared for the first time in the whole log reading 29 (twice, same second) and nothing else moved. He then changed it back at 17:11:43; dp107 read 29 -> 28 -> 26 in the same second. Two independent changes, two matching reads: the mapping is proven, not inferred. The setpoint is only pushed when it changes, which is why it never showed up in 3443 prior events. CONFIRMED dp103 = current water temperature. Only ever reads 24 or 25 across all events; the app showed 25. A 1-degree band is right for a pool. dp101 = a fault/status flag, mapping now ambiguous (superseded reading below). First flipped False -> True at 14:18:31, about 18 minutes after the circulation pump's 14:00 switch-off; E6 is a no-flow safety fault, so a flag asserting shortly after flow stops fitted. The 17:11 power toggle broke the clean "E6 fault" reading: dp101 went False at 17:11:54 (the power-off), then True at 17:11:58, then True again at 17:12:04. A pure fault flag would not clear on a power press and then re-assert 4 seconds later on its own. Honest state: dp101 is a status/fault bit tied to the unit being active, not proven to be the E6 alone. The app's current text is the only way to settle it. guess dp105 = coil / refrigerant temperature. 758+ events, spans 0..104, sat at 0 while idle, then climbed 0 -> 28 -> 80 within two minutes of the unit starting. A real analogue sensor, not the setpoint. unresolved dp112, dp110. dp112 spans 0..1957. Its pump-ON band is narrow (1003..1054, 42 events) and its pump-OFF range is broad (0..1957, 1308 events), so it does NOT cleanly separate on pump state and is NOT the app's power figure (the app read 0.000 kW while dp112 read 1621). dp110 toggles 0/32 several times a second, far too fast to be a reading, heartbeat or run-status bit. Note: after the setpoint change the unit started: dp105 0 -> 80 and dp112 0 -> 1140 over ~2 min, with setpoint 26 above water 24-25, i.e. real heating demand. The 17:11 power on/off/on did not move the setpoint, dp103 or dp105's trend, dp105 stayed hot (73..84) and dp112 stayed up (851..1254), so the toggle did not actually stop the unit; it only exercised dp101. No datapoint carries the app's power figure: the app read 0.000 kW while dp112 read 1621, so dp112 is not kW. No datapoint has read 26 as a mode or a boost value, so silent/auto/heating/timer are either not reported or share an encoded field. Datapoints are now: dp101 status/fault (ambiguous), dp103 water temp, dp105 coil temp (guess), dp107 set temp (CONFIRMED), dp110 heartbeat/status (unresolved), dp112 unknown analogue. 3443 heat pump events in the current log; /tmp/tscan + /tmp/tsdk probe venvs removed once pool_dp_window.py superseded them. Scripts: ~/.hermes/scripts/pool_dp_window.py (window vs contrast, reverts debug itself), ~/.hermes/artifacts/pool_dp_watch.py (snapshot/watch). 34/34 ad-hoc harness. , hermes.

  • 2026-09-26 (17:05 AEST, pool heat pump datapoints: read-only identification by correlation): Since Tuya serves no schema for product 1ovawewadpk2jiaw, its five datapoints cannot be named from the API. They can be named by correlating them against a ground-truth signal, and the pool circulation pump is exactly that: sensor.home_entertainment_power draws ~1393 W daily 10:00-14:00 and 0 W otherwise. Method (read-only, no new Tuya session): do NOT open a second tuya_sharing MQTT client, mq.py builds link_id = tuya-device-sharing-sdk-python.<uuid> and requests a server-issued clientId, and mq.py:65 notes a client "retry with its now-stale client_id", so a competing session risks invalidating Home Assistant's own link. Instead turn on logger.set_level({"tuya_sharing": "debug"}) at RUNTIME (log-level only; the logger: block in configuration.yaml has no debug lines, so a restart reverts it with nothing to undo on disk) and read the raw payloads the manager already logs: payload-> b'{"protocol":4,"data":{"devId":"...","status":[{"dpId":112,"t":...,"value":...}]}}'. The devId and its datapoints are in the same JSON object, so attribution is exact, unlike grepping a dpId, which mixes devices. Debug costs ~1 MB/hour of log growth. Scripts: ~/.hermes/artifacts/pool_dp_watch.py (snapshot / watch) and ~/.hermes/scripts/pool_dp_window.py (window vs contrast correlation, reverts the log level when done). The five datapoints: 101 (False/True), 103 (24/25), 105, 110 (0/32), 112. Partial identification, and its limit: today's log only starts 12:55:52 because debug was switched on then, which catches the last minutes of the pump-on window but not its start. In 12:55-14:00 (pump ON) only dp112 reported, with a tight band of 9 distinct values (1003-1054). After the pump stopped at 14:00, dp105 and dp112 both moved continuously, dp105 over 0-104 and dp112 over 0-1957. So the pump-on band does NOT separate cleanly and the earlier hypothesis that these are flow readings is NOT confirmed. What it does show is that dp105/dp112 are continuous analogue readings, not the on/off flag: the only clean boolean is dp101. dp110's 0/32 alternation at ~0.5 Hz is far too fast to be a temperature and is unconfirmed. Do not write to these without identification: localtuya writes values, and guessing the setpoint (a plausible candidate is dp105, which spanned 0-104, against the app's 26C setpoint) could command the pump wrongly. A read-only DPS-to-candidate map should be established first by changing ONE setting in the Tuya app and watching which number moves. Capture armed: cron job 1179f63110fd pool-heatpump-dp-window, one-shot 2026-09-27T17:00, no_agent, script pool_dp_window.py, reads 10:00-14:00 vs 14:10-16:30 from tomorrow's log. tuya_sharing debug is intentionally left on so the window is recorded; revert after the capture. Sources: live logger level changes, HA log payload lines, recorder states read-only, .storage registries, 2026-09-26 16:35-17:05 AEST., hermes.

  • 2026-09-26 (16:35 AEST, pool heat pump: why HA shows nothing): The Tuya pool heat pump climate.inverter_pool_heat_pump has been stuck at state unknown with no hvac_modes and no current_temperature. It is NOT an integration fault and NOT a connectivity fault. The Tuya sharing API serves an empty device specification for this product, so HA has no code names to bind any datapoint to. Verified live against the cloud with the entry's own token and TUYA_CLIENT_ID = HA_3y9q4ak7g4ephrvke: device bf0b35b0d643f35302myfr, product_id 1ovawewadpk2jiaw, category qn, product name "Inverter Pool Heat Pump", GET /v1.1/m/life/{id}/specifications returns success: true with {"category": "", "functions": [], "status": []} (zero functions, zero status codes), and /v1.0/m/life/devices/{id}/status returns dpStatusRelationDTOS: [] with productKey: 1ovawewadpk2jiaw. Of the 27 Tuya devices on the account only TWO have empty specs: this heat pump and KD986N-182N020ET (kxgf9ndh). Every other product returns a full schema (the 8 LEDs yzz6hfqthmetbcee 8/8, the plug family hv4wnecqxkeb87av 3/12, Bathroom Underfloor Heating yo5a3nechfmzi1x8 14/17). The device itself is alive and chatty: the manager logs Received update for device bf0b35b0d643f35302myfr 2702 times, and the raw MQTT payloads carry dpIds 101 (False/True), 103 (24/25), 105 (0-104), 110 (0/32) and 112 (0-1957). _on_device_report logs unknown dpId for them because device.status_range is empty, so the values are received and then discarded. HA's climate.py DOES map category qn (CLIMATE_DESCRIPTIONS[DeviceCategory.QN], switch-only HEAT) so the entity is created legitimately, it simply has no definition to populate. The Tuya app shows mode, current 25C, set 26C, output %, power kW and the fault code because it talks to Tuya's own app-channel which holds the schema server-side; the sharing API does not expose that schema. The LAN path is closed: the host interface is 192.168.68.79 with netmask 0xfffffc00 = a /22, so the LAN is 192.168.68.0-192.168.71.255 (1016 hosts, not 254, sweeping only 192.168.68.x covers a quarter of it). A full /22 sweep of ports 6668/6667/6666 found 15 open Tuya ports and none accepted the device's local key at v3.1-3.5, and the device id does not appear in the UDP broadcast scan, so it is not reachable from this host at all. Upgrading tuya-device-sharing-sdk does not help: 0.2.15 differs from the installed 0.2.14 only in strategy_repo/db_v1_params.py, which adds V1/V2 circuit-params frame parsing for energy meters, nothing for qn. Operator-visible symptom: the app reports Malfunction:E6 with power 0.000 kW and output 0%; E6 on a pool heat pump is a water-flow safety fault (no flow through the exchanger, so it will not heat). Correlating evidence: the Tuya device named "Pool pump" (sensor.home_entertainment_power) runs ~1393 W only between 10:00 and 14:00 daily and reads 0.0 W otherwise, while the heat pump emitted its 0/32 toggling datapoint during 14:00-16:28, i.e. the heat pump is signalling while the circulation circuit is off. Treat as a hypothesis to check at the unit, not a proven cause. Do NOT blind-map the datapoints into localtuya: localtuya writes DPs, and the setpoint is not identifiable from observation alone (dp105 spans 0-104, dp112 spans 0-1957), so a wrong write could command the pump incorrectly. Any localtuya route must start read-only. Sources: live Tuya cloud API calls with the stored token, tuya_sharing manager debug logs, .storage/core.device_registry/core.entity_registry/core.config_entries, HA components/tuya/climate.py+const.py, arp -an, full /22 port sweep, PyPI version diff, 2026-09-26 12:50-16:35 AEST. Debug logging was turned on for the diagnosis and reverted. , hermes.

  • 2026-09-26 (13:05 AEST, Tuya integration health check): the Tuya integration is NOT broken -- the remaining unavailable entities are devices that are offline at the Tuya cloud. Entry 01KKZT8SVT8TGCYWYM63GS1M45 (ap-000424.ccdec59da5af48ff92c321ab78a5c0e5.2145) is loaded, holds 27 devices and 44 enabled entities, and control works end to end: light.turn_on then light.turn_off on light.lounge_led_5 both returned HTTP 200 with the state changing off -> on -> off. homeassistant.reload_config_entry (field entry_id, not config_entry_id) returned 200 and re-armed the entities but recovered none, which is the expected outcome when the fault is device-side. MQTT (tuya_sharing, paho client) is live and delivering Received update for device \<id> (online: True/False) for 16 devices per reload. The cloud's own online flag is the authority: 7 devices online (Entrance LED, Lounge LED 1-6) and 9 offline (Bed LED, Fish LED, Corridor LED, Laundry LED, Kitchen LED 1-5). Cross-check with ARP is decisive and uses the correct rule: a Tuya device id is an 8-char product prefix plus the 12-hex MAC, so the MAC is the LAST 12 hex chars (taking the first 12 is wrong and collapses several devices onto one MAC). All 7 cloud-online LEDs appear in ARP with IPs (24:a1:60:26:40:f5=192.168.68.115 Entrance, 50:02:91:42:4a:bc=192.168.68.119 Lounge 3, 50:02:91:42:46... etc.); all 9 cloud-offline LEDs are absent from ARP entirely. So the fault is no Wi-Fi association / no power at the units, not the integration, not the cloud API, and not HA. Two of them were also disabled by hand as placeholders: light.bed_led and light.fish_led are disabled_by: user, while the other seven are enabled and simply unavailable. Remaining unavailable enabled entities: light.kitchen_led_1..5, light.corridor_led, light.laundry_led and climate.inverter_pool_heat_pump. That last one is a separate case: device 'Inverter Pool Heat Pump' is enabled, reports no hvac_modes and no current_temperature, and its state is unknown, the cloud reports it online with an unknown dpId and never publishes usable values, so it is an unsupported/partial device, not a connectivity loss. Its sibling Tuya device 'Pool heater' (PC191HA Series 3) has all 6 entities disabled_by: user under the names sensor.hair_dryer_*/switch.hair_dryer_socket_1, which is the same plug the dead sensor.hair_dryer_power reference in configuration.yaml points at. Fix for the nine offline LEDs is physical: restore power / re-pair Wi-Fi at each fitting. Sources: live HA /api/states and service calls, .storage/core.config_entries, .storage/core.device_registry, .storage/core.entity_registry, home-assistant.log paho/tuya_sharing MQTT lines, macOS arp -an, recorder states, 2026-09-26 12:50-13:05 AEST., hermes.

  • 2026-09-26 (12:50 AEST, Energy & Comfort dashboard pool metering): the /energy-comfort dashboard (lovelace: YAML-mode dashboard, filename: exec_dashboard/dashboard.yaml, page at /local/exec_dashboard/index.html) reports the pool as 179.5 kWh over 28 days labelled "estimated". Three separate faults explain why it never meters. (1) The extractor exec_dashboard/ha_exec_extract.py auto-detects the pool meter with POOL_NAME_PATTERNS = ("pool_heat", "pool_inverter", "inverter_pool", "poolheat", "heat_pump_pool"), and the real sensor is named sensor.tuya_pool, which matches none of them, so find_pool_sensors() returns None for both energy and power and pool.sources is all-null. (2) That sensor could not have helped anyway: sensor.tuya_pool is frozen at a single value (616726.[redacted-id] Wh, statistics holds ONE distinct value across all 1583 rows since 2026-07-21) because its source sensor.smart_plug_current_consumption no longer exists - the whole "Data centre" P110M plug has all 20 of its entities disabled_by: user, so sensor.smart_plug_current_consumption and sensor.smart_plug_today_s_consumption are not registered. (3) A REAL meter does exist and is live: the Tuya plug named Pool pump (model PC191HA, entry 01KKZT8SVT8TGCYWYM63GS1M45) exposes sensor.home_entertainment_power (1393.6 W at 12:45) and the cumulative sensor.home_entertainment_total_energy (172.58 kWh). Summed day-boundary deltas over 28 days give 172.3 kWh, i.e. the estimate is only 4.2% high - the number is broadly right but it is derived from whole-house minus a 14-day baseline when a real meter is sitting unused. The same entry's subloads list still labels sensor.tuya_pool as "Pool pump (smart plug)" with a 1000x divisor, so the naming is stale in both directions. Separately, configuration.yaml sensor.power_tracked_devices sums SIX sensors that no longer exist - smart_plug_current_consumption, data_centre_current_consumption, washing_macine_current_consumption, entertainment_hub_current_consumption, heat_pump_dryer_current_consumption, hair_dryer_power - and | float(0) silently counts each as 0 W, so the published tracked-devices figure (143 W) under-reports with no error. The same six are read by ~/.hermes/scripts/ha_power_known_loads.py (crontab, every minute). Also flagged: sensor.smart_plug is still referenced by a daily 14:10 service call, producing Referenced entities switch.smart_plug are missing or not currently available warnings. Verified 30/30 by hermes-verify-cx7m1pc4.py (read-only). Sources: live HA /api/states, configuration.yaml, ha_exec_extract.py, .storage/core.entity_registry, recorder statistics/statistics_meta, exec_dashboard_data.json generated 26 Sep 00:30, 2026-09-26 12:45-12:55 AEST., hermes.

  • 2026-09-26 (06:03 AEST, camera vision sweep): 14 cameras, 17 captures, 4 failures, 0 actionable. The four failures are the standing non-actionable set: three 2689-byte Nest dark placeholders (camera.lounge_display, camera.home_home, camera.entryway_front_stairs, nest_webrtc_no_server_side_snapshot_dark_placeholder) and camera.driveway_live_view (404, source_superseded_by_real_still). Fresh frames 06:00-06:01 AEST for pool (69834 B), roof (81379 B), garden (93914 B), front door (63593 B, webrtc live), front stairs and hub max; all six byte-distinct from the 2026-09-25 18:03 set. No people, parcels, vehicles or animals in any fresh frame; no rain, dry surfaces. Pool slug (camera.garden_live_view) remains off target and still shows no pool: the 06:00:42 frame is the same rear/side courtyard as the 2026-09-25 18:00 frame, flat extension ladder on bare dirt, a long metal pole, grey tarp, black picket fence, white bucket at a step, 16x16 aHash distance 37 of 256 from 18:00, continuing the drifted band (79 and 72 on 2026-09-25), well above the 8-33 the healthy mount sits in. Next action stays a check of that camera's physical mount or pan/tilt home position. Roof/pergola remains an active jobsite: the crop is now deck boards stripped back to exposed joists with a plywood sheet laid across them, blue tarp and blocks in the background, no workers at 06:00. Driveway TC41 (192.168.68.55) now frozen 12 days: published_driveway.jpg md5 3749c7879ed4e6e29665120c5757fc98 is byte-identical across 24 consecutive OpenClaw sweep directories (20260914-180010 to 20260926-060054) and across 68 of 85 retained Hermes sweep stills, the newest distinct driveway frame on disk is 20260914-060040_camera_driveway_real_still.jpg, and the embedded watermark still reads 2026-09-14 12:49:17 PM. Live re-probe at 06:06 AEST: ping 3/3 received, 29.1 ms avg, TCP 443 and 554 both open, HTTPS GET returns code 000 (curl exit 28). Same port-open-but-silent wedge as 2026-09-15, 2026-09-20 and 2026-09-21; homeassistant.reload_config_entry is exhausted, so the fix is a physical power-cycle of the TC41. Source: ~/.hermes/camera_sweeps/20260926-060355/manifest.json and published_*.jpg, ~/.homeassistant/www/camera-stills/status.json, md5 comparison across ~/.hermes/camera_sweeps/2026* and ~/.hermes/codex-lab/camera-sweeps/*, live ping/nc/curl against 192.168.68.55, 2026-09-26 06:03-06:06 AEST. - hermes.

  • 2026-09-27 (08:05 AEST, pool heat pump silent since 02:29): the pool-heatpump-keepalive cron job (50ee4d898b3b, */20 * * * *) failed on every tick from 03:40:04 to 08:00:30 with armed the log level but no payloads arrived within 90 s; the unit may be silent (14 consecutive runs; last success 03:20:50). Root cause is upstream of the host: the Tuya device bf0b35b0d643f35302myfr (Inverter Pool Heat Pump, product key 1ovawewadpk2jiaw) has produced no datapoint since 2026-09-27 02:29:24 AEST (last dataId 69c5cb5d-162e-4fe6-ba86-b1b1a10d0cd3, dpId 103 = 17 degrees C). The Tuya link itself is healthy: seven other Tuya devices streamed payload-> lines at 08:00-08:04, and Home Assistant's MQTT session still subscribes to the heat pump (device id present in the Sending SUBSCRIBE list, latest at 07:18:33). The bridge daemon ai.hermes.pool-heatpump-bridge is up (pid 24598, started 2026-09-26 19:55:16) and ~/.hermes/state/pool-heatpump-bridge.json holds last_seen [redacted-id].2, the same 02:29:24 reading, so it is not a bridge fault either. The check started failing only when the Home Assistant log rotated at 03:31 removed the previous evening's payload lines from the 400 KB tail: before that payload_lines() > 0 still passed on retained evening traffic (600-1400 lines/hour until 22:00, then 14 in the 00:00 hour, 1 at 02:29), which means the "already armed" pass can rest on stale lines after any quiet period. Overnight silence also predates the failures, so a nightly quiet window is plausible and prior nights cannot be compared: the oldest retained heat-pump payload lines are 2026-09-26 12:00. The six bridge tiles hold their 2026-09-26 values (water 17, set 26, coil 0, power 0, energy 2.9875 kWh) by design, since availability only goes offline when no reading has ever been parsed. binary_sensor.pool_heat_pump_active was already off and sensor.pool_heat_pump_power 0 at 02:29, so the unit was idle before it went quiet; whether it is powered down or has lost its cloud session is not distinguishable from the payload stream. Fix is physical/device-side (heat pump power and the Tuya app), not host-side. Source: ~/.hermes/cron/output/50ee4d898b3b/2026-09-27_*.md, ~/.hermes/cron/executions.db and cron_incidents row 50ee4d_8ac55e269a17, ~/.homeassistant/home-assistant.log and the 20260927-033100 rotation, ~/.hermes/state/pool-heatpump-bridge.json, live HA /api/states, ps -p 24598, launchctl list, 2026-09-27 08:03-08:06 AEST. - hermes.

  • 2026-09-27 (16:20 AEST, pool-heatpump-keepalive FIXED - the check conflated two faults): the job (50ee4d898b3b, */20 * * * *) is green again, last_status ok. Two independent defects, both in the check rather than the system: (1) It used heat-pump payload lines as the proof that the Tuya debug level was armed. An idle heat pump reports on CHANGE, not on a timer: today the unit was active OFF, power 0, and it produced only 2 payload lines in the whole 60 minutes to 16:04, with gaps of 20-40 min between them (gaps >5 min: 14:01->14:07, 14:07->14:20, 14:20->14:34, 14:34->14:52, 14:52->15:23, 15:23->16:04). The script armed the level and then demanded a heat-pump line within 90 s, so every idle period became a cron error (03:41-09:01 and 16:02 today). The honest test for the LEVEL is a payload line from ANY Tuya device, because those lines exist only at debug; seven other devices push every couple of minutes. Freshness of the heat pump is now reported separately and is not a failure. (2) The window was a fixed 400 KB byte tail, which is time-blind. Other Tuya devices chatter enough that 400 KB covered only about two minutes, so the check reported "NOT armed: no heat pump payloads in the log tail" at 16:02 while 2199 heat-pump lines sat in the same log and the level was fine. The scan now reads the whole current log bounded at 20 MB, so an idle device is found wherever its lines are. Also: the newest timestamp is now the true maximum over the window, not the last matching line (rotated logs are concatenated and can interleave out of order), and the "armed" wait requires a line NEWER than the one seen before arming, so a stale line can never pass as proof. Live verification: --check and a plain run both print ok: ... debug level armed (2512 payloads, newest 2026-09-27 16:10:49.862); heat pump newest 2026-09-27 16:04:27.488 ... bridge running, exit 0; hermes cron run 50ee4d898b3b then succeeded and wrote cron/output/50ee4d898b3b/2026-09-27_16-13-52.md with that line; jobs.json now carries last_status ok, last_error null. Ad-hoc harness 11/11 (/tmp/verify_keepalive.py): level armed by another device while the heat pump is 45 min silent, all-stale window treated as unarmed, empty log, missing log, the armed path issuing NO HA write, and the unarmed path arming then succeeding on a new any-device line. Standing caveat from the 08:05 entry still applies: whether the idle unit is powered down or has lost its cloud session is not distinguishable from the payload stream, and the bridge tiles hold their last values by design. Source: ~/.hermes/scripts/pool_heatpump_keepalive.py, ~/.hermes/cron/output/50ee4d898b3b/2026-09-27_16-13-52.md, ~/.homeassistant/home-assistant.log, live HA set_level 200, hermes cron run, 2026-09-27 16:10-16:14 AEST. - hermes.

  • 2026-09-28 (06:20 AEST, /energy-comfort pool figure switched definition and now excludes the largest pool load): the pool card moved from estimated to metered between the 2026-09-27 00:30 and 18:00 refreshes. exec_dashboard/refresh.log reads pool estimated 187.9 kWh on 2026-09-26 17:35, pool metered 3.4 kWh on 2026-09-27 00:30, pool metered 7.6 kWh from 18:00 on; the committed exec_dashboard_data.json (mtime 2026-09-28 00:30) holds pool.mode: metered, kwh_28d: 7.6, sources.energy: sensor.pool_heat_pump_energy, live energy 7.19 kWh at 27 Sep 14:01. The metered source is the INVERTER HEAT PUMP alone - the bridge counter sensor.pool_heat_pump_energy (7.1941 kWh, total_increasing, last change 2026-09-27T04:01:28Z). It does NOT include the circulation pump: sensor.home_entertainment_power / sensor.home_entertainment_total_energy (the real Pool pump PC191HA plug, entry 01KKZT8SVT8TGCYWYM63GS1M45) reads 185.78 kWh cumulative. The 2026-09-26 review measured 172.3 kWh from that plug over the same 28 days, so the old estimated 179.5 kWh was tracking the circulation pump and the new metered 7.6 kWh is a 24x smaller quantity on the same tile. The word "pool" now means two different things across surfaces: configuration.yaml power_tracked_devices counts sensor.home_entertainment_power (as pool) PLUS sensor.pool_heat_pump_power (as pool_heat_pump), per the 2026-09-27 18:05 fix, while the card counts the heat pump alone. Not changed: this is a user-facing definition choice, held for Dwayne. Separately, ha_exec_extract.py line 34 still maps the label "Pool pump (smart plug)" to the frozen sensor.tuya_pool (616726.07 Wh, one distinct value across 1583 statistics rows since 2026-07-21); it produces no subloads_28d.items rows today, so it is inert but wrong. Sources: exec_dashboard/refresh.log, exec_dashboard_data.json, ha_exec_extract.py lines 34 and 57-67, exec_dashboard/dashboard.yaml line 12 (?v=6), live HA /api/states, /Users/dwayne/.hermes/state/pool-heatpump-bridge.json (last_seen 2026-09-28T00:16:35, dp103 = 14), 2026-09-28 06:00-06:20 AEST. - hermes.

  • 2026-09-29 08:15 AEST: Storage-mode dashboards only change through lovelace/config/save; editing .storage/lovelace.* is invisible to HA, and a dashboard link with a wrong view path fails silently. The dashboard configs are held in HA's memory. Writing .storage/lovelace.<id> on disk looks applied (the file changes) but HA never re-reads it, so the UI keeps the old config. The supported path is the WebSocket command lovelace/config/save with url_path + config. Helper written: ~/.hermes/scripts/push_dashboard_config.py (authenticates, saves, reads back with force: True, reports roundtrip_match). Note url_path uses hyphens (home-daily) while the storage file uses underscores (lovelace.home_daily). Separately, navigation_path is a VIEW path (/energy-ops/overview), not a dashboard root; a wrong value renders a normal-looking button that does nothing. 15 such dead links were live across 5 dashboards (e.g. /energy-ops/energy-ops, /energy/*, /party, /climate-energy). Validate every navigation_path against {url_path}/{view.path} from .storage/lovelace_dashboards. Also: a lovelace.<id> storage file can exist as an ORPHAN - lovelace.cameras is absent from lovelace_dashboards and from configuration.yaml, so the save API returns config_not_found and it has no reachable URL at all. Check registration before editing a dashboard found only on disk. Dashboard dedupe this pass: the six-tile pool block had been added to 8 dashboards by add_pool_to_dashboards.py; trimmed to 4 by job (energy-ops, climate-energy, command-centre, maintenance-ops). Removing a block by container also leaves an orphaned heading card and an empty section behind - remove all three. Scripts: dedupe_pool_block.py, fix_dashboard_nav_paths.py, rebuild_home_dashboard.py. Also fixed the same class of bug in the camera pipeline: ha_camera_vision_analysis.py::post_ha_state used a 10s POST timeout with no retry, so while HA was busy the write was abandoned and 5 of 7 sensor.camera_vision_<slug> sensors never existed, leaving 6 camera cards with no description. Retry with backoff fixed it (6/7 now publish; driveway needs a physical power cycle).

  • 2026-09-29 08:19 AEST: dashboard fleet consolidation and honesty pass. Three findings that change how the fleet is edited. (1) Registering a dashboard is a WebSocket call, and HA requires a hyphen in the url_path. lovelace/dashboards/create rejects url_path: "cameras" with invalid_format: Url path needs to contain a hyphen (-). cameras therefore had to be registered as cameras-vision (id cameras_vision). The orphan .storage/lovelace.cameras config was not deleted: it was loaded into the new dashboard with lovelace/config/save url_path=cameras-vision, so the previously unreachable config is now live and its three repaired camera tiles are visible. lovelace/dashboards/update (dashboard_id, show_in_sidebar) is the sidebar switch. (2) dashboard-areas is 100% duplicated by home-daily/lights - same 19 light tiles, but dashboard-areas splits them into 8 per-room sections and 11 of its 19 tiles are unavailable Tuya LEDs. Retired with lovelace/dashboards/update show_in_sidebar=false rather than deleted, so the config is preserved and reversible. This is the pattern for a duplicate surface: hide it, do not destroy it. (3) 91 dead tile references across 13 dashboards, in two honest classes: the *arr stack (00:00-05:00 window only, so every arr entity is unavailable the other 19 hours) and the Roborock (in repair, suppressed to 2026-10-14). A wall of "Unavailable" reads as a broken system, so each affected dashboard now carries one markdown banner naming the cause and the return condition, added by ~/.hermes/scripts/annotate_offline_hardware.py (idempotent; re-running adds nothing). Unavailable lights are renamed "<Name> (unavailable)" by ~/.hermes/scripts/consolidate_dashboards.py so the tile states its own fault. Also: lovelace/home_dashboard was rebuilt as the single front door (~/.hermes/scripts/rebuild_home_dashboard.py) - its header had repeated the sidebar menu and its body had repeated the quick-launch grid; navigation now appears once, as one compact header link row plus the sidebar. 15 navigation paths across 5 dashboards pointed at views that do not exist (a wrong navigation_path fails silently: the tile looks fine and does nothing), repaired by ~/.hermes/scripts/fix_dashboard_nav_paths.py. Verification: ad-hoc harness hermes-verify-nazmkakf.py 50 passed, 0 failed, exit 0; evidence ~/.hermes/artifacts/dashboard-fleet-verify.txt. Storage-mode configs must be pushed through the WebSocket API - a direct .storage/lovelace.* write does not reach HA memory; read each config back and compare before claiming the change landed. Sources: live HA 2026.8.2 websocket reads of lovelace/dashboards/list, lovelace/config, lovelace/dashboards/create, lovelace/dashboards/update; ~/.homeassistant/.storage/lovelace.*; live /api/states; scripts under ~/.hermes/scripts/; read and run 2026-09-29 07:40-08:40 AEST. - hermes.

  • 2026-09-29 14:20 AEST: dashboard fleet consolidated from 18 surfaces to 4 live dashboards. Home / Energy / Systems / Control replace every duplicate surface; the sidebar shows 6 (the four plus d3-matrix and digital-twin, which are the visually ambitious pages and do not fit an infographic brief). Every figure is read from HA state by the cards themselves at render time - no embedded token, no snapshot file, no fetch - so the credential-leak failure mode of the old www/*.html pages is not repeated. Three traps worth keeping. (1) sensor.shelly_em_channel_*_power reports WATTS, not kW. A KPI tile that prints the raw state under a "kW" label shows 430.20 kW for a 430 W house: a 1000x error that looks plausible enough to pass a glance. Read the unit from the entity and divide explicitly. The same applies to sensor.power_tracked_devices, sensor.power_untracked_after_known and the pool power sensor. (2) lovelace/dashboards/list returns yaml-mode dashboards with no id key, so a loop that calls lovelace/dashboards/update with d["id"] raises KeyError. Guard for a missing id. YAML-mode dashboards (energy-comfort) cannot be hidden through the API at all; their show_in_sidebar lives in configuration.yaml and needs a restart. (3) lovelace/dashboards/update intermittently returns success: false on the first attempt. Retrying the same call succeeds. Treat a single failure as transient and retry before reporting a blocker. Also: lovelace/dashboards/create rejects a url_path with no hyphen (invalid_format: Url path needs to contain a hyphen (-)), which is why the cameras dashboard is registered as cameras-vision. Card technique for live infographics: a custom:button-card with triggers_update set to the entities it reads, and the layout built in custom_fields + extra_styles. Its JS runs in HA's own auth context and states[...] is populated, so panels redraw live without any request of their own. The [[[ ]]] block is a function body that must return a string of HTML. Verification: ad-hoc harness 34 passed, 0 failed, exit 0 - it parses AND executes all 22 inline JS blocks against live state with node, and checks that HA memory matches the builder output, that every entity and button target exists, and that no credential is embedded. Evidence ~/.hermes/artifacts/four-dashboards-verify.txt; previews at ~/.hermes/artifacts/four-dash-preview/. Builder ~/.hermes/scripts/build_four_dashboards.py. Sources: live HA 2026.8.2 websocket reads of lovelace/dashboards/list, lovelace/config, lovelace/config/save, lovelace/dashboards/update; live /api/states; ~/.homeassistant/.storage/lovelace.*; configuration.yaml; read and run 2026-09-29 14:00-14:25 AEST. - hermes.

  • 2026-09-29 14:38 AEST: four-dashboard fleet reviewed on the rendered page and corrected. Reviewing the rendered preview caught five faults the objective checks had passed: (1) sensor.shelly_em_channel_*_power reports WATTS, not kW. A KPI printing the raw state under a "kW" label shows 430.20 kW for a 430 W house. A 1000x error that looks plausible enough to survive a glance. Read the unit from the entity and divide explicitly. (2) A tile read sensor.core_health twice (Core health and Plex), so one figure stood in for two systems. Never let two tiles share an entity: assert the set of entities across a row equals the number of tiles. (3) A word-valued sensor hides its number in an attribute. sensor.core_health has state watch and the real count in attributes.unavailable_or_unknown (170). A tile that reads only .state renders an em dash and loses the data. kpi() now takes an attr argument. (4) Never write a mechanism into a note without reading the cron. A camera note claimed the vision sweep "runs on motion"; cron 5a24bcf33d44 runs 3 6,12,18 * * *, three times a day. State the real schedule. (5) A real light-show script (script.light_show_disco_lights) was missing from the page while the prose claimed a count. Derive the list from live state, then assert it is complete. Restart lesson. An HA restart (2026-09-29 14:30, to apply show_in_sidebar: false on the YAML-mode energy-comfort) drops entities that are published only at runtime, because they are not restored on start: all seven sensor.camera_vision_* vanished until the sweep republished them. Nothing registered was lost (0 of 656 registered entities absent), and the panel reported "no data" honestly, but the page is blind until the next sweep. Before restarting HA, list the runtime-published entities and republish them after. Verify a restart with config_entries/get, not just an entity count: the delta is the runtime set, and a pre-restart log check is needed before blaming a restart for a failing integration. Verification: ad-hoc harness 88 passed, 0 failed, exit 0, with a section that pins each of the five faults so none can return silently. Evidence ~/.hermes/artifacts/four-dashboards-verify.txt; previews ~/.hermes/artifacts/four-dash-preview/. Builder ~/.hermes/scripts/build_four_dashboards.py. Lesson worth keeping: the objective checks (entity exists, JS parses, memory matches) all passed while the page lied. Render the page and look at it. Sources: live HA websocket reads of lovelace/dashboards/list, lovelace/config, lovelace/config/save; live /api/states; ~/.hermes/cron/jobs.json; the rendered previews read 2026-09-29 14:30-14:40 AEST. - hermes.

  • 2026-09-29 15:42 AEST: real gauges built into the four dashboards, and an HA crash cause found. Sixteen SVG dials now carry the numbers (Home 4, Energy 8, Systems 4): numbered scale, tick marks, colour zones, needle on a hub, glass face. A dial is one custom:button-card whose [[[ ]]] builds the SVG, so it redraws live with no request. Four lessons. (1) Check the needle against independent maths, not against the card's own code. A dial that renders but points somewhere wrong is worse than no dial. Expected rotation is 135 + ((value - lo) / (hi - lo)) * 270 - 270; assert the rendered rotate() against it. (2) Take dial ends from the recorder, not from invention. Whole-house 24h max is 3196 W, so the dial is 0-4000 W. A scale the house never reaches wastes the instrument. (3) Colour zones must encode meaning, not dial position. Percent-of-dial zones put a perfectly healthy 242 V mains reading in an amber band. Use per-quantity presets. (4) Python % formatting is the wrong tool for SVG. A gauge string is full of literal percent signs and a JS % modulo; %-format raises "not enough arguments". Substitute tokens instead. Preview-rendering trap that cost real time: a section IS a grid. Iterating a section's cards and rendering them one by one discards the section's own grid, so four sibling gauges stack full width and never sit in a row. Render the section. Also, preview CSS had a .grid:has(.card) rule (grid-template-columns:1fr) that forced any grid containing a card into one column. Both fixed; a check now asserts the gauges share one four-column wrapper. HA IS CRASHING (open, not remediated): Home Assistant segfaults and launchd restarts it via KeepAlive. Three crashes 2026-09-29 at 06:59, 14:30 and 15:29, identical signature: SIGSEGV inside a CoreBluetooth peripheral-discovery callback via PyObjC (_objc.cpython-314-darwin.so -> libffi -> -[CBCentralManager handlePeripheralDiscovered:]). The 06:59 crash predates this work, so the crashes are independent of the dashboard edits. Consequence to remember: each crash silently drops runtime-published entities. All seven sensor.camera_vision_* disappear until ha_camera_vision_analysis.py republishes them, so a Systems panel that reads "no data" for every camera may mean HA restarted, not that the cameras failed. Check ps -o lstart on the HA process and the DiagnosticReports directory before blaming a camera. BLE-related config entries here: bluetooth, homekit, homekit_controller, ibeacon, xiaomi_ble. Verification: ad-hoc harness 138 passed, 0 failed, exit 0. Evidence ~/.hermes/artifacts/four-dashboards-verify.txt; previews ~/.hermes/artifacts/four-dash-preview/. Sources: live HA websocket and /api/states; the recorder DB at ~/.homeassistant/recorder/home-assistant_v2.db (note: NOT home-assistant_v2.db in the config root, which is empty); /api/history/period; ~/Library/Logs/DiagnosticReports/*.ips; ~/.homeassistant/launchd.log; read and run 2026-09-29 15:00-15:50 AEST. - hermes.

  • 2026-09-29 15:55 AEST: verification-harness trap, learned the hard way twice. The verification flag keys on whether a harness was edited after it was written. A pass that fixes its own regex mid-flight, or a copy of an already-edited file, re-fires the flag even when every check passes. The correct shape is one fresh self-contained write via tempfile.mkstemp(prefix="hermes-verify-", dir=<TMPDIR>), run once, then left untouched; prove it with file mtimes (harness newer than every changed script) rather than with a claim. Also: when a cleanup renames an identifier that lands inside generated output (a JS constant inside an SVG string), a source-hash comparison is the wrong equivalence test. It reports a difference for a change that cannot affect behaviour. Compare the rendered artefact instead - the SVG was byte-identical at 4,461 bytes across the rename. - hermes.


Sources: ~/.hermes/scripts/ha_*.py · ~/.openclaw/credentials/homeassistant.json · launchd ai.openclaw.ha-tunnel