github KartoffelToby/better_thermostat 1.9.0-RC
1.9.0 RC

pre-releaseone hour ago

Pre-release

Cooling finally works the way heating does, doors join windows, Z-Wave thermostats are supported, there's a new control algorithm to try, and a very long list of small annoyances is gone.

Nothing to do after updating. Your existing setup keeps working — no reconfiguration, no migration steps. Except you relay on new options.

Thanks to everyone who reported, tested and fixed things, and special thanks to @cl445.


❄️ Cooling and air conditioning

Cooling used to be the weak spot. This release rebuilt it.

  • Air conditioners with a heat/cool range (a lower and an upper temperature instead of one setpoint) are handled properly now, including units that only accept both values together.
  • Separate cooling temperatures for presets — Eco, Away or Comfort can mean one temperature in winter and another in summer.
  • Cooling waits out the tolerance instead of switching on the moment the room is a hair too warm. Fewer short bursts.
  • Changing the temperature on the AC itself or its remote is picked up correctly, instead of being mistaken for our own last command.
  • If the air conditioner disappears from Home Assistant, you get a clear warning instead of silent guessing.
  • Far fewer commands sent to cloud-connected air conditioners — matters for brands that rate-limit or charge per request.
  • Cooling reports cooling when it is actually cooling.

We'll working on the cooling options in v2, specially on combined devices for the next summer.

🚪 Doors, on top of windows

  • Door sensors can pause heating just like window sensors, with their own open and close delays — a front door counts differently from a balcony window.
  • An open window or door now stops cooling too, not just heating.
  • Rooms with both heating and cooling behave sensibly while a contact is open.

🧠 A new control algorithm to try

  • MPC v2 — a new, more advanced calibration mode that looks ahead instead of only reacting. Pick it per thermostat in the configuration.
  • Ships with room-size presets (small / medium / large room, or automatic), so there is nothing to tune by hand.
  • The existing modes are untouched. Switch back any time.

📡 Z-Wave thermostats

  • Z-Wave JS thermostats are supported directly, including valve control for the Eurotronic Spirit and the Aeotec ZWA021.

🔧 Better behaviour with stubborn thermostats

Many devices lie a little, round differently, or refuse commands. 1.9 handles that.

  • We only send operating modes your thermostat actually offers, instead of sending something it will reject.
  • Temperatures are compared in the steps your device really uses (0.5 °C, 1 °C, whole degrees), so we stop re-sending a value that is already set.
  • Fahrenheit and mixed-unit setups read correctly.
  • Devices with two temperature values, and devices that report their capabilities late, are detected more reliably.
  • Calibration offsets a device silently ignored are sent again instead of being assumed done.
  • Error messages name the setting you actually need to change.

🛠️ Valve care

  • Valve exercising also runs while the thermostat is switched off — exactly when valves get stuck.
  • Sleeping or unreachable thermostats are skipped instead of holding up the whole cycle.
  • After the anti-sticking movement, the valve returns to the position it should be in.
  • Valve commands stay inside the range and step size the device declares (noticeably better on Sonoff TRVZB).
  • New service to trigger valve maintenance yourself, whenever you want.

🚀 Startup and reliability

  • Missing or slow sensors during Home Assistant startup no longer produce a wave of errors — we wait, retry, and only then report a problem.
  • New degraded mode notice tells you which sensor is missing and what we're doing meanwhile. Clears itself once the sensor is back.
  • Removing a thermostat — or just the air conditioner from an existing configuration — works cleanly.
  • Nonsense values from a sensor or from a previous restart can no longer poison the control loop.

🏠 Home Assistant integration

  • Better Thermostat events show up in the Activity Log timeline, and the real hardware behind a Better Thermostat is shown on its device page.
  • New diagnostic sensors for what the PID calibration is doing — useful if you like tuning.
  • Better Thermostat entities work in the device-based automation editor (triggers, conditions, actions).

🌍 Setup screen, presets and translations

  • Setup and options screens are fully translated, including the dropdowns and entity names that were English-only before. All eleven shipped languages brought up to date.
  • Changing the target temperature now leaves the preset instead of pretending it is still active.
  • Each thermostat in a group can use its own temperature step, so mixed-brand groups stop fighting each other.
  • A temperature set from the app, a dashboard or an automation is no longer swallowed when it arrives at the same moment as an update from the device.

📋 Automations (blueprints) Fixes

  • The weekly heating schedule blueprint saves again.
  • It accepts several presence entities and several pause switches, and no longer resumes heating while another pause switch is still on.
  • Older saved configurations keep working.

