Sunday, 15 September 2013

Pi Central Heating

Pi Central Heating

I decided to replace my very annoying central heating controller with a Raspberry Pi. I thought it would be a fun project but would also give me a better and more controllable central heating system.



My central heating is very simple. It really comes down to two switches : one to turn on a pump which drives water round the system which in turn triggers the boiler to heat the water up and the other to divert that hot water round the radiators. So the control of this system (and in effect all the controller does) is to switch these two switches ‘on’ or ‘off’ at particular times of day.
This is very easy to do with a Raspberry Pi. It comes equipped with a GPIO (General Purpose Input Output) connector which gives several Input/Output control lines. Each of thesecan be programmed to either produce an output voltage or sense an input voltage.
For my system I needed four I/O lines. Two were needed to control the two switches and two more I used to sense the current state of the heating & hot water.
In order to actually control the heating you have to be a bit careful. The switches that control the system are switching mains electricity (240v) and so you can’t simply tie these into the Pi. It would melt. You have to interface to these switches somehow.
I chose to do this by buying another central heating controller. That sounds daft but I figured it gave me two advantages. Firstly I could wire my control lines into this and use it as a ‘buffer’ to protect the Pi from the mains voltages. Secondly, these controllers plug onto a standard back plate so  it meant that if I screwed it all up I could simply plug the old controller back in place while I fixed the problem. And in the meanwhile the wife could still have a hot bath.


Plus I found just the right type - a Drayton LP522 - on ebay for £2! Bargain.

So schematically it looks like this:


The controller has two switches on the front. These are override switches that allow you to manually turn the water/heating on and off. All the Pi has to do is short out the connections on the back of these switches momentarily and it appears to the controller as though someone has pressed the switch. 

So I soldered wires to the contact points on the circuit board of the controller and ran them out to the Pi.
Next I located to points on the controller board that changed voltage when the LED lights on the front were on or off. This gives an indication of the current state of the heating and hot-water. These are then connected to two input lines on the Pi GPIO.


 

In order to connect to the Pi it’s best to run through a bit of electronics to protect the Pi. So I built an interface board with two buffered outputs and two buffered inputs:

 

 


After that it was just a matter of putting it all in a box and wiring it up. I then drilled a hole through the bathroom wall so that I could mount the Pi outside the bathroom (I figured the Pi wouldn’t like being in a hot, wet, steamy environment. Plus it means I can check on it any time – even if someone is using the bathroom!).




I also added some lights and switches to the box – these allow me to override the HW & CH manually and the lights tell me the current state.
The rest is code. 
The Pi is connected to my WiFi router using a WiFi dongle. It is also set-up to run ‘headless’ (that is with no keyboard, mouse or screen plugged in). This means I can log on to the Pi from anywhere on the network – in fact anywhere in the world after I had forwarded Port 22 on my router to the Pi.
So I can program it from the laptop whilst sitting on the couch (in fact I did some of the programming when bored one afternoon sitting in a guest house in the Philippines!).


The software to control the heating is written in Python. The schedule for the on/off times is in a MySQL database which the Python controller reads and acts upon every fifteen seconds. There is also another Python process that checks the current state of the CH/HW and updates a row in the DB.
I then wrote a bunch of screens in PHP so that I can control the heating and edit the schedule. These screens are served up by an Apache Web server installed on the Pi. This means that you can edit, view and control the heating system from any PC in the house across the wireless network. In fact – after I forwarded some more ports on the router – I can now control it completely from any PC, anywhere. Even my phone. So I now have an internet-controlled heating system (I had to add some cookie controlled log-in pages when I did this just in case someone decided to take control of my heating!) 


The controller reads the schedule from a MySQL table and also looks for 'overrides' which are events triggered from the two buttons on the front of the box or virtual buttons presented on the browser. The effect of the buttons is to insert a new row in this override table each time there's a key press.

The tables look like this:

 create table schedule (
id INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
day VARCHAR(9), 

time TIME, 
hot_water VARCHAR(3), 
heating VARCHAR(3));

CREATE TABLE `override` (
  `heating` varchar(3) DEFAULT NULL,
  `hotwater` varchar(3) DEFAULT NULL,
  `status` varchar(1) DEFAULT NULL,
  `source` varchar(20) default null,
  `time` timestamp default current_timestamp
);

