Python thermostat control using XRF with generic IO to sense and switch

Having built a temperature sensor out of an XRF with the Thermistor firmware, I needed a way to switch a low power heater on and off too. I did not want to add another XRF for the heater control, so I used the Generic IO firmware on the XRF to perform both functions: measure temperature and control the heater.

For this project I used:

Here is a picture of my breadboard system.

The software that interacts with the XRF is on my Linux box and will be moved to the Raspberry Pi soon. Here is what it does:

  1. first it tries to reach my device by sending the LLAP HELLO message
  2. if there is no response, it waits for the LLAP STARTED message from the device when it gets powered up
  3. Once it can communicate with the device, it checks that the device runs the right firmware
  4. Then we go into the routine of reading the analog ADC value from Analog A (pin 11, where the thermistor bridge is connected) and converting it to degrees C
  5. It transmits the temperature in a properly formed LLAP message on behalf of the sensor, as it would if it was running Thermistor firmware
  6. If the temperature is below the threshold, it switches on the heater, the heater goes off on or below the threshold

In the code below we read the temperature 5 times at 10 second intervals and then terminate. When the system gets deployed, it'll read it less frequently and for longer. But this set up served me well while testing.

Here is the Python code:

#!/usr/bin/env python

import sys
import serial
from time import sleep
from math import log

devid = "RY"                # device id to communicate with
baud = 9600                 # baud rate
port = '/dev/ttyACM0'       # serial URF port on this computer

ser = serial.Serial(port, baud)
ser.timeout = 0

#-----------------

def request(device, request, retry):
    # sends a message to 'device' with content 'request'
    # returns 'response' from from device
    # retries a number of times, pausing longer between retries each time round
    
    poll = 1
    n = 0
    while (poll == 1 and n < retry):
        sleep(n)            # sleep longer each time I don't get a response
        ser.flushInput()    # clear input buffer
        ser.write('a' + device + request)  # see if our device is online
        sleep(1)            # delay before picking up response
        response = getresponse(devid)
        if len(response) > 1:
            response = response[3:12]
            poll = 0
        n = n + 1
    return(response)

#---------------
def getresponse(devid):     # obtain a llap message from devid
    if ser.inWaiting() >= 12:
        if ser.read() == 'a':  # llap message start
                message = 'a'
                if ser.read(2) == devid:
                            # response is from the targetted node
                    message = message + devid + ser.read(9)
                else:
                    message = '0'   # not a message from devid
        else:
                message = '1'   # not a llap formatted message
    else:
        message = '2'
    return(message)

#----------------

def getstarted(devid):      # wait for the STARTED message from devid
    t = 1
    while t == 1 :
        if ser.inWaiting() >=12:
            if ser.read() == 'a':   # llap message start
                message = 'a'
                if ser.read(2) == devid:    # message is from our device
                    if ser.read(9) == 'STARTED--':  # devid has started
                        t = 0
        ser.flushInput()
        sleep(1)
    return()

#----------------
def Thermistor(ANA):        # calculate the temperature from an ADC value
    beta = 3977             # beta value for the thermistor
    Rtemp = 25.0 + 273.15   # reference temperature (25C)
    Rresi = 10150           # reference resistance at reference temperature - adjust to calibrate
    Rtherm = (2048.0/ANA - 1)*10000  # value of the resistance of the thermistor
    T = Rtemp * beta / (beta + Rtemp * (log(Rtherm/Rresi)))
    T = T - 273.15          # convert from Kelvin to Celsius
    return(T)

#-----------------


def thermostat(tempd):
    # Thermostatic control program using Generic IO firmware on the XRF
    #
    # Attempt to communicate with our device
    # first send a HELLO request to see if the device is online.
    # if there is no suitable response, sit and wait for a STARTED message
    # once there is a response, ensure it is a device that supports Generic IO
    # then read the thermistor raw ADC value, convert it to Celcius
    # send LLAP message on behalf of the generic IO sensor (as if it was the temp sensor)
    # proceed to act as a thermostat and heater control

    reportfreq = 10     # seconds between readings from device
    
    # seek a connection with the device
    response = request(devid,'HELLO----',1)
    if response[0:5] != 'HELLO':
        getstarted(devid)       # wait for device to announce itself with STARTED message

    # check the firmware is Generic IO
    response = request(devid, 'DEVTYPE--',1)
    if response[0:3] != 'GEN':  # we are not dealing with a generic device
        print 'INVALID DEVICE'
        exit()

    # read the raw ADC value at Analog A and convert to temperature
    # do this every reportfreq seconds
    # finish after p readings for now
    p = 5
    n = 0
    heater = 0
    request(devid, 'OUTA0----',1)   # ensure heating is off
    while n < p: 
        response = request(devid,'ANAA-----',1)
        if response[0:4] == 'ANAA':
            ANA = float(response[-4:])
            TempA = Thermistor(ANA)   # convert ADC value to temperature
            if (TempA >= tempd and heater == 1):
                request(devid, 'OUTA0----',1)   # turn heating off
                heater = 0
            if (TempA < tempd and heater == 0):
                request(devid, 'OUTA1----',1)   # turn heating on
                heater = 1
            print '%.2f' %TempA + ' degrees C'
            # and maybe send the proper llap message on behalf of the node
            ser.write('a' + devid + 'TMPA' + '%.2f' %TempA)
        else:   # there was an error in the message
            print 'no or invalid response to ANAA'
        sleep(reportfreq)
        n = n + 1
    request(devid, 'OUTA0----',1)   # ensure heating is off when exiting
    heater = 0
    print 'END'

#thermostat(21.0)    # run the program from the Python shell

# This program is run from the command line like this: > python thermostat.py 21.5
# If you want to run this program from the Python shell, uncomment the previous line
# and comment out the lines below to the end of the file

if __name__ == "__main__":   # run the program from the command line
   import sys
   thermostat(float(sys.argv[1]))

For me this was a nice little project. Hope others can use some of it, or improve upon it!