Home Assistant marks a coordinator shut down only after the platforms are
unloaded, and the model save checked for shutdown before waiting for the
storage lock. A deletion could therefore be followed by a save that had
been waiting for it, recreating the file. Also, unloading cancels a running
scheduled update, which released the lock while the file write continued
in a worker thread.
- The coordinator is marked closing before platforms unload (restored if
unloading fails); closing updates, relay callbacks and saves do nothing.
- The save checks closing and a removed-model mark after acquiring the
lock, and runs shielded so the lock is held until the write finishes.
- Removal marks the model removed under the lock before deleting it.
- A failed setup also shuts the coordinator down.
Tests: unload and reload during a running optimization, removal during a
save, a save waiting for a deletion, simultaneous saves, repeated
setup/unload, shutdown ordering and failed setup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed: the check run after the optimizer compared only zone sensors,
outdoor temperature, thermostat and relay. A window opening, a UPS load
change or a wind change during the calculation went unnoticed; nothing
prevented an older update that finished late from overwriting a newer
result; and an update in flight during unload still saved.
- Every input a prediction uses is captured as a normalized snapshot
(zone and outdoor temperatures, wind speed/direction/gust, heat-source
power, window states, relay state and switch time, thermostat mode and
target) and compared after the calculation with tolerances (0.25 °C,
50 W, 1 m/s, 2 m/s gusts, 20° direction around the circle; exact for
relay, thermostat and windows). A meaningful change discards the plan
and lists the inputs in changed_inputs; noise does not. No immediate
recalculation: an off-schedule poll would break the 5-minute learning
interval; the next regular poll computes a fresh plan.
- Updates are numbered; one overtaken by a newer update, or running when
the entry unloads, keeps the newer data and skips saving and
optimizing. Configuration changes reload the entry, so they are covered.
- A per-entry storage lock makes a reloaded coordinator restore only
after the previous one's save finished, and integration removal waits
for it too, so a late save cannot recreate a deleted model file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each temperature sensor used to be its own "room", so a room with two
sensors counted twice and comfort importance was tied to sensors.
zones.py adds Zone: a name, one or more sensors with sensor weights, a
comfort weight, an optional target offset and adjacent zones (made
symmetric). Sensor weights only combine readings; comfort weights only
steer the MPC; neither touches the learned thermal coefficients.
Zones are config subentries (HA's native "Add zone" / edit / delete on
the integration page), with validation that a sensor belongs to one
zone and is not an outdoor source, and a sensor-weight step for
multi-sensor zones. Initial setup still asks for room sensors and
weights and creates one zone per sensor. Configure no longer asks for
rooms or weights.
Config entry version 3. Migrating from 2 turns each room sensor into a
one-sensor zone with its comfort weight and the sensor's entity ID as
the zone key, so learned models, forecast entities (now linked to their
zone, so deleting a zone removes them) and accuracy statistics carry
over. Older versions cannot load a version 3 entry; rolling back needs
the HA backup.
A zone learns only across polls that used the same sensors (a sensor
dropping out shifts the mean by its offset); the new poll outcome
"sensors_changed" reports this. Status renames: input_quality.zones
(per-sensor detail nested), excluded_zones, retired_zone_models; adds
zone_names. Invalid stored comfort weights now fall back to 1.0, so the
invalid_weights status is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Polling only sees the relay's current state and last change, so two or
more changes between polls (e.g. an on/off round trip) made the lag
estimate unknown and paused learning for about half an hour.
Subscribe to the relay's state changes (read-only) and integrate every
recorded change between polls. Because HA delivers these events one
loop iteration late, the current state stays authoritative and fills in
a change not yet recorded. Unknown periods (non on/off states, missed
transitions, event overflow) still restart the estimate with an
explicit error bound. Without the listener, behavior is as before.
HA skips unload callbacks after unexpected setup errors, so setup now
removes the listener itself on any failure and registers it for unload
only once setup succeeds.
Tested on HA Core 2025.12.0 and 2026.2.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>