Skip to content

HA template-sensor cookbook

HeatSync ships ~50 entities to Home Assistant via either the HACS custom integration or MQTT auto-discovery. The recipes below combine those into derived sensors, automations and Energy dashboard wiring. Drop them into configuration.yaml and reload.

All examples assume the default friendly-name slug heatsync_*. If you renamed the device, adjust the entity_ids accordingly.

Average heat-pump efficiency over the unit’s lifetime. Pair with the Energy dashboard.

template:
- sensor:
- name: "HeatSync lifetime SCOP"
unit_of_measurement: ""
icon: mdi:gauge
state: >-
{% set gen = states('sensor.heatsync_energy_generated') | float(0) %}
{% set con = states('sensor.heatsync_energy') | float(0) %}
{{ (gen / con) | round(2) if con > 0 else 'n/a' }}

Typical UK ASHP: 2.8–3.5. Below 2.5 → check curve, flow temp, fault codes.

Real-time efficiency from flow rate × ΔT × specific heat / power. Available only when the compressor is running.

template:
- sensor:
- name: "HeatSync instant COP"
unit_of_measurement: ""
availability: "{{ states('binary_sensor.heatsync_compressor') == 'on' }}"
state: >-
{% set p = states('sensor.heatsync_power') | float(0) %}
{% set flow = states('sensor.heatsync_flow_rate') | float(0) %}
{% set tin = states('sensor.heatsync_water_inlet') | float(0) %}
{% set tout = states('sensor.heatsync_flow_actual') | float(0) %}
{# heat output kW ≈ L/min × ΔT × 4.18 / 60 #}
{% set heat_kw = (flow * (tout - tin) * 4.18 / 60) %}
{{ (heat_kw * 1000 / p) | round(2) if p > 100 else 'n/a' }}

Caveats. This is a 60-second moving picture, not calorimeter-grade. Anti-condensation defrosts will dip it briefly. Useful as a “is it running efficiently right now” gauge, not for billing.

HeatSync’s /api/cost/status already exposes pence-cost calculations, but if you’d rather compute it inside HA (e.g. to use a dynamic Agile rate), here are the sensors. Replace 0.30 and 0.08 with your own peak / off-peak £/kWh.

template:
- sensor:
- name: "Heat pump running cost"
unit_of_measurement: "p/h"
device_class: monetary
state: >-
{% set p = states('sensor.heatsync_power') | float(0) %}
{% set hour = now().hour %}
{% set off_peak = hour >= 23 or hour < 7 %} {# match your DHW schedule window #}
{% set rate = 8.0 if off_peak else 30.0 %} {# p/kWh #}
{{ (p / 1000 * rate) | round(2) }}

For longer windows, use HA’s built-in Riemann sum integral helper on sensor.heatsync_power:

  1. Settings → Devices & services → Helpers → Create helper
  2. Integration → input sensor.heatsync_power (W), unit prefix k, method left, time unit hours
  3. Name it Heat pump kWh today (it auto-resets when its source has state_class: total_increasing).
  4. Apply a separate template sensor to multiply by tariff:
template:
- sensor:
- name: "Heat pump cost today"
unit_of_measurement: "£"
device_class: monetary
state: >-
{% set kwh = states('sensor.heat_pump_kwh_today') | float(0) %}
{# average rate is good enough at day scale; refine if Agile-style #}
{{ (kwh * 0.20) | round(2) }}
template:
- binary_sensor:
- name: "HeatSync off-peak window"
device_class: power
state: >-
{% set h = now().hour %}
{{ h >= 23 or h < 7 }}

Adjust the hours to match dhwSchedOffPeak* in HeatSync.

Use a Glance card with these entities side by side:

type: glance
title: Heat pump
entities:
- entity: sensor.heatsync_outdoor_temp
name: Outdoor
- entity: sensor.heatsync_flow_actual
name: Flow
- entity: sensor.heatsync_water_law_target
name: Curve target
- entity: sensor.heatsync_water_law_offset
name: Offset
- entity: binary_sensor.heatsync_compressor
name: Comp
automation:
- alias: "Heat-pump fault notification"
description: "Pushes to the phone when HeatSync raises a fault binary_sensor."
trigger:
- platform: state
entity_id: binary_sensor.heatsync_fault
to: "on"
action:
- service: notify.mobile_app_your_phone
data:
title: "⚠️ Heat-pump fault"
message: "Check the HeatSync dashboard for the error code."

If compressor toggles on more than 3× in an hour, ping. Symptom of either a too-aggressive water-law curve or load-comp Kp.

template:
- sensor:
- name: "Heat pump cycles last hour"
unit_of_measurement: "/h"
state: >-
{{ state_attr('binary_sensor.heatsync_compressor', 'cycles_last_hour') | int(0) }}
automation:
- alias: "Heat-pump short-cycling"
trigger:
- platform: numeric_state
entity_id: sensor.heat_pump_cycles_last_hour
above: 3
for: "00:30:00"
action:
- service: notify.persistent_notification
data:
title: "Heat pump short-cycling"
message: "Compressor cycled {{ states('sensor.heat_pump_cycles_last_hour') }}× in the last hour. Review curve / load-comp Kp."

(HeatSync’s /api/load-comp/status exposes cyclesLastHour too — same data, different consumer.)

Both sensor.heatsync_energy (consumed) and sensor.heatsync_energy_generated (thermal delivered) ship with state_class: total_increasing and the right device_class, so they slot straight into the Settings → Dashboards → Energy flow:

  • Individual devices → add HeatSync’s Energy sensor as consumption.
  • (Optional) Gas/Heat → add Energy generated if you want delivered-heat tracked separately.

The Energy dashboard automatically computes daily/weekly/monthly breakdowns, no template required.

HeatSync events (faults, schedule writes, OTAs) land on the device’s /api/events endpoint, not in HA. If you want them in HA’s Logbook too, expose them via MQTT — easiest is to publish a retained message on homeassistant/sensor/heatsync_event/state whenever a notable event fires. (Open feature; not shipped yet.)

The controller buckets today’s draw into heating / DHW / defrost / standby (/api/cost/status returns each as heatingKwh, dhwKwh, etc.). If you publish those via MQTT (or scrape the API) you can build a per-mode cost sensor:

template:
- sensor:
- name: "HeatSync heating cost today"
unit_of_measurement: "p"
state: >
{% set kwh = states('sensor.heatsync_heating_kwh') | float(0) %}
{% set rate = states('sensor.octopus_current_rate') | float(30) %}
{{ (kwh * rate) | round(0) }}

This pairs nicely with the Octopus Energy or any time-of-use integration that exposes the live unit rate. Repeat for dhw_kwh to track hot-water spend separately.

Hook HeatSync’s Away toggle into your “I’m leaving for holiday” flow. Calls the controller’s REST endpoint directly — no MQTT round-trip.

automation:
- alias: "HeatSync · Away when calendar says holiday"
trigger:
- platform: calendar
event: start
entity_id: calendar.family_holidays
action:
- service: rest_command.heatsync_away_on
- alias: "HeatSync · Home when holiday ends"
trigger:
- platform: calendar
event: end
entity_id: calendar.family_holidays
action:
- service: rest_command.heatsync_away_off
rest_command:
heatsync_away_on:
url: "http://heatsync.local/api/control/away?on=1"
method: POST
headers:
Cookie: "hs=YOUR_COOKIE_HERE"
heatsync_away_off:
url: "http://heatsync.local/api/control/away?on=0"
method: POST
headers:
Cookie: "hs=YOUR_COOKIE_HERE"

Get the cookie value by logging into the device in a browser and copying the hs cookie from devtools → Application → Cookies.

Compressor cycles are tracked daily on the energy ring (cycleCount on each /api/energy/daily entry). If you push that into HA as a sensor, alert when yesterday’s count exceeds a sensible ceiling:

automation:
- alias: "HeatSync · short-cycling alert"
trigger:
- platform: numeric_state
entity_id: sensor.heatsync_cycles_yesterday
above: 40
action:
- service: notify.mobile_app_phone
data:
title: "Heat pump short-cycling"
message: >
{{ trigger.to_state.state }} compressor starts yesterday.
Consider lowering the heating curve or load-comp Kp.

40 starts/day ≈ slightly less than 2/hour — well above the healthy 6–12 range for a modulating ASHP in shoulder weather.

Pair the daily energy entries (kwhElec and kwhThermal) into a weekly SCOP via a template sensor:

template:
- sensor:
- name: "HeatSync weekly SCOP"
state: >
{% set days = state_attr('sensor.heatsync_energy_daily', 'days') or [] %}
{% set ns = namespace(e=0, t=0) %}
{% for d in days[-7:] %}
{% set ns.e = ns.e + (d.kwhElec | float(0)) %}
{% set ns.t = ns.t + (d.kwhThermal | float(0)) %}
{% endfor %}
{{ (ns.t / ns.e) | round(2) if ns.e > 0 else 'n/a' }}

Compare against your install’s design SCOP from MCS. Persistent under-performance suggests the curve is too steep (poor low-temp efficiency) or the system is short-cycling.

When tomorrow’s forecast is unusually cold, pre-warm the house overnight using the heating Boost endpoint. Pair with any HA weather entity:

automation:
- alias: "HeatSync · pre-warm before cold snap"
trigger:
- platform: time
at: "22:00:00"
condition:
- condition: numeric_state
entity_id: weather.met_office_home
attribute: forecast.0.templow
below: -3
action:
- service: rest_command.heatsync_heating_boost

The Boost is +1 °C for 1 hour, then auto-restores. For more aggressive pre-warming, write the heating target directly via /api/control/heating-target?value=22.

The device keeps a NVS-backed ring of WARN/ERROR events at /api/events/persisted. Surface fresh entries as HA persistent notifications:

automation:
- alias: "HeatSync · surface persistent faults"
trigger:
- platform: state
entity_id: sensor.heatsync_persistent_fault_count
condition:
- condition: numeric_state
entity_id: sensor.heatsync_persistent_fault_count
above: 0
action:
- service: persistent_notification.create
data:
title: "HeatSync recorded a fault"
message: "{{ states('sensor.heatsync_latest_fault_msg') }}"
notification_id: heatsync_fault

Wire heatsync_persistent_fault_count to the events.length attribute from a REST sensor polling /api/events/persisted every few minutes.