Commit a9fa220c authored by Tom Hartley's avatar Tom Hartley

Moved to completely rewritten version of AirPi code. Custom plugins for input and output

parent 8183487a
[AirPi]
Watchdog = False
I2CBus = 0
LCD = False
Debug = True
NetRestart = True
NetRetries = 15
Frequency = 5
[Cosm]
Enabled = True
APIKEY = xgDrTMnzVPy4otPauOVQqByN6n-SAKxHV1F0VzduMS90az0g
FEEDID = 130835
[ADC]
ADC = MCP3008
MOSI = 23
MISO = 24
CLK = 18
CS = 25
[DHT22]
Enabled = True
DHTPin = 4
[BMP085]
Enabled = True
Altitude = 43
MSLP = True
[UVI-01]
Enabled = False
ADCPin = 4
[LDR]
Enabled = True
ADCPin = 0
[TGS2600]
Enabled = False
PullUp = 22000
ADCPin = 1
[MICS-2710]
Enabled = False
ADCPin = 2
PullUp = 10000
Resistance = 90000
[MICS-5525]
Enabled = False
ADCPin = 3
PullUp = 100000
Resistance = 190000
......@@ -5,6 +5,8 @@ A Raspberry Pi weather station and air quality monitor.
This is the code for the project located at http://airpi.es
Currently it is split into Upload.py, which handles getting all the data together and uploading it to the internet, and a variety of smaller peices of code for getting the data from each of the many sensors attached to the Pi.
Currently it is split into airpi.py, as well as multiple input and multiple output plugins. airpi.py collects data from each of the input plugins specified in sensors.cfg, and then passes the data provided by them to each output defined in outputs.cfg. The code for each sensor plugin is contained in the 'sensors' folder and the code for each output plugin in the 'outputs' folder.
Some of the files are based off code for the Raspberry Pi written by Adafruit: https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code
For installation instructions, see airpi.es/kit.php
#!/usr/bin/python
from time import sleep
from sys import exit
import ConfigParser
import os.path
import datetime
import eeml
import eeml.datastream
import subprocess, os, sys
import RPi.GPIO as GPIO
from interfaces.DHT22 import DHT22
from interfaces.BMP085 import BMP085
from interfaces.MCP3008 import MCP3008, MCP3208, AQSensor, LightSensor
from interfaces.PiPlate import Adafruit_CharLCDPlate
import thinkspeak
import curses
class DataPoint():
def __init__(self,value,name,unit,decimalplaces,uploadID,shortName=None):
self.value = value
self.name = name
self.unit = unit
self.decimals = decimalplaces
self.uploadID = uploadID
self.sName = shortName
def roundedValue(self):
formatString = '{0:.' + str(self.decimals) + 'f}'
return formatString.format(self.value)
def mainUpload(stdscr):
try:
os.chdir(os.path.dirname(sys.argv[0]))
except:
pass
# Load the config file if it exists
if not os.path.isfile('AirPi.cfg'):
print "Unable to access config file : AirPi.cfg"
exit(1)
config = ConfigParser.ConfigParser()
print config.read('AirPi.cfg')
WDOGON = config.getboolean("AirPi", "Watchdog")
if WDOGON:
sleep(1)
wdog = os.open('/dev/watchdog',os.O_RDWR)
try:
# Get basic options from config file
bus = config.getint("AirPi", "I2CBus")
LCDENABLED = config.getboolean("AirPi", "LCD")
DEBUG = config.getboolean("AirPi", "Debug")
LOGGER = config.getboolean("Cosm", "Enabled")
FREQUENCY = config.getint("AirPi", "Frequency")
NETRESTART = config.getboolean("AirPi", "NetRestart")
NETRETRIES = config.getint("AirPi", "NetRetries")
if LCDENABLED:
lcd = Adafruit_CharLCDPlate.Adafruit_CharLCDPlate(busnum=bus)
lcd.clear()
lcd.backlight(lcd.ON)
lcd.message(" AirPi\n by Alyssa & Tom")
sleep(2)
lcd.clear()
lcd.message("Air Quality and \nWeather Station")
if not DEBUG:
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(22,GPIO.OUT)
GPIO.setup(21,GPIO.OUT)
# Get the ADC details from the config
SPIMOSI = config.getint("ADC", "MOSI")
SPIMISO = config.getint("ADC", "MISO")
SPICLK = config.getint("ADC", "CLK")
SPICS = config.getint("ADC", "CS")
adc = config.get("ADC", "ADC").lower()
if adc == 'mcp3008':
adc = MCP3008.MCP3008(SPIMOSI,SPIMISO,SPICLK,SPICS)
elif adc == 'mcp3208':
adc = MCP3208.MCP3208(SPIMOSI,SPIMISO,SPICLK,SPICS)
else:
print "You must specify a valid ADC in AirPi.cfg"
exit(1)
# Get the DHT22 details from the config
DHTEn = config.getboolean("DHT22", "Enabled")
if DHTEn:
DHTPin = config.getint("DHT22", "DHTPin")
dht = DHT22.DHT22(DHTPin)
# Get the BMP085 details from the config
BMPEn = config.getboolean("BMP085", "Enabled")
if BMPEn:
bmp = BMP085.BMP085(bus=bus)
BMPMSLP = config.getboolean("BMP085", "MSLP")
BMPALT = config.getint("BMP085", "Altitude")
# Get the UVI-01 details from the config
UVIEn = config.getboolean("UVI-01", "Enabled")
if UVIEn:
UVADC = config.getint("UVI-01", "ADCPin")
uvSensor = LightSensor.LightSensor(adc,UVADC)
# Get the LDR details from the config
LDREn = config.getboolean("LDR", "Enabled")
if LDREn:
LightADC = config.getint("LDR", "ADCPin")
lightSensor = LightSensor.LightSensor(adc,LightADC)
# Get the TGS2600 details from the config
TGSEn = config.getboolean("TGS2600", "Enabled")
if TGSEn:
AQADC = config.getint("TGS2600", "ADCPin")
pullup = config.getint("TGS2600", "PullUp")
airSensor = AQSensor.AQSensor(adc,AQADC,pullup)
# Get the MICS-2710 details from the config
NO2En = config.getboolean("MICS-2710", "Enabled")
if NO2En:
NO2ADC = config.getint("MICS-2710", "ADCPin")
pullup = config.getint("MICS-2710", "PullUp")
resist = config.getint("MICS-2710", "Resistance")
no2Sensor = AQSensor.AQSensor(adc,NO2ADC,resist,pullup)
# Get the MICS-5525 details from the config
COEn = config.getboolean("MICS-5525", "Enabled")
if COEn:
COADC = config.getint("MICS-5525", "ADCPin")
pullup = config.getint("MICS-5525", "PullUp")
resist = config.getint("MICS-5525", "Resistance")
coSensor = AQSensor.AQSensor(adc,COADC,resist,pullup)
if LOGGER:
API_KEY = config.get("Cosm", "APIKEY", 1)
FEED = config.getint("Cosm", "FEEDID")
API_URL = '/v2/feeds/{feednum}.xml' .format(feednum=FEED)
failCount = 0
currentDisplay = 0
ts = thinkspeak.thinkspeak()
# Continuously append data
while(True):
if WDOGON:
os.write(wdog,"0")
datas = []
if DHTEn:
dht.get_data()
d = DataPoint(dht.temp(),"Temp-DHT","C",1,-1)
if d.value != False:
datas.append(d)
datas.append(DataPoint(dht.humidity(),"Humidity","%",1,1,"Humidity"))
if TGSEn:
datas.append(DataPoint(airSensor.get_quality(),"Air Quality"," ",2,2,"AQ"))
if LDREn:
datas.append(DataPoint(lightSensor.get_light_level(),"Light Level","Lux",2,3,"Light"))
if UVIEn:
datas.append(DataPoint(uvSensor.get_uv_level(),"UV Level","UVI",2,9,"UV"))
if BMPEn:
datas.append(DataPoint(bmp.readTemperature(),"Temp-BMP","C",1,0,"Temp"))
if BMPMSLP:
datas.append(DataPoint(bmp.readMSLPressure(BMPALT),"Pressure","Pa",1,4,"Pres"))
else:
datas.append(DataPoint(bmp.readPressure(),"Pressure","Pa",1,4,"Pres"))
datas.append(DataPoint(bmp.readAltitude(),"Altitude","m",1,-1))
if NO2En:
datas.append(DataPoint(no2Sensor.get_NO2(),"NO2","ppm",3,6,"NO2"))
datas.append(DataPoint(no2Sensor.get_quality(),"NO2 ohms","ohms",1,8))
if COEn:
datas.append(DataPoint(coSensor.get_CO(),"CO","ppm",3,5,"CO"))
datas.append(DataPoint(coSensor.get_quality(),"CO ohms","ohms",1,7))
if DEBUG and (stdscr == None):
for dp in datas:
print dp.name + ":\t" + dp.roundedValue() + " " + dp.unit
if stdscr != None:
a = 0
for dp in datas:
if dp.uploadID != -1:
a+=1
stdscr.addstr(5 + (a * 2), 3, dp.name + ":\t" + dp.roundedValue() + " " + dp.unit)
stdscr.clrtoeol()
stdscr.refresh()
if LOGGER:
#Attempt to submit the data to cosm
try:
# pd: handle thinkspeak stuff as well
#ts.send(datas)
ts.pisend(datas)
#pac = eeml.datastream.Cosm(API_URL, API_KEY)
#for dp in datas:
# if dp.uploadID!=-1:
# pac.update([eeml.Data(dp.uploadID, dp.roundedValue())])
#pac.put()
if stdscr == None:
print "Uploaded data at " + str(datetime.datetime.now())
GPIO.output(22, True)
#if LCDENABLED:
#lcd.backlight(lcd.GREEN)
failCount = 0
except KeyboardInterrupt:
raise
except:
GPIO.output(21, True)
if LCDENABLED:
lcd.backlight(lcd.ON)
print "Unable to upload data at " + str(datetime.datetime.now()) + ". Check your connection?"
if NETRESTART:
failCount+=1
if failCount>NETRETRIES:
subprocess.Popen(["sudo", "/etc/init.d/networking", "restart"])
subprocess.Popen(["sudo", "/sbin/dhclient", "eth0"])
failCount=0
if LCDENABLED:
usedValues = []
for a in datas:
if a.sName!=None:
usedValues.append(a)
data1 = usedValues[currentDisplay*2]
lcd.clear()
if currentDisplay < 3:
data2 = usedValues[currentDisplay*2 + 1]
lcd.message(data1.sName + ": " + data1.roundedValue() + " " + data1.unit + "\n" + data2.sName + ": " + data2.roundedValue() + " " + data2.unit)
else:
lcd.message(data1.sName + ": " + data1.roundedValue() + " " + data1.unit + "\n")
sleep(FREQUENCY-1)
GPIO.output(22, False)
GPIO.output(21, False)
currentDisplay+=1
if currentDisplay == 4:
currentDisplay = 0
except KeyboardInterrupt:
if WDOGON:
os.write(wdog,"V")
os.close(wdog)
raise
cursesEnabled = 0
if cursesEnabled:
curses.wrapper(mainUpload)
else:
mainUpload(None)
#This file takes in inputs from a variety of sensor files, and outputs information to a variety of services
import sys
sys.dont_write_bytecode = True
import ConfigParser
import time
......@@ -29,19 +31,19 @@ for i in sensorNames:
try:
try:
filename = sensorConfig.get(i,"filename")
except:
except Exception:
print("Error: no filename config option found for sensor plugin " + i)
raise
try:
enabled = sensorConfig.getboolean(i,"enabled")
except:
except Exception:
enabled = True
#if enabled, load the plugin
if enabled:
try:
mod = __import__('sensors.'+filename,fromlist=['a']) #Why does this work?
except:
except Exception:
print("Error: could not import sensor module " + filename)
raise
......@@ -49,17 +51,17 @@ for i in sensorNames:
sensorClass = get_subclasses(mod,sensor.Sensor)
if sensorClass == None:
raise AttributeError
except:
except Exception:
print("Error: could not find a subclass of sensor.Sensor in module " + filename)
raise
try:
reqd = sensorClass.requiredData
except:
except Exception:
reqd = []
try:
opt = sensorClass.optionalData
except:
except Exception:
opt = []
pluginData = {}
......@@ -97,19 +99,19 @@ for i in outputNames:
try:
try:
filename = outputConfig.get(i,"filename")
except:
except Exception:
print("Error: no filename config option found for output plugin " + i)
raise
try:
enabled = outputConfig.getboolean(i,"enabled")
except:
except Exception:
enabled = True
#if enabled, load the plugin
if enabled:
try:
mod = __import__('outputs.'+filename,fromlist=['a']) #Why does this work?
except:
except Exception:
print("Error: could not import output module " + filename)
raise
......@@ -117,16 +119,16 @@ for i in outputNames:
outputClass = get_subclasses(mod,output.Output)
if outputClass == None:
raise AttributeError
except:
except Exception:
print("Error: could not find a subclass of output.Output in module " + filename)
raise
try:
reqd = outputClass.requiredData
except:
except Exception:
reqd = []
try:
opt = outputClass.optionalData
except:
except Exception:
opt = []
if outputConfig.has_option(i,"async"):
......
[Main]
uploadDelay = 2 ;how long to wait before uploading again
#!/usr/bin/python
import smbus
# ===========================================================================
# Adafruit_I2C Base Class
# ===========================================================================
class Adafruit_I2C :
def __init__(self, address, bus=0, debug=False):
self.address = address
self.bus = smbus.SMBus(bus)
self.debug = debug
def reverseByteOrder(self, data):
"Reverses the byte order of an int (16-bit) or long (32-bit) value"
# Courtesy Vishal Sapre
dstr = hex(data)[2:].replace('L','')
byteCount = len(dstr[::2])
val = 0
for i, n in enumerate(range(byteCount)):
d = data & 0xFF
val |= (d << (8 * (byteCount - i - 1)))
data >>= 8
return val
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:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
def writeList(self, reg, list):
"Writes an array of bytes using I2C format"
try:
self.bus.write_i2c_block_data(self.address, reg, list)
except IOError, err:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
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:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
def readS8(self, reg):
"Reads a signed 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)
if (result > 127):
return result - 256
else:
return result
except IOError, err:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
def readU16(self, reg):
"Reads an unsigned 16-bit value from the I2C device"
try:
hibyte = self.bus.read_byte_data(self.address, reg)
result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1)
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:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
def readS16(self, reg):
"Reads a signed 16-bit value from the I2C device"
try:
hibyte = self.bus.read_byte_data(self.address, reg)
if (hibyte > 127):
hibyte -= 256
result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1)
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:
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
#!/usr/bin/python
import time
import math
from Adafruit_I2C import Adafruit_I2C
# ===========================================================================
# BMP085 Class
# ===========================================================================
class BMP085 :
i2c = None
# Operating Modes
__BMP085_ULTRALOWPOWER = 0
__BMP085_STANDARD = 1
__BMP085_HIGHRES = 2
__BMP085_ULTRAHIGHRES = 3
# BMP085 Registers
__BMP085_CAL_AC1 = 0xAA # R Calibration data (16 bits)
__BMP085_CAL_AC2 = 0xAC # R Calibration data (16 bits)
__BMP085_CAL_AC3 = 0xAE # R Calibration data (16 bits)
__BMP085_CAL_AC4 = 0xB0 # R Calibration data (16 bits)
__BMP085_CAL_AC5 = 0xB2 # R Calibration data (16 bits)
__BMP085_CAL_AC6 = 0xB4 # R Calibration data (16 bits)
__BMP085_CAL_B1 = 0xB6 # R Calibration data (16 bits)
__BMP085_CAL_B2 = 0xB8 # R Calibration data (16 bits)
__BMP085_CAL_MB = 0xBA # R Calibration data (16 bits)
__BMP085_CAL_MC = 0xBC # R Calibration data (16 bits)
__BMP085_CAL_MD = 0xBE # R Calibration data (16 bits)
__BMP085_CONTROL = 0xF4
__BMP085_TEMPDATA = 0xF6
__BMP085_PRESSUREDATA = 0xF6
__BMP085_READTEMPCMD = 0x2E
__BMP085_READPRESSURECMD = 0x34
# Private Fields
_cal_AC1 = 0
_cal_AC2 = 0
_cal_AC3 = 0
_cal_AC4 = 0
_cal_AC5 = 0
_cal_AC6 = 0
_cal_B1 = 0
_cal_B2 = 0
_cal_MB = 0
_cal_MC = 0
_cal_MD = 0
# Constructor
def __init__(self, address=0x77, mode=1, bus=0, debug=False):
self.i2c = Adafruit_I2C(address, bus)
self.address = address
self.debug = debug
# Make sure the specified mode is in the appropriate range
if ((mode < 0) | (mode > 3)):
if (self.debug):
print "Invalid Mode: Using STANDARD by default"
self.mode = self.__BMP085_STANDARD
else:
self.mode = mode
# Read the calibration data
self.readCalibrationData()
def readCalibrationData(self):
"Reads the calibration data from the IC"
self._cal_AC1 = self.i2c.readS16(self.__BMP085_CAL_AC1) # INT16
self._cal_AC2 = self.i2c.readS16(self.__BMP085_CAL_AC2) # INT16
self._cal_AC3 = self.i2c.readS16(self.__BMP085_CAL_AC3) # INT16
self._cal_AC4 = self.i2c.readU16(self.__BMP085_CAL_AC4) # UINT16
self._cal_AC5 = self.i2c.readU16(self.__BMP085_CAL_AC5) # UINT16
self._cal_AC6 = self.i2c.readU16(self.__BMP085_CAL_AC6) # UINT16
self._cal_B1 = self.i2c.readS16(self.__BMP085_CAL_B1) # INT16
self._cal_B2 = self.i2c.readS16(self.__BMP085_CAL_B2) # INT16
self._cal_MB = self.i2c.readS16(self.__BMP085_CAL_MB) # INT16
self._cal_MC = self.i2c.readS16(self.__BMP085_CAL_MC) # INT16
self._cal_MD = self.i2c.readS16(self.__BMP085_CAL_MD) # INT16
if (self.debug):
self.showCalibrationData()
def showCalibrationData(self):
"Displays the calibration values for debugging purposes"
print "DBG: AC1 = %6d" % (self._cal_AC1)
print "DBG: AC2 = %6d" % (self._cal_AC2)
print "DBG: AC3 = %6d" % (self._cal_AC3)
print "DBG: AC4 = %6d" % (self._cal_AC4)
print "DBG: AC5 = %6d" % (self._cal_AC5)
print "DBG: AC6 = %6d" % (self._cal_AC6)
print "DBG: B1 = %6d" % (self._cal_B1)
print "DBG: B2 = %6d" % (self._cal_B2)
print "DBG: MB = %6d" % (self._cal_MB)
print "DBG: MC = %6d" % (self._cal_MC)
print "DBG: MD = %6d" % (self._cal_MD)
def readRawTemp(self):
"Reads the raw (uncompensated) temperature from the sensor"
self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READTEMPCMD)
time.sleep(0.005) # Wait 5ms
raw = self.i2c.readU16(self.__BMP085_TEMPDATA)
if (self.debug):
print "DBG: Raw Temp: 0x%04X (%d)" % (raw & 0xFFFF, raw)
return raw
def readRawPressure(self):
"Reads the raw (uncompensated) pressure level from the sensor"
self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READPRESSURECMD + (self.mode << 6))
if (self.mode == self.__BMP085_ULTRALOWPOWER):
time.sleep(0.005)
elif (self.mode == self.__BMP085_HIGHRES):
time.sleep(0.014)
elif (self.mode == self.__BMP085_ULTRAHIGHRES):
time.sleep(0.026)
else:
time.sleep(0.008)
msb = self.i2c.readU8(self.__BMP085_PRESSUREDATA)
lsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+1)
xlsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+2)
raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - self.mode)
if (self.debug):
print "DBG: Raw Pressure: 0x%04X (%d)" % (raw & 0xFFFF, raw)
return raw
def readTemperature(self):
"Gets the compensated temperature in degrees celcius"
UT = 0
X1 = 0
X2 = 0
B5 = 0
temp = 0.0
# Read raw temp before aligning it with the calibration values
UT = self.readRawTemp()
X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
B5 = X1 + X2
temp = ((B5 + 8) >> 4) / 10.0
if (self.debug):
print "DBG: Calibrated temperature = %f C" % temp
return temp
def readPressure(self):
"Gets the compensated pressure in pascal"
UT = 0
UP = 0
B3 = 0
B5 = 0
B6 = 0
X1 = 0
X2 = 0
X3 = 0
p = 0
B4 = 0
B7 = 0
UT = self.readRawTemp()
UP = self.readRawPressure()
# You can use the datasheet values to test the conversion results
# dsValues = True
dsValues = False
if (dsValues):
UT = 27898
UP = 23843
self._cal_AC6 = 23153
self._cal_AC5 = 32757
self._cal_MC = -8711
self._cal_MD = 2868
self._cal_B1 = 6190
self._cal_B2 = 4
self._cal_AC3 = -14383
self._cal_AC2 = -72
self._cal_AC1 = 408
self._cal_AC4 = 32741
self.mode = self.__BMP085_ULTRALOWPOWER
if (self.debug):
self.showCalibrationData()
# True Temperature Calculations
X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
B5 = X1 + X2
if (self.debug):
print "DBG: X1 = %d" % (X1)
print "DBG: X2 = %d" % (X2)
print "DBG: B5 = %d" % (B5)
print "DBG: True Temperature = %.2f C" % (((B5 + 8) >> 4) / 10.0)
# Pressure Calculations
B6 = B5 - 4000
X1 = (self._cal_B2 * (B6 * B6) >> 12) >> 11
X2 = (self._cal_AC2 * B6) >> 11
X3 = X1 + X2
B3 = (((self._cal_AC1 * 4 + X3) << self.mode) + 2) / 4
if (self.debug):
print "DBG: B6 = %d" % (B6)
print "DBG: X1 = %d" % (X1)
print "DBG: X2 = %d" % (X2)
print "DBG: B3 = %d" % (B3)
X1 = (self._cal_AC3 * B6) >> 13
X2 = (self._cal_B1 * ((B6 * B6) >> 12)) >> 16
X3 = ((X1 + X2) + 2) >> 2
B4 = (self._cal_AC4 * (X3 + 32768)) >> 15
B7 = (UP - B3) * (50000 >> self.mode)
if (self.debug):
print "DBG: X1 = %d" % (X1)
print "DBG: X2 = %d" % (X2)