How to: Raspberry Pi HLT/Boiler Controller

Discussion in 'Beer Brewing "How-To" Guides' started by Robbo100, Jan 10, 2014.

Help Support The Homebrew Forum UK by donating:

  1. Jan 10, 2014 #1

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    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.

    [​IMG]

    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 dont 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:34bhgoy6]
    • Two primary functions, HLT Mode and Boiler Mode[/*:m:34bhgoy6]
    • HLT Mode:
      [list:34bhgoy6]
    • Display shows the target HLT temperature and current boiler temperature[/*:m:34bhgoy6]
    • When target temperature is reached, the elements are switched off (and then one element is used intermittently to maintain temperature)[/*:m:34bhgoy6]
    • Audio alarm is triggered when target temperature is reached[/*:m:34bhgoy6]
    • 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:34bhgoy6]
    [/*:m:34bhgoy6]
    [*]Boiler Mode:
    • System is commanded to Boiler Mode by pressing both temperature control buttons at the same time[/*:m:34bhgoy6]
    • System turns both elements on[/*:m:34bhgoy6]
    • 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:34bhgoy6]
    • At 15:00 remaining, alarm is triggered again, for aroma hops prompt[/*:m:34bhgoy6]
    • At 00:00 the alarm is sounded again and display prompts to go into cooling mode[/*:m:34bhgoy6]
    • When Temperature Down button pressed, both elements turn off, and display shows the boiler temperature[/*:m:34bhgoy6]
    • When 28 degrees is reached, the alarm is sounded again to alert the completion of the cooling cycle[/*:m:34bhgoy6]
    [/*:m:34bhgoy6][/list:u:34bhgoy6]

    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:34bhgoy6]
    • 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:34bhgoy6]
    • 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 dont have a brewing fridge myself).[/*:m:34bhgoy6]

    Some photos of the device in action:
    In HLT Mode:
    [​IMG]

    Connected to the boiler:
    [​IMG]

    Full Boil Mode:
    [​IMG]
     
  2. Jan 10, 2014 #2

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    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:m7u0ou3m]
    • 1 x 560 Ohm resistor[/*:m:m7u0ou3m]
    • 4 x M2 bolts and nuts (to hold the kettle plug sockets in the project box)[/*:m:m7u0ou3m]
    • An inch square piece of electronics strip board for mounting components (not mandatory, but will give a better result)[/*:m:m7u0ou3m]
    • 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:m7u0ou3m]
    • Your existing kettle cables to cut, and connect to the IEC connectors[/*:m:m7u0ou3m]
    • Some wires to patch it all together[/*:m:m7u0ou3m]
    • Some 13A cable to wire the kettle plugs to the relays[/*:m:m7u0ou3m]
    • A soldering iron[/*:m:m7u0ou3m]
    • A hot glue gun or similar fixing method (to stick the components into the project box)[/*:m:m7u0ou3m]
    • A dremmel to cut holes in the project box[/*:m:m7u0ou3m]

    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:m7u0ou3m]
    • 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:m7u0ou3m]
    • 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:m7u0ou3m]
    • Ensure your 240V cables are 2.5mm cable (to carry the 10A load).[/*:m:m7u0ou3m]
    • 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:m7u0ou3m]
    • Ensure you use your relays to switch the LIVE (brown) cable from your kettle elements, not the NEUTRAL (blue) cable[/*:m:m7u0ou3m]
    • 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:m7u0ou3m]
    • 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:m7u0ou3m]

    Click on the image to get a larger version
    [​IMG]


    This is what my device looks like partly installed in the project box:
    [​IMG]

    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):
    [​IMG]

    I have one set of mains power inputs and outputs on each side of the project box, as shown here:
    [​IMG]

    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:
    [​IMG]

    Outside the boiler:
    [​IMG]

    Both probes (viewed from outside):
    [​IMG]
     
  3. Jan 10, 2014 #3

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    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:

    [​IMG]

    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:

    [​IMG]

    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:
    pi@192.168.1.4'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 = ((temperature1+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 text 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. Jan 10, 2014 #4

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    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:162qz2v3]
    • 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:162qz2v3]
    • 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:162qz2v3]
    • Artificially heat up the thermometers (e.g. with a hair dryer) and check that the boiler temperature increases correctly.[/*:m:162qz2v3]
    • Press both temperature buttons at the same time and check that the system goes into Full Boil Mode.[/*:m:162qz2v3]

    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. Jan 10, 2014 #5

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    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 1: 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 2: 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.
     
  6. Jan 10, 2014 #6

    EskiBrew

    EskiBrew

    EskiBrew

    Regular.

    Joined:
    Oct 7, 2008
    Messages:
    303
    Likes Received:
    6
    Location:
    High Peak District
    Nicely done :cool:

    Please remember people :electric: can kill!
     
  7. Jan 10, 2014 #7

    Fil

    Fil

    Fil

    Landlord. Supporting Member

    Joined:
    Oct 17, 2011
    Messages:
    4,115
    Likes Received:
    975
    Cheers, Cracking Job with both the project and the write up.... think a few imitations will appear soon ;)
     
  8. Jan 10, 2014 #8

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    Excellent.

    Please post photos of your results!
     
  9. Jan 11, 2014 #9

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    Coming soon: View your HLT/Boiler Temperature and Element Status over your network using a web browser on another PC.

    [​IMG]
     
  10. Jan 18, 2014 #10

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    Just to keep everyone informed. I have got the graphing function working for the boiler/HLT but have now adapted the device software (with no hardware changes) to also control a brewing fridge, with the following features:

    - The fridge controller is built upon my existing boiler controller code, and the software automatically detects if the device is connected to the boiler/HLT or the Fridge and runs the appropriate code. I have also added error checking and reporting for the thermometers, so the users knows which one is broken or unplugged.

    - At startup the user selects the number of days that are required for fermentation.

    - next, the device cycles through each day, asking the user to set the desired fermentation temperature.

    - when complete, the device saves the above data to a file, so that it can be automatically recovered in the event of a power failure to the device.

    - whilst active the device works without user intervention, to regulate the wort temperature to the appropriate temperature for each given day.

    - the device display cycles through a range of parameters (showing each for 8 seconds), such as temperature data, target temperatures, remaining days, current day, fridge/heater status, and the date and time of the last power interruption (if any).

    - the device includes fridge compressor protection, so that the fridge can never be turned on within 3 mins of it being turned off.

    - the device data logs all of the key parameters and displays them on a graph to any computer on your network via your web browser. Currently it is set to log every 5 mins.

    I am currently doing a 10 day test with no fridge or heater connected, to make sure it doesn't have any bugs, and then I will do a real test with a FV full of water in the real fridge.

    Fingers crossed it all works fine. I will post the code when proven working.
     
  11. Jan 19, 2014 #11

    hairybiker

    hairybiker

    hairybiker

    Landlord.

    Joined:
    Jun 30, 2010
    Messages:
    692
    Likes Received:
    4
    Location:
    North of 't Border
    Quick Q what are you powering the pi from? I don't see a psu mentioned.
     
  12. Jan 19, 2014 #12

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    A mobile phone charger with a micro-USB cable (this is the standard way to power a Pi).
     
  13. Jan 19, 2014 #13

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    So are many (any) people thinking of making one of these?
     
  14. Jan 19, 2014 #14

    hairybiker

    hairybiker

    hairybiker

    Landlord.

    Joined:
    Jun 30, 2010
    Messages:
    692
    Likes Received:
    4
    Location:
    North of 't Border
    I thought I would just mention it as you didn't specify any psu and no wiring was shown powering the pi.
    Wouldn't it also have to be at least 1A as you are driving the relays & the display as well as the pi?

    I have a spare one that I am thinking about using for this, just need the slice of pi/o (I have the slice of pi!) kit, and a box. The pi would/may have to be removable though (I use it to listen to internet radio at the mo).
     
  15. Jan 19, 2014 #15

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    You could adapt the code to trigger the GPIOs from your slice of Pi, rather than the current V1.2 board.

    However, for the cost of a new Slice of Pi/o 1.2, it probably isn't worth the bother.
     
  16. Jan 19, 2014 #16

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    The power supply for the Pi is mentioned at para 3.2.1
     
  17. Jan 19, 2014 #17

    hairybiker

    hairybiker

    hairybiker

    Landlord.

    Joined:
    Jun 30, 2010
    Messages:
    692
    Likes Received:
    4
    Location:
    North of 't Border
    Being pedantic that is only for the setup where it doesn't need to be fitted to the box :D

    I will get a pi/o later when SHMBO gets paid (not having any money myself :whistle: ) I am waiting for the display & relays to come from China anyway so no rush. (just over 1 ea !)
     
  18. Jan 19, 2014 #18

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    Fair play! I am delighted that someone is going to use the How-To!

    I have found a glitch with the fridge code which needs some attention, but won't get time to fix it for a couple of weeks, but it shouldn't be a big effort.
     
  19. Jan 19, 2014 #19

    bob-the-dog

    bob-the-dog

    bob-the-dog

    Active Member

    Joined:
    Sep 25, 2012
    Messages:
    44
    Likes Received:
    0
    Location:
    Hull
    I a going to have a go. Had. a pi since they came out and this how to has allowed to to understand more about the pi than other places I've looked at and I'm just starting to get geared up for AG. A really great 'how to'.
    Would it be possible to add a timer to automatically start heating the HLT to save time on brew day?
    Cheers.
     
  20. Jan 19, 2014 #20

    Robbo100

    Robbo100

    Robbo100

    Regular.

    Joined:
    Dec 21, 2012
    Messages:
    401
    Likes Received:
    21
    Nice feature request!!!

    Hmmm I don't see why not. I should be able to add this in about 60 lines of code I should think.

    It will also reduce energy costs by allowing the liquor to get to room temperature over night.

    However, I might recommend a boil dry detection function before activating this sort of unattended boiler use. In my HLT I have put the two thermometer bulkheads just above the elements. I was planning on using the low resistivity of the fluid as a switch, that is "turned off" when there is no water covering the bulkheads.
     

Share This Page