Dundee Smarthome

Hacking Together A Heating System

by on Oct.05, 2010, under House Electronics, House Software, House Updates

For the last while I've been busy coding our heating system, it has now been in production for a few days and it hasn't failed - yet!  I figured that since it's running ok, I would write up the software side of things.  Scott's written up the hardware side of things and how he created the graphs with PHP. There's a lot of content in this post so I've added in some navigation links.


Sections:

 


Equipment

Here's what we used to build the heating system:

 

↑ Back to Navigation

 

Physical Build

Ok, so it looks like I've (Scott...) been drafted in to describe the electrical setup of the system. I'll go through this in two parts; First the Low Voltage (Arduino / Dallas) side of the system; Then the high voltage (Boiler / Zones) side of the system.

**Gratuitous Electrical Warning Follows **

It shouldn't need to be said that some of the below work is dangerous, and shouldn't be attempted if you don't know what you are doing.

That said, thats where the fun is - I'm just not being held liable if you kill yourself through your own fault!

-----------------------------

 

↑ Back to Navigation

Low Voltage System

The low voltage system is pretty simple. Each room has a thermostat box (a room thermostat, with the guts ripped out) with a run of Cat6 cable to it. This was to ensure signal quality, but it also gives a few spare pairs which comes in useful should you ever want to add - or break any pairs....

