Graph of temperatures inside and outside

A fancy Home Assistant automation that checks the weather and figures out when to turn on your heater

So in the continuing saga with my mom’s home-automated furnace, it got extra cold recently and I noticed it wasn’t getting up to temperature in time for her to wake up. I figured I could come up with a formula to compute the time needed to come to temperature and turn on the furnace at a dynamic time in the morning so it’d be just right.

Graph of temperatures inside and outside
Temperatures at my mom’s house over a few days

The Home Assistant GUI only shows the past 24 hours, but the Recorder component saves data for as long as you want. I have mine cutting it off after a week. you can query the data to get nice csv files for processing with sqlite commands like this:

sqlite3 -csv home-assistant_v2.db  'select last_updated, state from states where entity_id="sensor.multisensor_temperature";' >inside.csv
sqlite3 -csv home-assistant_v2.db  'select last_updated, state from states where entity_id="sensor.dark_sky_temperature";' >outside.csv
sqlite3 -csv home-assistant_v2.db  'select last_updated, state from states where entity_id="switch.living_room_heat";' >heater.csv

The simplest solution here is to just do a first-order approximation and say that heatup duration is probably proportional to the temperature delta between inside and outside at the moment you need to make a decision. This is rough because you don’t know what the outside temperature will due during the heat-up period, but it should be good enough for most purposes.

The Python package, pandas makes it really easy to process these data. The code below processed the raw data and computed the least-squares fit of the relationship between the temperature delta and the heatup duration:

import csv
import datetime

import pandas

import numpy as np
import matplotlib.pyplot as plt

def read_data():
    inside = pandas.read_csv('inside.csv', parse_dates=[0],names=['Time','Temperature'],index_col=0)
    inside = inside[inside>-1000] # filter crap readings
    outside = pandas.read_csv('outside.csv', parse_dates=[0],names=['Time','Temperature'],index_col=0)
    heater = pandas.read_csv('heater.csv', parse_dates=[0],names=['Time','Status'],index_col=0)
    return inside, outside, heater

def find_heatup_durations(heater, status='off'):
    """Figure out how long the heater was on for each morning."""
    last_time = next(heater.iterrows())[0]
    heatup_durations = []
    for time, df in heater.iterrows():
        diff = time - last_time
        if df['Status']==status and diff > datetime.timedelta(hours=1):
            heatup_durations.append((last_time, diff)) # remember when it turned on too
        last_time = time
    return heatup_durations

def find_temperatures_at_times(temperatures, times):
    """Find what the temperature was during the morning heatup."""
    blocks = []
    for start_time, duration in times:
        blocks.append((start_time, temperatures[start_time: start_time + duration]))
    return blocks

def analyze_heatups(durations, outside_temps, inside_temps):
    """Look at the heatup dynamics and try to build a model."""
    initial_outside = np.array([o[1].values[0] for o in outside_temps])
    initial_inside = np.array([o[1].values[0] for o in inside_temps])
    durations_in_minutes = np.array([d[1].total_seconds() for d in durations])/60.0
    delta = (initial_inside-initial_outside)[:,0]
    slope, intercept = np.polyfit(delta, durations_in_minutes,1)

    model_temp = np.linspace(min(delta), max(delta), 20)
    model_duration = slope * model_temp + intercept

    ax = plt.gca()
    #plt.plot(initial_outside, durations_in_seconds,'o')
    plt.plot(delta, durations_in_minutes,'o', label='Data')
    plt.plot(model_temp, model_duration,'-', label='Model')
    plt.text(0.2,0.1,'D = {:.1f} T + {:.1f}'.format(slope, intercept), transform = ax.transAxes)
    plt.xlabel('Initial delta Temperature (F)')
    plt.ylabel('Duration of heatup (min)')
    plt.title('Duration of heatups')

if __name__ == '__main__':
    inside,outside,heater = read_data()
    durations = find_heatup_durations(heater)
    outside_temps = find_temperatures_at_times(outside, durations)
    inside_temps = find_temperatures_at_times(inside, durations)
    analyze_heatups(durations, outside_temps, inside_temps)

The results are in! It’s fairly noisy, but should get the job done.

Heatup duration plotted
Heatup duration vs. temperature delta

So now we have to get Home Assistant to turn on the heat that many minutes before the target “wake up time”. Templates triggers to the rescue. The template trigger for a 7:45am wakeup looks like this:

    platform: template
    value_template: >
        {% if states("sensor.multisensor_temperature") |float < -50 or states("sensor.multisensor_temperature") |float > 120 %}
        {% elif (7*60+45)- (now().hour * 60 + now().minute) < (states('sensor.multisensor_temperature') |float  - states('sensor.dark_sky_temperature') |float )  * 7.5 - 196.0  %}
        {% else %}
        {% endif %}

Whenever the indoor or outdoor temperature sensors change, this checks to see if the number of minutes between now and 7:45am is less than the required duration for full heatup and triggers if so. You’ll want to turn off the automation as part of its own action and turn it back on when the system shuts down for the night (in another automation).

If you want to try to get extra fancy you can do some thermodynamics calculations and use Newton’s law of cooling to try to figure out a more robust model. I fiddled with it a bit but didn’t get anything super interesting. Here is a plot of some intermediate cooling analysis. The coefficients on the top left are supposed to have a constant slope (they’re the relation between temperature gradient and heat flux dQ/dt. Most of them are approximately constant but it’s quite noisy. The linear model above suffices.

Noisy graphs
Analysis of some cooling periods with numerical differentiation.

One thought on “A fancy Home Assistant automation that checks the weather and figures out when to turn on your heater”

  1. This is amazing !

    Maybe this could be an addition to the generic_thermostat component, so that it could be able to use an external temperature sensor and improve its algorithm over time.

    I have a netatmo thermostat (which does check the weather) but the more I use HASS, the more I think I should have just use a remote switch and built it myself

Leave a Reply

Your email address will not be published. Required fields are marked *