Home Automation: An Intelligent Thermostat

Almost six months ago I posted an article on reverse engineering the communication protocol of a Worcester-Bosch DT10RF wireless thermostat using a soundcard as a logic analyser. The aim of the project was to be able to take control of my Worcester-Bosch Greenstar 30CDi combi-boiler and hopefully improve on the quality and efficiency of the existing DT10RF Optimising Digistat.

Now that we are well into winter, I can report on the progress and effectiveness of the project.

I’ve never been particularly satisfied with the DT10RF. The “optimising” feature only works to delay the on-time of the first time-slot of the day – only the morning start-up is optimised. The optimisation will only delay the heating coming on in the morning for up to one hour, and it does nothing to optimise the time the heating switches off. You still need to basically guess what time the heating should be on or off to obtain the desired comfort levels.

We can do better than that.

Even modern intelligent thermostats such as Nest don’t seem to be that intelligent. Sure, they can learn your schedule and notice automatically if the house is empty. But I don’t need that. I’m more than capable of programming my schedule into a device. I don’t want to set a time at which the heating should come on, I want to be able to set a time at which the house is up to temperature and have the thermostat itself work out when to start heating. And I want it to work out what time it can stop heating based on prior knowledge of how quickly the house will cool.

Having tested the proof of concept it was time to build a prototype device.

Controller Base

I used an Atmel AVR ATmega328 micro-controller as the base of the system. It controls the 433MHz transmissions, and also monitors the local temperature using an analogue LM35.

The LM35 produces a signal of 0V + 10mV/C. It is connected to a non-inverting op-amp with the gain configured to be 12.497. This increases the LM35 output to 124.973mV/C. The ATmega328 has a 10-bit ADC with a 0-5V scale. This means each step of the ADC is 4.883mV. Our resolution is thus 4.883mV / 124.973mV/C which is 0.039C per ADC step with a range of 0-40C. This should be more than sufficient for standard indoor temperatures in the UK. There is also a fixed 2.5V reference supply used to ensure accurate calibration of the ADC between each temperature reading. The ATmega328 has a built-in 1.1V reference that can be used for ADC calibration, but it is not that accurate and so an external reference is preferred.

The controller also uses a Nordic nRF24L01+ 2.4GHz RF transceiver so that it can communicate with other temperature nodes, and obtain an overview of the total house environment.

An internal view of the device is shown below. It doesn’t look that impressive since it’s built on Veroboard, but at least it works! The two red LEDs indicate when the associated RF module is transmitting, and the green LED indicates when the thermostat is “calling for heat” and the boiler is firing.

Controller Base Internal

So far I have just one temperature “node” that communicates with the “base”. This is currently sensing the living room temperature. As time allows I’ll be adding more “nodes” so that the system can measure the temperature of the entire house.

The nodes are almost exactly the same design as the base controller, except it doesn’t need to have the 433MHz module, and I’ve added an LCD display to read out the date, time, current local temperature, and a repeater of the “calling for heat” status. Instead of the green LED, the nodes show a clear “on” or “off” in text using the display. Neat!

Controller Node-1

The controller-base is connected to a PC where a Python script is used for the main decision making. This is purely for ease of coding whilst testing different heating and cooling algorithms. Once a final system is perfected, this could easily be migrated to the micro-controller and the whole thing become self-contained.

The heating schedule is read from an XML file, a section of which is shown below. There is an entry for each day of the week, and every day can have any number of heating “phases” throughout the day. At the moment I am using only two – for morning and evening.

    <tuesday>
        <phase>
            <node>1</node>
            <high_at>6.25</high_at>
            <high_setpoint>17</high_setpoint>
            <low_at>7.5</low_at>
            <low_setpoint>17</low_setpoint>
        </phase>

        <phase>
            <node>1</node>
            <high_at>17</high_at>
            <high_setpoint>19</high_setpoint>
            <low_at>22</low_at>
            <low_setpoint>18</low_setpoint>
        </phase>
    </tuesday>

Shorly after midnight, the system loads the heating schedule for the required day. It then enters the “READY” state where it monitors the temperature of a given “master” temperature node and works out when it needs to start heating. Which node is the “master” node can change as required and could be, for example, a bedroom in the morning and then the living room in the evening.

0.006111 STATE==START
0.006111 Loading the daily schedule.
0.006111 Today is Tuesday.
0.006111 Node: 1.  High Temp 17.0C at 6.25 hours.
         Low Temp 17.0C at 7.5 hours.
0.006111 STATE==READY

The first heating phase, above, says that we want the house to be at 17C at 06:15, and we want to maintain that temperature until 07:30.

Using its model of the heating coefficients of the house and the overall system, it monitors the current temperature and determines that 0.9 hours of heating is required, and that the boiler needs to fire at 05:21. This time will vary day-to-day based on how quickly the house is cooling, and how quickly the boiler can push heat into the house. Rather than have to guess what time to turn the heating on, this system has worked it out for us automatically.

5.360833 Heating required: 0.90 hours.
         Heating on at: 5.35hours.
5.360833 STATE==HEATINGSTART
5.360833 Calibration curve stopped.
5.361111 Cooling coefficients updated.
5.361111 Calibration curve started.
5.361111 STATE==HEATING

