# 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.