Predicting Boiler Heat Time

I have an electric hot water cylinder. I also have ability to switch the boiler on and off with a smart switch.

With that I also have a temperature probe on the side of the water cylinder. And an extra temperature sensor inside the room itself.

What I would like to be able to do is know how long it will take to heat up to a certain temperature.

So right now I have a sensor template that estimates when the tank will reach a certain temperature:

{{ 
(now().timestamp() + ((states('sensor.water_tank_target_temperature')  -(states('sensor.hot_water_tank_actual_temperature') | float)) / 0.3266)*60) |as_datetime
}}

In the example above, I manually measured how many degrees the water tank heated up after 2 hours of being on, and ended up with 0.3266°C per minute.

This does actually give me a rough estimate of when the water tank will be heated up, but it tends to end up being off by up to an hour. This is because the template assumes a linear temperature increase, when there is a big difference in the time it takes to heat from 20°C to 30°C as compared to 50°C to 60°C. It’s also based off a measurement I took once, rather than multiple measurements.

Does anyone know if there are any integrations out there that can use past data to correctly estimate how long my hot water tank takes to heat up?

If not does anyone who’s better at algorithms know of a way to calculate heating time using a curve rather than a linear temperature assumption?

This can help you devise a formula for a curve:

Hello I Am The Milk Man My Milk Is Delicious

Feed this some data points and it draws the line for you providing the value as an output.

The other one may do similar, but this is part of core.

1 Like

Ahh! I was thinking this should be there somewhere, but I could not find it. Looked for filter, translate (obviously bad name for this) and other things. Compensation! That was it!

It’s one of Petro’s integrations, he’s going to be sad you didn’t know about it.

Hi @Sir_Goodenough

Sorry to bother you more on this one. I exported some readings from the hot water tank heating up and put them into a compensation sensor like below:

hot_water_tank_compensation:
  source: sensor.hot_water_tank_actual_temperature
  data_points:
    - [0.0, 23.4]
    - [8.646, 24.8]
    - [128.45, 26.3]
    - [188.854, 27.8]
    - [368.558, 29.5]
    - [488.363, 31.1]
    - [608.166, 32.7]
    - [727.968, 34.3]
    - [847.777, 35.8]
    - [967.58, 37]
    - [1087.384, 38.4]
    - [1207.201, 39.7]
    - [1326.999, 40.7]
    - [1446.797, 41.9]
    - [1747.384, 44.3]
    - [1866.117, 45.3]
    - [2045.813, 46.3]
    - [2405.729, 47.4]
    - [2765.142, 48.5]
    - [3124.545, 49.5]
    - [4023.593, 50.5]

For the arrays above, the first value is the amount of seconds it took to reach a temperature, and the second value is the temperature. So, for example, it took 4023s to reach 50.5°C from 23.4°C.

This ends up creating a new compensation sensor. As an example, the water tank is currently 31°C, and the compensation value is 30.67

That seems good so far, but how do I apply this 30.67 value to my original formula for calculating time to heat, it doesn’t seem to be close to my original value of 0.3266? I don’t think I’m good enough at calculations to figure out where I’ve gone wrong.

(now().timestamp() + ((states('sensor.water_tank_target_temperature')  -(states('sensor.hot_water_tank_actual_temperature') | float)) / 0.3266)*60) |as_datetime

Your data list is back to front and only takes into account the end temperature. It has no concept of the start temperature. The time to rise from 49 to 50.5 is going to be different from the time to rise from 23 to 50.5.

You will need a list of [temperature rise required, time to rise that amount] then feed this compensation sensor with a sensor containing the temperature rise required to give you the time required.

1 Like

It’s going to draw a line for the 2nd number. So given the first number, you can see where on the line the 2nd number is predicted to be.
So maybe put the temps in first, and it gives you a number of seconds. I’m not sure which way gets you to where you want, but you might have to think thru it and consider options.

Or just use this formula in a template sensor: How To Calculate Time To Heat Water

The problem the OP is having though, is that at higher temperatures, the heat loss of the tank increases, thus increasing the time to reach setpoint.

1 Like

If you collect accurate data at those levels, it will draw the curve… and even extrapolate beyond it if you like.

Thanks for the help everyone, I have the compensation sensors working now.