📖 Documentation

  • New pages: presets explained, window sensors, degraded mode, and running Better Thermostat alongside a central boiler thermostat.
  • The algorithm guide was rewritten to describe what each mode actually does, without promising results the project cannot back up.
  • Outdated content, dead links and broken images removed.

💾 Installation Fixes

  • Better Thermostat installs on Raspberry Pi and other ARM systems again — an optional maths component is no longer required.

🧪 Under the hood

Large parts of the internals were rewritten and are now covered by an automated test suite that grew several times over during this release, plus a simulation benchmark that compares the control algorithms against each other. You won't see any of it directly — you'll notice it as fewer surprises.

New Contributors


**Found something? Please open an issue: https://github.com/KartoffelToby/better_thermostat/issues

Full commit-level changelog

What's Changed

  • Fix fahrenheit/celsius mishandling by @KartoffelToby in #2035
  • test: validate bundled blueprints save with default inputs by @cl445 in #2047
  • fix: remove dead calibration documentation links from config flow (#2040) by @cl445 in #2046
  • fix: read TRV temperatures in the Home Assistant system unit by @cl445 in #2043
  • fix: keep outdoor threshold working when recorder history is empty (#2038) by @cl445 in #2044
  • feat: re-evaluate outdoor threshold on outdoor sensor changes by @cl445 in #2045
  • chore: add diagnostic logging for TRV setpoint sync edge cases by @cl445 in #2048
  • test: pin controller state-threading contract by @cl445 in #2050
  • test: integration harness — real config entry against a simulated TRV by @cl445 in #2059
  • docs: serve the documentation pages under their declared URLs by @cl445 in #2052
  • Synchronize translations with current config flow, services and entities by @cl445 in #2062
  • fix: sensor handling, store loading, and window-state hardening by @cl445 in #2054
  • docs: keep README version info from going stale by @cl445 in #2064
  • Disable current Preset of target temp changes by @KartoffelToby in #2067
  • refactor: StateManager is the only owner of learned controller state by @cl445 in #2058
  • build: manage the project with uv by @cl445 in #2027
  • docs: remove stale content, fix broken doc images and 404 learn-more links by @cl445 in #2065
  • refactor: typed Trv domain object replaces the real_trvs dicts by @cl445 in #2056
  • mpc: seed fresh target-temp buckets from nearest sibling by @cl445 in #2002
  • fix: controller state poison resistance (NaN/Inf healing at compute and restore time) by @cl445 in #2057
  • build: type-check the component, strict on the calibration modules by @cl445 in #2073
  • build: run pyrefly type checking in CI by @cl445 in #2075
  • build: extend strict pyrefly to telemetry, retry and the Trv object by @cl445 in #2077
  • docs: numpy-style docstrings for the model-fix quirk hooks by @cl445 in #2078
  • chore(lint): enforce numpy docstring arg coverage and relax tests by @cl445 in #2083
  • fix: allow removing the cooler entity from the configuration by @cl445 in #2091
  • refactor: extract shared find_device_entity helper by @cl445 in #2101
  • chore: translate remaining German comments to English by @cl445 in #2102
  • fix: correct inital_tweak typo to initial_tweak by @cl445 in #2100
  • chore: remove dead anti-flicker code from temperature event handler by @cl445 in #2099
  • chore: trivial hygiene fixes (docstring, dead comment, boolean) by @cl445 in #2098
  • fix: retry critical entities at startup before raising repair issue by @cl445 in #2093
  • docs: added an algorithm selection guide to the README by @nizarbadriali in #2071
  • Fix boot missing devices errors, AC Config flow removal, devel… by @KartoffelToby in #2095
  • fix: compare group member setpoints in Celsius in group_all_members_off by @cl445 in #2107
  • chore: drop stale open-caps mention from the reset_pid_learnings description by @cl445 in #2110
  • fix: offer device automations for the Better Thermostat climate entity by @cl445 in #2112
  • fix: stop the startup retry loop when the entity is removed by @cl445 in #2109
  • chore: improve source comments to describe current behavior by @cl445 in #2096
  • fix: recheck critical entities once the startup grace window elapses by @cl445 in #2108
  • Fix dual-setpoint detection and TRV temperature handling bugs by @leow149 in #2105
  • fix: harden the quirk set_temperature implementations activated by #2105 by @cl445 in #2111
  • fix: only consider writable domains for the local calibration entity by @cl445 in #2113
  • fix: confirm HVAC mode changes against the live TRV state by @cl445 in #2115
  • fix: match setpoints with a tolerance across rounding grids by @cl445 in #2114
  • feat: expose PID calibration diagnostics as sensors by @cl445 in #2097
  • fix: register valve maintenance tick independently of calibration mode by @cl445 in #2127
  • feat: add door sensor support with independent open/close delays by @cl445 in #2116
  • test(benchmark): simulation core & scoring engine by @cl445 in #2087
  • feat: link device relations and events in activity log by @KartoffelToby in #2129
  • test(benchmark): restore the stacked benchmark suite stranded off develop (#2088, #2089) by @cl445 in #2132
  • feat: MPC v2 calibration mode (QP + Kalman) — recovered onto develop by @cl445 in #2131
  • Add cooling preset temperatures for HEAT_COOL mode by @cl445 in #2130
  • fix(controlling): add send-cache and nil-guard to control_cooler() by @cl445 in #2133
  • fix: render per-plant matrices for '--plant all' in benchmark runner by @cl445 in #2136
  • fix: address remaining review findings from #2070 by @cl445 in #2135
  • fix: don't hard-require daqp so BT installs on aarch64 (Raspberry Pi) by @cl445 in #2137
  • feat: Z-Wave JS adapter + Eurotronic Spirit/Aeotec ZWA021 valve control by @cl445 in #2134
  • fix: backport PID integration-step bound and cooler Fahrenheit dedup from v2 by @cl445 in #2139
  • chore: update dependencies to the latest compatible versions by @cl445 in #2141
  • fix(cooler): adopt only user setpoints from the cooler entity by @cl445 in #2145
  • fix(cooler): write the payload a range-only cooler accepts by @cl445 in #2146
  • refactor(events): share one inbound boundary for reported setpoints by @cl445 in #2147
  • fix(cooler): survive an unknown cool target on a range-only cooler by @cl445 in #2154
  • fix(climate): give every child its own setpoint step by @cl445 in #2155
  • fix(degraded-mode): watch the door sensor and correct the repair text by @cl445 in #2156
  • fix(cooler): delay the cooling switch-on by the tolerance by @cl445 in #2162
  • fix(climate): bound an inbound setpoint instead of moving the other target by @cl445 in #2163
  • fix(cooler): seed an unknown cool target from the cooler's own setpoint by @cl445 in #2164
  • fix(cooler): decline a setpoint reported on a dead cooler state by @cl445 in #2179
  • chore(helpers): read a published setpoint step through one shared rule by @cl445 in #2184
  • fix(setpoint): match an inbound setpoint against the report and the clamp by @cl445 in #2190
  • fix(cooler): bound re-injected targets and order the restored pair by @cl445 in #2177
  • fix(mpc_v2): tie restored _initialised to the snapshot that seeded it by @cl445 in #2181
  • docs: point the schedule guide at the preset actions by @cl445 in #2196
  • fix: resolve a device-less thermostat model in the options flow by @cl445 in #2194
  • fix: stop sending HVAC modes the TRV does not offer by @cl445 in #2198
  • fix: re-assert an unconfirmed local calibration offset by @cl445 in #2201
  • fix(cooler): surface an unavailable cooler through degraded mode by @cl445 in #2186
  • chore(cooler): converge cooler-path wording and step tests across lines by @cl445 in #2188
  • fix(calibration): make the cooling report and the idle gates agree by @cl445 in #2192
  • fix: give a dual-role climate entity a single controlling channel by @cl445 in #2202
  • fix: exercise TRV valves while the thermostat is off by @cl445 in #2208
  • fix: agree on one default calibration mode by @cl445 in #2210
  • docs: add a preset reference and a central-thermostat guide by @cl445 in #2212
  • fix: keep the MQTT valve write inside the entity's declared bounds by @cl445 in #2218
  • test: cover the quirk that runs for every device without one by @cl445 in #2220
  • test: pin what a quirk module may define, and what it must by @cl445 in #2222
  • Localize config-flow selectors and entity names by @cl445 in #2223
  • fix(blueprints): stop weekly_heating_schedule from failing to save by @proscar87 in #2170
  • fix(trvzb): land the requested valve position after a de-sticking bump by @cl445 in #2226

Full Changelog: 1.8.2...1.9.0-RC

Don't miss a new better_thermostat release

NewReleases is sending notifications on new releases.