Skip to content

About

Delta-based thermostat synchronization for Home Assistant

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

35 Commits

Folders and files

Repository files navigation

ClimateSync

HACS Custom License: MIT

Developed with Plugwise Emma in mind. Mix source climates (current_temperature and temperature) with numerical heating-demand delta sensors in °C. Destinations may expose either a single temperature target or a target_temp_low / target_temp_high range.

Release 1.4.1

Demand thresholds now accept 0.001 °C steps, with an activation minimum of 0.001 °C. Defaults stay at 0.3 °C activation / 0.1 °C deactivation. For precise external demand sensors, a small positive activation threshold and zero deactivation can distinguish a tiny active request from zero source demand. For example, a 0.030 °C source delta is active with a 0.001 °C threshold, yet rounds a 20.3 °C destination reading back to a 20.3 °C target. With no source demand, the configured fixed or relative idle policy applies. A target equal to current temperature does not guarantee zero device heating power. No automatic change to existing thresholds, sources, or idle settings.

Release 1.4.0

Choose Setback target (fixed idle temperature) or Setback delta (current destination temperature minus setback), followed by one numeric field with a 0.5 °C input step. Delta accepts zero. Target uses the destination minimum; all computed targets respect the destination limits and configured maximum. When relative mode lacks a current temperature, it falls back to the advertised minimum and exposes that fallback in diagnostics. Delta 0 is a target equal to current temperature, not a command guaranteeing zero boiler demand. Existing installations migrate to fixed target mode with their effective value unchanged. Sources, hysteresis and update safeguards are preserved.

Release 1.3.2

The minimum destination change is inclusive: a configured 0.1 °C change sends both upward and downward steps of exactly 0.1 °C, with tolerance for decimal floating-point representation. Identical targets remain quiet, even with a zero minimum. Mismatch tracking uses the same decision as delivery. Source selection, demand hysteresis, rounding and rate limiting are unchanged.

Release 1.3.0

Climate sources and numerical delta sensors can now be used together, or either type on its own. Both feed the same maximum-delta calculation and primary-source selection. Missing or invalid inputs contribute zero without blocking healthy rooms. Existing source selections, hysteresis, rounding, rate limiting and destination control remain unchanged; no room switches to a new source automatically. PI-to-delta conversion remains in external helpers, not in ClimateSync.

Release 1.2.7

This release adds a downloadable Home Assistant diagnostics file. It captures the configured source and destination roles, current source health and deltas, demand state, setpoint calculation, timestamps, counters, and the latest error. It contains no credentials and does not change ClimateSync's control behaviour.

Release 1.2.6

This release adds two first-class heating-demand binary sensors. Heating Demand Active follows all configured sources, while Primary Heating Demand Active follows only the user-selected primary subset. Both use the exact same configured activation and deactivation thresholds; the primary scope does not change destination control. The setup and options flows are now a documented five-step wizard with Next buttons between steps and Submit only on the final step.

Release 1.2.5

This release retains the clearer source-health diagnostics from 1.2.4 while restoring the active idle fallback when no usable source remains. That fail-safe withdraws a potentially stale heating request instead of leaving the last destination target untouched.

Release 1.2.4

This release makes partial source failures explicit and safe. A source climate that is off but still exposes a valid current temperature is treated as a usable inactive room with zero heating demand. Unavailable or incomplete sources are reported as degraded_source_data, while healthy rooms continue to drive the destination. ClimateSync suppresses destination writes only when no usable source remains.

Release 1.2.3

This release adds configurable heating-demand hysteresis. Small positive room deltas no longer start the destination thermostat immediately; by default, demand starts at 0.3 °C and stops at 0.1 °C. The existing destination-setpoint anti-flap and rounding behaviour remain unchanged. Heating range destinations are supported by controlling target_temp_low while preserving their current target_temp_high atomically.

ClimateSync is a HACS-ready Home Assistant custom integration that implements delta-based thermostat synchronisation. It reads heating demand from source climates (target minus current temperature) and/or already calculated numerical delta sensors, then drives a single destination thermostat by continuously adjusting its target temperature.

Why delta-based instead of copying the highest setpoint?

Instead of simply syncing the highest source setpoint to the destination, ClimateSync uses the maximum delta (largest gap between target and current temperature across all rooms) and adds it to the destination's own current temperature:

destination_target = destination_current_temperature + delta_max

This approach was designed with the Plugwise Emma in mind. The Emma is connected to the central-heating boiler (CV) over OpenTherm and internally calculates a desired CV water temperature based on its own current delta. By feeding the Emma the home-wide maximum delta on top of its own measured temperature, ClimateSync lets the Emma function as a full OpenTherm controller — it sees the "hardest working" room's demand and adjusts the boiler modulation accordingly.