For anyone interested in doing the same here’s the process I’m using to calculate time to heat my hot water tank (or immersion as we call it in the UK).

For me I use 2 temperature probe sensors touching the tank, one close to the top and the other at the bottom. You don’t need to use 2, but more sensors may be more accurate.

I specifically use this one, but any kind of probe could be fine:

Then I turn on the immersion for a while and watch the temperature increase in homeassistant. I’m able to control my immersion through homeassistant, but being able to control is not necessary to calculate time to heat.

Once it’s increased enough, I select the heat data history and export it in homeassistant to a CSV file.

It should look like below:

entity_id,state,last_changed
sensor.hot_water_tank_top_temperature_sensor_temperature,17.5,2025-05-08T14:30:00.000Z
sensor.hot_water_tank_top_temperature_sensor_temperature,18.5,2025-05-08T14:33:27.261Z
sensor.hot_water_tank_top_temperature_sensor_temperature,19.8,2025-05-08T14:36:26.922Z
sensor.hot_water_tank_top_temperature_sensor_temperature,21,2025-05-08T14:39:26.553Z
sensor.hot_water_tank_top_temperature_sensor_temperature,22.1,2025-05-08T14:41:26.313Z
...

From there I want to convert that data to something readable by a compensation sensor. To do this I wrote a quick python script:

"""
Python script that converts an exported homeassistant CSV
into compensation sensor list.

Takes 2 args:
    1. The name of the input CSV file.
    2. The desired max temperature of the compensation sensor.
"""

import csv
from datetime import datetime
import sys

# Define the end time as a datetime object
end_time = None

# Open and read the CSV file
with open(sys.argv[1], mode='r') as file:
    reader = csv.reader(file)
    
    # Skip the header row
    next(reader)


    # Remove temperatures higher than desired temperature
    filtered_list = filter(lambda x: float(x[1]) <= float(sys.argv[2]), reader)

    # Sort the list by date
    sorted_list = sorted(filtered_list,key=lambda x: x[2], reverse=True)
    # Iterate over each row in the CSV file
    for index, row in enumerate(sorted_list):
        _entity_id, state, last_changed = row
        last_changed_time = datetime.strptime(last_changed, "%Y-%m-%dT%H:%M:%S.%fZ")
        if index == 0:
            end_time = last_changed_time
        
        # Calculate the difference between starendt time and last changed time
        diff_seconds = (end_time - last_changed_time).total_seconds()
        print (f"      - [{state}, {diff_seconds}]")

So I would just call this script like so:

python3 convert.py history.csv 38.8

The 38.8 above is the temperature I want to achieve.
Specifically I have found that 38.8°C is the temperature that the bottom temperature sensor reads when the tank is just hot enough to fill a bath.
This number will be different for anyone else trying to set this up themselves, and can only be figured out by running a bath a few times and finding out what the minimum temperature you can fill a bath at is.

If you have multiple temperature sensors, you will have to run this script again for the other sensors.
For me, I like the top temperature sensor to read 46.8°C and the bottom to read 38.8°C to be sure the bath is hot enough so I’ll run the script twice like so:

python3 convert.py top_sensor_history.csv 46.8
python3 convert.py bottom_sensor_history.csv 38.8

I also did some testing and found out my ideal temperature to take a shower, which for me was 38°C at the top and 30°C at the bottom. So again I run the scripts:

python3 convert.py top_sensor_history.csv 38.8
python3 convert.py bottom_sensor_history.csv 30

These scripts all end up producing some yaml like so:

    - [17.5, 5040.43]
    - [18.5, 4833.169]
    - [19.8, 4653.508]
    - [21, 4473.877]
    - [22.1, 4354.117]
    - [23.1, 4234.338]
    - [24.1, 4114.588]
    ...

I just have to copy that yaml into my compensation sensors homeassistant config so I end up with this:

