Controlling Domestic Hot Water Supply with Raspberry Pi

Contents

  1. Background
  2. Hardware
  3. Pi Wiring
  4. Heating System Wiring
  5. Software
  6. Remote Access
  7. Gas Consumption Impact

Background

I want to implement a Raspberry Pi controlled heating and hot water system. On the hot water side, the objectives are

The first step in this plan was to instrument our hot water system, see here for the details of that exercise.

Hardware

Box for Hot Water controlling Pi and associated relays

As objective of this project is to switch on the hot water system, it follows that 240 volts would be involved somewhere down the line. So the relay(s) switching the boiler on would need to be located where it would not be possible for anyone to encounter them unexpectedly.

So to house the Raspberry Pi and associated relays I bought a suitable box from our local electrical component shop, to contain the following items.

  1. The Raspberry Pi
  2. An opto-isolating relay board
  3. A small piece of variboard

The modifications made to the box are shown to the right, and are

  1. Addition of three holes to mount three RJ45 sockets on the outside of the box
  2. Addition of three slots, both in the box, and the overlap in the box lid for
Relay Board Foot

I made extensive use of Aruldite to locate the box contents. The "feet" for the relay board were made out of the "crosses" designed for spacing tiles when tiling, with one leg of the X/cross cut off.

With the protruding leg pared back just enough to pursuade the board over each leg, the board was not about to fall off, but it might be possible to remove the board in the future should the need arise.

The RJ45 sockets were a tolerance fit, but as the box gauge was too great for the socket clips to locate them securely, I ran arudite on the inside of each sockets to ensured that they would not fall out.

Pi Wiring

Relays

The wiring diagram provided by the manufacturer for the Opto Isolating Relay board is shown below.

Opto Isolation board circuit diagram

Not Relay Board shown above, is that the relay (K1) requires 5 volts. As the Raspberry Pi GPIO pins provide 3.3 volts, I needed to connect terminal 3/JD-VCC above (red in the picture to the right) to one of the Raspberry Pi +5v pins.

The opto-isolating part of the circuit above is powered by a Raspberry Pi +3.3v pin (terminal 2 above, and orange to the right).

For hot water and central heating control purposes, I connected IN1 and IN2 to GPIO 23 and GPIO 24 on the Raspberry Pi.

1-wire temperature probes

As the wiring of 1-wire (DS18D20) temperature probes is described in numerous places on the web, I will not detail here. For now I will just point out that the variboard in the top right of the busy picture below provides a place for the 4.7K resistor, and a means of connecting the two RJ45 sockets used for 1-wire probes to the GPIO 4 pin (and GND and 3.3v pins) on the Raspberry Pi.

Other

The other connections made in the busy picture below are

Box with everything in it Box with lid on

Heating System Wiring

The two options I considered for implementing Pi control over the heating and hot water are
  1. Using three relays:
    • One to de-power the existing house timeswitch,
    • One to control the central heating, and
    • One to control the hot water
  2. Using two relays:
    • One to control the central heating, and
    • One to control the hot water

The first option has the benefit that the existing timeswitch can have a sensible time regime still set up. So in the event that the Raspberry Pi is unavailable for some reason, the traditional central heating control is defaulted back into service (hot water still gets hot, and heating still comes on).

I implemented the second option though, as it allows one to still use the existing timeswitch buttons to boost the hot water or heating without first having to find a computer/tablet/smart phone.

I thus implemented the Raspberry Pi relays in parallel to the existing timeswitch, to effectively let the Pi "override" the existing timeswitch,...,. and changed the existing timeswitch time pattern regimes to "always off".

Software

To configure a GPIO pin to control a relay, one first needs to tell the Raspberry Pi that one wishes to use the GPIO pin, and then whether one wishes to use it as in input or output pin. The following three lines of script will set up GPIO 23 for controlling a relay, and then set the relay to off (slightly confusingly, 1 is off and 0 is on):

  echo 23 >/sys/class/gpio/export
  echo out >/sys/class/gpio/gpio23/direction
  echo 1 >/sys/devices/virtual/gpio/gpio23/value

By way of a utility, I have a little script called relay_status which contains the following to enable me to quickly see how the Pi relays are current configured. (If the GPIO port is not configured, the response of the cat /sys/devices/virtual/gpio/gpio23/value will not be 0, so the script works whether or not the GPIO ports have been configured.)

#!/bin/bash
#

