Files
goodwe-addon/goodwe_controller/DECISIONS/SAFETY-112-waiting-for-home-assistant.md
T
adminandClaude Opus 5.5 df6bc0f76b
checks / unit (pull_request) Successful in 36m27s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-112 review: grade the waiting flag where hass.py sets it
- a 502 or refused read turns waiting on, the next good read off, and a
  read timeout never (two mutants survived without this)
- the flag changes no write with live readings, not only with 502 ones
- the page script's amber dot and batteries-tab banner for "waiting"
- DECISIONS: about 5 minutes per start stage, not "at most"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:10:27 +02:00

4.9 KiB

SAFETY-112 - control waits for Home Assistant to finish starting

Status: for review. Tests: test_safety112.py, test_messages.py. Control is unchanged. The add-on now says plainly that it is waiting.

Cause

The Supervisor's proxy refuses every add-on request with 502 until Core's state is RUNNING. The add-on reads every battery reading and writes every power request through that proxy, so it cannot control until then. Readings that are already live inside Home Assistant do not help.

Source, Supervisor 2026.08.0 (the image the live site ran at CFG-04):

  • supervisor/api/proxy.py, APIProxy.api() (REST, used for reads and number.set_value) and APIProxy.websocket() both start with if not await self.sys_homeassistant.api.check_api_state(): raise HTTPBadGateway.
  • supervisor/homeassistant/api.py, check_api_state() returns state.core_state == "RUNNING" or state.offline_db_migration. The state comes from Core's /api/core/state.
  • Upstream main has the same gate in both handlers (read 2026-10-13).
  • A Core restart restarts only the Core container (homeassistant/core.py, restart()). The add-on keeps running.

The add-on itself has no wait. Nothing in app/ reads Core's state or waits for an event. The loop reads its inputs every second, a 502 gives None, and missing inputs give 0 W. P1 backoff is not involved: the live site reads the meter straight from the HomeWizard (homewizard_local), not through Home Assistant.

Measured

Repro stack safety112-*, all removed afterwards:

  • Home Assistant 2026.9.4, as live.
  • A test integration whose async_setup sleeps, so Core stays NOT_RUNNING.
  • A fake meter and two fake inverters that update every 2 s from load.
  • The Supervisor's own APIProxy and HomeAssistantAPI from the 2026.08.0 image. Only the add-on token lookup, Core's access token and the container check are stubbed.
  • The add-on on meter_source: homewizard_local against a fake HomeWizard.

Times are seconds after docker restart of Home Assistant:

run Core NOT_RUNNING entities live in HA Core RUNNING proxy 502 first write wait from live readings
base 3050fa5, N=2 13.1 17.1 253.1 0.9-252.7 (1 132 requests) 254.7 237.6
base 3050fa5, N=1 13.1 18.1 256.1 1.5-255.1 (573 requests) 257.2 239.1
this change, N=2 12.2 17.2 313.2 1.4-312.5 (1 406 requests) 317.6 300.4
this change, N=1 12.2 17.2 255.2 1.2-254.1 (571 requests) 257.1 240.0

The first write comes 1.0-4.4 s after RUNNING, so the whole wait is the proxy's. With a 400 s sleep Core moved on at 300.88 s on its own (bootstrap's stage timeout), so one slow integration holds a start stage for about 5 minutes. The whole start can take longer: the live one took 414 s.

Before this change the page said "Not ready — Battery level: no usable reading", which sends the reader to Developer Tools for an entity that is fine.

Rule

  1. No way around the Supervisor. The add-on still talks to Home Assistant only through the proxy.
  2. While the proxy does not answer on the API (hass.OUTAGE.waiting()), the first status row is red: "Home Assistant is not answering, perhaps it is still starting. Control resumes by itself when it answers." The banner reads "Not ready — Waiting for Home Assistant to finish starting". The Home pill reads "Waiting for Home Assistant", in English and Dutch.
  3. A safety stop, a stalled control loop or shadow mode keeps its own banner and state over the wait, because none of them clears by itself.
  4. The log has one WARNING when Home Assistant stops answering, an INFO line every 5 minutes while it is down, and an INFO line with the length of the wait when it answers again.
  5. Only the API counts. The meter websocket alone does not, because the meter has its own row.

Why it is safe

The change is text only. cycle(), the writes, the heartbeat, the stand-down on shutdown and every watchdog are untouched. test_safety112.py part 3 shows the same targets and writes with the flag on and off, for N=1 and N=2.

Open, for the owner

The add-on cannot control while Home Assistant starts. These ways around it were not taken:

  • Talk to Core directly with a long-lived token (http://homeassistant:8123). Core's own API answers while NOT_RUNNING. In the repro /api/core/state answered from 12-13 s, and a direct number.set_value 10 s into NOT_RUNNING returned 200 and set the value. The cost is a secret stored at every client site, which hass.py exists to avoid.
  • Talk to the control board directly (ESPHome native API), so control does not need Home Assistant at all. This is a large change.
  • Remove the slow start at the site. On 2026-10-12 the Solis integration's discovery held the start. Fixing or disabling it shortens the wait there, but not on a client site with another slow integration.