We set up a Home Assistant automation to divert excess solar power to a hot water system using a Shelly Pro 1PM relay. The concept was simple: when the solar system is exporting more than a threshold, turn the hot water on. When there is not enough export, turn it off.
The automation ran every five minutes. It monitored net power. It checked the relay state. But for days, nothing happened. The hot water never turned on, even on sunny days with plenty of export.
This article covers what went wrong and how we fixed it.
The Hardware
- Shelly Pro 1PM (
switch.shellypro1pm_80f3dac87e8c): a DIN-rail relay with power monitoring, rated for up to 15A. The hot water element draws roughly 3.85 kW. - Solar inverter: monitored through the SEMS integration, providing
sensor.net_power_in_out_power_2. - Home Assistant running on a QEMU VM (HAOS).
The Automation (First Attempt)
The automation was designed to check every five minutes whether solar export exceeded a configurable threshold, and turn the relay on or off accordingly.
- id: hot_water_solar_control
alias: Solar hot water control
triggers:
- minutes: /5
trigger: time_pattern
- trigger: state
entity_id:
- sensor.net_power_in_out_power_2
- input_number.hot_water_export_threshold
conditions:
- condition: state
entity_id: sun.sun
state: above_horizon
actions:
- variables:
net_power: '{{ states("sensor.net_power_in_out_power_2") | float(0) }}'
export_threshold: '{{ states("input_number.hot_water_export_threshold") | float(300) }}'
- if: net_power < -export_threshold and not shelly_on
then: turn on
- if: net_power >= -export_threshold and shelly_on
then: turn off
It looked correct. The sun was up. The solar was exporting 4.8 kW. The threshold was 300 W. But the relay stayed off.
Problem 1: The Sun Gate
The automation had a top-level condition:
conditions:
- condition: state
entity_id: sun.sun
state: above_horizon
This is a gate. When the sun is down, the automation does not run at all. None of the rules evaluate. The relay keeps its last state until the next sunrise.
If the relay was turned on during the day — say, by an earlier version of the automation — it would stay on all night. There was no rule to turn it off at sunset.
The fix: remove the sun gate and add a dedicated sunset cleanup rule.
- if: not sun_up and shelly_on
then: turn off
The sunset event triggers the automation, this rule catches it and turns the relay off.
Problem 2: The Unit Mismatch
This was the real head-scratcher.
The automation compared two values:
net_powerfromsensor.net_power_in_out_power_2export_thresholdfrominput_number.hot_water_export_threshold
The threshold was set to 3600 W. The solar was exporting 4.8 kW. The automation checked:
net_power < -export_threshold
Which evaluated as:
-4.8 < -3600
That is false. The sensor reports in kW. The threshold is in W. The automation was comparing kilowatts against watts.
The values never matched.
The fix: multiply the net power reading by 1000 so both values are in watts.
net_power: '{{ states("sensor.net_power_in_out_power_2") | float(0) * 1000 }}'
After this fix, the comparison became:
-4800 < -3600
True. The relay turned on immediately.
Problem 3: The Oscillation Risk
Once the hot water turns on, it draws about 3.85 kW. This drops the net export. If the original threshold was used as both the on and off threshold, a cycle would occur:
- Export > 3600 W → relay on
- Heater draws 3850 W → net export drops to ~950 W
- Export < 3600 W → relay off
- Export returns to 4800 W → relay on
- Repeat every five minutes
This is a classic hysteresis problem.
The fix: use two different thresholds with a wide hysteresis band.
- Turn on when exporting more than 3600 W (net power < -3600 W)
- Turn off only when importing from the grid (net power >= 0 W)
The 3600 W gap means the relay stays on as long as there is any export, even after the heater loads it down.
- if: sun_up and net_power < -export_threshold and not shelly_on
then: turn on
- if: sun_up and net_power >= 0 and shelly_on
then: turn off
After this change, the relay ran continuously for the rest of the day without cycling.
The Final Automation
The finished automation has three rules, evaluated in order, every five minutes:
| Rule | Condition | Action |
|---|---|---|
| 0 | Sun down + relay on | Turn off |
| 1 | Sun up + exporting > 3600 W + relay off | Turn on |
| 2 | Sun up + importing (net ≥ 0 W) + relay on | Turn off |
- id: hot_water_solar_control
alias: Solar hot water control
description: Divert excess solar to hot water.
triggers:
- minutes: /5
trigger: time_pattern
- trigger: state
entity_id:
- sensor.net_power_in_out_power_2
- input_number.hot_water_export_threshold
- event: sunrise
trigger: sun
- event: sunset
trigger: sun
actions:
- variables:
net_power: '{{ states("sensor.net_power_in_out_power_2") | float(0) * 1000 }}'
export_threshold: '{{ states("input_number.hot_water_export_threshold") | float(300) }}'
shelly_on: '{{ is_state("switch.shellypro1pm_80f3dac87e8c", "on") }}'
sun_up: '{{ is_state("sun.sun", "above_horizon") }}'
- if: not sun_up and shelly_on
then: switch.turn_off
- if: sun_up and net_power < -export_threshold and not shelly_on
then: switch.turn_on
- if: sun_up and net_power >= 0 and shelly_on
then: switch.turn_off
mode: single
Debugging Sensors
While troubleshooting, we added template sensors to expose each piece of the automation logic on a dashboard card. They made it much easier to see what the automation was evaluating in real time.
| Sensor | Purpose |
|---|---|
sensor.hot_water_debug_net_power | Net power in watts |
sensor.hot_water_debug_export_threshold | Current threshold |
sensor.hot_water_debug_exporting | YES if exporting above threshold |
sensor.hot_water_debug_sun | Sun status |
sensor.hot_water_debug_verdict | Plain-text explanation of the current state |
What We Learned
- Check your units. kW and W look similar on paper but produce wildly different comparisons. The sensor may report in a different unit than you expect.
- Sun conditions should not be gates. If you need sun-dependent logic, build it into individual rules rather than blocking the entire automation. Otherwise there is nothing to turn things off at sunset.
- Hysteresis prevents toggling. Without a deadband between turn-on and turn-off thresholds, any automation that controls a significant load will oscillate.
- Debug sensors save time. Exposing the internal variables of an automation as template sensors on a dashboard makes it obvious when something is wrong.
Leave a Reply