CH=$(cat /sys/devices/virtual/gpio/gpio23/value)
HW=$(cat /sys/devices/virtual/gpio/gpio24/value)

if [ "$CH" = 0 ]
then
  echo "Central Heating is on"
else
  echo "Central Heating is off"
fi

if [ "$HW" = 0 ]
then
  echo "Hot Water is on"
else
  echo "Hot Water is off"
fi 

The initial version of control implemented is based on a simple config as below.

0000 off
0700 top3 45
0900 top3 42
1100 top2 40
1630 all 55
1730 top3 45
1900 top3 42
2200 top2 40
2300 off 

Essentially, from each time in the config file, the hot water will either be off, or will have a target. The line 0700 top3 45 indicates that from 07:00 (until 09:00) the Pi will ensure that the top three temperature probes in the hot water cylinder have an average temperature of at least 45C.

The script initially used to implement hot water control using this config file is shown below. The script below is started by a cron job (just after midnight) and has it's output directed to a file in /var/log (so that using ramlog accumulates all SD card writes up, performing just a single real write per day).

#!/bin/bash
#

export PATH=/home/pi/script:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

today=$(date +"%Y%m%d")
w1_file=/var/log/w1_logs/w1.processed.$today
config_file=/home/pi/hw_config

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

function msg {
  echo $(date +"%Y%m%d %H:%M:%S") $*
}

function fudge_cyl_temps {
  cat $w1_file | awk '
    BEGIN {
      fudge_factor[1] = 1.912014231;
      fudge_factor[2] = 2.2290226523;
      fudge_factor[3] = 2.1228034638;
      fudge_factor[4] = 1.835252397;
      fudge_factor[5] = 2.2397413977;
      fudge_factor[6] = 1.4193471931;
      fudge_factor[7] = 1.9187531469;
      fudge_factor[7] = 1.75;
    }

    /[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/ {
      loft_temp_agg += $11;
      loft_temp_array[num_proc++] = $11;
      if (num_proc > 12) {
        loft_temp_agg -= loft_temp_array[num_proc-12];
        last_hour_average = loft_temp_agg / 12;
      }
      else {
        last_hour_average = loft_temp_agg / num_proc;
      }

      for (i=1; i<=7; i++) {
        printf("%7.3f ", fudge_factor[i] * ($(i+1) - last_hour_average) + last_hour_average);
      }
      printf("\n");
    }
  ' |tail -1
}

function get_latest_temps_from_loft_pi {
  sudo /usr/bin/rsync -aPv rsync://192.168.0.17/w1/*proc* /var/log/w1_logs 2>&1 >/dev/null
}

function average_cylinder_temp {
  fudge_cyl_temps | awk '{ print int(($1 + $2 + $3 + $4 + $5 + $6 + $7)/7 + 0.5); }'
}

function average_top_two {
  fudge_cyl_temps | awk '{ print int(($1 + $2)/2 + 0.5); }'
}

function average_top_three {
  fudge_cyl_temps | awk '{ print int(($1 + $2 + $3)/3 + 0.5); }'
}

function next_refresh_time {
  date +"%M %S" |awk '
    {
      min = $1 % 5;
      sec = $2;

      # Wait until 15 seconds after the next temperature reading cron should have occurred in the loft
      print 315 - (min * 60 + sec);
    }
  '
}

function get_current_target {
  cat $config_file |awk -v now=$(date +"%H%M") '
    BEGIN {
      action = "off";
      target = "off";
    }

    $1 <= now {
      action = $2;
      target = $3;
    }

    END {
      print action, target;
    }
  '
}

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

msg "Process $$ running"

CH=$(cat /sys/devices/virtual/gpio/gpio23/value)
HW=$(cat /sys/devices/virtual/gpio/gpio24/value)

if [ "$CH" = 0 ]
then
  msg "Central Heating is on"
else
  msg "Central Heating is off"
fi

if [ "$HW" = 0 ]
then
  msg "Hot Water is on"
else
  msg "Hot Water is off"
fi

msg "Config file ($config_file):"
cat $config_file


msg "Current target:" $(get_current_target)

while [ $today = $(date +"%Y%m%d") ]
do
  # Get current target
  current_action=$(get_current_target |cut -d" " -f 1)
  current_target=$(get_current_target |cut -d" " -f 2)
  # echo "Action $current_action, target $current_target"

  get_latest_temps_from_loft_pi

  # Assess target criteria
  case $current_action in
    off) hw=off
         currval="n/a"
         ;;
    top2) currval=$(average_top_two)
          if [ $currval -lt $current_target ]
          then
            hw=on
          else
            hw=off
          fi
          ;;
    top3) currval=$(average_top_three)
          if [ $currval -lt $current_target ]
          then
            hw=on
          else
            hw=off
          fi
          ;;
    all) currval=$(average_cylinder_temp)
         if [ $currval -lt $current_target ]
         then
           hw=on
         else
           hw=off
         fi
         ;;
    *) msg "Action \"$current_action\" unknown"
       hw=off
       currval="n/a"
       ;;
  esac

  HW=$(cat /sys/devices/virtual/gpio/gpio24/value)

  # Debug information
  (
    echo
    date
    echo Target: $current_action $current_target, HW = $HW, hw = $hw, currval = $currval
  ) >>/var/log/hw_controller/hw.$(date +"%Y%m%d").debug

  if [ $HW = 1  -a  $hw = on ]
  then
    msg "Turning hot water on (target $current_action $current_target, current $currval)"
    echo 0 >/sys/devices/virtual/gpio/gpio24/value
    msg "Cyl temps:" $(fudge_cyl_temps)
  else
    if [ $HW = 0  -a  $hw = off ]
    then
      msg "Turning hot water off (target $current_action $current_target, current $currval)"
      echo 1 >/sys/devices/virtual/gpio/gpio24/value
      msg "Cyl temps:" $(fudge_cyl_temps)
    fi
  fi

  sleep $(next_refresh_time)
