Divert solar surplus to the hot water cylinder instead of exporting at 4 ¢
The immersion heater steps up in 400 W increments to absorb PV surplus, holding grid import between -50 W and +150 W for four hours. In a 4.2 kWp array this captured 6.8 kWh on a clear April day instead of exporting it at the feed-in rate.
# HearthNodes recipe 16 - solar surplus to hot water
alias: "Energy - divert PV surplus to the cylinder"
id: hn_energy_solar_surplus_water
description: "Hold grid between -50 W and +150 W by modulating the immersion"
mode: single
max_exceeded: silent
trigger:
- platform: numeric_state
entity_id: sensor.grid_power
below: -250
for: "00:01:00"
variables:
band_low_w: -50
band_high_w: 150
step_w: 400
pwm_period_s: 10
cylinder_c: "{{ states('sensor.cylinder_temperature') | float(0) }}"
current_w: "{{ states('sensor.immersion_power') | float(0) }}"
error_w: "{{ states('sensor.grid_power') | float(0) }}"
next_w: "{{ [[current_w + (band_high_w - error_w), 0] | max, 3000] | min }}"
duty: "{{ (next_w / 3000 * 100) | round(0) }}"
condition:
- condition: numeric_state
entity_id: sensor.cylinder_temperature
below: 62
- condition: numeric_state
entity_id: sensor.battery_soc
above: 55
- condition: state
entity_id: binary_sensor.high_power_appliance_active
state: "off"
- condition: template
value_template: "{{ cylinder_c | float(0) < 62 }}"
action:
- service: number.set_value
target:
entity_id: number.immersion_setpoint
data:
value: 62
- service: script.pwm_switch
data:
switch: switch.immersion_element
duty_pct: "{{ duty }}"
period_s: "{{ pwm_period_s }}"
step_w: "{{ step_w }}"
- service: sensor.record
data:
entity_id: sensor.exported_energy_today
note: "compare diversion against the feed-in rate"
# Grid meter loss -> irradiance-based forecast model, so the cylinder is never
# heated from grid import by accident. Cylinder probe failure stops diversion
# entirely: heating water to an unknown temperature is worse than exporting.
Trigger · numeric_state
sensor.grid_power
Grid power stays below -250 W (exporting) for 60 seconds.
Step-by-step
- switch.immersion_elementModulate the immersion in 400 W steps using a 10-second PWM period; longer periods make the meter read a bouncing value and the regulator hunts.
PWM · 400 W steps · 10 s period - number.immersion_setpointCap the target at 62 °C for the diverter and leave the cylinder's own mechanical thermostat at 70 °C as the independent safety limit.
set 62 °C · hard limit 70 °C - sensor.exported_energy_todayLog exported versus diverted energy daily so the diversion strategy can be judged against the actual feed-in tariff.
log · compare against diversion - input_boolean.cylinder_boostA wall button gives a 60-minute manual boost that ignores the surplus logic, for the evening a fourth guest arrives.
manual 60 min boost overrides everything
Hold the grid at a small import, never at zero
Targeting exactly 0 W makes the system oscillate between importing and exporting several times a minute. Holding between -50 W and +150 W means the meter sees a tiny import most of the time, which is stable and costs almost nothing. This is the single most important tuning value.
Let the battery win below 55 % state of charge
Diverting surplus to hot water while the battery is at 30 % means you buy grid power that evening. The 55 % threshold reflects a household that uses 4 kWh overnight; recalculate it from your own overnight consumption rather than copying the number.
Cap the diverter below the cylinder's own thermostat
62 °C target with a 70 °C mechanical limit gives two independent layers. A diverter that trusts its own sensor with no mechanical backup is one failed probe away from boiling the cylinder. Check the mechanical thermostat annually by hand.
Hardware
| Model | Protocol | Link | Role in this chain |
|---|---|---|---|
| Matter smart relay with power metering (e.g. Shelly or equivalent) | matter |
wired | Switches and measures the immersion load; the measurement is what closes the control loop. |
| Bidirectional grid meter (CT clamp or the inverter's own meter) | matter |
wired | Provides the signed grid power value; a one-directional meter cannot tell export from import. |
| Cylinder temperature probe on a 1 m immersion pocket | zigbee |
wireless | Strapped to the cylinder's pocket, not the pipe, so it reads stored water rather than flow temperature. |
When it fails
If the grid meter drops out, the diverter falls back to a solar-forecast model that estimates surplus from irradiance, which is less accurate but keeps the cylinder from being heated from the grid. If the cylinder probe fails, diversion stops entirely and a notification says so — heating water to an unknown temperature is worse than exporting cheaply.