Each box contains a Dallas One-Wire DS18B20 temperature sensor (which we are running in non-parasitic power mode. It just didn't make sense to waste time waiting for power, when we already had the spare pairs to deliver it), and a 3mm green LED. (The LED is superfluous really - it's only existance is because the room-stats had one, which we removed in the gutting...

The image below shows the electrical schematic for 2 zones. As far as the others go, the dallas devices are all in parallel, with the LEDs on digital pins 3+zone_id (31,32 etc).

The schematic shows a 1K6 pullup resistor on the 1-wire bus, as opposed to the 4k7 shown in most others.

We found, along with others, that when the size of the 1-wire bus increases, the value of the pullup resistor must drop (I would say to overcome the added volt drop over the additional cabling, but I'm not completely sure..)

The LED's share 1 330R resistor between them, connected to the cathode of the system. Ideally, we'd have one per LED to avoid complete failure of the LED system with one component, but it doesn't seem to be causing us any issues so far...

↑ Back to Navigation

High Voltage System

Relay Box

This is the 'fun bit'!

The zone valves use 240V to open, with a spring return - in esscence, when they are powered off, they are closed - power them on and they open - simple!

We've setup each zone valve to a common Neutral/Ground and switched the Live feed using the Common / Normally Open terminals of each channel relay on the RS232 Interface.

There are 8 channels, but only actually 7 zones - so we've used the last output (Channel 8 ) to switch the room-stat connection to the boiler. This allows us to shut the boiler down, when heat is not required. These connections are normally 240V, but may vary depending on the boiler...

The RS232 interface simply connects back to the Serial1 pin of the aduino and ground.

I made a few modifications to the RS232 interface to allow it to accept direct ttl connections from the arduino - I didnt see much point in double inverting the data using Max232 chips.

As it happens - the interface contains a 232 chip internally to drop down for the microcontroller which operates the relays - so all that required to be done was to remove the chip, and add some jumper wires to connect directly to the microcontroller pins.


↑ Back to Navigation

System Architecture

Heres how its all connected together:

↑ Back to Navigation

The Software

The software for the Arduino is pretty simple, if a bit ugly and in need of some refinement.  In a nutshell this is how it runs in pseudo code:

If client is connected Then
  If client is updating Then
    set the new targets
  End If
  get the newest temperature readings
  send back html or xml depending on the request
Else If no client is connected Then
  get the newest temperature readings
  check against the targets
  If the room is too hot Then
    turn off the zone valve
  Else If the room is too cold Then
    turn on the rooms zone valve
  End If
  If any zone valve is open Then
    turn on the boiler
  Else If no zone valves are open Then
   turn off the boiler
  End If
End If

This seems fairly straight forward, and it kind of is if you're using a programming language that you know well. I basically took a very quick crash course on C++ to do this. So with that in mind, here's a link to the source code for the Arduino program:  Heating Controller code
Basically there is a struct which represents a room, it contains an id value, a sensor id value, target temperature and current temperature as well as the zone valve state.  When the Arduino sketch starts, 8 of these structs are created at in the setup function with their target temperature set to 5 degrees C.  These are added to another struct which acts as a list.  On the loop function we check to see if there is a client and if so deal with that client, otherwise keep monitoring and checking the temperatures against the targets and act as necessary by opening/closing the motorized zone valves and turning the boiler on or off accordingly.

To set temperature targets, you just call the arduino from the browser with a list of sensor id's and target values like this: /set.html?id=1&val=34&id=.. The arduino code will receive this data and will update the values in the appropriate stucts.  To get a list of struct data, you call /get.html which prints out an XHTML page containing a table of data.  We've also set it up to return an xml file containing the struct data for the graphing tool, more on this later, by calling /xml.xml

Since that was working away quite the thing, the next step was to build a swanky interface.
↑ Back to Navigation

 

The Web Interface

Having recently worked on some stuff with JavaScript, I've fallen for the charms of JQuery. It's quite simply one of the most useful web development frameworks ever!  What we've done here is create two PHP scripts that use curl to call the get.html page and call the set.html page.  The code for these pages are straight forward:

data.php:

<?php
//Grab the html page with the table
$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL, "http://172.16.11.220/get.html");
curl_setopt ($ch, CURLOPT_HEADER, 0);
curl_exec ($ch);
curl_close ($ch);
?>

update.php:

<?php
//call the update
$id = $_GET['id'];
$val = $_GET['val'];
$qry_str = "?id={$id}&val={$val}";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://172.16.11.220/set.html' . $qry_str);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, '60');
$content = trim(curl_exec($ch));
curl_close($ch);
?>

These pages are then called from the web interface with the wonders of the JQuery .load() function.  The JQuery is also pretty straight forward, and again, I've crashed coursed it.  So with that in mind (again), here's the JQuery script:

      function loadData(){
        $(".sensordata").load("ajaxpages/data.php .datatable", function(response, status, xhr) {
           if (status == "error") {
              $(this).html = ("<h1>Sorry, there was an error<h1>" + status) ;
           }
           else
           {
             //Zebra stripe the table
             $(".datatable tbody tr:odd").addClass("alternaterow");
             $(".datatable").tablesorter();
             $(".datatable").bind("sortStart",function() {
               $(".datatable tbody tr:odd").removeClass("alternaterow");
             }).bind("sortEnd",function() {
               $(".datatable tbody tr:odd").addClass("alternaterow");
             });
           }
        });
      }

      function loadBoilerData(){
        $("#sysinfo").load("ajaxpages/data.php #boilerInformation", function(response, status, xhr) {
           if (status == "error") {
              $(this).html = ("<h1>Sorry, there was an error<h1>" + status) ;
           }
        });
      }

      function sendData()
      {
        $.get("ajaxpages/update.php", {id : $("#id").val(), val: $("#val").val()},
        function(){
          loadData();
          loadBoilerData();
        });
      }

      $(document).ready(function()
      {
          loadData();
          loadBoilerData();
          $("#update").submit(function(){return false;});
          $("#formbutton").click(function(){sendData();});
          setInterval(function(){loadData();loadBoilerData();},50000);

      });

Here's some pics of the web interface, because everybody loves pictures:


↑ Back to Navigation

Graphing the temperatures

Scott then went and found some graphing software that works nice with PHP called JPGraph to allow us to view the temperature history over certain periods of time.

At the moment, the graphs use somewhat arbitray time scales, but the system is configured to read every 2 mins, and I'm displaying 30 reads per graph - so the system is filling in the blanks with averages!

The graphs are generated by reading the database on the fly (a cron collects the XML data and parses it every 2 minutes). Even with anti-aliasing enabled, the graphs render quickly!

Collect_xml.php

<?php

/**
*Connect MySQL Data
*/
$dbhost = 'db17.dsh.local';
$dbuser = 'house_temps';
$dbpass = 'this_isnt_the_password!';

$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die                      ('Error connecting to mysql');

$dbname = 'house_temps';
mysql_select_db($dbname);

/**
* Initialize the cURL session
*/

$ch = curl_init();

/**
* Set the URL of the page or file to download.
*/

curl_setopt($ch, CURLOPT_URL,'http://172.16.11.220/xml.xml');

/**
* Create a new file
*/

$fp = fopen('xml.xml', 'w');

/**
* Ask cURL to write the contents to a file
*/

curl_setopt($ch, CURLOPT_FILE, $fp);

/**
* Execute the cURL session
*/

curl_exec ($ch);

/**
* Close cURL session and file
*/

curl_close ($ch);
fclose($fp);

/**
*Pass Data to XML Parser
*/
$arduinoxml =  simplexml_load_file("xml.xml");

/**
*Remove XML
*/
unlink("xml.xml");

/**
*Build Timestamp
*/
$timestamp=time();

/**
*Loop the XML
*/
foreach ($arduinoxml->sensor as $sensor) {

  /**
  *Insert Data to DB
  */
  $query= "INSERT into data(zone_id,date_time,data) VALUES($sensor->id,'$timestamp',$sensor->current)";
  mysql_query($query);
} 

/**
*Cleanup
*/
mysql_close($conn);
?>

60_min_graph.php - The reverse data routines have to be there, we need historical data - the graph plots L>R but the data sort returns R>L!

<?php

require_once('../includes/jpgraph.php');

require_once('../includes/jpgraph_line.php');

/**
*Callback Function
*/
function year_callback($aLabel) {
    return date('H:i',$aLabel);
}

/**
*Connect MySQL Data
*/

$dbhost = 'db17.dsh.local';
$dbuser = 'house_temps';
$dbpass = 'its_really_really_not!';

$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die                      ('Error connecting to mysql');
$dbname = 'house_temps';
mysql_select_db($dbname);

/**
*Get Sensor Data
*/

//Get Zone 1
$query="SELECT date_time,data from data WHERE zone_id=1 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo[]=$row['data'];
$txlabs[]=date('H:i',$row['date_time']);
}

