IN PROGRESS - Raspberry Pi HLT/Boiler Controller

The Homebrew Forum

Help Support The Homebrew Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

Robbo100

Regular.
Joined
Dec 21, 2012
Messages
401
Reaction score
26
Robbo's Raspberry Pi Dual Element Boiler/HLT Controller

This How-To Guide will show you how to use a £25 Raspberry Pi mini computer to control a home-made Dual Element Tesco Kettle HLT/Boiler (see the introduction below for full capability). The actual hardware being brought together in this guide are very versatile, and can not only act as an HLT/Boiler Controller, but could also be used for many other tasks, such as a fridge/heater Controller (with some minor changes to the software). In fact, the device is so flexible, it would be quite possible to get the device to control a fridge with a continously changing user defined temperature profile, data-log the temperatures, then display the data graphically over the internet for you to monitor from your iPhone.

EF15F767-86D9-4DF8-9645-E1C3C800F583_zpseyzzmrhp.jpg


This guide is intended for anyone who has basic computer skills and basic electronic skills (i.e. anyone who can follow a simple wiring diagram, do some soldering, and use Microsoft Windows). The software installation side of the guide is written in a step by step fashion, covering every key-press required to get the device working, so it should be simple to follow.

It should be noted however, that this guide does involve wiring mains voltage components, which is dangerous if not done correctly.
The author accepts no responsibility for anyone injuring themselves (or worse) using this guide. Please treat mains electricity with the respect it deserves, IT CAN KILL!

Contents of this How-To Guide
Post 1 - Introduction
Post 2 - How to Build Your Device
Post 3 - Raspberry Pi Software Installation
Post 4 - Running Software for the First Time
Post 5 - Making Software Run at Boot-Up
Post 5 - Frequently Asked Questions

1) Introduction

The device in this guide, is essentially a very low cost (£25) credit card sized computer, which is connected to some external devices to make it useful. The connected devices are, two push button switches, two thermometers, a simple text display (two lines of 16 characters), and two relays which can switch mains power devices of up to 10 Amps each (e.g. two kettle elements, or a fridge and a heater).

For this guide, I will explain how to build the device, and how to load some code (that I have already written for you) to make a Boiler/HLT Controller. I have very successfully used this to control my DIY plastic Boiler/HLT, which is heated by two Tesco Extra Value Kettle elements (each powered from a different 13A plug). The device has made a massive difference to my brew-day, because it means I don’t have to keep monitoring the temperatures, starting stopwatches etc. It just sorts it all out for me, all the way from the mash to the cool down.

It should be noted that I am using two 2.2 kW Tesco extra value kettles, one connected to each of the two relays, which will draw under slightly under 10A each (the kettles were supplied with 10A rated power cords), however, if you are using elements rated any higher, e.g. a 2.4 kW element, then you will need to use high specification relays (and IEC kettle sockets/plugs) than me (I have provided an example of such a relay in the component list below). Using too low a relay specification will lead to over-heating, and potentially fire or other damage.

YOU DO NOT NEED TO UNDERSTAND HOW TO WRITE ANY SOFTWARE, however, for background information (for anyone interested), the software is written in Python, which I coded onto the Pi remotely using WebIDE. WebIDE is an amazingly useful bit of software that allows you to connect to the Pi from another computer and program the Pi through your Chrome web browser.

The Python script runs automatically at boot and doesn't need a monitor or keyboard. There is no user intervention required, just power it on, wait 20 seconds for it to boot, and off you go!