CREATE TABLE `current_state` (
  `heating` varchar(3) DEFAULT NULL,
  `hotwater` varchar(3) DEFAULT NULL,
  `mode` varchar(6) DEFAULT NULL
)


 CREATE TABLE `log` (
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `source` varchar(20) DEFAULT NULL,
  `message` varchar(255) DEFAULT NULL
)

 CREATE TABLE `template` (
  `name` varchar(30) DEFAULT NULL,
  `day` varchar(9) DEFAULT NULL,
  `time` time DEFAULT NULL,
  `hot_water` varchar(3) DEFAULT NULL,
  `heating` varchar(3) DEFAULT NULL
)

The 'Log' table is to allow the software to log various events for debugging/monitoring. The template table allows me to save schedules as templates (e.g. 'holiday', 'summer', winter'). The 'current_state' table only has one row and holds the current state of the HW/Heating as detected by another background process. This looks at the GPIO lines and updates the DB so that the browser can display the state without connecting to the GPIO. In this way all the browser PHP stuff only has to talk to the DB - this keeps things cleaner. The 'mode' column is for switching between 'AUTO' (running from the schedule) and 'manual' (where it just obeys the override buttons).

The controller python looks like this:


#! /usr/bin/env python
import wiringpi
import MySQLdb
import sys
from GPIOpins import GPIO_HWstate
from GPIOpins import GPIO_CHstate
from GPIOpins import GPIO_switchHW
from GPIOpins import GPIO_switchCH
from time import sleep

intervalTime=7

gpio = wiringpi.GPIO(wiringpi.GPIO.WPI_MODE_GPIO) 
gpio.pinMode(GPIO_switchHW,gpio.OUTPUT) 
gpio.pinMode(GPIO_switchCH,gpio.OUTPUT)
gpio.pinMode(GPIO_CHstate,gpio.INPUT)
gpio.pinMode(GPIO_HWstate,gpio.INPUT)

###############################################################################

def log(logmessage):
    # Open database connection
    logdb = MySQLdb.connect("localhost","root","xxxxxxx","heating" )

    # prepare a cursor object using cursor() method
    logcursor = logdb.cursor()

    # Prepare SQL query to INSERT a record into the database.
    sql = """INSERT INTO log(source,
             message)
             VALUES ('controller','"""+logmessage+"""')"""
    try:
       logcursor.execute(sql)
       logdb.commit()
    except:
       logdb.rollback()

    # disconnect from server
    logdb.close()
       

###############################################################################
   
def switch(switchpin, statepin, desiredstate):
    print "switch pin ", switchpin, " statepin ", statepin, " desired state ", desiredstate

    # first check the state of the channel (HW or CH)
    currState = gpio.digitalRead(statepin)

    # '1' indicates off, '0' indicates on!
    if (currState==1):
        currState = "OFF"
    else:
        currState = "ON"

    print "current state is ", currState

    # check if there is anything needed to do:
    if (desiredstate!=currState):
        print "switching"
        gpio.digitalWrite(switchpin,gpio.HIGH)
        sleep(0.25)
        gpio.digitalWrite(switchpin,gpio.LOW)

    return

###############################################################################

print "controller starting"
log("controller starting")

firsttime=True

# open the database
connection = MySQLdb.connect(host="localhost", user="root", passwd="xxxxxx", db="heating")

cursor = connection.cursor ()

cursor.execute("select ucase(dayname(curdate()))")
row = cursor.fetchone()
last_day = row[0]

cursor.execute("select curtime()")
row = cursor.fetchone()
last_time=row[0]
cursor.close()
connection.close()

print "start is ", last_day, " time=", last_time

sleep(intervalTime)

while True:
    # open the database
    connection = MySQLdb.connect(host="localhost", user="root", passwd="xxxxxx", db="heating")

    cursor = connection.cursor ()

    # get today's day
    cursor.execute("select ucase(dayname(curdate()))")
    row = cursor.fetchone()
    day = row[0]

    # get the current time
    cursor.execute("select curtime()")
    row = cursor.fetchone()
    time=row[0]
    #print "now it is ", day, " time=", time

    # if the day has changed then we will just run a query from the last point in time up to midnight...
    if last_day != day:
        print "day change"
        # save the name of 'today' so that we can put it back later:
        tomorrow=day

        # now pretend that the current time is 1 second to midnight 'yesterday':
        day=last_day
        time='23:59:59'
        daychange=True
    else:
        daychange=False

    # first see if the mode is 'AUTO' or 'MANUAL'
    query = "select mode from current_state"
    cursor.execute(query)
    data = cursor.fetchall()
    for row in data:
        mode = row[0]

    #print "select * from schedule where day='"+day+"' and time>'" + str(last_time) + "' and time<='" + str(time) + "' order by time"
    query = "select * from schedule where day='"+day+"' and time>'" + str(last_time) + "' and time<='" + str(time) + "' order by time"
    cursor.execute( query)
    data = cursor.fetchall()
    for row in data:
        row_id = row[0]
        day = row[1]
        time = row[2]
        hot_water = row[3]
        heating = row[4]
        print "id=", row_id, " day=", day, " time=", time, " hot_water=", hot_water, " heating=", heating
        if mode=='AUTO':
            print "executing at ", day, time
            logline="scheduled event id="+str(row_id)+" day="+day+" time="+str(time)+" hot_water="+hot_water+" heating="+heating
            log(logline)
            switch(GPIO_switchHW, GPIO_HWstate, hot_water)
            switch(GPIO_switchCH, GPIO_CHstate, heating)

            #update_query= "update current_state set heating='"+ heating +"', hotwater='"+ hot_water + "'"
            #cursor.execute(update_query)
            #connection.commit()


    # if this was a day change then reset the 'last' point in time to midnight:
    if daychange :
       last_day=tomorrow
       last_time='00:00:00'
    else:
       # record the last point in time ready for the next interval
       last_time=time
       last_day=day


    # check for overrides:
    query = "select * from override where status='P' order by time"
    cursor.execute(query)
    data = cursor.fetchall()
    for row in data:
        heating=row[0]
        hotwater=row[1]

        print "execute override"
        logline="override  hot_water="+hotwater+" heating="+heating
        log(logline)
        if heating=='ON':
            switch(GPIO_switchCH, GPIO_CHstate, "ON")
        if heating=='OFF':
            switch(GPIO_switchCH, GPIO_CHstate, "OFF")

        if hotwater=='ON':
            switch(GPIO_switchHW, GPIO_HWstate, "ON")
        if hotwater=='OFF':
            switch(GPIO_switchHW, GPIO_HWstate, "OFF")
    cursor.execute("update override set status='C'")
    connection.commit()
    cursor.close()
    connection.close()

    sleep(intervalTime)


  The GPIOpins.py merely holds the pin assignments do that they are common between the various python scripts.


GPIO_HWstate =10
GPIO_CHstate =9
GPIO_btnCH = 8
GPIO_btnHW = 11
GPIO_switchHW = 3
GPIO_switchCH = 2
GPIO_ledHWred = 27
GPIO_ledCHred = 22
GPIO_ledHWgreen = 4
GPIO_ledCHgreen = 17







 
Below is an example of the web page. This is the main page (and is in fact a later version that includes the changes for the thermostat stuff detailed in the next post). There are also login, logout and various other bits of php that are fairly typical examples of simple security using php - you can find them all over the web (I did!)

<html>
 <head>
 <title>Pi Central Heating</title>
 <link rel="stylesheet" type="text/css" href="heatingstyle.css">
 <meta http-equiv="refresh" content="9" >
 </head>
 <body>
 <?php
    session_start();
    if (!(isset($_SESSION['user']) && $_SESSION['user'] != '')) {
       header ("Location: login.html");
    }

    $height = $_GET['height'];
    $width  = $_GET['width'];

    //echo("<p>" . $height . $width . "</p");
    // connect to the DB
    include('connect.php');
    if (! @mysql_select_db("heating") ) {
      echo( "<P>Unable to locate the heating " .       
            "database at this time.</P>" ); 
      exit();
    }

    $result = mysql_query("select hotwater, heating, mode from current_state");
    if (!$result)
    {
      echo("<P>Error performing query: " .
            mysql_error() . "</P>"); 
      exit();
    }
    $row = mysql_fetch_array($result);
    $currHeating = $row["heating"];
    $currHotwater = $row["hotwater"];
    $mode = $row["mode"];
    if ($width>1000)
    {
       if ($currHotwater=="ON")
       {
         ?>
         <TD><a href="override.php?service=HW&onoff=OFF"><img src="images/HWon.jpg" width=150 height=70/></a></TD>
         <?php
       }
       else
       {
         ?>
         <TD><a href="override.php?service=HW&onoff=ON"><img src="images/HWoff.jpg" width=150 height=70/></a></TD>
         <?php
       }
       if ($currHeating=="ON")
       {
         ?>
         <TD><a href="override.php?service=CH&onoff=OFF"><img src="images/CHon.jpg" width=150 height=70/></a></TD>
         <?php
       }
       else
       {
         ?>
         <TD><a href="override.php?service=CH&onoff=ON"><img src="images/CHoff.jpg" width=150 height=70/></a></TD>
         <?php
       }
    }
    else
    {
       if ($currHotwater=="ON")
       {
         ?>
         <TD><a href="override.php?service=HW&onoff=OFF"><img src="images/HWon.jpg" width=40% height=25%/></a></TD>
         <?php
       }
       else
       {
         ?>
         <TD><a href="override.php?service=HW&onoff=ON"><img src="images/HWoff.jpg" width=40% height=25%/></a></TD>
         <?php
       }
       if ($currHeating=="ON")
       {
         ?>
         <TD><a href="override.php?service=CH&onoff=OFF"><img src="images/CHon.jpg" width=40% height=25%/></a></TD>
         <?php
       }
       else
       {
         ?>
         <TD><a href="override.php?service=CH&onoff=ON"><img src="images/CHoff.jpg" width=40% height=25%/></a></TD>
         <?php
       }
    }
    echo('<p></p>');
    $result = mysql_query("SELECT curtime() curtime, dayname(curdate()) daynm");
    if (!$result)
    {
      echo("<P>Error retrieving time and date" . mysql_error() . "</P>");
    }
    $row = mysql_fetch_array($result);
    $currtime = $row["curtime"];
    $dayname = $row["daynm"];
   
    if ($mode=='AUTO')
    {
      echo('<a href="override.php?service=MODE&mode=MANUAL"><img src="images/slideAuto.gif" width=100 height=50/></a>');
    }
    else
    {
      echo('<a href="override.php?service=MODE&mode=AUTO"><img src="images/slideManual.gif" width=100 height=50/></a>');
    }
    echo("<p>" . $dayname . "  " . $currtime . "</p>");
?>
<br><br>
<a href="schedule.php">Edit Schedule</a>
<br>
<a href="showlog.php">Show Log</a>
<br>
<a href="logout.php">Logout</a>
</body>
</html>

 

26 comments:

  1. Well thought out! Nice job! What does your wife think?

    ReplyDelete
    Replies
    1. She seems quietly impressed. Especially when I reprogrammed the on/off times at her request whilst lying in bed on Sunday morning.

      Delete
  2. My needs are more complicated. I have a 2 heat stages, 2 cooling stages, a variable speed blower, 3 zones, and 3 thermostats. Currently, the system only takes the thermostats as input, controls the dampers for the zones. The thermostats only input is one or two stage heat or cooling. I would rather get 3 temperature readings indoors, and one outdoors, program the desired temperatures with a smartphone/tablet, and have the Pi decide how much heating or cooling is needed, and control the fan speed and dampers. It would also have to be concerned with how long the heat and cooling were on or off, as the heat can only be cycled so many times before the heat exchanger cracks from fatigue failure, and the compressor needs to allow the pressure to go down before it is restarted.

    This article motivated me to take a closer look at the electronics I would need.

    ReplyDelete
    Replies
    1. Blimey. That sounds a complex system. But the complexity is all in the software, the hardware should be straightforward - it's either an input or an output!
      Except of course for the temperature - and that is my next aim. I'd like to add temp sensing - one for inside, one outside and one on the hot water tank. For that I've been looking at the XBee radio modules. Wireless temp sensing would be the way to go, I think.

      Delete
  3. Try an aluminum heat sink or Aluminum Chill Plate under the circuit board . Just a thought .
    http://www.chtechnology.com/heatsinks.html

    ReplyDelete
    Replies
    1. It seems to be cool enough in the box at the moment. I made a few slits for the air to flow and that seems to be sufficient.

      Thanks for the tip though!

      Delete
  4. any chance of sharing your pyton source? I've made something without an auto mode (on a schedule) on php and using mysql and cron to action it

    it works, but failed all attempts to get a schedule...
    thanks

    ReplyDelete
  5. Nice job, I am tempted to have a go following your excelent advice and well written article. Shame about the php though. I would much prefer using ruby and probably sinatra for the web app. That way you can use a simple orm like datamapper or active record to talk to the data. Or you could even use mongodb - makes a great local datastore and ruby has a simple adapter. I haven't tried mongo on the PI yet though.

    Good luck with the temperature sensing, let us know how you get on.

    ReplyDelete
  6. thanks
    was that your password for the DB there? :-)
    what did you use to select dates/times for the schedule in php?

    ReplyDelete
  7. Errr... it was! Not any more...!

    The date/time stuff is all there in the controller. The schedule table has day names ('Monday', 'Tuesday' etc) and times. The code selects a row for the correct day that has a time between now and the last time it ran (15 seconds ago). So each iteration looks for schedule items in a 15 second window.
    There's a little bit of extra logic that has to cope with the day roll over in htere too.

    ReplyDelete
  8. Did you install a real time clock on the Pi to ensure correct date and time setting should the Pi be rebooted ?

    ReplyDelete
  9. No... doesn't seem to be necessary. I hadn't really thought about it but I assume the Pi is picking up the time from my network. And I guess the internal clock runs even though the Pi is off....
    I like problems that don't crop up if I don't think about them!

    ReplyDelete
  10. Great blog! Your idea is really good very well written and useful to all blogger especially for me.

    Heating & Cooling Richmond Hill

    ReplyDelete
  11. Exactly what I'm wanting to do! What opto-couplers and transistors did you use?

    ReplyDelete
  12. The opto isolators are 4N25 dil packages. The transistors I used were bc108's or something similar. Very cheap, nothing special.

    ReplyDelete
  13. Am i able to get a copy of the source?

    ReplyDelete
    Replies
    1. And the php web interface? Thanks.

      Delete
  14. The controller is listed above. The web pages are quite simple, although I often fiddle with them a bit to improve the look. I'll list the main page source above as well but there are other pages that allow me to edit the schedule or set the thermostats.

    This version includes the thermostat stuff that I describe in the '...revisited' post. In this the heating can be 'on' logically but in fact be switched 'off' because the target temperature has been reached. It makes the screen a little more complex because I show the on/off state on the CH/HW buttons and the *actual* state by two little on/off graphics below them.

    There's also a 'mode' button that controls whether the system follows the schedule or just runs continuously until you click a button.

    Everything is stored on the database. The screen is really just displaying and modifying the data on there.

    Hope that helps!

    ReplyDelete
  15. thanks for that, i was wondering if you could share your files for download i have all the hardware setup but can't seem to get the software side working. thanks lots for this :-)

    ReplyDelete
  16. Hi there - this is a great article.

    I am working on a similar venture using a LP522 and plan to use an Arduino as the controller.

    Can you explain your wiring connections to the LP522 board. I can see the switches use white/black wires for Hot Water Advance, red/yellow for Heating Advance.

    From the image the brown seems to connect to the Select button?

    Can’t see where the green wire connects to.


    What does the brown, blue, green and orange connections do?

    Your help would be appreciated.

    ReplyDelete
  17. I somewhat over-engineered the connections!
    I put a wire either side of each switch - so four wires in all - which in retrospect wasn't required because two of them simply connect to zero volts.
    I then put two more wires in to read the 'state' of the heating and hot water. To do this I tracked the locations on the board where the LEDs are connected. Through some experimentation I found points that were high/low when the LEDs were on/off. This allowed me to get the Pi to read what the current state is which was useful because I was able to cater for 'human intervention' at the central heating controller. So you can still press the buttons on the controller panel and have them turn the CH/HW on or off. That was all done in the python code.
    So in all I had 8 wires : 2 for each button
    1 for each LED
    1 on zero volts and 1 on 12v (I think it was 12... might have been 5. Can't recall)
    As you can see I now have probably 3 wires all connected to zero volts!
    So you could get away with 1 for each button, 1 for each LED, 1 on 0v and 1 on 5v - 6 in all.

    ReplyDelete
  18. Thank you so much for posting this. I never would have thought to do this with my heating controller. I did the same as you, bought a cheap replacement off ebay and made the connections. I control mine simply through webiopi at the moment but I intend to add temperature sensors and some logic to it in the near future.

    I also interfaced the android tasker app with the webiopi API so I can issue google now voice commands to my boiler. Even my wife was impressed with that.

    ReplyDelete
  19. You may have one of the many types of heating systems that you will be needing in your home.

    ReplyDelete
  20. Your article is very helpful for me. I will follow your instruction. Thank you.
    central heating

    ReplyDelete