//Reverse Data
      $i=count($tempo);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor1[$x]=$tempo[$j];
      $x++;
      }
      $i=count($txlabs);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $xlabs[$x]=$txlabs[$j];
      $x++;
      }

//Get Zone 2
$query="SELECT data from data WHERE zone_id=2 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo2[]=$row['data'];
}

//Reverse Data
      $i=count($tempo2);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor2[$x]=$tempo2[$j];
      $x++;
      }

//Get Zone 3
$query="SELECT data from data WHERE zone_id=3 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo3[]=$row['data'];
}

//Reverse Data
      $i=count($tempo3);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor3[$x]=$tempo3[$j];
      $x++;
      }

//Get Zone 4
$query="SELECT data from data WHERE zone_id=4 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo4[]=$row['data'];
}

//Reverse Data
      $i=count($tempo4);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor4[$x]=$tempo4[$j];
      $x++;
      }

//Get Zone 5
$query="SELECT data from data WHERE zone_id=5 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo5[]=$row['data'];
}

//Reverse Data
      $i=count($tempo5);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor5[$x]=$tempo5[$j];
      $x++;
      }

//Get Zone 6
$query="SELECT data from data WHERE zone_id=6 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo6[]=$row['data'];
}

//Reverse Data
      $i=count($tempo6);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor6[$x]=$tempo6[$j];
      $x++;
      }