Here is a List of Functions of the Pre-Written Software:

  • Changeable Welcome Screen at boot[/*:m:3lw4jer9]
  • Two primary functions, HLT Mode and Boiler Mode[/*:m:3lw4jer9]
  • HLT Mode:
    [list:3lw4jer9]
  • Display shows the target HLT temperature and current boiler temperature[/*:m:3lw4jer9]
  • When target temperature is reached, the elements are switched off (and then one element is used intermittently to maintain temperature)[/*:m:3lw4jer9]
  • Audio alarm is triggered when target temperature is reached[/*:m:3lw4jer9]
  • Target temperature can be varied using Temperature UP and DOWN buttons (alarm function is reset if the target temperature is changed if the alarm has already gone off)[/*:m:3lw4jer9]
[/*:m:3lw4jer9]
[*]Boiler Mode:
  • System is commanded to Boiler Mode by pressing both temperature control buttons at the same time[/*:m:3lw4jer9]
  • System turns both elements on[/*:m:3lw4jer9]
  • When boil established (96 deg C), alarm is triggered (to prompt bitter hops) display then shows a countdown from 59:59 down to 00:00[/*:m:3lw4jer9]
  • At 15:00 remaining, alarm is triggered again, for aroma hops prompt[/*:m:3lw4jer9]
  • At 00:00 the alarm is sounded again and display prompts to go into cooling mode[/*:m:3lw4jer9]
  • When Temperature Down button pressed, both elements turn off, and display shows the boiler temperature[/*:m:3lw4jer9]
  • When 28 degrees is reached, the alarm is sounded again to alert the completion of the cooling cycle[/*:m:3lw4jer9]
[/*:m:3lw4jer9][/list:u:3lw4jer9]

Future Development Potential
  • I would like to add a run-dry detection function to turn off the elements when the fluid levels go below a certain point (useful safety feature during the sparge).[/*:m:3lw4jer9]
  • I will probably add a function to cycle one of the elements off during the boil to reduce the strength of the boil (and save electricity).[/*:m:3lw4jer9]
  • I might write some software to make the device act as a brewing fridge/heater controller, but I would need some convincing that there are enough users wanting it (since I don’t have a brewing fridge myself).[/*:m:3lw4jer9]

Some photos of the device in action:
In HLT Mode:
C789CA21-C4C7-49EF-9B80-CEA129DE6E24_zpsrd8qydj9.jpg


Connected to the boiler:
92E59AA7-9D64-4BE8-9544-E94B167C2B39_zpsvz6ftnqp.jpg


Full Boil Mode:
C368B6F0-590B-4C31-8547-A0E9CF877900_zpsfhtt7keb.jpg
 
2) Building your device

2.1) Helpful as I am, I do not intend to provide you with complete step-by-step instructions for building the physical device. The build will depend upon the components you are able to buy (i.e. the size of the box you buy), and the quality of finish that you require. However, it is a fairly simple process for any reasonable DIYer. Essentially, all you need to do, is wire the components together in accordance with the wiring diagram below, and then fit all the components into your project box.

2.2) Below is a list of equipment you will need to purchase, and some example sources (note, other suppliers are available, but these are listed as fairly well priced examples that I found easily in the internet). You should be able to pick it all up for under £80:


2.2.1) You will also need the following items:

  • 3 x 10K Ohm resistors[/*:m:3n5oh6e6]
  • 1 x 560 Ohm resistor[/*:m:3n5oh6e6]
  • 4 x M2 bolts and nuts (to hold the kettle plug sockets in the project box)[/*:m:3n5oh6e6]
  • An inch square piece of electronics strip board for mounting components (not mandatory, but will give a better result)[/*:m:3n5oh6e6]
  • A stereo male and female audio jack connector (or something similar) to allow you to disconnect the device from the boiler thermometers for storage)[/*:m:3n5oh6e6]
  • Your existing kettle cables to cut, and connect to the IEC connectors[/*:m:3n5oh6e6]
  • Some wires to patch it all together[/*:m:3n5oh6e6]
  • Some 13A cable to wire the kettle plugs to the relays[/*:m:3n5oh6e6]
  • A soldering iron[/*:m:3n5oh6e6]
  • A hot glue gun or similar fixing method (to stick the components into the project box)[/*:m:3n5oh6e6]
  • A dremmel to cut holes in the project box[/*:m:3n5oh6e6]

2.3) Below is the wiring diagram that you need to follow to create the device. When putting the device together, you will need to consider the following:

  • I suggest you wire it all together (with sufficiently long cable lengths) and then fit it into the box when you have got the device (and software) working, otherwise, you may have to dismantle the device to debug it.[/*:m:3n5oh6e6]
  • Do not test the mains voltage element of the circuit until it is all in the box with the lid on - YOU WILL KILL YOURSELF. However, you can use a circuit tester to make sure that the relay switching function is working.[/*:m:3n5oh6e6]
  • Make sure that your equipment is secured into the project box (I used a hot glue gun, but other methods will work). Failure to do this, could lead to your equipment moving around in the box and shorting out your 240V cables and blowing up in a big way![/*:m:3n5oh6e6]
  • Ensure your 240V cables are 2.5mm cable (to carry the 10A load).[/*:m:3n5oh6e6]
  • You may want to mount some of the resistors on some strip circuit board to make wiring easier. I did this for the resistors only, and it made life much easier.[/*:m:3n5oh6e6]
  • Ensure you use your relays to switch the LIVE (brown) cable from your kettle elements, not the NEUTRAL (blue) cable[/*:m:3n5oh6e6]
  • When you construct your Slice of Pi/o, the device address does not matter, but I soldered A0, A1 and A2 pads all to ground (please read the Slice of Pi/o instructions if you don't understand this)[/*:m:3n5oh6e6]
  • If you use a metal project box, it must be earthed, and you must make sure your components (e.g. bottom of circuit boards) are insulated from the metal box to avoid short circuit[/*:m:3n5oh6e6]

Click on the image to get a larger version
RaspberryPiHLTBoilerController-Robbo100_zps8323cea9.jpg



This is what my device looks like partly installed in the project box:
PiInsidesPreGule_zps02349749.jpg


This is the inside of my device with all the equipment fitted and glued in place (note: I plan to put hot glue around the mains cables to electrically insulate them):
659BDC97-E4F8-4D56-B98C-C0A85E3BCE52_zpsv8k4tsgi.jpg


I have one set of mains power inputs and outputs on each side of the project box, as shown here:
406DEFF0-72CB-400B-9641-6C341B93F9C3_zpscgzcllfz.jpg


2.4) Installing the thermometers in your HLT/Boiler

2.4.1) For this project I have chosen to have two thermometers, because I wanted to make sure I was taking an average temperature from more than one part of the boiler. This is probably a bit of overkill, but never mind. I measure my temperature by putting the thermometer within a DIY bulkhead made from copper 15 mm plumbing items. Effectively, I use a tank connector (http://www.screwfix.com/p/tank-coupler-15mm/82376), fitted the wrong way round into the tank, and then I attach a short (20 mm long) section of 15 mm copper pipe, with an end-cap soldered onto it. I then push the thermometer into the outside of the bulkhead and seal it with glue, as shown in the three photos below:

Inside the boiler:
F815A034-B54B-4E80-8FA2-506B67BAFCBC_zpsp85gfmc4.jpg


Outside the boiler:
784ED171-DC59-4FB9-B0A5-7CC34D1EC409_zpsqhirqzna.jpg


Both probes (viewed from outside):
8D9828A2-12FC-466F-B629-39224F80CBDE_zpsyfr0x9zk.jpg
 
3) Raspberry Pi Software Installation

3.1) Loading Operating System onto SD Card

3.1.1) Using your Windows PC, download the latest version of Raspbian (a Raspberry Pi Operating System) in .zip format, from this website: http://www.raspberrypi.org/downloads (the file will be listed in the from of YYYY-MM-DD-wheezy-raspbian.zip).

3.1.2) Unzip the file (and remember where you have unzipped it).

3.1.3) Insert your SD card into your Windows PC SD card reader (if Windows asks you what you want to do with the inserted media, just click cancel). NOTE: All data will be wiped from this SD card, so please backup any files you wish to keep.

3.1.4) Download Win32 Disk Imager by clicking on this link: http://sourceforge.net/projects/win32diskimager/files/latest/download?source=files. Once downloaded, Win32 Disk Imager does not need installing, to run the program, simply double click on the downloaded file and extract the zip file contents, and then navigate to the resultant un-zipped folder and double click on the file Win32DiskImager.exe.

3.1.5) You will be presented with a screen like this:

win32-imagewriter.png


3.1.6) Click on the folder icon to the left of the long while image file box and navigate to the unzipped YYYY-MM-DD-wheezy-rasbian.img file, then click Open. Then, click on the drop-down box underneath where it says "Device", and select the drive letter for your SD card. Then click "Write" - You will be asked if you are sure you want to continue, Click "Yes". The wheezy-raspbian operating system will be written to your SD card, and will take a couple of minutes.

3.2) Setting Up Your Raspberry Pi's Software

3.2.1) Remove your SD card from your Windows PC and insert it into your Pi's SD slot (which should also have your Slice of Pi/o and circuit connected to it). Connect a network cable between your Pi's network port and your home router, and then power-up the Pi using a Micro-USB cable (e.g. mobile phone charger).

3.2.2) After the Pi has booted up (when the green light stops flashing), you will need to find out the IP address of your Pi. You should be able to find this out, by logging onto your router and looking at the listed connected devices (it should be in the format like "192.168.1.4" (with the last number likely to be something different to "4"). If you can't establish this using your router, you may be able to use some software like this: http://angryip.org/w/Screenshots.

3.2.3) Download "Putty.exe" to your Windows PC from here: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html. Run Putty.exe, and you will be presented with a screen like this:

putty-openssh-3.gif


3.2.4) In the box titled "Host Name (or IP address):" enter your Raspberry Pi's IP address that you noted from your router, then click "Open". You will be warned about a potential security breach, just click "Yes" to continue.

3.2.5) You will be shown a black screen with "login as:" on the screen. Enter:

Code:
pi

The system will reply

Code:
[email protected]'s password:

and you need to type

Code:
raspberry

You are now connected to your Raspberry Pi and able to execute commands to your heart's content (if only you knew some!).

Firstly, we should update the software on the Pi, by typing the following:

Code:
sudo apt-get update

followed by:

Code:
sudo apt-get upgrade

Both of these commands will take a couple of minutes to download and install the updates. You may be asked if you are happy to use additional disk space, simply type "y" then hit enter.

3.2.6) The Pi Operating system has a built in configuration program, which can be run by typing:

Code:
sudo raspi-config

This will bring up a configuration screen. All you need to do is highlight item 1 (Expand Filesystem) and hit enter. Then navigate to the "Finish" option and hit enter again. This will restart the Pi.

3.2.7) We also need to install some other software for certain functions. So re-start Putty, then do the following:

To enable the software for the Slice of Pi/o, type:

Code:
sudo nano /etc/modprobe.d/raspi-blacklist.conf
(note: "nano" is a text editor)

This will bring up a screen that shows the following:

Code:
# blacklist spi and i2c by default (many users don't need them)

blacklist spi-bcm2700
blacklist i2c-bcm2708
All you need to do is put a "#" in front of the i2c blacklist line, so it looks like this:

Code:
# blacklist spi and i2c by default (many users don't need them)

blacklist spi-bcm2700
#blacklist i2c-bcm2708
Then press Ctrl and X sumultanously, then key "Y" (to overwrite existing file, then hit enter to save the file).

Next, type the following:

Code:
sudo nano /etc/modules
Immediately after the line that says "snd-bcm2835" add the following line:

Code:
i2c-dev
Then press Ctrl and X sumultanously, then key "Y" (to overwrite existing file, then hit enter to save the file).

Now install the i2c-tools package by typing:

Code:
sudo apt-get install i2c-tools
Now add a new user to the i2c group by typing:

Code:
sudo adduser pi i2c
finally, you will need to reboot your Pi for the changes to take effect, by typing:

Code:
sudo shutdown -r now
You will need to close down Putty once the connection is lost during the Pi reboot.

Restart Putty and connect to your Pi as before, and then, to install the python-smbus python module, type the following:
Code:
sudo apt-get install python-smbus

3.3) Creating the Boiler Controller Code

3.3.1) You are now ready to create your Boiler Program files and write them onto the Raspberry Pi. Open up Notepad (START > Programs > Accessories > Notepad). Select ALL of the text in the code box below (not including the title saying "Code:"), right click on it and select "copy".

Code:
#!/usr/bin/python

import smbus

# ===========================================================================
# Adafruit_I2C Class
# ===========================================================================

class Adafruit_I2C :

  @staticmethod
  def getPiRevision():
    "Gets the version number of the Raspberry Pi board"
    # Courtesy quick2wire-python-api
    # [url]https://github.com/quick2wire/quick2wire-python-api[/url]
    try:
      with open('/proc/cpuinfo','r') as f:
        for line in f:
          if line.startswith('Revision'):
            return 1 if line.rstrip()[-1] in ['1','2'] else 2
    except:
      return 0

  @staticmethod
  def getPiI2CBusNumber():
    # Gets the I2C bus number /dev/i2c#
    return 1 if Adafruit_I2C.getPiRevision() > 1 else 0

  def __init__(self, address, busnum=-1, debug=False):
    self.address = address
    # By default, the correct I2C bus is auto-detected using /proc/cpuinfo
    # Alternatively, you can hard-code the bus version below:
    # self.bus = smbus.SMBus(0); # Force I2C0 (early 256MB Pi's)
    # self.bus = smbus.SMBus(1); # Force I2C1 (512MB Pi's)
    self.bus = smbus.SMBus(
      busnum if busnum >= 0 else Adafruit_I2C.getPiI2CBusNumber())
    self.debug = debug

  def reverseByteOrder(self, data):
    "Reverses the byte order of an int (16-bit) or long (32-bit) value"
    # Courtesy Vishal Sapre
    byteCount = len(hex(data)[2:].replace('L','')[::2])
    val       = 0
    for i in range(byteCount):
      val    = (val << 8) | (data & 0xff)
      data >>= 8
    return val

  def errMsg(self):
    print "Error accessing 0x%02X: Check your I2C address" % self.address
    return -1

  def write8(self, reg, value):
    "Writes an 8-bit value to the specified register/address"
    try:
      self.bus.write_byte_data(self.address, reg, value)
      if self.debug:
        print "I2C: Wrote 0x%02X to register 0x%02X" % (value, reg)
    except IOError, err:
      return self.errMsg()

  def write16(self, reg, value):
    "Writes a 16-bit value to the specified register/address pair"
    try:
      self.bus.write_word_data(self.address, reg, value)
      if self.debug:
        print ("I2C: Wrote 0x%02X to register pair 0x%02X,0x%02X" %
         (value, reg, reg+1))
    except IOError, err:
      return self.errMsg()

  def writeList(self, reg, list):
    "Writes an array of bytes using I2C format"
    try:
      if self.debug:
        print "I2C: Writing list to register 0x%02X:" % reg
        print list
      self.bus.write_i2c_block_data(self.address, reg, list)
    except IOError, err:
      return self.errMsg()

  def readList(self, reg, length):
    "Read a list of bytes from the I2C device"
    try:
      results = self.bus.read_i2c_block_data(self.address, reg, length)
      if self.debug:
        print ("I2C: Device 0x%02X returned the following from reg 0x%02X" %
         (self.address, reg))
        print results
      return results
    except IOError, err:
      return self.errMsg()

  def readU8(self, reg):
    "Read an unsigned byte from the I2C device"
    try:
      result = self.bus.read_byte_data(self.address, reg)
      if self.debug:
        print ("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" %
         (self.address, result & 0xFF, reg))
      return result
    except IOError, err:
      return self.errMsg()

  def readS8(self, reg):
    "Reads a signed byte from the I2C device"
    try:
      result = self.bus.read_byte_data(self.address, reg)
      if result > 127: result -= 256
      if self.debug:
        print ("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" %
         (self.address, result & 0xFF, reg))
      return result
    except IOError, err:
      return self.errMsg()

  def readU16(self, reg):
    "Reads an unsigned 16-bit value from the I2C device"
    try:
      result = self.bus.read_word_data(self.address,reg)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
      return result
    except IOError, err:
      return self.errMsg()

  def readS16(self, reg):
    "Reads a signed 16-bit value from the I2C device"
    try:
      result = self.bus.read_word_data(self.address,reg)
      if (self.debug):
        print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
      return result
    except IOError, err:
      return self.errMsg()

if __name__ == '__main__':
  try:
    bus = Adafruit_I2C(address=0)
    print "Default I2C bus is accessible"
  except:
    print "Error accessing default I2C bus"

3.3.2) Paste the code into a new document in Notepad. Now save the document in a location that you will easily remember with the file name "Adafruit_I2C.py" but before you click "OK", select the "Save as type" to "All Files *.*".

3.3.3) Repeat the same process for the following code, but using the file name "Boiler_Controller.py".

Code:
#!/usr/bin/python
# /usr/share/adafruit/webide/repositories/my-pi-projects/boiler_Mk2/Boiler_Controller_LCD.py

#*************************************************************************************
#*************************************************************************************
#*************************************************************************************

# This HLT/Boiler Controller software has been written by Robbo100 (username from 
# [url]www.thehomebrewforum.co.uk[/url]). This software uses elements of code from Daniel Berlin 
# (with some changes by Adafruit Industries/Limor Fried) and Matt Hawkins.

#*************************************************************************************
#*************************************************************************************
#*************************************************************************************



# Copyright 2012 Daniel Berlin (with some changes by Adafruit Industries/Limor Fried)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal  MCP230XX_GPIO(1, 0xin
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

#------------------------------------------------------------------------------
#                    IMPORT OTHER FUNCTIONS AND CLASSES
#------------------------------------------------------------------------------

from Adafruit_I2C import Adafruit_I2C
import smbus
import time
import os
import glob
import RPi.GPIO as GPIO
import threading
import datetime
from threading import Thread

#------------------------------------------------------------------------------
#                 SETUP THE ONE WIRE THERMOMETER SYSTEM
#------------------------------------------------------------------------------

# Initialise the One Wire Thermometer 
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

base_dir = '/sys/bus/w1/devices/'
#*************************************************************************************
# The two device addresses MUST be changed to match the used device, otherwise the
# software WILL NOT RUN
#*************************************************************************************
# Setup Thermometer 1 Location
device1_folder = glob.glob(base_dir + '28-0000057ce739')[0] #BOILER DEVICE - CHANGE TO MATCH YOUR DEVICE
device1_file = device1_folder + '/w1_slave'
# setup Thermomenter 2 Location
device2_folder = glob.glob(base_dir + '28-0000057dc7da')[0] #BOILER DEVICE - CHANGE TO MATCH YOUR DEVICE
device2_file = device2_folder + '/w1_slave'

def read_temp_raw_1():
    f = open(device1_file, 'r')
    lines = f.readlines()
    f.close()
    return lines

def read_temp_1():
    lines = read_temp_raw_1()
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.001)
        lines = read_temp_raw_1()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c_1 = float(temp_string) / 1000.0
        temp_f_1 = temp_c_1 * 9.0 / 5.0 + 32.0
        return temp_c_1

def read_temp_raw_2():
    f = open(device2_file, 'r')
    lines = f.readlines()
    f.close()
    return lines

def read_temp_2():
    lines = read_temp_raw_2()
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.001)
        lines = read_temp_raw_2()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c_2 = float(temp_string) / 1000.0
        temp_f_2 = temp_c_2 * 9.0 / 5.0 + 32.0
        return temp_c_2

#------------------------------------------------------------------------------
#                    INITIALISE THE SLICE OF PI/O
#------------------------------------------------------------------------------

MCP23017_IODIRA = 0x00
MCP23017_IODIRB = 0x01
MCP23017_GPIOA  = 0x12
MCP23017_GPIOB  = 0x13
MCP23017_GPPUA  = 0x0C
MCP23017_GPPUB  = 0x0D
MCP23017_OLATA  = 0x14
MCP23017_OLATB  = 0x15
MCP23008_GPIOA  = 0x09
MCP23008_GPPUA  = 0x06
MCP23008_OLATA  = 0x0A

class Adafruit_MCP230XX(object):
    OUTPUT = 0
    INPUT = 1

    def __init__(self, address, num_gpios):
        assert num_gpios >= 0 and num_gpios <= 16, "Number of GPIOs must be between 0 and 16"
        self.i2c = Adafruit_I2C(address=address)
        self.address = address
        self.num_gpios = num_gpios

        # set defaults
        if num_gpios <= 8:
            self.i2c.write8(MCP23017_IODIRA, 0xFF)  # all inputs on port A
            self.direction = self.i2c.readU8(MCP23017_IODIRA)
            self.i2c.write8(MCP23008_GPPUA, 0x00)
        elif num_gpios > 8 and num_gpios <= 16:
            self.i2c.write8(MCP23017_IODIRA, 0xFF)  # all inputs on port A
            self.i2c.write8(MCP23017_IODIRB, 0xFF)  # all inputs on port B
            self.direction = self.i2c.readU8(MCP23017_IODIRA)
            self.direction |= self.i2c.readU8(MCP23017_IODIRB) << 8
            self.i2c.write8(MCP23017_GPPUA, 0x00)
            self.i2c.write8(MCP23017_GPPUB, 0x00)

    def _changebit(self, bitmap, bit, value):
        assert value == 1 or value == 0, "Value is %s must be 1 or 0" % value
        if value == 0:
            return bitmap & ~(1 << bit)
        elif value == 1:
            return bitmap | (1 << bit)

    def _readandchangepin(self, port, pin, value, currvalue = None):
        assert pin >= 0 and pin < self.num_gpios, "Pin number %s is invalid, only 0-%s are valid" % (pin, self.num_gpios)
        #assert self.direction & (1 << pin) == 0, "Pin %s not set to output" % pin
        if not currvalue:
             currvalue = self.i2c.readU8(port)
        newvalue = self._changebit(currvalue, pin, value)
        self.i2c.write8(port, newvalue)
        return newvalue

    def pullup(self, pin, value):
        if self.num_gpios <= 8:
            return self._readandchangepin(MCP23008_GPPUA, pin, value)
        if self.num_gpios <= 16:
            lvalue = self._readandchangepin(MCP23017_GPPUA, pin, value)
            if (pin < 8):
                return
            else:
                return self._readandchangepin(MCP23017_GPPUB, pin-8, value) << 8

    # Set pin to either input or output mode
    def config(self, pin, mode):
        if self.num_gpios <= 8:
            self.direction = self._readandchangepin(MCP23017_IODIRA, pin, mode)
        if self.num_gpios <= 16:
            if (pin < 8):
                self.direction = self._readandchangepin(MCP23017_IODIRA, pin, mode)
            else:
                self.direction |= self._readandchangepin(MCP23017_IODIRB, pin-8, mode) << 8

        return self.direction

    def output(self, pin, value):
        # assert self.direction & (1 << pin) == 0, "Pin %s not set to output" % pin
        if self.num_gpios <= 8:
            self.outputvalue = self._readandchangepin(MCP23008_GPIOA, pin, value, self.i2c.readU8(MCP23008_OLATA))
        if self.num_gpios <= 16:
            if (pin < 8):
                self.outputvalue = self._readandchangepin(MCP23017_GPIOA, pin, value, self.i2c.readU8(MCP23017_OLATA))
            else:
                self.outputvalue = self._readandchangepin(MCP23017_GPIOB, pin-8, value, self.i2c.readU8(MCP23017_OLATB)) << 8

        return self.outputvalue

        self.outputvalue = self._readandchangepin(MCP23017_IODIRA, pin, value, self.outputvalue)
        return self.outputvalue

    def input(self, pin):
        assert pin >= 0 and pin < self.num_gpios, "Pin number %s is invalid, only 0-%s are valid" % (pin, self.num_gpios)
        assert self.direction & (1 << pin) != 0, "Pin %s not set to input" % pin
        if self.num_gpios <= 8:
            value = self.i2c.readU8(MCP23008_GPIOA)
        elif self.num_gpios > 8 and self.num_gpios <= 16:
            value = self.i2c.readU8(MCP23017_GPIOA)
            value |= self.i2c.readU8(MCP23017_GPIOB) << 8
        return value & (1 << pin)

    def readU8(self):
        result = self.i2c.readU8(MCP23008_OLATA)
        return(result)

    def readS8(self):
        result = self.i2c.readU8(MCP23008_OLATA)
        if (result > 127): result -= 256
        return result

    def readU16(self):
        assert self.num_gpios >= 16, "16bits required"
        lo = self.i2c.readU8(MCP23017_OLATA)
        hi = self.i2c.readU8(MCP23017_OLATB)
        return((hi << 8) | lo)

    def readS16(self):
        assert self.num_gpios >= 16, "16bits required"
        lo = self.i2c.readU8(MCP23017_OLATA)
        hi = self.i2c.readU8(MCP23017_OLATB)
        if (hi > 127): hi -= 256
        return((hi << 8) | lo)

    def write8(self, value):
        self.i2c.write8(MCP23008_OLATA, value)

    def write16(self, value):
        assert self.num_gpios >= 16, "16bits required"
        self.i2c.write8(MCP23017_OLATA, value & 0xFF)
        self.i2c.write8(MCP23017_OLATB, (value >> 8) & 0xFF)

# RPi.GPIO compatible interface for MCP23017 and MCP23008

class MCP230XX_GPIO(object):
    OUT = 0
    IN = 1
    BCM = 0
    BOARD = 0
    def __init__(self, busnum, address, num_gpios):
        self.chip = Adafruit_MCP230XX(busnum, address, num_gpios)
    def setmode(self, mode):
        # do nothing
        pass
    def setup(self, pin, mode):
        self.chip.config(pin, mode)
    def input(self, pin):
        return self.chip.input(pin)
    def output(self, pin, value):
        self.chip.output(pin, value)
    def pullup(self, pin, value):
        self.chip.pullup(pin, value)

if __name__ == '__main__':

#------------------------------------------------------------------------------
#                    CONFIGURE THE SLICE OF PI/O INPUT AND OUTPUTS
#------------------------------------------------------------------------------

    # Set num_gpios to 8 for MCP23008 or 16 for MCP23017!

    # mcp = Adafruit_MCP230XX(address = 0x20, num_gpios = 8) # MCP23008
    mcp = Adafruit_MCP230XX(address = 0x20, num_gpios = 16) # MCP23017

    # List outputs here
    mcp.config(7, mcp.OUTPUT) # Buzzer  
    mcp.config(12, mcp.OUTPUT) # Relay 1 on
    mcp.config(13, mcp.OUTPUT) # Relay 2 on
    
    # Set inputs with the pullup resistor enabled here
    mcp.config(14, mcp.INPUT)
    mcp.pullup(14, 1)
    mcp.config(15, mcp.INPUT)
    mcp.pullup(15, 1)

    # Set all outputs to off for startup (note - some outputs are reversed
    # depending upon connected device)
    mcp.output(7, 0)
    mcp.output(12, 1)
    mcp.output(13, 1)

#------------------------------------------------------------------------------
#                            HD44780 LCD SETUP
#------------------------------------------------------------------------------

    # Author : Matt Hawkins
    # Site   : [url]http://www.raspberrypi-spy.co.uk[/url]
    # Date   : 26/07/2012

    # The wiring for the LCD is as follows:
    # 1 : GND
    # 2 : 5V
    # 3 : Contrast (0-5V)*
    # 4 : RS (Register Select)
    # 5 : R/W (Read Write)       - GROUND THIS PIN
    # 6 : Enable or Strobe
    # 7 : Data Bit 0             - NOT USED
    # 8 : Data Bit 1             - NOT USED
    # 9 : Data Bit 2             - NOT USED
    # 10: Data Bit 3             - NOT USED
    # 11: Data Bit 4
    # 12: Data Bit 5
    # 13: Data Bit 6
    # 14: Data Bit 7
    # 15: LCD Backlight +5V**
    # 16: LCD Backlight GND
    
    # Define GPIO to LCD mapping
    LCD_RS = 17
    LCD_E  = 18
    LCD_D4 = 22 
    LCD_D5 = 23
    LCD_D6 = 24
    LCD_D7 = 25
    
    # Define some device constants
    LCD_WIDTH = 16    # Maximum characters per line
    LCD_CHR = True
    LCD_CMD = False

    LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
    LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line 

    # Timing constants
    E_PULSE = 0.00005
    E_DELAY = 0.00005

#------------------------------------------------------------------------------
#                       HD44780 LCD INITIALISATION
#------------------------------------------------------------------------------

    def lcd_init():
        # Initialise display
        lcd_byte(0x33,LCD_CMD)
        lcd_byte(0x32,LCD_CMD)
        lcd_byte(0x28,LCD_CMD)
        lcd_byte(0x0C,LCD_CMD)  
        lcd_byte(0x06,LCD_CMD)
        lcd_byte(0x01,LCD_CMD)  

    def lcd_string(message):
        # Send string to display

        message = message.ljust(LCD_WIDTH," ")  

        for i in range(LCD_WIDTH):
            lcd_byte(ord(message[i]),LCD_CHR)

    def lcd_byte(bits, mode):
        # Send byte to data pins
        # bits = data
        # mode = True  for character
        #        False for command

        GPIO.output(LCD_RS, mode) # RS

        # High bits
        GPIO.output(LCD_D4, False)
        GPIO.output(LCD_D5, False)
        GPIO.output(LCD_D6, False)
        GPIO.output(LCD_D7, False)
        if bits&0x10==0x10:
            GPIO.output(LCD_D4, True)
        if bits&0x20==0x20:
            GPIO.output(LCD_D5, True)
        if bits&0x40==0x40:
            GPIO.output(LCD_D6, True)
        if bits&0x80==0x80:
            GPIO.output(LCD_D7, True)

        # Toggle 'Enable' pin
        time.sleep(E_DELAY)    
        GPIO.output(LCD_E, True)  
        time.sleep(E_PULSE)
        GPIO.output(LCD_E, False)  
        time.sleep(E_DELAY)      

        # Low bits
        GPIO.output(LCD_D4, False)
        GPIO.output(LCD_D5, False)
        GPIO.output(LCD_D6, False)
        GPIO.output(LCD_D7, False)
        if bits&0x01==0x01:
            GPIO.output(LCD_D4, True)
        if bits&0x02==0x02:
            GPIO.output(LCD_D5, True)
        if bits&0x04==0x04:
            GPIO.output(LCD_D6, True)
        if bits&0x08==0x08:
            GPIO.output(LCD_D7, True)

        # Toggle 'Enable' pin
        time.sleep(E_DELAY)    
        GPIO.output(LCD_E, True)  
        time.sleep(E_PULSE)
        GPIO.output(LCD_E, False)  
        time.sleep(E_DELAY)
    
#------------------------------------------------------------------------------
#                           BOIL FUNCTION
#------------------------------------------------------------------------------

    def boil_mode(boil_temperature):
        global target_temperature_glob
        target_temperature_glob = boil_temperature
        global Line1_String
        global Line2_String
        lcd_byte(LCD_LINE_1, LCD_CMD)
        Line1_String = " FULL BOIL MODE "
        lcd_string(Line1_String)
        lcd_byte(LCD_LINE_2, LCD_CMD)
        Line2_String = ("Boiler Temp = " + (str(int(Temperature))))
        lcd_string(Line2_String)
        time.sleep(1);
        target_temperature_glob = 120
        while (True):
            lcd_byte(LCD_LINE_1, LCD_CMD)
            Line1_String = " FULL BOIL MODE "
            lcd_string(Line1_String)
            lcd_byte(LCD_LINE_2, LCD_CMD)
            Line2_String = ("Boiler Temp = " + (str(int(Temperature))))
            lcd_string(Line2_String)
            if (mcp.input(15) < 2):
                lcd_init()
            if (mcp.input(15) < 2) & (mcp.input(14) < 2):
                time.sleep(1);
                target_temperature_glob = 72
                break
            if (Temperature > 96): # SET TO 96 DEG
                buzzer_count = 0
                while (buzzer_count < 6):
                    mcp.output(7, 1)
                    time.sleep(1)
                    mcp.output(7, 0)
                    time.sleep(1)
                    buzzer_count = buzzer_count + 1
                mcp.output(7, 0)
                Boil_Start_Time = datetime.datetime.now()
                Elapsed_Time = datetime.datetime.now()-Boil_Start_Time
                Countdown = ((60*60)-Elapsed_Time.total_seconds())
                #Countdown = ((60)-Elapsed_Time.total_seconds()) #Countdown Test Line
                while (Countdown > 0):
                    Elapsed_Time = datetime.datetime.now()-Boil_Start_Time
                    Countdown = ((60*60)-Elapsed_Time.total_seconds())
                    #Countdown = ((60)-Elapsed_Time.total_seconds()) #Countdown Test Line
                    if ((int(Countdown//60))<10):
                        Countdown_Mins = ("0" + (str(int(Countdown//60))))
                    else:
                        Countdown_Mins = str(int(Countdown//60))
                    if (int((Countdown - (int((Countdown//60)*60))))<10):
                        Countdown_Secs = ("0" + str(int(Countdown - (int((Countdown//60)*60)))))
                    else:
                        Countdown_Secs = str(int(Countdown - (int((Countdown//60)*60))))
                    global Countdown_String
                    Countdown_String = "Boil Time " + Countdown_Mins + ":" + Countdown_Secs
                    # Display temperatures on LCD
                    lcd_byte(LCD_LINE_1, LCD_CMD)
                    Line1_String = " FULL BOIL MODE "
                    lcd_string(Line1_String)
                    lcd_byte(LCD_LINE_2, LCD_CMD)
                    Line2_String = (Countdown_String)
                    lcd_string(Line2_String)
                    if (mcp.input(15) < 2):
                        lcd_init()
                    if ((Countdown < (60*15)) & (Countdown > ((60*15)-5))):
                        buzzer_count = 0
                        while (buzzer_count < 6):
                            mcp.output(7, 1)
                            time.sleep(1)
                            mcp.output(7, 0)
                            time.sleep(1) 
                            buzzer_count = buzzer_count + 1
                        mcp.output(7, 0)
                # Display temperatures on LCD
                lcd_byte(LCD_LINE_1, LCD_CMD)
                Line1_String = " FULL BOIL MODE "
                lcd_string(Line1_String)
                lcd_byte(LCD_LINE_2, LCD_CMD)
                Line2_String = (" Boil Complete ")
                lcd_string(Line2_String)
                buzzer_count = 0
                while (buzzer_count < 6):
                    mcp.output(7, 1)
                    time.sleep(1)
                    mcp.output(7, 0)
                    time.sleep(1) 
                    buzzer_count = buzzer_count + 1
                mcp.output(7, 0)
                while(mcp.input(14) > 2):
                    if (mcp.input(15) < 2):
                        lcd_init()
                    lcd_byte(LCD_LINE_1, LCD_CMD)
                    Line1_String = (" READY FOR COOL ")
                    lcd_string(Line1_String)
                    lcd_byte(LCD_LINE_2, LCD_CMD)
                    Line2_String = ("PRESS TEMP DOWN")
                    lcd_string(Line2_String)
                # Start the cooling process
                target_temperature_glob = 1 # This is needed to force the boiler to OFF in the main scropt
                while(Temperature > 28): # SET TO 28 DEG
                    # Display temperatures on LCD
                    lcd_byte(LCD_LINE_1, LCD_CMD)
                    Line1_String = ("  COOLING MODE ")
                    lcd_string(Line1_String)
                    lcd_byte(LCD_LINE_2, LCD_CMD)
                    Line2_String = ("Boiler Temp = " + (str(int(Temperature))))
                    lcd_string(Line2_String)
                    time.sleep(1)
                    if (mcp.input(15) < 2):
                        lcd_init()
                buzzer_count = 0
                while (buzzer_count < 6):
                    mcp.output(7, 1)
                    time.sleep(1)
                    mcp.output(7, 0)
                    time.sleep(1)
                    buzzer_count = buzzer_count + 1
                mcp.output(7, 0)
                while(mcp.input(14) > 2):
                    if (mcp.input(15) < 2):
                        lcd_init()
                    lcd_byte(LCD_LINE_1, LCD_CMD)
                    Line1_String = ("  COOLING MODE ")
                    lcd_string(Line1_String)
                    lcd_byte(LCD_LINE_2, LCD_CMD)
                    Line2_String = ("Cooling Complete")
                    lcd_string(Line2_String)
                    time.sleep(3)
                    lcd_byte(LCD_LINE_1, LCD_CMD)
                    Line1_String = ("SHUTDOWN CONTLR?")
                    lcd_string(Line1_String)
                    lcd_byte(LCD_LINE_2, LCD_CMD)
                    Line2_String = (" HOLD TEMP DOWN ") 
                    lcd_string(Line2_String)
                    time.sleep(3)
                # Shutdown Pi
                lcd_byte(LCD_LINE_1, LCD_CMD)
                Line1_String = ("*****REMOVE*****")
                lcd_string(Line1_String)
                lcd_byte(LCD_LINE_2, LCD_CMD)
                Line2_String = ("**BOILER POWER**")
                lcd_string(Line2_String)
                buzzer_count = 0
                while (buzzer_count < 12):
                    mcp.output(7, 1)
                    time.sleep(0.5)
                    mcp.output(7, 0)
                    time.sleep(0.5)
                    buzzer_count = buzzer_count + 1
                mcp.output(7, 0)
                time.sleep(8)
                lcd_byte(LCD_LINE_1, LCD_CMD)
                Line1_String = (" SHUTTING DOWN ")
                lcd_string(Line1_String)
                lcd_byte(LCD_LINE_2, LCD_CMD)
                Line2_String = ("Please Wait...")
                lcd_string(Line2_String)
                time.sleep(3)
                os.system("sudo shutdown -r now")

#------------------------------------------------------------------------------
#                    DISPLAY AND BUTTON DETECTION FUNCTION
#------------------------------------------------------------------------------

    def display(target_temperature):
        global target_temperature_glob
        target_temperature_glob = target_temperature
        global Line1_String
        global Line2_String
        while (True):
            # Detect Switch Presses
            if (mcp.input(15) < 2):
                target_temperature_glob = target_temperature_glob+1
                lcd_init()
            if (mcp.input(14) < 2):
                target_temperature_glob = target_temperature_glob-1
            if target_temperature_glob < 0:
                target_temperature_glob = 0
            if target_temperature_glob > 99:
                target_temperature_glob = 99 
            if (mcp.input(15) < 2) & (mcp.input(14) < 2):
                boil_temperature = target_temperature_glob
                boil_mode(boil_temperature)
            time.sleep(0.15)
            
            # Display temperatures on LCD
            lcd_byte(LCD_LINE_1, LCD_CMD)
            Line1_String = ("Target Temp = " + (str(target_temperature_glob)))
            lcd_string(Line1_String)
            lcd_byte(LCD_LINE_2, LCD_CMD)
            Line2_String = ("Boiler Temp = " + (str(int(Temperature))))
            lcd_string(Line2_String)
                
#------------------------------------------------------------------------------
#                     MAIN TEMPERATURE READING FUNCTION
#------------------------------------------------------------------------------
    print "Starting Program (CTRL+Z to quit)"
    # Startup Display
    GPIO.setmode(GPIO.BCM)       # Use BCM GPIO numbers
    GPIO.setup(LCD_E, GPIO.OUT)  # E
    GPIO.setup(LCD_RS, GPIO.OUT) # RS
    GPIO.setup(LCD_D4, GPIO.OUT) # DB4
    GPIO.setup(LCD_D5, GPIO.OUT) # DB5
    GPIO.setup(LCD_D6, GPIO.OUT) # DB6
    GPIO.setup(LCD_D7, GPIO.OUT) # DB7
    # Initialise display
    lcd_init()
    # Initialise the program parameters
    temp_reached = 0
    global Temperature
    Temperature = 99
    tens = 0
    units = 0
    target_temperature = 72
    temp_reached = 0
    Line1_String = ""
    Line2_String = ""
    # Wait for User to start boiler
    lcd_byte(LCD_LINE_1, LCD_CMD)
    Line1_String = ("ROBBO'S BREWERY") # You can change this (and any other text in "quotes") to anything you want to display on the screen
    lcd_string(Line1_String)
    lcd_byte(LCD_LINE_2, LCD_CMD)
    Line2_String = ("BOILER CONTROL")
    lcd_string(Line2_String)
    print ""
    print ""
    print ""
    print ""
    print "" 
    print ""
    print ""
    print ""
    print ""
    print ""
    print ""
    print "    Average Temperature = "
    print "     Target Temperature = "
    print " ____________________________________"
    print "       ", Line1_String
    print "       ", Line2_String
    print " ____________________________________"
    time.sleep(5) 
    while(mcp.input(15) > 2):
        lcd_byte(LCD_LINE_1, LCD_CMD)
        Line1_String = (" READY TO START")
        lcd_string(Line1_String)
        lcd_byte(LCD_LINE_2, LCD_CMD)
        Line2_String = (" PRESS TEMP UP")
        lcd_string(Line2_String)
        time.sleep(0.5)
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print "    Average Temperature = "
        print "     Target Temperature = "
        print " ____________________________________"
        print "       ", Line1_String
        print "       ", Line2_String
        print " ____________________________________"
    
    # Start Display Thread
    display_thread = Thread(target = display, args = (target_temperature, ))
    display_thread.start() 
    
    # Loop the following tasks forever
    while(True):
        # Read Temperatures From Thermometers, calculate the average, and print 
        # them, along with the current global target temperature parameter, to 
        # the terminl screen
        temperature1 = read_temp_1()
        temperature2 = read_temp_2()
        Temperature = ((temperature2+temperature2)/2)
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print ""
        print "    Average Temperature = ", Temperature
        print "     Target Temperature = ", target_temperature_glob
        print " ____________________________________"
        print "       ", Line1_String
        print "       ", Line2_String
        print " ____________________________________"
        # If the measured temperature is lower than the target temperature, then
        # turn on the boilers (0 is ON, 1 os OFF)
        
        # Boiler 1
        if (Temperature < target_temperature_glob):
            mcp.output(12, 0)
        else:
            mcp.output(12, 1)
    
        # Boiler 2
        if (Temperature < (target_temperature_glob-1)):
            mcp.output(13, 0)
        else:
            mcp.output(13, 1)

        # Heating Mode Temperature Reached Buzzer 
        if ((Temperature > target_temperature_glob) & (temp_reached == 0)) & (target_temperature_glob != 1):
            buzzer_count=0
            while (buzzer_count < 6):
                mcp.output(7, 1)
                time.sleep(1)
                mcp.output(7, 0)
                time.sleep(1)
                buzzer_count = buzzer_count + 1
            mcp.output(7, 0)
            temp_reached=1

        if ((temp_reached == 1) & (Temperature <(target_temperature_glob-4))):
            temp_reached=0
    GPIO.cleanup()

3.4) Mandatory Changes to the Code

3.4.1) Before we copy the files to your Pi, we will need to make two small changes. It is easiest to do this using Notepad (because you can use "find" to locate the test we are changing). However, before we open the file, we need to work out how to change the software.

3.4.2) Every DS18B20 thermometer has it's own unique address, and that address needs to be entered into the python script. In order to identify the addresses, you need to do the following into the Pi using Putty.exe (see Para 3.2.3 for Putty instructions):

To install the software for the thermometers, type:

Code:
sudo modprobe w1-gpio
Then:
Code:
sudo modprobe w1-therm

Then type:

Code:
cd /sys/bus/w1/devices
(this will change the directory shown on the Putty screen)
Followed by:

Code:
dir
(to list the contents of the directory /sys/bus/w1/devices)

This will show three directories, firstly one called "w1_bus_master1" (which you can ignore), and two called something like "28-0000057dc7da". These two directories are your thermometer addresses. Write them both down carefully ready for the next step.

3.4.3) Now we need to add these addresses to the python program. So, open up the file "Boiler_Controller.py" using Notepad. Use the Notepad text find function to look for "28-000" and it will take you to the correct part of the code. You will see a section of code like below:

Code:
#*************************************************************************************
# The two device addresses MUST be changed to match the used device, otherwise the
# software WILL NOT RUN
#*************************************************************************************
# Setup Thermometer 1 Location
device1_folder = glob.glob(base_dir + '28-0000057ce739')[0] #BOILER DEVICE - CHANGE TO MATCH YOUR DEVICE
device1_file = device1_folder + '/w1_slave'
# setup Thermomenter 2 Location
device2_folder = glob.glob(base_dir + '28-0000057dc7da')[0] #BOILER DEVICE - CHANGE TO MATCH YOUR DEVICE
device2_file = device2_folder + '/w1_slave'

All you need to do, is change the two addresses to match each of your thermometer addresses, then save the file.

3.5) Optional Code Change

3.5.1) You may wish to change the welcome screen to say something other then ROBBO'S BREWERY. If so, search for "ROBBO" and change the test within the quote marks to anything you want (the line must be less than 16 characters).

3.6) Loading the Files onto Your Raspberry Pi

3.6.1) To do this, use the following link to download WinSCP and then install it: http://sourceforge.net/projects/winscp/files/latest/download

3.6.2) Once installed, run WinSCP and it will show you a similar screen to the one you saw with Putty.exe. Enter the IP Address of your Raspberry Pi in the "Host Name" box, and then in the "User name:" box enter "pi" and in the "Password:" box, enter "raspberry", then click "Login". If you are asked about a potential security breech, just click "Update".

3.6.3) On the left hand side of the screen you will be shown your Windows PC's file system, and on the right, you will have that of your Raspberry Pi. Simply find the two python files you have created, and drag them and drop them from the PC side to the Pi side (this will put the files in the /home/pi directory on the Pi). When this is done, exit WinSCP, and move to the next section to run the program for the first time.
 
4) Running Software for the First Time

4.1) Running the python code is very simple. All you need to, is use putty to type the following line:

Code:
sudo python /home/pi/Boiler_Controller.py

4.2) When you run this, your Putty window will show you some information about the average temperature and target temperature (I used this for fault finding the code during development of the software), plus you will also see a repeat of the words that will be written on the display.

4.3) You can test the system is working by following the on-screen prompts after the welcome page (pressing the up temperature button to start), then by doing some quick checks to make sure the buttons, buzzer and relays work. the functions are as follows:

  • In HLT mode, press the temperature up and down buttons to change the target temperature. [/*:m:103yuz7a]
  • Try reducing the target temperature to a value below the current boiler temperature and checking that the relays switch off and the buzzer sounds.[/*:m:103yuz7a]
  • Increase the target temperature more than 4 degrees higher than the current boiler temperature, and then drop the target temperature below the current temperature again, checking that the alarm goes off again.[/*:m:103yuz7a]
  • Artificially heat up the thermometers (e.g. with a hair dryer) and check that the boiler temperature increases correctly.[/*:m:103yuz7a]
  • Press both temperature buttons at the same time and check that the system goes into Full Boil Mode.[/*:m:103yuz7a]

If all of this works, then you have done it all correctly. If not, then you will need to do some fault finding to work out the problem! :wha:
 
5) Making Software Run at Boot-Up

5.1) Once your system is working correctly, you will want to configure it so that it starts automatically at boot-up. You don't want to have to connect to your Pi with Putty everytime you want to do a brew!

5.2) In order to achieve this, you will need to do the following:

5.2.1) Using Putty, type the following:
Code:
sudo nano /bin/autologin.sh

5.2.2) When the nano text editor opens, type the following:
Code:
#!/bin/bash
/bin/login -f root

5.2.3) Then exit the nano text editor by pressing CTRL and X at the same time, then if asked if you want to overwrite the file, press "y" then hit enter.

5.2.4) Now change the permissions of the autologin.sh file, by typing:
Code:
chmod a+x /bin/autologin.sh

5.2.5) Now edit the file “/etc/inittab”, but typing:
Code:
sudo nano /etc/inittab

5.2.6) Find a line that looks very similar this:
Code:
1:2345:respawn:/sbin/getty --noclear 38400 tty1
change it to say:
Code:
#1:2345:respawn:/sbin/getty --noclear 38400 tty1
(i.e. just put a # at the begining of the line)

Now, immediately after the line you just changed, add the following line:
Code:
1:2345:respawn:/sbin/getty -n -l /bin/autologin.sh 38400 tty1

5.2.7) Then exit the nano text editor by pressing CTRL and X at the same time, then if asked if you want to overwrite the file, press "y" then hit enter.

5.2.8) Now edit the file “/root/.bashrc” by typing:

Code:
sudo nano /root/.bashrc
add the following 3 lines to the end of the existing lines of text:
Code:
if [[ $(tty) == '/dev/tty1' ]]; then
python /home/pi/Boiler_Controller.py
fi

3.2.9) Now, to check that these changes have worked, simply reboot your Raspberry Pi by typing:

Code:
sudo reboot

You can close your Putty window, and you should never need to use it again! The device is now fully complete and functional.

Remove the network cable from the Pi, glue all the items into the box and seal it up!

Please remember, that the program will not run if the wrong thermometers are connected, or if no thermometers are connected. Therefore, if your system includes a connector for disconnecting the boiler from the controller (for storage), then please make sure you always connect the boiler thermometers BEFORE powering on the controller.

Enjoy! :drink:

6) Frequently Asked Questions

6.1) Question: Why does my display go crazy shortly after the boiler elements are turned on sometimes?

6.1.1) Answer: This is caused by electromagnetic interference, whereby the display processor is interfered with my the change in magnetic current, and power spikes associated with the mains voltage supplying the kettle elements. This can not be completely fixed, but it can be minimised by installing a clip-on ferrite core positioned around the cables leading to the display (as close to the display as possible) http://www.maplin.co.uk/p/ferrite-clip-on-hem3012-n89ab. I have also added some code which will re-initialise the display whenever you press the "Temperature Up" button (in any mode). So, if your screen goes crazy, press the button and it should return to normal.

6.2) Question: Why is my display difficult to read at some angles because of very high contrast?

6.2.1) Answer: This is because I have simplified the display circuit slightly to save components. Pin 3 (contrast) has been connected directly to ground, however, ideally it should be held at somewhere above ground. The best way to optimise the display is to buy a small variable 10k Ohm resistor and connect one side of the resistor to 5V, the other to ground, and the variable output to pin 3 of the display. You can then use the variable resistor to set the contrast perfectly for your device and operating lighting.
 
Looks good and a neat build. My only comment would be about using 10 amp relays to control the HLT and the boiler. The relay needs to be bigger if you are going to control even a 2.4kw element load, which is 10 amps exactly. (Watts/V= Amps) Also once the HLT is up to temp it is going to be switching on and off quite rapidly to maintain the temp. This will wear out the relay fairly quickly. A better option would be using SSR's controlled with the 5v feed from the pi. The boiler won't switch on and off as much, so probably okay. The max you will be able to control is 2.4kw elements which is 10amps!!!! I would recommend a bigger relay with more capacity. One 2.4kw element is a bit small for a boiler to achieve a rolling boil on 30lt of wort. Using SSR's would be a better option if this is being used to control the HLT and boil. These would need heatsinks so the project box would have to be bigger. To manage a fermenting fridge this will be fine as the loads are not as great, even with a small tube heater.

I don't have a pi so will take your word that the code all works. But apart from the relay/SSR issue it looks good. Nice and neat.

Hope that helps.
 
Thanks Bob

The Jk12 (Tesco) kettle is 2.2 KW,
So within the spec of the relay.

However, you raise a very good point, and I should put a warning about the max capacity of the 10A rated relays.

The boiler full of water, has so much thermal inertia, that they only turn on every 10 mins of so, so I am not worried about relay wear (I have built some hysteresis into the code to minimise this problem).
 
Regarding the ability to get the water to boil, the system uses two elements powered from different relays from different mains plugs. So I have a 4.4 KW system.

Perhaps I should make this clearer.

Rob
 
Re the element I seem to remember reading that they can draw more than their amperage if dirty, ie, covered in scale...I'll ask M to contribute, as if so that 10A is a bit close to the bone :hmm:
 
However, the supplier of the kettle will have to declare their maximum draw for worst case conditions.

The cable that came with the kettle is also rated as max 10A (just like the relay), so there should be no difference in safety between the cable and the relay.
 
Now you have edited the kw/amp issue it should be fine. One thing to remember with 'How To's' is you have to write them in idiot language as the skill / ability of the members vary widely, and unfortunately some who may attempt the project may not be blessed. Well done and it might entice more to consider automation with the use of a pi or arduino. :thumb:
 
Hi Robbo100,

Looks good. Not had time to check the code for bugs but you would know if it did not work.

Constructive feedback (not meant to be critical):

1) Regarding the element, it will draw more than rated current when cold so the relay might be inadequate. In terms of scaling up, that tends to make the element overheat which can trip the cutout. Provided the trips are in place, that should not be a problem. Would be better though as Bob suggests to use an SSR - you could then even implement better control on the Pi - e.g. PWM output based on the temperature after going through a PID algorithm - that would be neat :thumb:

2) IEC connector - if using a higher power element then the IEC connector may be inadequate.

3) Add a note - if you must use a metal enclosure, it must be earthed!

4) In the electrical diagram you show switching the neutral wire. That is not good practice since the element can remain live. The live wire should always be switched.

5) Similarly the wiring in the box shows the same - needs changing.

As far as publishing on the forum goes, points 4 and 5 need addressing before it can go live since they are safety critical :cheers:
 
Thanks M :thumb: I made the same mistakes re switching the neutral, wonder where we both got that impression from :hmm:
Re the IEC connectors, I found them incredibly hard to find in a higher rating than 10A easily, and have lost the link to mine.
M, might be able to confirm this but I also remember something about SSR's used near rating will generate more heat than higher rated SSR's...important in small enclosures heat sink dependent....again could be tosh..it's just stuck in my mind :roll:

All this shames me as to what I've forgotten about my system :oops: :( ...what's that old saying...use it or lose it....very applicable to my memory.
 
Thanks Eski

I will make the changes to items 2-5 and then publish. It might take a while because I will need to rework the box and re-photograph.

Vossy, will the forum be unlocked for me to post, it will someone else upload it?

I don't really understand item 1, but will have a read-up and consider it for future improvements.

Thanks

Rob.
 
Robbo100 said:
I will make the changes to items 2-5 and then publish. It might take a while because I will need to rework the box and re-photograph.

I wonder if it could be done (sleight if hand) in a paint package - might be quicker... :cool:
 
Maybe, but since I should do this to my own unit in any case, I should probably just get on with it.

I will have a play tonight.
 
Actually, how about I leave the photos as they are, but use Paint to put a prominent note on the photo in a bright colour saying:

"This photo is incorrect in that the relays should switch the Live wire (brown) and not the Neutral wire (blue). Please ensure you follow the wiring diagram and not this photo for wiring instructions"

Then update the wiring diagram to correct the wiring.

I would also add a line in the build guidance section to mandate live wire switching.

Would you be happy with this?
 
Back
Top