Discard predictions only when their inputs change
A finished plan was discarded whenever any watched State object changed during the save or calculation, including diagnostic-only weather sensors and the thermostat's current_temperature/hvac_action. The proposed-heat entity then went unavailable for five minutes without cause. Compare only the values the prediction uses: room and outdoor temperature states and units, thermostat mode and target, and relay state and last change. Also pass config_entry to the coordinator explicitly instead of relying on HA's ContextVar fallback, which HA reports as deprecated usage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3646ba876
commit
2a0a94c640
3 files changed
+40
-7
No files matched your search
@@ -32,7 +32,8 @@ class HeatingMPCCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""The existing generic thermostat stays the only actuator owner."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=5))
|
||||
super().__init__(hass, _LOGGER, config_entry=entry, name=DOMAIN,
|
||||
update_interval=timedelta(minutes=5))
|
||||
self.entry = entry
|
||||
self.config = {**DEFAULTS, **entry.data, **entry.options}
|
||||
self.rooms: list[str] = self.config[CONF_ROOMS]
|
||||
@@ -106,6 +107,23 @@ class HeatingMPCCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._lag = {"time": now, "heater_on": heater_on, "lag": lag, "uncertainty": uncertainty}
|
||||
return steady
|
||||
|
||||
def _prediction_inputs(self) -> tuple:
|
||||
"""Values the prediction depends on, used to detect changes during calculation."""
|
||||
heater = self.config[CONF_HEATER]
|
||||
entities = sorted(set(self.rooms) | set(sources_for(self.config, "temperature")) |
|
||||
{self.config[CONF_CLIMATE], heater})
|
||||
values = []
|
||||
for entity_id in entities:
|
||||
state = self.hass.states.get(entity_id)
|
||||
if state is None:
|
||||
values.append((entity_id, None))
|
||||
continue
|
||||
attrs = state.attributes
|
||||
values.append((entity_id, state.state, attrs.get("unit_of_measurement"),
|
||||
attrs.get("temperature"), attrs.get("temperature_unit"),
|
||||
state.last_changed if entity_id == heater else None))
|
||||
return tuple(values)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
now = datetime.now(timezone.utc)
|
||||
heater = self.hass.states.get(self.config[CONF_HEATER])
|
||||
@@ -116,9 +134,9 @@ class HeatingMPCCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
entity_id for metric in WEATHER_SOURCES
|
||||
for entity_id in sources_for(self.config, metric)
|
||||
}
|
||||
watched = set(self.rooms) | weather_entity_ids | {
|
||||
self.config[CONF_CLIMATE], self.config[CONF_HEATER]}
|
||||
snapshot = {entity: self.hass.states.get(entity) for entity in watched}
|
||||
snapshot = {entity: self.hass.states.get(entity)
|
||||
for entity in set(self.rooms) | weather_entity_ids}
|
||||
inputs_before = self._prediction_inputs()
|
||||
room_quality = {}
|
||||
temps = {}
|
||||
for room in self.rooms:
|
||||
@@ -273,10 +291,11 @@ class HeatingMPCCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
heating_cost=float(self.config[CONF_HEAT_COST]),
|
||||
comfort_band=float(self.config[CONF_BAND]),
|
||||
)
|
||||
# HA State objects are immutable snapshots. Discard a result if any
|
||||
# input changed while the worker was calculating the hypothetical plan.
|
||||
# Discard a result if a value used by the prediction changed while
|
||||
# saving or while the worker calculated the hypothetical plan. Optional
|
||||
# diagnostic weather sensors are not prediction inputs.
|
||||
output = await self.hass.async_add_executor_job(job)
|
||||
if any(self.hass.states.get(entity) is not state for entity, state in snapshot.items()):
|
||||
if self._prediction_inputs() != inputs_before:
|
||||
status["status"] = "inputs_changed"
|
||||
return status
|
||||
except (ValueError, RuntimeError, OverflowError) as err:
|
||||
|
||||
Reference in new issue
Block a user