You’ll notice in the log above, just before actually entering the heating state there is an interesting line “cooling coefficients updated”. The system has been logging temperatures all night while everyone is in bed and the house is cooling.

We can now fit these data to an exponential decay function and calculate our cooling coefficients. These will change day-to-day depending on the external ambient temperature – the house obviously cools more quickly when the ambient temperature is lower. We need to calibrate the system cooling curve every night. An example is shown below.

2014 January 14 - Cooling Curve

Now we start heating.

6.253611 STATE==HEATINGCOMPLETE
6.253611 We were 0.00 hours away from target.
6.253611 Calibration curve stopped.
6.253611 Heating coefficients updated.
6.253611 STATE==STABLE

Just as expected, 54 minutes later the master node is up to temperature. We hit our target to the minute. Great!

2014 January 14 - Morning

Again, you’ll see another interesting line in the log – heating coefficients updated. If we had missed our target, then that means our estimate of how quickly the house would heat up was wrong, and so we need to recalibrate. An example is shown below.

2014 January 14 - Heating Curve

We start logging a heating curve as soon as the call-for-heat flag is raised. It takes a little while for the temperature to go up, since the boiler has to heat up the whole system first. Likewise, the radiators gradually become less efficient as the house warms up and the temperature difference between the house and radiators themselves reduces. Fitting a simple straight line function through these data seems to be sufficient to average-out the variations. I might try some more sophisticated functions and see if they work better.

7.505000 Phase 0 completed.
7.505000 Node: 1.  High Temp 19.0C at 17.0 hours.
         Low Temp 18.0C at 22.0 hours.
7.505000 STATE==READY

At 07:30 the first phase is complete and everyone is leaving the house. The system loads up the next heating phase and the cycle repeats.

This time we would like the system to be at 19C at 17:00, and we are happy for the temperature to have dropped to 18C by 22:00. No point heating the house until it’s time for bed, and then having the temperature comfortable until the early hours of the morning. We may as well stop heating earlier, accept a tolerable drop in temperature, and save some money.

15.461667 Heating required: 1.54 hours.
          Heating on at: 15.46hours.
15.461667 STATE==HEATINGSTART
15.461667 STATE==HEATING

The system estimates 1.54 hours of heating required, and starts firing at 15:28.

17.321389 STATE==HEATINGCOMPLETE
17.321389 We were 0.32 hours late.
17.321389 STATE==STABLE

We reach our set-point at 17:19. Oops, we are 19 minutes late. Oh well. I consider that to be acceptable performance, and at 17:00 we were only 0.2C below target. That’s basically perfect. Maybe the ambient temperature today was a little lower than yesterday. The heating coefficients will soon be recalibrated again automatically to take care of that.

18.347222 Topup required: 0.3C.
18.347222 STATE==TOPUP
18.597500 Topup: Maximum duration reached.
18.597500 STATE==STABLE

Once up to temperature, we need to maintain the set-point. Using simple timed burns seems to be the most effective. A fifteen minute top-up burn based on a 0.25C hysteresis below set-point works well. It prevents the temperature dropping too much, without resulting in an unacceptable level of over-shoot.

All the while maintaining the temperature, the system is calculating how soon it can stop heating based on the cooling coefficients it determined this morning.

21.266389 Cooling required: 0.74 hours.
          Cooling start at: 21.26 hours.
21.266389 STATE==COOLINGSTART
21.266389 Calibration curve started.
21.266389 STATE==COOLING

We stop topping-up the heat at 21:16 since the system has calculated that it will take 0.74 hours for the current temperature to drop to 18C.

22.000278 STATE==COOLINGCOMPLETE
22.000278 We are still 0.1C too warm.
22.000278 Phase 1 completed.
22.000278 All phases complete.
22.000278 STATE==DONE
22.000278 Waiting for next day.

We get to 22:00, our cooling set-point, and the temperature is 18.1C. We are still 0.1C above our set-point. Again, that is basically perfect. All phases are done, and the system enters standby while it waits for the next day to begin.

Here is the temperature plot for the evening phase.

2014 January 14 - Evening

The period between 17:00 and 22:00 when the system was supposed to be maintaining a fixed temperature looks to be a little random. This is due to normal human activity – people moving around, opening and closing doors etc. Even so, the system still manages to maintain the set-point to within ±0.5C which I consider to be quite impressive.

Here is the whole 24 hour period.

2014 January 14

Since we are logging data, we now also have access to details that a normal thermostat can’t tell us – such as exactly when the heating was on and how long the boiler has been burning for today.

2014 January 14 - Call For Heat

Interestingly, if we look at a different day where the system was left to get on with its job without human activity messing things up, then the temperature can be maintained to remarkable accuracy. The amplitude of the temperature variation in the plot below is just ±0.2C. Hopefully, with some further work, the stability will be improved for when random human activity is also present.

2013 November 8 - Stable

In summary, the system is capable of taking a fixed schedule and calculating the variable schedule required to compensate for changing weather conditions. We eliminate excessive heating due to guessing when to programme the boiler to fire, whilst at the same time without sacrificing comfort levels.

The next stages are to add more temperature nodes to increase the overview of the entire house system, and also add a web interface for updating the schedule.

Thanks for reading! I hope you found something interesting, and maybe even something to help you along with your own projects.