Even without an Emma, this delta-based method is more accurate than copying setpoints because it accounts for how far the destination already is from equilibrium.


Features

  • Event-driven updates — reacts immediately to temperature changes.
  • Mixed sources: climate entities and explicit numerical delta sensors in °C.
  • Periodic resync (configurable, default 60 s) to recover from missed events.
  • Anti-flap: only sends climate.set_temperature when a nonzero change reaches a configurable minimum (default 0.2 °C).
  • Configurable demand hysteresis: start heating at 0.3 °C room delta and stop at 0.1 °C by default.
  • All-source and primary-source heating-demand binary sensors, both driven by the configured hysteresis.
  • Destination target selection: ordinary temperature (default) or heating-range target_temp_low.
  • Rate limiting: maximum one service call per 10 seconds (configurable).
  • Rich diagnostic sensors plus a downloadable diagnostics file for support and troubleshooting.
  • No controllable entities — all control is internal via climate.set_temperature.

Installation

Via HACS (recommended)

  1. Open HACS → Integrations → ⋮ → Custom repositories.
  2. Add https://github.com/Patrick1610/ClimateSync as an Integration.
  3. Search for ClimateSync and install.
  4. Restart Home Assistant.

Manual

  1. Copy custom_components/climatesync/ to your <config>/custom_components/ directory.
  2. Restart Home Assistant.

Configuration

Navigate to Settings → Devices & Services → Add Integration → ClimateSync.

Step 1 — Source rooms

Choose either or both fields; together they must contain at least one source:

  • Source climate entities: existing room thermostats exposing current_temperature and temperature. Their contribution remains max(target - current, 0); an off climate contributes zero.
  • Heating-demand delta sensors (°C): sensors whose numeric state is already the required heating gap in Celsius. For example, an external helper can translate 20 percentage points of raw PI demand × 0.03 °C per percentage point into 0.60 °C. Select that helper, not the raw percentage sensor or a room temperature sensor. ClimateSync does not perform PI conversion or phase selection.

The highest delta wins, not the sum. A mixture of 0.60 °C from a PI helper, 0.40 °C from an office climate and 0.25 °C from a bathroom climate produces 0.60 °C. With the destination measuring 20.3 °C, its raw target is 20.9 °C. Normal demand thresholds, rounding and limits still apply.

Missing, unknown, unavailable, nonnumeric or non-finite delta values contribute zero. A delta sensor must report unit_of_measurement: °C; other or missing units are also treated as invalid zero demand. Healthy sources continue to drive the destination, with existing source-health diagnostics showing the failed sources. If all sources fail, ClimateSync withdraws demand using the existing idle fallback.

Move one room at a time: select its delta helper and remove that room's old climate sources from ClimateSync. Leaving both selected can let the old temperature gap overrule PI modulation. Other rooms can stay on climate sources. Review the room's primary-source role in step 2. No source, primary role or setting is replaced automatically by an upgrade. Do not select ClimateSync's own calculated output sensors as inputs, which would create a feedback loop.

Step 2 — Primary sources

The primary-source checkboxes show Home Assistant display names while retaining entity IDs internally. Missing names fall back to the entity ID; duplicate names include the ID for disambiguation. Opening or saving the wizard does not change source identities or their existing primary/secondary role just because labels changed. This applies to both climate and delta-sensor sources.

Select the subset of sources (climates and/or delta sensors) that may enable other rooms to piggyback on an existing heating run. General destination control still uses all sources. A secondary room can therefore start heating when it falls below its own target, but it does not turn on Primary Heating Demand Active.

Existing installations migrate with every existing source selected as primary, so the upgrade does not silently narrow their demand scope.

Step 3 — Destination & setpoint settings

Field Default Description
Destination climate entity — The thermostat that ClimateSync will control.
Destination target Target temperature Select ordinary temperature or lower target target_temp_low for a heating range climate.
Setback mode Setback target Fixed idle target, or current destination temperature minus a non-negative setback.
Maximum setpoint 35.0 °C Hard ceiling for the destination setpoint.
Rounding mode 1 decimal Step size used for the computed setpoint.
Rounding direction nearest Whether the computed setpoint is rounded down, normally, or up within the selected rounding mode.

Step 4 — One idle value

