Release 0.2.2

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>
This commit is contained in:
buzeraandClaude Opus 5.5 committed 2026-10-26 21:55:02 +02:00
1 parent 67fbda79a6
commit cd8b65ac2f
6 files changed
+236 -145

No files matched your search

+92 -134
View File
@@ -1,4 +1,4 @@
# Heating MPC 0.2.1 — custom integration for Home Assistant OS
# Heating MPC 0.2.2 — custom integration for Home Assistant OS
**Experimental, observation-only (shadow mode).** It **never** changes the Generic Thermostat, the physical relay, the boiler's setpoint, `heat/off`, or Comfort/Away/Sleep. No Docker add-on, MQTT or InfluxDB connection is needed.
@@ -7,7 +7,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
## Install / upgrade
1. Create a Home Assistant backup.
2. Copy the entire folder `custom_components/heating_mpc` into `/config/custom_components/heating_mpc` (replace its contents when upgrading from v0.1.0).
2. Copy the entire folder `custom_components/heating_mpc` into `/config/custom_components/heating_mpc` (replace its contents when upgrading; read the upgrade notes in [CHANGELOG.md](CHANGELOG.md) first).
3. Restart Home Assistant **Core**.
4. If new: **Settings → Devices & services → Add integration → Heating MPC**. If installed already: select **Heating MPC → Configure** (Options).
5. Choose the existing thermostat and physical heating switch (read-only), then individual room sensor entities.
@@ -18,7 +18,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
## Reconfigurable weather inputs
| Measurement | Required? | Used in the thermal model in 0.2.0? |
| Measurement | Required? | Used in the thermal model? |
|---|---|---|
| Outdoor temperature | **Yes** (1–10 prioritized sensors) | **Yes**: estimates thermal losses; constant over the 2 h forecast window |
| Wind speed | No | No, diagnostics/source comparison only |
@@ -38,10 +38,10 @@ Switching only the **weather-provider sensors** does not reset learned thermal p
## Model, sampling and output
- Every 5 minutes: reads HA sensor states and observed relay state; learns a per-room linear thermal model `dT = a*(T_out-T_room) + b*effective_heating + c`.
- Learns only from valid 5-minute transitions; uses a smoothed heating-demand proxy to approximate radiator lag.
- Learns only from valid 5-minute transitions; uses a smoothed heating-demand proxy to approximate radiator lag. Relay changes are timestamped by a read-only state listener, so the lag estimate follows every switch between polls.
- After at least 200 transitions and samples with both heating states, runs an ON/OFF MPC search for the next 60–180 minutes (default 120) in 15-minute command blocks. Protects against rapid simulated toggling.
- Uses room comfort weights and the **existing** Generic Thermostat's target and heat/off state. **The simulated ON/OFF demand never operates hardware**.
- Persists model parameters using HA's `.storage` so HA restarts do not discard the trained data.
- Persists model parameters using HA's `.storage` so HA restarts do not discard the trained data. Models of rooms removed in Configure are kept (up to 24) and restored if the room is added back.
Entities (entity IDs may vary):
@@ -59,143 +59,101 @@ Entities (entity IDs may vary):
- No weather forecast: assumes outdoor temperature remains constant during simulated horizon.
- No Recorder/InfluxDB training backfill. It learns only from samples collected after installation.
- The demand switch is not a burner-flame or real gas consumption measurement.
- Full HAOS/UI runtime integration has **not** been tested; Python/model/unit/source tests are offline checks only.
- Not yet tested on HAOS or with real devices. Automated tests run the integration in a Home Assistant Core test harness (HA 2025.12.0 and 2026.2.3) with simulated entities.
- Home Assistant Core APIs and config-flow selectors can change; take a backup before trying this experimental custom integration.
## Offline tests
## Diagnostics
The **Heating MPC Status** entity's state is one of `waiting_for_sensors`,
`learning`, `paused` (thermostat not in `heat`), `waiting_for_target`,
`invalid_weights`, `ready`, `inputs_changed` or `prediction_error`. Proposed heat
is available only while it is `ready`. Useful attributes:
- `input_quality` — for each room and each weather source (grouped by metric):
`status` (`valid`, `missing`, `unknown`, `unavailable`, `stale`,
`invalid_timestamp`, `future_timestamp` or `invalid_value_or_unit`),
`age_seconds` (time since the sensor last reported to HA) and
`max_age_seconds_24h` (largest age seen at polls in the last 24 hours; resets on
reload and can read up to one poll, 5 minutes, low).
- `heat_lag` and `heat_lag_uncertainty` — the radiator-lag estimate (0 cold, 1 fully
warm) and its error bound. Learning waits while the bound is above 0.1, for
example for about 30 minutes after an HA restart.
- `sample_result` — why the last poll was or was not learned: `first_sample`,
`learned`, `partly_rejected` (an outlier in some room), `interval_out_of_range`,
`relay_changed`, `outside_source_changed` or `lag_uncertain`.
- `models` — per-room coefficients, accepted sample counts and the in-sample
5-minute error (`mae_5m`), which is **not** a forecast accuracy.
- `retired_room_models` — removed rooms whose learned models are kept.
`input_quality` and `weather_comparison` change every poll, so they are not
written to the Recorder; the other attributes are.
### Choosing freshness limits
Room and weather report-age limits are set during setup and in **Configure**
(5–1440 minutes; defaults 30 for rooms and 120 for weather). A room sensor older
than its limit pauses learning and forecasts for **all** rooms until it reports
again. A stale outdoor source falls back to the next one in its list; optional
weather metrics never block. Recovery starts a new sampling interval, so nothing is
learned across an outage.
Within the limit, the last reading counts as current, so a too-generous limit lets
a frozen value be learned for a while. A too-tight limit pauses the integration
whenever a sensor is quietly between reports. Freshness uses HA's `last_reported`:
the time a sensor last reported to HA, even with an unchanged value. It is not the
age of the physical measurement, so a provider re-publishing cached data looks
fresh, and sensors that report only on change can look stale in a steady room.
After a day of running, set each limit comfortably above (about 1.5–2×) the largest
`max_age_seconds_24h` among the sensors it covers. The relay and thermostat have no
age limit.
## Release notes
See [CHANGELOG.md](CHANGELOG.md) for changes and upgrade notes per version, and
[AUDIT.md](AUDIT.md) for audit findings and test evidence.
## Developer checks and Forgejo/Gitea Actions
The HA Core test suite runs against **Home Assistant 2025.12.0** (the minimum in
`hacs.json`, `requirements-test.txt`) and **2026.2.3** (newest published test
fixtures, `requirements-test-latest.txt`), both on Python 3.13. Use a dedicated
development virtual environment; these test dependencies are not installed with
the integration and do not change its local, standard-library model inference.
```bash
python3.13 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-test.txt # or requirements-test-latest.txt
sh scripts/check.sh
```
The check script runs dependency consistency checks, the offline tests, the HA
Core tests, compilation, package metadata, translation and changelog checks, and
diff whitespace checks. The 2025.12.0 requirements pin a compatible DNS library
because pycares 5 breaks that release's dependency. The script also disables a
transitive HA test dependency's optional metadata download. The offline tests also
run without Home Assistant installed:
```bash
python -m unittest discover -s tests -v
```
## v0.2.1: integration visibility and clean uninstall
- Corrects the manifest classification from `helper` to `service`. Existing config entries should move from the Helpers listing to **Settings → Devices & services → Integrations** after replacing the files and restarting Home Assistant Core. No deletion/reconfiguration is needed to upgrade.
- When the integration config entry is deleted from the Integrations page, Home Assistant removes its registered entities, and `async_remove_entry` removes the locally persisted learned-model data (`.storage/heating_mpc.<entry_id>`). The custom component source folder remains on disk until manually removed.
- MPC behavior is otherwise unchanged and remains read-only / shadow-only.
## Unreleased: v0.2.1 reliability audit
See [AUDIT.md](AUDIT.md) for findings, fixes, test evidence and remaining gaps.
No active control was added; the manifest version has not been advanced.
- Fix config-entry v1 migration, obsolete room forecast cleanup and coordinator
cleanup after a raised platform setup error.
- Validate saved models atomically and add an explicit payload schema version.
Compatible legacy coefficients survive; readiness now requires at least 30 new
accepted ON and 30 OFF samples **per room** when loading legacy models without
per-room counters. The existing 200-total-sample requirement also applies.
- Reject intervals containing relay changes or outside-provider changes. Accept
only 270–330-second intervals and normalize small timing differences. This can
slow learning; it never generates extra heating cycles.
- Normalize thermostat targets to Celsius; reject invalid weights and optimizer
parameters. Status can report `invalid_weights` or `inputs_changed`; neither
publishes a proposal. Input changes during calculation discard the prediction.
- Model diagnostics include each room's accepted total/ON/OFF sample counts.
Predictions remain **counterfactual**: the proposed plan is not the actual
Generic Thermostat's future command sequence.
Offline lifecycle tests use HA doubles. Actual HA/HAOS lifecycle
validation and held-out prediction accuracy remain unfinished. Back up before
trying these changes; follow the safe trial and rollback notes in AUDIT.md.
## Unreleased: sensor freshness
Room and weather report-age limits are available during setup and in Options
(5–1440 minutes). Defaults are 30 minutes for rooms and 120 minutes for weather;
existing entries use these defaults until changed. Celsius/Fahrenheit handling,
compatible learned models and entity IDs are unchanged.
Stale room readings or an absence of fresh outdoor temperature sources pause
learning and predictions. Weather selection skips stale sources; optional weather
outages do not block predictions. Recovery begins a new sampling interval, so no
training spans an outage. Status attribute `input_quality` lists each room and each
weather source by metric, with report age in seconds and a reason: `valid`,
`missing`, `unknown`, `unavailable`, `stale`, `invalid_timestamp`,
`future_timestamp` or `invalid_value_or_unit`.
Freshness uses HA `last_reported`, falling back to `last_updated` only on older HA
versions without that field. It measures HA state reports, not the age of the
original physical measurement: a provider republishing cached data can look fresh.
Some integrations report only changed values; choose limits appropriate to their
reporting behavior. The relay and thermostat are event-driven references and do
not receive these numeric-sensor age limits. Sensor alignment and relay event
history remain future work. These checks run on the existing five-minute poll.
After backing up and upgrading, check report ages in Status, adjust limits in
Options to match your sensors, and verify fallback/recovery with an unavailable
source. Generic Thermostat remains the sole controller. No HAOS trial was performed.
## Unreleased: radiator-lag tracking and learner stability
See the second-pass section of [AUDIT.md](AUDIT.md). Still shadow-only; no new
options, translations or entities; the manifest version is unchanged.
- **Radiator lag now follows the relay.** Previously the lag estimate jumped to
fully hot or fully cold at each relay change, which biased the learned heating
coefficient low and gave wrong starting points to forecasts right after a boiler
cycle. A relay change between polls is now integrated at its timestamp.
- **Learning pauses briefly when the lag is unknown**: after an HA restart, a relay
outage, or two relay changes inside one five-minute poll. The Status attribute
`heat_lag_uncertainty` shows the error bound; learning resumes once it is at most
0.1, about 32 minutes after a restart. Forecasts continue meanwhile.
- **Long OFF periods no longer destabilize learning.** The learner's covariance is
bounded, so a summer without heating cannot make coefficients jump when heating
resumes, and older saved models with large covariance are loaded rather than reset.
- **Optional weather sources can be removed in Configure.** Clearing wind, cloud or
any additional metric now saves an empty list instead of restoring the old sources.
- The proposed-heat entity is no longer marked unavailable when only a
diagnostic weather sensor or the thermostat's current temperature changed during
the calculation.
- New Status attributes: `heat_lag`, `heat_lag_uncertainty`, `sample_result` (why the
last poll was or was not learned).
**After upgrading:** saved models load unchanged, but coefficients learned before
this change were fitted with the wrong lag. Expect them to re-converge over a few
days of heating and judge forecasts only on data after the upgrade, or delete and
re-add the integration for a clean model (this deletes learned data). After a
restart, check that `sample_result` moves from `lag_uncertain` to `learned` within
about 35 minutes. Generic Thermostat remains the sole relay controller. No HAOS
trial was performed.
## Developer checks and Forgejo/Gitea Actions
The HA Core test suite targets **Home Assistant 2025.12.0 on Python 3.13**,
matching the minimum version in `hacs.json`. Run in a dedicated development
virtual environment; these test dependencies are not installed in the integration
and do not change its local, standard-library model inference.
```bash
python3.13 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-test.txt
sh scripts/check.sh
```
The check script runs dependency consistency checks, 56 offline tests, 16 tests
using the real HA Core/fixture package, compilation, lightweight package metadata
and translation checks, and diff whitespace checks. A compatible DNS dependency
is pinned because pycares 5 breaks the older HA dependency's import. The script
also disables a transitive HA test dependency's optional metadata download.
HA tests cover setup, config/options forms, migration, reload and entity cleanup,
scheduled sampling and poll cancellation, stored model restoration/deletion,
missing inputs, manual off/presets and optimizer failure. A fixture rejects all
HA service calls throughout each test. HA Core runs locally with simulated input
entities and mocked storage I/O; this does not validate HAOS, physical devices,
disk failures or full process restarts. The original standalone unittest command
still works without installing HA.
scheduled sampling and poll cancellation, the relay listener, stored model
restoration, retention and deletion, missing inputs, manual off/presets, optimizer
failure and Recorder exclusions. A fixture rejects all HA service calls throughout
each test, and a static test fails if actuator service calls are added to the
source. HA Core runs locally with simulated input entities and mocked storage I/O;
this does not validate HAOS, physical devices, disk failures or full process restarts.
`.gitea/workflows/tests.yml` runs the same checks for pushes and pull requests on
the dedicated `bs-node` Actions runner label (`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled.
The runner must support the checkout and setup-uv actions. CI uses pinned uv
0.9.6 to create a Python 3.13 virtual environment from standalone Python builds;
`.gitea/workflows/tests.yml` runs the same checks for both HA versions on pushes
and pull requests, on the dedicated `bs-node` Actions runner label
(`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled, and the
runner must support the checkout and setup-uv actions. CI uses pinned uv 0.9.6 to
create a Python 3.13 virtual environment from standalone Python builds;
`setup-python` cannot provision this version in the Debian Bookworm job image.
The environment includes pip for the existing check script. Action caching is
explicitly disabled to avoid requiring GitHub cache services on Gitea.
Action caching is disabled to avoid requiring GitHub cache services on Gitea.
Dependencies, Python builds and action downloads need internet access in CI;
model inference does not. Remote CI execution has not yet been verified. Local
validation used macOS and Python 3.13; Linux runner validation remains necessary.
No runtime integration behavior or manifest version changed in this test commit.
model inference does not. Remote CI execution has not yet been verified; local
validation used macOS.