I have a hot water cylinder heated via a gas boiler that supplies the house hot water including a power shower. The water is heated via a coil, like this:
I have three DS18B20 temperature sensors on the tank: one at the top buried in the insulation next to the electric immersion heater (not shown on diagram), and one cable-tied to both flow and return pipes as close to the tank as possible.
From these sensors, I derive a weighted “tank heat charge” percentage figure into a template sensor, which is shown on my Home Assistant page and gives a good idea how much hot water is in the tank: useful for knowing if there’s enough for a shower or if the boiler needs to be run first.
However, when the boiler does run, the two sensors on the coil immediately report a very high temperature and totally skew the measurement whilst the water is being heated: the “charge” jumps to 100%.
What’s needed is a “damped” sensor that has the physical aspects of the tank in its configuration: the rate of heating should not be too quick, although the rate of discharge can be quicker — a shower removes heat faster than the boiler can replenish it.
I did experiment with the filter
integration and its lowpass
filter, trying to combine a “fast” and a “slow” filtered version of the raw value, but ran into issues over updates — if the calculated value jumps to 100% and stays there, it does not send new values to the filter, which gets stuck at a lower value.
Eventually I designed this setup:
- raw
sensor
value - an
input_number
- a smoothed template
sensor
reflecting the value of theinput_number
- driven by this automation:
- alias: Tank value update
trigger:
- platform: state
entity_id: sensor.time
condition: []
action:
- service: input_number.set_value
data_template:
entity_id: input_number.hw_tank_smooth_value
value: >
{% set told = states('input_number.hw_tank_smooth_value')|float %}
{% set tnew = states('sensor.hw_tank_heat_pct_raw')|float %}
{% set max_up_per_min = 2 %}
{% set max_down_per_min = 10 %}
{% if tnew > told %}
{{ [tnew, told + max_up_per_min]|min|round(0) }}
{% else %}
{{ [tnew, told - max_down_per_min]|max|round(0) }}
{% endif %}
So every minute, triggered by an update from sensor.time
, it compares the prior value stored in the input_number
and adjusts it in line with the new raw value, but with the change limited to the max_up_per_min
and max_down_per_min
variables. In my setup, the maximum change is +2% or -10% per minute.
Here’s a plot with the drop from my 06:50 morning shower and then the heating cycle from the boiler starting at 07:45:
Seems to work well. I’ve posted it here a) in case it helps someone else and b) to see if I’ve missed a simpler way to do it, as the whole input_number
thing seems a bit of a bodge.