Timestamp relay changes with a read-only state listener
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>
This commit is contained in:
1 parent
af0a564279
commit
955c679f5e
6 files changed
+185
-22
No files matched your search
@@ -161,3 +161,24 @@ async def test_freshly_written_relay_state_defers_learning(hass, entry, inputs,
|
||||
await hass.async_block_till_done()
|
||||
assert coordinator.data["sample_result"] == "lag_uncertain"
|
||||
assert coordinator.learners["sensor.room"].count == 0
|
||||
|
||||
|
||||
async def test_relay_listener_integrates_changes_between_polls(hass, entry, inputs, freezer):
|
||||
coordinator = await setup(hass, entry)
|
||||
start = dt_util.utcnow()
|
||||
for offset, state in ((60, "on"), (200, "off")): # on/off round trip inside one poll
|
||||
freezer.move_to(start + timedelta(seconds=offset))
|
||||
hass.states.async_set("switch.boiler", state)
|
||||
await hass.async_block_till_done()
|
||||
for minutes, expected in ((5, "relay_changed"), (10, "learned")):
|
||||
future = start + timedelta(minutes=minutes, seconds=1)
|
||||
freezer.move_to(future)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
assert coordinator.data["sample_result"] == expected
|
||||
# Without the listener this round trip would restart the estimate (about 0.46).
|
||||
assert coordinator.data["heat_lag_uncertainty"] < 0.05
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
hass.states.async_set("switch.boiler", "on")
|
||||
await hass.async_block_till_done()
|
||||
assert not coordinator._relay_events
|
||||
Reference in new issue
Block a user