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.
For those interested in the code, here it is!

Note: the code will not run unless you have the thermometers connected to the GPIO in accordance with the circuit diagram. Each thermometer has it's own physical address, and you will need to edit the script to set your own address (when you have your thermometers). I will explain how to do this on the How-To guide (or later on in this thread if someone acquires some thermometers before the guide is finished).

There are two files that need to be created, and put in the same directory. One is a simple copy of an Adafruit python script used to get the Slice of Pi/o working, and the other is the one that I have put together (this is the one you execute).

ADAfruit_I2C.py (must have this file name exactly as shown for my code to work).

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"

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/'
# Setup Thermometer 1 Location
#*************************************************************************************
# The two device addresses MUST be changed to match the used device, otherwise the
# software WILL NOT RUN
#*************************************************************************************
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()

To run the program (assuming they are in /home/pi) type:
Code:
sudo python /home/pi/Boiler_Controller.py
 
Required Components (note, other suppliers are available, but these are listed as fairly well priced examples that I found easily in the internet):

Raspberry Pi (with SD card) - http://raspberrypi.rsdelivers.com/p...berry-pi-type-b-with-8gb-sd-card/7858654.aspx

Slice of Pi/o - http://www.maplin.co.uk/p/slice-of-pio-pcb-kit-for-raspberry-pi-n98nq

16x2 Liquid Crystal Display - http://www.ebay.co.uk/itm/like/2812...ff11=ICEP3.0.0&ff12=67&ff13=80&ff14=83&ff19=0

DS18B20 1 Wire TO92 Thermometer (two needed, but the code can be easily changed to just use 1) - http://www.ebay.co.uk/itm/DS18B20-D...mponents_Supplies_ET&var=&hash=item41789ddcee

2 Channel 5v Relay Module - http://www.ebay.co.uk/itm/2-Channel...al_Components_Supplies_ET&hash=item27dca7791c

5V Buzzer (1 required) - http://www.ebay.co.uk/itm/2x-Miniat...al_Components_Supplies_ET&hash=item2584bfd869

Push Button Switches (2 required) - http://www.ebay.co.uk/itm/360665095...169/i.html?_from=R40&_nkw=360665095339&_rdc=1

Connector - IEC C13 Female Kettle Power Chassis Socket (2 required) - http://www.ebay.co.uk/itm/191005895612?ssPageName=STRK:MEWNX:IT&_trksid=p3984.m1497.l2649

IEC Male Chassis C14 Kettle Socket Plug Connector (2 required) - http://www.ebay.co.uk/itm/IEC-Male-...n_Cables_Leads_Connectors&hash=item2587372b01

3 Pin Kettle Male IEC DJ Mains Connector C14 10A Plug (2 required) - http://www.ebay.co.uk/itm/261342249787?ssPageName=STRK:MEWNX:IT&_trksid=p3984.m1497.l2649

3 Pin Kettle Female Plug IEC Mains Connector C13 10A (2 required) - http://www.ebay.co.uk/itm/251309590276?ssPageName=STRK:MEWNX:IT&_trksid=p3984.m1497.l2649

ABS BLACK PLASTIC ELECTRONICS PROJECT BOX ENCLOSURE 177 X 120 X 83MM - http://www.ebay.co.uk/itm/190937721363?ssPageName=STRK:MEWNX:IT&_trksid=p3984.m1497.l2649

Clip-On Ferrite Core (you may not need this, but I had to put one of these round the wires that connect to the 16x2 display to reduce interferece that caused the display to show garbage) - http://www.maplin.co.uk/p/ferrite-clip-on-hem3012-n89ab

You will also need the following items:

3 x 10K Ohm resistors
1 x 560 Ohm resistor
4 x M2 bolts and nuts (to hold the kettle plug sockets in the project box)
An inch square piece of electronics strip board for mounting components (not mandatory, but will give a better result)
A stereo male and female audio jack connector (or something similar) to allow you to disconnect the device from the boiler thermometers for storage)
Your existing kettle cables to cut, and connect to the IEC connectors
Some wires to patch it all together
Some 13A cable to wire the kettle plugs to the relays
A soldering iron
A hot glue gun (to stick the components into the project box)
A dremmel to cut holes in the project box
 
As much as I love all the geeky stuff this is WAY over my head. Might stick to my original idea of a stc1000. Unless someone can make them for forum members.
 
Diggerg said:
As much as I love all the geeky stuff this is WAY over my head. Might stick to my original idea of a stc1000. Unless someone can make them for forum members.

I wouldn't personally make something like this for anyone, since it would need to be compliant with CE regulations (which it probably isn't) and so can not be sold.
 
Robbo100 said:
Diggerg said:
As much as I love all the geeky stuff this is WAY over my head. Might stick to my original idea of a stc1000. Unless someone can make them for forum members.

I wouldn't personally make something like this for anyone, since it would need to be compliant with CE regulations (which it probably isn't) and so can not be sold.


If someone were to supply all the necessary bits could they then just 'donate' for your time in doing the coding? Naturally they would pay all postage so effectively is not being sold. Just one friend helping out another?
 
Way over my head too but I'll have a go anyway :lol: Thanks for the write up :thumb:
 
I intend to write the guide so that ANYONE can make this, and long as they can do the following
1) read
2) make a simple circuit using the wiring diagram I have provided
3) use a soldering iron
4) do basic functions using a windows computer (e.g. Copy and paste text and run simple windows programmes.

So, if you can do all of the above, you can make one of these.
 
How-To is now complete, and is just awaiting admin to load it onto the forum :thumb:

:cheers:
 
Got my son a pi for xmas. He may get tired of it as time goes by. So I will have a use for it when he does.

I was wondering if this would work in the following way:-

If I got a water tank, and sunk my demijohns into it up to the shoulder, would this keep them up to temp in the garage?

The missus thinks the two demijohns I have on the go at the moment are the only ones I intend to have. :lol:

If I can keep them in the garage then the dining room floor will not be littered with bottles of fermenting wine and she can get on with the job of sampling :drunk:
 
The Pi will do whatever you code it to do, whether the hot bath will work is another question, but it sounds possible in principle.
 

Latest posts

Back
Top