hot_water_tank_bottom_bath_compensation:
  source: sensor.hot_water_tank_bottom_temperature_sensor_temperature
  unique_id: hot_water_tank_bottom_bath_compensation
  data_points:
    # First Value: Temperature
    # Second value: Seconds away from target temp (38.8)
    - [17.2, 5723.929]
    - [18.2, 5453.238]
    - [19.2, 5213.101]
    - [20.2, 4913.558]
    - [21.3, 4671.924]
    - [22.5, 4374.367]
    - [23.5, 4134.724]
    - [24.5, 3895.076]
    - [25.5, 3655.427]
    - [26.7, 3355.889]
    - [27.7, 3115.754]
    - [28.8, 2816.211]
    - [30, 2516.665]
    - [31.1, 2217.118]
    - [32.2, 1917.565]
    - [33.2, 4045.995]
    - [34.5, 1677.934]
    - [35.7, 1018.44]
    - [36.7, 718.895]
    - [37.8, 419.355]
    - [38.8, 0]

hot_water_tank_bottom_shower_compensation:
  source: sensor.hot_water_tank_bottom_temperature_sensor_temperature
  unique_id: hot_water_tank_bottom_shower_compensation
  data_points:
    # First Value: Temperature
    # Second value: Seconds away from target temp (30)
    - [17.2, 3207.264]
    - [18.2, 2936.573]
    - [19.2, 5213.101]
    - [20.2, 2696.436]
    - [21.3, 2155.259]
    - [22.5, 1857.702]
    - [23.5, 1618.059]
    - [24.5, 1378.411]
    - [25.5, 1138.762]
    - [26.7, 839.224]
    - [27.7, 599.089]
    - [28.8, 299.546]
    - [30, 0]

hot_water_tank_top_bath_compensation:
  source: sensor.hot_water_tank_top_temperature_sensor_temperature
  unique_id: hot_water_tank_top_bath_compensation
  data_points:
    # First Value: Temperature
    # Second value: Seconds away from target temp (46.8)
    - [17.5, 5040.43]
    - [18.5, 4833.169]
    - [19.8, 4653.508]
    - [21, 4473.877]
    - [22.1, 4354.117]
    - [23.1, 4234.338]
    - [24.1, 4114.588]
    - [25.5, 3934.933]
    - [26.5, 3815.163]
    - [27.8, 3635.022]
    - [29.1, 1585.055]
    - [30.3, 3455.375]
    - [31.5, 3096.072]
    - [33.7, 2916.428]
    - [34.8, 2557.115]
    - [35.8, 2377.477]
    - [36.8, 2197.818]
    - [38, 1958.276]
    - [39.1, 1718.255]
    - [40.1, 1478.721]
    - [41.2, 1239.19]
    - [42.3, 999.657]
    - [43.4, 700.213]
    - [44.5, 460.684]
    - [45.6, 221.138]
    - [46.8, 0]

hot_water_tank_top_shower_compensation:
  source: sensor.hot_water_tank_top_temperature_sensor_temperature
  unique_id: hot_water_tank_top_shower_compensation
  data_points:
    # First Value: Temperature
    # Second value: Seconds away from target temp (38)
    - [17.5, 3082.154]
    - [18.5, 2874.893]
    - [19.8, 2695.232]
    - [21, 2515.601]
    - [22.1, 2395.841]
    - [23.1, 2276.062]
    - [24.1, 2156.312]
    - [25.5, 1976.657]
    - [26.5, 1856.887]
    - [27.8, 1676.746]
    - [29.1, 1585.055]
    - [30.3, 1497.099]
    - [31.5, 1137.796]
    - [33.7, 958.152]
    - [34.8, 598.839]
    - [35.8, 419.201]
    - [36.8, 239.542]
    - [38, 0]


From there I can create some template sensors in the UI:

  • Shower Ready Time (Bottom)
    {{ 
    (now().timestamp() + states('sensor.hot_water_tank_bottom_shower_compensation')| float) |as_datetime
    }}
    
  • Shower Ready Time (Top)
    {{ 
    (now().timestamp() + states('sensor.hot_water_tank_top_shower_compensation')| float) |as_datetime
    }}
    
  • Shower Ready Time
    {% if states('sensor.shower_ready_time_bottom_temp_sensor') > states('sensor.shower_ready_time_top_temp_sensor') %} 
    {{ states('sensor.shower_ready_time_bottom_temp_sensor') }}
    {% else %} 
    {{states('sensor.shower_ready_time_top_temp_sensor') }}
    {% endif %}
    

With all that data combined I can now very accurately estimate when I can have a shower or a bath:

1 Like