//Get Zone 7
$query="SELECT data from data WHERE zone_id=7 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo7[]=$row['data'];
}

//Reverse Data
      $i=count($tempo7);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor7[$x]=$tempo7[$j];
      $x++;
      }
//Get Zone 8
$query="SELECT data from data WHERE zone_id=8 ORDER BY id DESC LIMIT 30";
$result=mysql_query($query);
while($row = mysql_fetch_array($result)){
$tempo8[]=$row['data'];
}

//Reverse Data
      $i=count($tempo8);
      $x=0;
      for( $j=$i-1; $j>-1; $j--){
      $sensor8[$x]=$tempo8[$j];
      $x++;
      }
mysql_close($conn);

// Width and height of the graph
$width = 760; $height = 530;

// Create a graph instance
$graph = new Graph($width,$height);

// Specify what scale we want to use,
// int = integer scale for the X-axis
// int = integer scale for the Y-axis
$graph->SetScale('textint');

//Position of legend
$graph->legend->Pos(0.01,0.92);
$graph->legend->SetLayout(LEGEND_HOR); 

// Setup a title for the graph
$graph->title->Set('Room Temperatures - Last 60 Minutes');

// Setup titles and X-axis labels
$graph->xaxis->title->Set('');
$graph->xaxis->SetTickLabels($xlabs);
$graph->xaxis->SetLabelAngle(90);

// Setup Y-axis title
graph->yaxis->title->Set('Temperature oC');

// AA On
$graph->img->SetAntiAliasing();

// Create the linear plot - sensor 1
$lineplot=new LinePlot($sensor1);
$lineplot->SetLegend("Scott Room");
$lineplot->SetColor("blue");

// Add the plot to the graph
$graph->Add($lineplot);

// Create the linear plot - sensor 2
$lineplot2=new LinePlot($sensor2);
$lineplot2->SetLegend("Huntly Room");
$lineplot2->SetColor("limegreen");

// Add the plot to the graph
$graph->Add($lineplot2);

// Create the linear plot - sensor 3
$lineplot3=new LinePlot($sensor3);
$lineplot3->SetLegend("Office");
$lineplot3->SetColor("red");

// Add the plot to the graph
$graph->Add($lineplot3);

// Create the linear plot - sensor 4
$lineplot4=new LinePlot($sensor4);
$lineplot4->SetLegend("Hallway");
$lineplot4->SetColor("brown");

// Add the plot to the graph
$graph->Add($lineplot4);

// Create the linear plot - sensor 5
$lineplot5=new LinePlot($sensor5);
$lineplot5->SetLegend("Bathroom");
$lineplot5->SetColor("orange");

// Add the plot to the graph
$graph->Add($lineplot5);

// Create the linear plot - sensor 6
$lineplot6=new LinePlot($sensor6);
$lineplot6->SetLegend("Kitchen");
$lineplot6->SetColor("purple");

// Add the plot to the graph
$graph->Add($lineplot6);

// Create the linear plot sensor 7
$lineplot7=new LinePlot($sensor7);
$lineplot7->SetColor("deeppink");
$lineplot7->SetLegend("Living Room");

// Add the plot to the graph
$graph->Add($lineplot7);

// Create the linear plot - sensor 8
$lineplot8=new LinePlot($sensor8);
$lineplot8->SetColor("black");
$lineplot8->SetLegend("Outside");

// Add the plot to the graph
$graph->Add($lineplot8);

// Display the graph
$graph->Stroke();

?>

↑ Back to Navigation

Getting the Software

Until I get the server setup with gitweb e.t.c, the software can be found on my github.com page Enjoy!

Feel free to leave a comment if you'd like any more info on the system, or have any suggestions for improvement!

:,

1 Comment for this entry

1 Trackback or Pingback for this entry

Leave a Reply

 

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Blogroll

A few highly recommended websites...

Archives

All entries, chronologically...