done

msg "Process $$ exiting"
exit 0

Sample log file contents are

20140624 00:06:02 Process 13527 running
20140624 00:06:02 Central Heating is off
20140624 00:06:02 Hot Water is off
20140624 00:06:02 Config file (/home/pi/hw_config):
0000 off
0700 top3 45
0900 top3 42
1100 top2 40
1630 all 55
1730 top3 45
1900 top3 42
2200 top2 40
2300 off
20140624 00:06:02 Current target: off
20140624 07:00:16 Turning hot water on (target top3 45, current 33)
20140624 07:00:16 Cyl temps: 32.943 33.375 33.265 32.339 31.616 29.961 29.348
20140624 07:30:16 Turning hot water off (target top3 45, current 47)
20140624 07:30:16 Cyl temps: 45.486 47.425 47.131 46.760 46.189 45.142 37.771
20140624 16:30:16 Turning hot water on (target all 55, current 30)
20140624 16:30:16 Cyl temps: 43.184 38.692 31.910 27.747 22.314 22.925 20.150
20140624 17:30:16 Turning hot water off (target top3 45, current 55)
20140624 17:30:16 Cyl temps: 55.961 54.654 53.488 53.863 52.715 55.426 48.373
20140625 00:00:15 Process 13527 exiting

A fragment of the debug file is

Tue Jun 24 16:20:16 BST 2014
Target: top2 40, HW = 1, hw = off, currval = 41

Tue Jun 24 16:25:16 BST 2014
Target: top2 40, HW = 1, hw = off, currval = 41

Tue Jun 24 16:30:16 BST 2014
Target: all 55, HW = 1, hw = on, currval = 30

Tue Jun 24 16:35:17 BST 2014
Target: all 55, HW = 0, hw = on, currval = 30

Tue Jun 24 16:40:16 BST 2014
Target: all 55, HW = 0, hw = on, currval = 32

Tue Jun 24 16:45:16 BST 2014
Target: all 55, HW = 0, hw = on, currval = 35

Tue Jun 24 16:50:16 BST 2014
Target: all 55, HW = 0, hw = on, currval = 38 

Remote Access

As I have set up ssh (secure shell) access to the Raspberry Pi from the internet, the secondary objective is implemented the moment the Pi has control over the hot water circuit.

A more user friendly interface will follow for remote control will follow, but for now the ssh access is more than adequate for my purposes.

Gas Consumption Impact

Over the summer months of July and August this year (since commissioning the Raspberry Pi hot water control), our gas consumption has reduced by 39.6% compared to the same two months last summer.

I was pretty impressed by that saving. I concede that not all of the saving could be attributed to the new control logic, as once I could demonstrate to a member of the family that their shower was using most of a cylinder of hot water, that member of the family reduced their shower hot water usage by a factor of about four! So some of the savings made would be behavioural change related rather than as a consequence of the control logic.

Based on the above figure, I am more than happy that the electricity cost of having the two Raspberry Pies running 24x7 is saved many times over by the consequential gas consumption savings.