Each ## [x.y.z] section becomes the GitHub release notes for tag vx.y.z, created automatically by
.github/workflows/ci.yml after tests pass on the default branch. Bump
custom_components/daikin_mpc/manifest.json version and add a section here for every release.
0.2.0 was never tagged (superseded by 0.2.1 the same day).
- HA warning "state class 'measurement' … impossible considering device class ('energy')" for Predicted heat pump electricity 24h: the forecast no longer has the energy device class (that class only allows meter totals); it keeps the measurement state class and its long-term statistics.
-
Measured heat gains. Heat that can be measured is counted separately from the learned "base gains":
- inverter losses: new input, using
sensor.solis_loss_power(PV + battery − AC output). It is counted 100 % as heat (winter balance 0.8 ± 0.4), about 100 W standby plus 5 % of throughput; - sun through the windows, from the weather-station radiation (1.5 W per W/m²);
- hot-water cylinder standing loss (1.5 W/K × tank − room).
Forecasts use hour-of-day profiles and Solcast. A new Heat gains now sensor shows the breakdown and whether every source is available. See
docs/heat_gains.md. - inverter losses: new input, using
-
House electricity input, using
sensor.house_power(AC-side house load fromdocs/ha/packages/energy.yaml). Heat pump, EV charger and loft IT are subtracted. It counts 0 % as heat by default: last winter's daily balance showed no measurable effect (0.03 ± 0.08), and 0.7 made the fit clearly worse. The share is an option and will be learned from clean data later.
-
Priors depend on the configured gain sources:
- with measured inverter losses: UA 90 W/K, base gains 70 W (winter 2025/26 daily balance);
- otherwise unchanged at 94 W/K and 440 W.
Existing installations: after mapping the new inputs, set Heat loss coefficient 90 and Base internal gains 70 under Configure, if you ever saved the options.
-
Battery-power loss estimate (4.5 %) is now only a fallback without an inverter-loss sensor.
- Learned base gains, profiles and the night regression restart when the gain sources change. The day of the change is not learned from.
- An unavailable gain source makes the hour incomplete instead of adding 0 W. Days with incomplete gains are not learned from.
- An unavailable heat-pump, EV or outside-load meter no longer turns their consumption into household heat.
- Free-cooling capacity estimate corrected for measured gains.
- Measured gains no longer count towards the "no heating that day" check.
- Sun forecast only with a radiation sensor.
- Stale house/inverter readings (> 2 h) rejected.
- Stricter loading of saved profiles.
- Thermal capacity learned from free-cooling nights. With the heat pump off, the night-time cooling
rate measures the building's time constant directly; with UA from the daily learner this gives C. Clean
night hours only (22:00–06:00, compressor off, no hot water, indoor–outdoor difference ≥ 6 K), ≈ 50-day
memory; used once at least 4 nights identify it. The Thermal capacity sensor shows the source, the
time constant and the number of nights. See
docs/capacity_learning.md.
- Default thermal capacity 5.5 kWh/K (was 3.0): with heating off, the model cooled the house about twice as fast as it really cools (time constant 32 h vs ≈ 60 h in last winter's no-heating nights). If you ever saved the options, set "Thermal capacity" to 5.5 there (Daikin MPC → Configure).
- Changed defaults/options now apply while nothing has been learned yet (previously the first saved state kept the old values).
- M3 replay with the new capacity: optimised vs simple timer +28 % on the raw tariff (was +17 %).
Ninth review round (Codex) — attacks on the two accounting rules of 0.4.7: 3 defects, 4 test gaps; no
wrongly rejected day found in normal operation (incl. both clock-change days). See
docs/review_2026-10.md.
- Saved state is a detached snapshot: HA writes it later in a worker thread while polling continues; the in-progress hour's totals were live references, so a poll in between could be counted twice after a restart.
- A meter reset is a discontinuity: the energy between the last reading and the reset is unknown, so that date is no longer used for learning (it was counted as complete with energy missing).
- Clock going backwards: samples are ignored until time passes the last one, and the current date is not used for learning (repeated hours had inflated a 24-hour energy balance).
- New tests: learning switched off changes nothing, RT-modulation bounds, exact thermostat thresholds,
standby needs three off hours, plus regression tests for each fix.
tools/mutation_check.py: 40 mutations, all caught.
Eighth review round (Codex): 4 code defects, 2 test gaps, and the finding that the restart/day
completeness logic was still a set of patches. See docs/review_2026-10.md.
- Restored all-or-nothing: meter baselines, the in-progress hour and the current date's hours are one accounting state. If any part of the saved state is invalid, none is restored; meters then start new baselines and the affected date is not used for learning (previously a rejected hour could leave a day with missing energy that was still learned from).
- A date counts only if every meter was observed continuously from its start to its end: a meter starting a new baseline (first reading, re-mapping, lost state) invalidates the date it falls in, unless exactly at midnight, and also the previous date when midnight passed since the last sample (re-mapping exactly at midnight previously left the ending day "complete" without its last 5 minutes).
- A mapped heating switch that is unavailable no longer counts as "heating on": telemetry is reported incomplete and no heating forecast or recommendation is made until it is readable again.
- Standby power is learned once per day (it was updated twice on days without a COP sample).
- New tests: recommendation start state follows the thermostat demand signal; standby subtracted for the
actual 23/25-hour day length; plus regression tests for each fix.
tools/mutation_check.py: 32 mutations, all caught (one more retired as equivalent, with the reason in the script).
Seventh review round (Codex): 5 code defects, 4 test gaps, see docs/review_2026-10.md.
- A day is not used for learning when any meter started counting after midnight (late first reading, meter re-mapped, lost state): previously one counter starting at 00:30, or a re-mapped meter at noon, still produced a "complete" day with missing energy.
- A restart shortly after midnight on a clock-change day no longer reports that day as 24 hours: the installation's time zone is saved and applied to restored times.
- A saved in-progress hour with inconsistent averages is rejected instead of breaking every update.
- Battery charging price: a cheap period cut short by the spring clock change (e.g. 01:30–02:30 keeps 02:00–02:30) is now found.
- New tests: heat-pump output cap, small meter decreases treated as noise, thermal-capacity learning,
exact expiry of Predbat's last slot, plus regression tests for each fix.
tools/mutation_check.pynow checks 27 mutations; all are caught.
Sixth review round (Codex): 5 code defects and 12 test-suite gaps, see docs/review_2026-10.md.
- After a restart, a first day that was incomplete (integration started mid-day) stayed eligible for learning; the "first day partial" state is now saved. A fresh start during the midnight hour (e.g. 00:30) no longer counts as a complete day: energy must have been observed from the hour's start.
- A restart late on a clock-change day no longer turns its 25/23-hour length into 24 hours (the day's time zone is taken from live data, not from restored timestamps).
- Battery charging price around clock changes: a cheap slot in the non-existent spring hour is no longer counted, and the repeated autumn hour is checked in both its BST and GMT occurrences.
- Restored hour records without temperature means are rejected instead of crashing the next day's aggregation.
- New tests close the gaps found by the review: stale recommendation cleared and re-planned on recovery, optimiser throttle and retry after failure, startup before entities exist, unit conversion end to end, comfort-period option validation, plan times and diagnostics, WebSocket redirect refusal, full engine state round-trip, clock-change days through the engine (incl. restarts), cost = energy × price, Predbat timestamp offsets, heating switched off through the engine, offline outdoor-temperature coverage.
tools/mutation_check.py: re-introduces each of 20 fixed defects/gaps and confirms a test fails — all 20 are caught.
Fifth review round (Codex, 3 findings on 0.4.3), see docs/review_2026-10.md.
- HA restarts no longer lose the current day's energy: the in-progress hour and the day's hours are now saved with the learned state, so a restart (e.g. for an update) neither drops energy nor lets a partial day into learning. Without saved state (first start mid-day) the first date is skipped.
- Battery charging price considers every rate change in the preceding 24 h, so short cheap slots (e.g. 15 minutes) are no longer missed.
- Offline days need valid indoor and outdoor temperatures for all but two hours; otherwise their means and temperature changes are not used.
Fourth review round (Codex, 4 findings on 0.4.2), see docs/review_2026-10.md.
- Energy just before midnight was booked to the next day: each meter increment is now split across hours by the time it covers (e.g. 23:55–00:00 stays on the old day), so daily energy matches the daily temperatures used for learning.
- A day is also withheld from learning when a mapped meter (not only the bridge) is unavailable at midnight.
- Battery charging price: the cheapest rate in the 24 h before each time, using the rate that actually applies then (Predbat where it covers that time, the configured tariff otherwise), so an expired Predbat series no longer sets prices.
- Offline days must have all calendar hours (day length from local midnights); temperature changes are only taken between two complete consecutive days.
Third review round (Codex, 5 findings on 0.4.1), see docs/review_2026-10.md.
- A bridge outage that spans midnight no longer lets the earlier day into learning before the outage is resolved: a day is closed only if the meter counters were readable at the boundary.
- Battery-aware price: the charging cost now comes from Predbat's rates when available (the fallback tariff's cheap rate was used even when Predbat's cheapest rate was higher).
- Re-mapping a meter entity no longer books the difference between two meters as energy: counter baselines are stored with their entity and dropped when the mapping changes. (After this update the baselines restart once; the current day is not used for learning.)
- Stored COP bins with heat but no electricity are rejected.
- Offline daily energy requires a valid counter reading at both day boundaries (series starting or ending mid-day no longer yield partial totals).
Second review round (Codex, 10 findings on 0.4.0), see docs/review_2026-10.md.
- Optimiser could report "cannot be held" although a compliant plan existed: state merging kept only the cheapest path per temperature bin; it now also keeps the warmest and coolest. Replay: days below 20 °C 4 → 2 (the remaining two are physically unavoidable in the model). Runtime ≈ 0.7 s per plan.
- "Feasible" and the reported room range now both cover the planned period (not the given start value).
- Meter counters are only read while the bridge heartbeat is fresh, so a frozen bridge cannot hide an outage; a counter gap across midnight now invalidates both days.
- Stored COP and pending-day state fully validated and typed (e.g. negative standby or text values were accepted and could break later updates).
- Malformed weather responses no longer interrupt the update; Predheat comparison values are converted and range-checked like room temperatures.
ha_client.py: WebSocket connections that were redirected to another origin are refused before the token is sent (requireswebsockets>=14).- Offline daily energy keeps increments across same-day gaps (booked to the next reading) and drops days touched by a gap across midnight, instead of silently undercounting.
tools/codex_review.md: run Codex with stdin closed (it otherwise waits for input).
Fixes from an adversarial code review (Codex), see docs/review_2026-10.md.
- Daylight saving (clocks go back 25 Oct): all elapsed-time arithmetic now in UTC. Previously the repeated 01:00 hour merged into one, sample intervals went negative, forecasts and plan times shifted by an hour, and 23/25-hour days were treated as 24 h in the building learner and offline fit.
- Meter counters after a restart/outage: increments across a gap are no longer booked into the first hour after it; across midnight they are dropped and that day is not used for learning (counter timestamps are now persisted).
- Flow-temperature offset is learned only during space heating (DHW and defrost had shifted it by several K).
- Building learner only pairs consecutive days.
- Hard comfort limits are now strict: plans are ranked by time outside 20–22 °C first, then cost, so no price can buy a violation; "feasible" no longer has a hidden 0.05 K tolerance.
- Standby no longer counted twice in the electricity forecast: the COP now excludes standby (prior recomputed: 3.06 at 1 °C … 4.82 at 13 °C); standby is added once. Learned COP data is reset (new basis).
- Units: entities in Wh/MWh, kW, °F/K, L/h or m³/h are converted; unsupported units are reported instead of misread. Weather forecasts use the entity's temperature unit; NaN or implausible values are ignored.
- Weather points in the past no longer distort the outdoor-temperature forecast.
- Malformed Predbat rate attributes fall back to the configured tariff instead of failing the update.
- Corrupt stored state never prevents start-up (each section falls back to priors; covariance checked).
- Recommendations are cleared as soon as telemetry becomes invalid, and recomputed on the next poll instead of after 15 minutes (e.g. after HA restarts before the bridge is back); plan times refer to when the plan was made. Schedule text marks the next day ("18:00–18:00 next day").
- Optimiser validates input lengths (no crash on short price/forecast series).
- The bridge heartbeat entity is now required (P1P2MQTT publishes on change only; without it a frozen bridge cannot be detected). Map it via Reconfigure if not set.
ha_client.py: exact read-only endpoint allowlist (the/api/prefix admitted GET webhooks); redirects refused so the token is never forwarded.ha_export.py: naive dates are Europe/London (option--tz), not the machine's time zone.- Offline fit: hourly windows never span gaps or DHW exclusions; counter increments from statistics
sum(reset-adjusted); actual day lengths. Replays handle DST days; Predheat replay notes thatfitted/calibratedhold-out errors are optimistic (physical parameters fitted on all days).
- Preferred temperature periods (option, default
07:00-09:00=21, 18:00-24:00=21): the optimiser aims for these temperatures (soft target); outside them only the lowest allowed temperature (default 20 °C) applies. Hard limits 20–22 °C unchanged.
- Default lowest room temperature 20.0 °C (was 20.5 °C).
- Replay compares against a simple timer schedule and reports the shortfall against the preferred periods.
- Every 15 minutes a 24 h plan of hourly room setpoints (0.5 °C steps) that minimises electricity cost while keeping the room between 20.0 and 22.0 °C (configurable). No fixed night setback: lower night setpoints only when they save money.
- Prices from Predbat (
predbat.rates,predbat.rates_export, including VPP events) with the configured tariff as fallback; round-trip efficiency from Predbat's loss settings. - Battery- and export-aware pricing (default): energy outside the cheap window is valued at
min(import, max(cheap rate / efficiency, export rate)), i.e. the opportunity cost of not exporting. Raw import tariff selectable. - New sensors: Recommended room setpoint (with the hourly plan), Expected saving 24h (model estimate vs the current schedule), Recommendation (plain-language reason).
- Options: lowest/highest room temperature, price basis, fallback import/export tariffs.
tools/m3_replay.pyanddocs/m3_replay_report.md: replay on winter 2025/26.
- Predheat comparison ignores Predheat values older than 30 minutes, so a disabled or stopped Predheat (whose last states remain in HA) is no longer scored as if it were a current forecast.
- Plain-language names and help texts for every field in the setup and Reconfigure forms (e.g. Room thermostat asking for heat instead of Thermostat heating demand). No functional change.
- Optional Thermostat heating demand role (suggested
binary_sensor.bridge0_mode_valve_zone_main, verified in a live test): the forecast starts from the actual thermostat state instead of inferring it from the compressor (which lags demand by several minutes and cycles on water temperature). Existing installs: Reconfigure; the field is pre-filled.
- Suggested "DHW active" entity is now
binary_sensor.bridge0_dhw_dhw_demand(on only while the cylinder is being heated).binary_sensor.bridge0_dhw_dhwmeans "DHW enabled", is always on, and made every hour count as DHW. Existing installs: Reconfigure and change the DHW active field.
- Setup/reconfigure no longer rejects the Predheat comparison entities when Predbat is not running
(its
predheat.*states only exist while Predbat runs); they are always pre-filled.
- Reconfigure (Settings → Devices & services → Daikin MPC → ⋮ → Reconfigure): edit the entity mapping in place without losing learned state. Unmapped optional roles are pre-filled with verified defaults.
- Predheat comparison entities are entered as text, because
predheat.*states are not registered entities and the entity picker could drop them.
- Optional external heat-pump meter roles (
ext_w,ext_kwh) and the Daikin DHW electricity counter (dhw_elec_kwh), pre-filled in the setup form. - Live COP-by-outdoor-temperature learner on the external-meter basis (includes standby and pump), monotone, with forgetting and fallback to the prior curve.
- Standby power learned from compressor-off hours; new
sensor.daikin_mpc_standby_power.
sensor.daikin_mpc_predicted_heat_pump_electricity_24h(renamed) now includes standby; attributesspace_heating_kwh_24h,standby_kwh_24h,cop_source.- COP prior recomputed from winter 2025/26 with the external meter (2.93 at 1 °C … 3.53 at 13 °C).
Remove and re-add the integration so the new optional entities appear in the setup form.
- First Home Assistant release (shadow mode, observation only, no writes to the heat pump).
- Config/options flow with verified, pre-filled entity mapping; 5-minute coordinator; bridge heartbeat.
- Hour/day aggregation, bounded daily learner for heat loss, gains and thermal capacity.
- 24 h room-temperature and heating-electricity forecast (thermostat gating, RT modulation, 25 °C LWT floor).
- Self-scoring of 1/3/6 h forecasts, including Predheat comparison; persistence; diagnostics.