Run ruff with pyflakes, syntax-error and bugbear rules only (no style
rules) over the integration, tests and scripts as part of check.sh and
CI. The codebase passes as is. Target Python 3.9 because the offline
tests are also run on older interpreters, which also leaves out the
zip(strict=) rule that needs 3.10. ruff 0.14.0 pinned in both test
requirement files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home Assistant 2026.3+ shows custom integrations' own brand images from
custom_components/<domain>/brand/. Add a radiator with a forecast curve
(solid past, "now" marker, dashed prediction) as icon.png/icon@2x.png
(256/512 px, transparent) plus dark-theme variants, so the integration
is easy to spot in Devices & services. Older HA versions ignore the
folder and show the generic placeholder; the integration is unaffected.
scripts/make_brand_icons.py regenerates the images with Pillow from the
test requirements; the package check verifies sizes and transparency.
Listed under 0.2.3, which has not been deployed yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump the manifest to 0.2.3 for the stale-room policy: unusable room
sensors are left out with weights re-normalized, and an opt-in checkbox
keeps a stale sensor's last value for up to 24 hours. 0.2.2 was never
deployed, so the changelog marks it as such and the 0.2.3 notes point
0.2.1 users at both entries. ROADMAP and AUDIT record the decision and
the open control-design questions (minimum weight coverage, outdoor
temperature outages).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single stale, unavailable, missing or invalid room sensor paused
learning and forecasts for every room. That is not acceptable once the
MPC may control heating.
Now such a room is left out: the weighted temperature, learning and the
optimizer use the remaining rooms with their weights re-normalized, and
a room learns only across poll pairs where it was usable at both ends.
Readiness considers only the rooms used. Everything pauses only when no
room is usable or a shared input (relay, thermostat, outdoor
temperature) is missing.
A new setup/Configure checkbox, room_stale_use_last_value (default off),
keeps using a stale room sensor's last value instead, for sensors that
report only on change. It never applies to unavailable or invalid
sensors, or to a sensor silent for more than 24 hours.
Status adds excluded_rooms, weight_coverage and a per-room "used" flag.
English and Romanian labels and help text added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump the manifest to 0.2.2 and add CHANGELOG.md with upgrade, reset and
rollback notes covering all reliability work since 0.2.1. The README
drops its accumulated "Unreleased" sections in favor of the changelog
and gains a Diagnostics section explaining the Status attributes and how
to choose freshness limits. The package check now fails if the manifest
version has no changelog entry. ROADMAP marks the Milestone 0 items now
backed by tests and resets the next-session backlog; AUDIT.md records
this round's findings.
Still observation-only. Tested in the HA Core harness with HA 2025.12.0
and 2026.2.3; not on HAOS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add requirements-test-latest.txt pinning the newest published
pytest-homeassistant-custom-component (0.13.316, HA 2026.2.3, Python
3.13) and run the workflow as a matrix over both requirement files. No
DNS-library pin is needed for the newer release. Both environments pass
scripts/check.sh locally, with no HA deprecation warnings logged for
this integration. Remote runner execution is still unverified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Choosing room and weather freshness limits required knowing how long
each sensor normally goes without reporting, which HA does not show.
Add max_age_seconds_24h to every source in input_quality: the largest
report age seen at polls over the last 24 hours (hourly buckets, reset
on reload, underestimates by up to one poll interval).
input_quality and weather_comparison change every poll, so the Recorder
stored a new attribute row every five minutes. Mark them unrecorded;
they remain visible on the Status entity. Model coefficients and other
attributes are still recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving wrote only the currently configured rooms, so removing a room in
Configure (even by accident) discarded its learned model at the next
save, and adding it back started from scratch.
Keep validated models of removed rooms (at most 24, oldest dropped
first) in an optional retired_learners payload key and restore them when
the room is added back. Payload schema_version stays 2: the key is
additive and older 0.2.x readers ignore it. Replacing the relay still
starts a fresh model, as before. Status lists retired_room_models.
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>
Record findings, evidence, compatibility notes and remaining gaps in
AUDIT.md; add user-facing upgrade notes to README and update test
counts; note progress against Milestone 0 in ROADMAP. No version bump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HA suite rejects service calls at runtime, but only on exercised
paths. Parse the integration source and fail on service calls or
registration, actuator method names, service-name strings, or any
platform other than sensor/binary_sensor (AGENTS.md safety rule 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Optional weather source fields used the current selection as the form
default. A cleared field that the frontend omits was refilled with the
old sources by voluptuous, and an options key missing from the
submission fell back to the original setup data. Optional providers
could therefore not be removed in the UI.
Pre-fill with suggested values instead, and save omitted optional lists
as empty so they override older entry data. No UI strings changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The lag proxy was reset to the new relay command at every relay change
and after any skipped interval, so it read 1.0 straight after ON and 0.0
straight after OFF. This disabled the lag model around every boiler
cycle: on a synthetic lagged plant cycled by a hysteresis thermostat the
learner's heat gain came out 34% low (0.079 vs 0.12) and the loss term
43% low, and the optimizer started post-transition plans from the wrong
lag.
Track the lag at every poll with a valid relay state, independently of
room sensors. Integrate a single change between polls at its
last_changed time. For ambiguous cases (round trip between polls, relay
outage, restart, clock jump) restart from time-in-state with an explicit
error half-width, and learn only once it is at most 0.1 (about 32 min
after an HA restart). The same synthetic test now learns 0.1195/0.00389
(true 0.12/0.004).
Expose heat_lag, heat_lag_uncertainty and sample_result on Status so
users can see why a poll was or was not learned. The HA test fixture
relay now has an hour-old timestamp; a new test covers a freshly
written relay state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With forgetting 0.999 and no heat excitation (summer, long OFF periods),
the heat-coefficient variance grew by 1/0.999 per sample without limit:
about 8.7e12 after 104 simulated OFF days. The first ON samples could
then swing coefficients to their bounds, and accumulated asymmetry
approached the absolute 1e-8 restore tolerance, past which a restart
silently reset the whole model.
Symmetrize P and bound its diagonal by the prior after every update
(row/column scaling keeps it positive definite). Restore uses a relative
round-off tolerance and applies the same bound, so wound-up v0.2.1
models load instead of being discarded. Payload schema is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>