In Setback target mode, enter the fixed idle target (default 5 °C, bounded by the selected destination's minimum/maximum and configured ceiling). In Setback delta mode, enter the reduction (minimum 0 °C, default 0). Both use a 0.5 °C input step. At a measured 20.7 °C, setback 10 requests 10.7 °C and setback 0 requests 20.7 °C, before rounding and device limits. A zero delta does not guarantee zero device heat demand. If the relative mode has no valid current reading, ClimateSync requests the device minimum; if even that is unavailable it reports destination unavailable without sending an invalid target.

Step 5 — Demand & safeguards

Option Default Description
Resync interval 60 s How often ClimateSync checks even without state changes.
Minimum destination change 0.2 °C Send a changed destination setpoint when the difference reaches this minimum. It does not determine demand state.
Heating-demand activation threshold 0.3 °C Delta required to switch either demand scope on.
Heating-demand deactivation threshold 0.1 °C Delta at or below which an active demand scope switches off. Must be lower than activation.
Minimum send interval 10 s At most one destination service call per this many seconds.

Options Flow — reconfigure everything via the settings gear

After setup, open the integration → Configure (⚙ gear icon) to get the same five-step wizard again. Intermediate steps show Next; only the final step shows Submit. You can change:

  • Step 1: add or remove source rooms;
  • Step 2: choose the primary subset;
  • Step 3: change destination and setpoint behaviour;
  • Step 4: set the fixed target or relative setback;
  • Step 5: change hysteresis, resync, anti-flap, and rate limiting.
Option Default Description
Destination thermostat — Change which thermostat is controlled.
Destination target Target temperature Select ordinary temperature or lower target target_temp_low for a heating range climate.
Idle temperature 5.0 °C Temperature sent when no room needs heating.
Maximum setpoint 35.0 °C Hard ceiling for the destination setpoint.
Rounding mode 1 decimal Step size used for setpoints.
Rounding direction nearest floor, nearest, or ceiling rounding within the selected mode.
Resync interval 60 s How often ClimateSync checks even without state changes.
Minimum change threshold 0.2 °C Send a changed setpoint when the difference reaches this minimum.
Heating-demand activation threshold 0.3 °C Minimum room delta required to activate the destination thermostat.
Heating-demand deactivation threshold 0.1 °C Delta at or below which active heating demand stops. Must be lower than the activation threshold.
Minimum send interval 10 s At most one service call per this many seconds.

Algorithm

For each source climate entity (room):
    current = current_temperature attribute
    target  = temperature attribute
    if state == off and current is valid:
        source is usable but inactive; delta = 0
    elif current or target is missing/unavailable:
        source is degraded; delta = 0
    else:
        source is active; delta = max(target - current, 0)

For each selected numerical delta sensor:
    if state is missing, invalid, non-finite, or unit is not °C:
        source is degraded; delta = 0
    else:
        source is usable; delta = max(numeric state, 0)

If no usable source remains:
    apply the configured idle policy instead of the previous active demand

delta_max = max(all room deltas)

If demand is inactive and delta_max >= demand_activation_threshold:
    demand becomes active
If demand is active and delta_max <= demand_deactivation_threshold:
    demand becomes inactive

primary_delta_max = max(deltas of configured primary sources)
Apply the same activation/deactivation hysteresis independently to
primary_delta_max to calculate primary_demand_active

If demand is inactive:
    setpoint_raw = idle_value if idle_mode == target else destination_current_temperature - idle_value
Else:
    setpoint_raw = destination_current_temperature + delta_max
    # The destination target is set to its own current temperature
    # plus the largest demand across all source rooms. This means the
    # destination "feels" the same heating gap as the hardest-working
    # room, which is critical for OpenTherm controllers like the
    # Plugwise Emma that modulate boiler output based on their own
    # observed delta.

rounded_setpoint = round_setpoint(setpoint_raw, rounding_mode, rounding_direction)
setpoint_final = min(rounded_setpoint, maximum_setpoint)

If the target changed AND abs(destination_current_target - setpoint_final) >= min_change_threshold:
    If time_since_last_call >= min_send_interval:
        If destination_target == temperature:
            climate.set_temperature(destination, temperature=setpoint_final)
        Else:
            climate.set_temperature(
                destination,
                target_temp_low=min(setpoint_final, current_target_temp_high),
                target_temp_high=current_target_temp_high,
            )

Home Assistant requires both range bounds in the same set_temperature action. ClimateSync therefore reads the upper target again immediately before applying a lower target, preserves it in the same action, and never controls cooling through target_temp_high.

Rounding mode and direction

The rounding mode determines the step size. The rounding direction determines where the raw setpoint lands within that step size. ClimateSync applies epsilon-safe floor/nearest/ceiling rounding to avoid floating-point edge cases around step boundaries.

nearest is the default and preserves the historical ClimateSync behaviour for existing installations. If an older config entry does not contain rounding_direction, ClimateSync treats it as nearest.

Mode Example input Result
0.5 steps 19.3 19.5
1 decimal 19.33 19.3
2 decimals 19.333 19.33
Direction Behaviour
floor Always round down within the selected mode.
nearest Round to the nearest step, matching the old behaviour.
ceiling Always round up within the selected mode.

Plugwise Emma examples:

Mode + direction Raw setpoint Sent setpoint
0.5 steps + ceiling 19.1 19.5
0.5 steps + ceiling 19.5 19.5
0.5 steps + ceiling 19.6 20.0
0.5 steps + floor 19.1 19.0
0.5 steps + floor 19.9 19.5
1 decimal + ceiling 19.11 19.2
1 decimal + floor 19.19 19.1

For Plugwise Emma, 0.5 steps can be useful because Emma commonly accepts half-degree setpoints. Combining 0.5 steps with ceiling can help when small RoomMind deltas would otherwise disappear through normal rounding, for example a raw setpoint of 19.1 becoming 19.5 instead of 19.0.


Entities

All entities are attached to a ClimateSync device. Sensors (setpoint, deltas, destination target) are regular entities; the status sensor is classified as diagnostic. Home Assistant can also download a diagnostics file from the integration or device page.

Binary sensors

Heating Demand Active — binary_sensor.climatesync_heating_demand_active

Logical regulation demand across all configured sources. It switches on at the configured activation threshold and remains on until the maximum delta reaches the configured deactivation threshold. It means ClimateSync has a qualified heating request; it does not prove that the boiler is physically firing or that the destination accepted the last write.

Primary Heating Demand Active — binary_sensor.climatesync_primary_heating_demand_active

The same independently retained hysteresis state, calculated only over the configured primary subset. Use this entity to enable secondary rooms to piggyback without allowing those secondary rooms to trigger one another. It is unavailable when none of the configured primary sources has usable data.

Both entities expose their scope, current maximum delta, leading source, thresholds, source list, and source-health diagnostics as attributes.

Sensors

Computed setpoint — sensor.climatesync_1_destination_setpoint

Attribute Description
destination_entity_id The controlled thermostat
destination_target Selected destination target: temperature or target_temp_low
destination_current_temperature Current measured temperature at destination
destination_current_target Current target temperature at destination
delta_max Max delta used for this computation
demand_active Whether the hysteresis currently considers heating demand active
demand_activation_threshold Configured room delta required to start demand
demand_deactivation_threshold Configured room delta at or below which demand stops
rounding_mode Active rounding mode
rounding_direction Active rounding direction
raw_setpoint Unrounded destination_current_temperature + delta_max, or idle temperature when no room needs heating
rounded_setpoint Raw setpoint after rounding mode and rounding direction
final_setpoint Final setpoint after rounding and maximum-setpoint clamp
idle_temperature Absolute idle temperature (legacy attribute)
idle_mode / idle_value Configured fixed target or relative reduction
idle_setpoint Calculated absolute idle target before rounding and limits
idle_fallback_active Relative mode lacks current temperature and uses the device minimum

State: the final setpoint that ClimateSync wants to apply (destination_current_temperature + delta_max, rounded and capped by maximum setpoint).

Max delta — sensor.climatesync_2_delta_max

Attribute Description
room_deltas Map of {entity_id: delta} for all rooms
leading_room Entity id of the room with the highest delta
usable_source_count Number of sources that can safely participate
degraded_source_entities Unavailable or incomplete sources ignored for demand
inactive_source_entities off sources with a valid measurement and zero demand

State: the maximum delta across all rooms.

Per-room delta sensors — sensor.climatesync_delta_<slug>

One sensor per configured source, climate or numerical delta sensor.

Attribute Description
source_entity_id The source entity this sensor tracks
current_temperature Current temperature for a climate; null for a delta sensor
target_temperature Target temperature for a climate; null for a delta sensor
raw_delta target - current, or the numeric delta sensor state (may be negative)
source_status active, inactive_off, unavailable, missing_temperature, invalid_delta, or invalid_delta_unit

State: max(raw_delta, 0) — the effective heating demand for this room.

Destination current target — sensor.climatesync_destination_current_target

Shows the destination thermostat's actual current target temperature in real time, making it easy to compare against the computed setpoint without switching to the destination device.

Attribute Description
destination_entity_id The controlled thermostat
destination_current_temperature Current measured temperature at destination

State: the destination's current temperature attribute (its active target).

Diagnostic

Status — sensor.climatesync_status (most important)

States:

State Meaning
ok Everything is in sync, no issues.
rate_limited A setpoint update was suppressed because the last call was too recent.
destination_unavailable The destination climate entity is unavailable or unknown.
degraded_source_data One or more sources are unavailable or incomplete, but at least one usable source remains. Valid rooms continue to control the destination.
missing_source_data No usable source remains. ClimateSync applies the configured idle fallback until a source recovers.
apply_failed The climate.set_temperature service call threw an exception. Check last_error.
mismatch The destination's actual target deviates from the desired setpoint beyond the threshold. ClimateSync will attempt to correct this on the next cycle.

Attributes:

Attribute Description
last_update_time ISO timestamp of the last evaluation
last_service_call_time ISO timestamp of the last climate.set_temperature call
last_desired_setpoint What ClimateSync computed as the ideal setpoint
last_applied_setpoint What was last actually sent to the destination
current_destination_target The destination's actual temperature attribute right now
rounding_mode Active rounding mode
rounding_direction Active rounding direction
raw_setpoint Last unrounded setpoint
rounded_setpoint Last rounded setpoint before maximum-setpoint clamp
final_setpoint Last final setpoint after rounding and clamp
mismatch_seconds How long (seconds) the desired and actual setpoint have been diverging
mismatch_since ISO timestamp of when the mismatch started (null when in sync)
resync_count Number of periodic resyncs since startup
apply_attempts Total service call attempts since startup
apply_failures Total service call failures since startup
evaluation_count Total evaluation cycles since startup
skipped_anti_flap Times a setpoint update was skipped because the change was within the threshold
skipped_rate_limit Times a setpoint update was skipped due to rate limiting
last_error Last exception message, if any
source_count Number of configured climate and delta-sensor sources
usable_source_count Number of sources currently safe to use
degraded_source_entities Sources currently ignored because data is unavailable or incomplete
inactive_source_entities off sources that remain valid measurements with zero demand

Troubleshooting

Destination is not accepting the setpoint

Some thermostats (e.g. Plugwise Emma) only accept specific temperature steps. Use the 0.5 steps rounding mode in that case. If small RoomMind deltas are rounded away, use Rounding direction = ceiling so a raw setpoint such as 19.1 is sent as 19.5 instead of 19.0.

mismatch_seconds keeps growing

An external automation or the user may be overriding the destination's target. ClimateSync will keep trying to reapply on every evaluation cycle and resync. Check if another integration or automation is fighting over the thermostat.

rate_limited appears frequently

The source rooms are changing temperature very rapidly. Increase min_send_interval in the Options Flow to reduce chatter.

missing_source_data

No selected source currently provides usable data. ClimateSync actively applies the configured idle temperature so an earlier heating request cannot remain in effect. Restore at least one source with a valid current_temperature and temperature, an off source with a valid current_temperature, or a finite numerical delta sensor with a °C unit.

degraded_source_data

At least one source is unavailable or lacks a required temperature attribute. Other usable rooms continue to be processed. Inspect degraded_source_entities and each delta sensor's source_status attribute.

destination_unavailable

The destination thermostat is offline. No service calls are made. ClimateSync will recover automatically once the entity becomes available again.

apply_failed

Check last_error in the sensor.climatesync_status attributes. Most likely the climate entity does not support the climate.set_temperature service or the entity id is wrong.


Compatibility

ClimateSync uses only the standard climate.set_temperature service. It reads standard source climate attributes (current_temperature, temperature) and/or numerical heating-demand delta sensors in °C. Delta sensors remain read-only inputs; ClimateSync never writes them. It works with any climate integration that follows the standard HA climate platform contract, including but not limited to:

  • Plugwise Emma / Smile
  • Generic Thermostat
  • ESPHome climate components
  • Z-Wave thermostats
  • Zigbee thermostats (ZHA / Zigbee2MQTT)
  • Google Nest (via the Nest integration)

Tests

The coordinator, entities, diagnostics and setup/options wizard have isolated unit tests. Install pytest, pytest-asyncio and voluptuous, then run:

PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -p pytest_asyncio.plugin -q

Only the asyncio plugin is enabled explicitly so unrelated Home Assistant test plugins in a shared environment cannot change these unit tests. GitHub Actions runs the same suite, plus the existing hassfest and HACS validation workflows.


License

MIT — see LICENSE.

About

Delta-based thermostat synchronization for Home Assistant

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages