From 425a544cd74293afeff19c6193305c2afe0c70c3 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 15:46:30 +0200 Subject: [PATCH 1/9] New Module added DS18S20 Code refactoring --- .idea/vcs.xml | 6 + airpi.py | 354 +++++++++++++++++++----------------------- helpers/__init__.py | 0 outputs.cfg | 2 +- outputs/output.py | 15 +- outputs/print.py | 21 +-- outputs/xively.py | 39 ++--- sensors.cfg | 21 ++- sensors/analogue.py | 121 ++++++++++----- sensors/bmp085.py | 89 ++++++----- sensors/bmpBackend.py | 256 ------------------------------ sensors/bmpbackend.py | 258 ++++++++++++++++++++++++++++++ sensors/dht22.py | 86 +++++----- sensors/ds18s20.py | 47 ++++++ sensors/mcp3008.py | 119 +++++++------- sensors/sensor.py | 17 +- 16 files changed, 774 insertions(+), 677 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 helpers/__init__.py delete mode 100644 sensors/bmpBackend.py create mode 100644 sensors/bmpbackend.py create mode 100644 sensors/ds18s20.py diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/airpi.py b/airpi.py index a82aa08..285b15e 100644 --- a/airpi.py +++ b/airpi.py @@ -1,203 +1,169 @@ -#This file takes in inputs from a variety of sensor files, and outputs information to a variety of services +# 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 RPi.GPIO as GPIO import ConfigParser import time import inspect import os -from sys import exit + +from helpers import exceptions from sensors import sensor from outputs import output -def get_subclasses(mod,cls): - for name, obj in inspect.getmembers(mod): - if hasattr(obj, "__bases__") and cls in obj.__bases__: - return obj - - -if not os.path.isfile('sensors.cfg'): - print "Unable to access config file: sensors.cfg" - exit(1) - -sensorConfig = ConfigParser.SafeConfigParser() -sensorConfig.read('sensors.cfg') - -sensorNames = sensorConfig.sections() - -GPIO.setwarnings(False) -GPIO.setmode(GPIO.BCM) #Use BCM GPIO numbers. - -sensorPlugins = [] -for i in sensorNames: - try: - try: - filename = sensorConfig.get(i,"filename") - except Exception: - print("Error: no filename config option found for sensor plugin " + i) - raise - try: - enabled = sensorConfig.getboolean(i,"enabled") - except Exception: - enabled = True - - #if enabled, load the plugin - if enabled: - try: - mod = __import__('sensors.'+filename,fromlist=['a']) #Why does this work? - except Exception: - print("Error: could not import sensor module " + filename) - raise - - try: - sensorClass = get_subclasses(mod,sensor.Sensor) - if sensorClass == None: - raise AttributeError - except Exception: - print("Error: could not find a subclass of sensor.Sensor in module " + filename) - raise - - try: - reqd = sensorClass.requiredData - except Exception: - reqd = [] - try: - opt = sensorClass.optionalData - except Exception: - opt = [] - - pluginData = {} - - class MissingField(Exception): pass - - for requiredField in reqd: - if sensorConfig.has_option(i,requiredField): - pluginData[requiredField]=sensorConfig.get(i,requiredField) - else: - print "Error: Missing required field '" + requiredField + "' for sensor plugin " + i - raise MissingField - for optionalField in opt: - if sensorConfig.has_option(i,optionalField): - pluginData[optionalField]=sensorConfig.get(i,optionalField) - instClass = sensorClass(pluginData) - sensorPlugins.append(instClass) - print ("Success: Loaded sensor plugin " + i) - except Exception as e: #add specific exception for missing module - print("Error: Did not import sensor plugin " + i ) - raise e - - -if not os.path.isfile("outputs.cfg"): - print "Unable to access config file: outputs.cfg" - -outputConfig = ConfigParser.SafeConfigParser() -outputConfig.read("outputs.cfg") - -outputNames = outputConfig.sections() - -outputPlugins = [] - -for i in outputNames: - try: - try: - filename = outputConfig.get(i,"filename") - except Exception: - print("Error: no filename config option found for output plugin " + i) - raise - try: - enabled = outputConfig.getboolean(i,"enabled") - except Exception: - enabled = True - - #if enabled, load the plugin - if enabled: - try: - mod = __import__('outputs.'+filename,fromlist=['a']) #Why does this work? - except Exception: - print("Error: could not import output module " + filename) - raise - - try: - outputClass = get_subclasses(mod,output.Output) - if outputClass == None: - raise AttributeError - except Exception: - print("Error: could not find a subclass of output.Output in module " + filename) - raise - try: - reqd = outputClass.requiredData - except Exception: - reqd = [] - try: - opt = outputClass.optionalData - except Exception: - opt = [] - - if outputConfig.has_option(i,"async"): - async = outputConfig.getbool(i,"async") - else: - async = False - - pluginData = {} - - class MissingField(Exception): pass - - for requiredField in reqd: - if outputConfig.has_option(i,requiredField): - pluginData[requiredField]=outputConfig.get(i,requiredField) - else: - print "Error: Missing required field '" + requiredField + "' for output plugin " + i - raise MissingField - for optionalField in opt: - if outputConfig.has_option(i,optionalField): - pluginData[optionalField]=outputConfig.get(i,optionalField) - instClass = outputClass(pluginData) - instClass.async = async - outputPlugins.append(instClass) - print ("Success: Loaded output plugin " + i) - except Exception as e: #add specific exception for missing module - print("Error: Did not import output plugin " + i ) - raise e - -if not os.path.isfile("settings.cfg"): - print "Unable to access config file: settings.cfg" - -mainConfig = ConfigParser.SafeConfigParser() -mainConfig.read("settings.cfg") - -lastUpdated = 0 -delayTime = mainConfig.getfloat("Main","uploadDelay") -redPin = mainConfig.getint("Main","redPin") -greenPin = mainConfig.getint("Main","greenPin") -GPIO.setup(redPin,GPIO.OUT,initial=GPIO.LOW) -GPIO.setup(greenPin,GPIO.OUT,initial=GPIO.LOW) -while True: - curTime = time.time() - if (curTime-lastUpdated)>delayTime: - lastUpdated = curTime - data = [] - #Collect the data from each sensor - for i in sensorPlugins: - dataDict = {} - val = i.getVal() - if val==None: #this means it has no data to upload. - continue - dataDict["value"] = i.getVal() - dataDict["unit"] = i.valUnit - dataDict["symbol"] = i.valSymbol - dataDict["name"] = i.valName - dataDict["sensor"] = i.sensorName - data.append(dataDict) - working = True - for i in outputPlugins: - working = working and i.outputData(data) - if working: - print "Uploaded successfully" - GPIO.output(greenPin,GPIO.HIGH) - else: - print "Failed to upload" - GPIO.output(redPin,GPIO.HIGH) - time.sleep(1) - GPIO.output(greenPin,GPIO.LOW) - GPIO.output(redPin,GPIO.LOW) +sys.dont_write_bytecode = True + + +def get_subclasses(mod, cls): + for name, obj in inspect.getmembers(mod): + if hasattr(obj, "__bases__") and cls in obj.__bases__: + return obj + + +class AirPi: + CONST_SENSORSCFG = "sensors.cfg" + CONST_OUTPUTSCFG = "outputs.cfg" + CONST_SETTINGSCFG = "settings.cfg" + + def __init__(self): + if not os.path.isfile(self.CONST_SENSORSCFG): + raise exceptions.AirPiException("Unable to access config file: sensors.cfg") + + if not os.path.isfile(self.CONST_OUTPUTSCFG): + raise exceptions.AirPiException("Unable to access config file: outputs.cfg") + + if not os.path.isfile(self.CONST_SETTINGSCFG): + raise exceptions.AirPiException("Unable to access config file: settings.cfg") + + def load_from_configuration(self, configuration_name): + config = ConfigParser.SafeConfigParser() + config.read(configuration_name) + sections = config.sections() + + print "Configurations found for " + configuration_name + + # We need to set the module + module_dir = "sensors" + if configuration_name == self.CONST_OUTPUTSCFG: + module_dir = "outputs" + + inst_array = [] + for section in sections: + print section + try: + enabled = True + if config.has_option(section, "enabled"): + enabled = config.getboolean(section, "enabled") + + async = False + if config.has_option(section, "async"): + async = config.getboolean(section, "async") + + if enabled: + if not config.has_option(section, "filename"): + raise exceptions.AirPiException("No filename config option found for " + module_dir + + " plugin " + section) + + file_name = config.get(section, "filename") + mod = __import__(module_dir + '.' + file_name, fromlist=['a']) + + # Load subclasses + sub_class = None + if module_dir == "sensors": + sub_class = get_subclasses(mod, sensor.Sensor) + + if module_dir == "outputs": + sub_class = get_subclasses(mod, output.Output) + + if sub_class is None: + raise exceptions.AirPiException("Could not find a subclass for " + module_dir + " in module" + + file_name) + reqd = sub_class.requiredOptions + opt = sub_class.optionalOptions + plugin_data = {} + + # Get required configurations + for required_field in reqd: + if config.has_option(section, required_field): + plugin_data[required_field] = config.get(section, required_field) + else: + raise exceptions.AirPiException( + "Missing required field '" + required_field + "' for sensor plugin " + section) + + # Get optional configurations + for optional in opt: + if config.has_option(section, optional): + plugin_data[optional] = config.get(section, optional) + + # Create an instance of that sensor + inst_class = sub_class(plugin_data) + inst_class.async = async + + # Append that instance for processing + inst_array.append(inst_class) + + print ("Success: Loaded "+module_dir+" plugin " + section) + + except exceptions.AirPiException as ae: + raise ae + except Exception: + raise exceptions.AirPiException("Did not import "+module_dir+" plugin: " + section) + + return inst_array + + def main(self): + # Read main configurations + main_config = ConfigParser.SafeConfigParser() + main_config.read(self.CONST_SETTINGSCFG) + delay_time = main_config.getfloat("Main", "uploadDelay") + red_pin = main_config.getint("Main", "redPin") + green_pin = main_config.getint("Main", "greenPin") + + # Set GPIO settings + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers. + GPIO.setup(red_pin, GPIO.OUT, initial=GPIO.LOW) + GPIO.setup(green_pin, GPIO.OUT, initial=GPIO.LOW) + + # Load plugins + sensor_plugins = self.load_from_configuration(self.CONST_SENSORSCFG) + output_plugins = self.load_from_configuration(self.CONST_OUTPUTSCFG) + + last_updated = 0 + + while True: + current_time = time.time() + if (current_time - last_updated) > delay_time: + last_updated = current_time + data = [] + + # Collect the data from each sensor + for i in sensor_plugins: + data_dict = {} + val = i.getVal() + if val is None: # this means it has no data to upload. + continue + data_dict["value"] = i.getVal() + data_dict["unit"] = i.valUnit + data_dict["symbol"] = i.valSymbol + data_dict["name"] = i.valName + data_dict["sensor"] = i.sensorName + data.append(data_dict) + working = True + + for i in output_plugins: + working = working and i.output_data(data) + + if working: + print "Uploaded successfully" + GPIO.output(green_pin, GPIO.HIGH) + else: + print "Failed to upload" + GPIO.output(red_pin, GPIO.HIGH) + + time.sleep(1) + GPIO.output(green_pin, GPIO.LOW) + GPIO.output(red_pin, GPIO.LOW) + + +if __name__ == "__main__": + AirPi().main() diff --git a/helpers/__init__.py b/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/outputs.cfg b/outputs.cfg index 6326dba..6aeaa68 100644 --- a/outputs.cfg +++ b/outputs.cfg @@ -4,6 +4,6 @@ enabled=on [Xively] filename=xively -enabled=on +enabled=off APIKey=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY FeedID=XXXXXXXXXX diff --git a/outputs/output.py b/outputs/output.py index c27ec9c..93002a8 100644 --- a/outputs/output.py +++ b/outputs/output.py @@ -1,6 +1,9 @@ -class Output(): - def __init__(self,data): - raise NotImplementedError - - def outputData(self,dataPoints): - raise NotImplementedError +class Output: + requiredOptions = [] + optionalOptions = [] + + def __init__(self, data): + raise NotImplementedError + + def output_data(self, data_points): + raise NotImplementedError diff --git a/outputs/print.py b/outputs/print.py index 9185556..9d6b8be 100644 --- a/outputs/print.py +++ b/outputs/print.py @@ -1,14 +1,15 @@ import output import datetime + class Print(output.Output): - requiredData = [] - optionalData = [] - def __init__(self,data): - pass - def outputData(self,dataPoints): - print "" - print "Time: " + str(datetime.datetime.now()) - for i in dataPoints: - print i["name"] + ": " + str(i["value"]) + " " + i["symbol"] - return True + + def __init__(self,data): + pass + + def output_data(self, data_points): + print "" + print "Time: " + str(datetime.datetime.now()) + for i in data_points: + print i["name"] + ": " + str(i["value"]) + " " + i["symbol"] + return True diff --git a/outputs/xively.py b/outputs/xively.py index 2bb72a1..813aff8 100644 --- a/outputs/xively.py +++ b/outputs/xively.py @@ -2,22 +2,25 @@ import requests import json + class Xively(output.Output): - requiredData = ["APIKey","FeedID"] - optionalData = [] - def __init__(self,data): - self.APIKey=data["APIKey"] - self.FeedID=data["FeedID"] - def outputData(self,dataPoints): - arr = [] - for i in dataPoints: - arr.append({"id":i["name"],"current_value":i["value"]}) - a = json.dumps({"version":"1.0.0","datastreams":arr}) - try: - z = requests.put("https://api.xively.com/v2/feeds/"+self.FeedID+".json",headers={"X-ApiKey":self.APIKey},data=a) - if z.text!="": - print "Xively Error: " + z.text - return False - except Exception: - return False - return True + requiredOptions = ["APIKey", "FeedID"] + + def __init__(self, data): + self.APIKey = data["APIKey"] + self.FeedID = data["FeedID"] + + def output_data(self, data_points): + arr = [] + for i in data_points: + arr.append({"id": i["name"], "current_value": i["value"]}) + a = json.dumps({"version": "1.0.0", "datastreams": arr}) + try: + z = requests.put("https://api.xively.com/v2/feeds/" + self.FeedID + ".json", + headers={"X-ApiKey": self.APIKey}, data=a) + if z.text != "": + print "Xively Error: " + z.text + return False + except Exception: + return False + return True diff --git a/sensors.cfg b/sensors.cfg index ffe9398..0822d3e 100644 --- a/sensors.cfg +++ b/sensors.cfg @@ -1,12 +1,12 @@ [BMP085-temp] filename=bmp085 -enabled=on +enabled=off measurement=temp i2cbus = 1 [BMP085-pres] filename=bmp085 -enabled=on +enabled=off measurement=pres mslp=on i2cbus = 1 @@ -18,7 +18,7 @@ enabled=on [DHT22] filename=dht22 -enabled=on +enabled=off measurement=humidity pinNumber=4 @@ -26,7 +26,7 @@ pinNumber=4 filename=analogue enabled=on pullUpResistance=10000 -measurement=Light_Level +measurement=light adcPin = 0 sensorName = LDR @@ -40,7 +40,7 @@ sensorName=TGS2600 [MiCS-2710] filename=analogue -enabled=on +enabled=off pullDownResistance=10000 measurement=Nitrogen_Dioxide adcPin=2 @@ -48,7 +48,7 @@ sensorName=MiCS-2710 [MiCS-5525] filename=analogue -enabled=on +enabled=off pullDownResistance=100000 measurement=Carbon_Monoxide adcPin=3 @@ -56,7 +56,14 @@ sensorName=MiCS-5525 [Mic] filename=analogue -enabled=on +enabled=off measurement=Volume adcPin=4 sensorName=ABM_713_RC + +[DS18S20] +filename=ds18s20 +enabled=on +pinNumber=4 +sensorName=DS18S20 + diff --git a/sensors/analogue.py b/sensors/analogue.py index f4064d0..84b6001 100644 --- a/sensors/analogue.py +++ b/sensors/analogue.py @@ -1,45 +1,82 @@ import mcp3008 import sensor + + class Analogue(sensor.Sensor): - requiredData = ["adcPin","measurement","sensorName"] - optionalData = ["pullUpResistance","pullDownResistance"] - def __init__(self, data): - self.adc = mcp3008.MCP3008.sharedClass - self.adcPin = int(data["adcPin"]) - self.valName = data["measurement"] - self.sensorName = data["sensorName"] - self.pullUp, self.pullDown = None, None - if "pullUpResistance" in data: - self.pullUp = int(data["pullUpResistance"]) - if "pullDownResistance" in data: - self.pullDown = int(data["pullDownResistance"]) - class ConfigError(Exception): pass - if self.pullUp!=None and self.pullDown!=None: - print "Please choose whether there is a pull up or pull down resistor for the " + self.valName + " measurement by only entering one of them into the settings file" - raise ConfigError - self.valUnit = "Ohms" - self.valSymbol = "Ohms" - if self.pullUp==None and self.pullDown==None: - self.valUnit = "millvolts" - self.valSymbol = "mV" - - def getVal(self): - result = self.adc.readADC(self.adcPin) - if result==0: - print "Check wiring for the " + self.sensorName + " measurement, no voltage detected on ADC input " + str(self.adcPin) - return None - if result == 1023: - print "Check wiring for the " + self.sensorName + " measurement, full voltage detected on ADC input " + str(self.adcPin) - return None - vin = 3.3 - vout = float(result)/1023 * vin - - if self.pullDown!=None: - #Its a pull down resistor - resOut = (self.pullDown*vin)/vout - self.pullDown - elif self.pullUp!=None: - resOut = self.pullUp/((vin/vout)-1) - else: - resOut = vout*1000 - return resOut - + requiredOptions = ["adcPin", "measurement", "sensorName"] + optionalOptions = ["pullUpResistance", "pullDownResistance"] + + def __init__(self, data): + + self.adc = mcp3008.MCP3008.sharedClass + + self.adcPin = int(data["adcPin"]) + self.valName = data["measurement"] + self.sensorName = data["sensorName"] + self.pullUp, self.pullDown = None, None + self.valUnit = "Ohms" + self.valSymbol = "Ohms" + + if "pullUpResistance" in data: + self.pullUp = int(data["pullUpResistance"]) + + if "pullDownResistance" in data: + self.pullDown = int(data["pullDownResistance"]) + + if self.pullUp is not None and self.pullDown is not None: + print "Please choose whether there is a pull up or pull down resistor for the " + self.valName + " measurement by only entering one of them into the settings file " + raise ConfigError + + if self.pullUp is None and self.pullDown is None: + self.valUnit = "millvolts" + self.valSymbol = "mV" + + def getVal(self): + + # Get result from adc + result = self.adc.readADC(self.adcPin) + + print self.adc.readADC(0) + print self.adc.readADC(1) + print self.adc.readADC(2) + print self.adc.readADC(3) + print self.adc.readADC(4) + print self.adc.readADC(5) + print self.adc.readADC(6) + print self.adc.readADC(7) + + if result == 0: + print "Check wiring for the " + self.sensorName + " measurement, no voltage detected on ADC input " + str(self.adcPin) + return None + if result == 1023: + print "Check wiring for the " + self.sensorName + " measurement, full voltage detected on ADC input " + str(self.adcPin) + return None + + if self.pullDown is not None: + return self.getPullDown(result) + elif self.pullUp is not None: + return self.getPullUp(result) + else: + return self.getResult(result) + + def getPullDown(self, result): + vin = 3.3 + vout = float(result) / 1023 * vin + return (self.pullDown * vin) / vout - self.pullDown + + def getPullUp(self, result): + vin = 3.3 + vout = float(result) / 1023 * vin + return self.pullUp / ((vin / vout) - 1) + + def getResult(self, result): + vin = 3.3 + vout = float(result) / 1023 * vin + return vout * 1000 + + def get_data(self): + pass + + +class ConfigError(Exception): + pass diff --git a/sensors/bmp085.py b/sensors/bmp085.py index ce2855e..b1dff1c 100644 --- a/sensors/bmp085.py +++ b/sensors/bmp085.py @@ -1,46 +1,51 @@ import sensor -import bmpBackend +import bmpbackend + class BMP085(sensor.Sensor): - bmpClass = None - requiredData = ["measurement","i2cbus"] - optionalData = ["altitude","mslp","unit"] - def __init__(self,data): - self.sensorName = "BMP085" - if "temp" in data["measurement"].lower(): - self.valName = "Temperature" - self.valUnit = "Celsius" - self.valSymbol = "C" - if "unit" in data: - if data["unit"]=="F": - self.valUnit = "Fahrenheit" - self.valSymbol = "F" - elif "pres" in data["measurement"].lower(): - self.valName = "Pressure" - self.valSymbol = "hPa" - self.valUnit = "Hectopascal" - self.altitude = 0 - self.mslp = False - if "mslp" in data: - if data["mslp"].lower in ["on","true","1","yes"]: - self.mslp = True - if "altitude" in data: - self.altitude=data["altitude"] - else: - print "To calculate MSLP, please provide an 'altitude' config setting (in m) for the BMP085 pressure module" - self.mslp = False - if (BMP085.bmpClass==None): - BMP085.bmpClass = bmpBackend.BMP085(bus=int(data["i2cbus"])) - return + def get_data(self): + pass + + bmpClass = None + requiredData = ["measurement", "i2cbus"] + optionalData = ["altitude", "mslp", "unit"] + + def __init__(self, data): + self.sensorName = "BMP085" + if "temp" in data["measurement"].lower(): + self.valName = "Temperature" + self.valUnit = "Celsius" + self.valSymbol = "C" + if "unit" in data: + if data["unit"] == "F": + self.valUnit = "Fahrenheit" + self.valSymbol = "F" + elif "pres" in data["measurement"].lower(): + self.valName = "Pressure" + self.valSymbol = "hPa" + self.valUnit = "Hectopascal" + self.altitude = 0 + self.mslp = False + if "mslp" in data: + if data["mslp"].lower in ["on", "true", "1", "yes"]: + self.mslp = True + if "altitude" in data: + self.altitude = data["altitude"] + else: + print "To calculate MSLP, please provide an 'altitude' config setting (in m) for the BMP085 pressure module" + self.mslp = False + if BMP085.bmpClass is None: + BMP085.bmpClass = bmpbackend.BMP085(bus=int(data["i2cbus"])) + return - def getVal(self): - if self.valName == "Temperature": - temp = BMP085.bmpClass.readTemperature() - if self.valUnit == "Fahrenheit": - temp = temp * 1.8 + 32 - return temp - elif self.valName == "Pressure": - if self.mslp: - return BMP085.bmpClass.readMSLPressure(self.altitude) * 0.01 #to convert to Hectopascals - else: - return BMP085.bmpClass.readPressure() * 0.01 #to convert to Hectopascals + def getVal(self): + if self.valName == "Temperature": + temp = BMP085.bmpClass.readTemperature() + if self.valUnit == "Fahrenheit": + temp = temp * 1.8 + 32 + return temp + elif self.valName == "Pressure": + if self.mslp: + return BMP085.bmpClass.readMSLPressure(self.altitude) * 0.01 # to convert to Hectopascals + else: + return BMP085.bmpClass.readPressure() * 0.01 # to convert to Hectopascals diff --git a/sensors/bmpBackend.py b/sensors/bmpBackend.py deleted file mode 100644 index b5ba677..0000000 --- a/sensors/bmpBackend.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/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) - print "DBG: B4 = %d" % (B4) - print "DBG: B7 = %d" % (B7) - - if (B7 < 0x80000000): - p = (B7 * 2) / B4 - else: - p = (B7 / B4) * 2 - - X1 = (p >> 8) * (p >> 8) - X1 = (X1 * 3038) >> 16 - X2 = (-7375 * p) >> 16 - if (self.debug): - print "DBG: p = %d" % (p) - print "DBG: X1 = %d" % (X1) - print "DBG: X2 = %d" % (X2) - - p = p + ((X1 + X2 + 3791) >> 4) - if (self.debug): - print "DBG: Pressure = %d Pa" % (p) - - return p - - def readAltitude(self, seaLevelPressure=101325): - "Calculates the altitude in meters" - altitude = 0.0 - pressure = float(self.readPressure()) - altitude = 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903)) - if (self.debug): - print "DBG: Altitude = %d" % (altitude) - return altitude - - return 0 - - def readMSLPressure(self, altitude): - "Calculates the mean sea level pressure" - pressure = float(self.readPressure()) - T0 = float(altitude) / 44330 - T1 = math.pow(1 - T0, 5.255) - mslpressure = pressure / T1 - return mslpressure - -if __name__=="__main__": - bmp = BMP085() - print str(bmp.readTemperature()) + " C" - print str(bmp.readPressure()) + " Pa" diff --git a/sensors/bmpbackend.py b/sensors/bmpbackend.py new file mode 100644 index 0000000..c822688 --- /dev/null +++ b/sensors/bmpbackend.py @@ -0,0 +1,258 @@ +#!/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) + print "DBG: B4 = %d" % (B4) + print "DBG: B7 = %d" % (B7) + + if (B7 < 0x80000000): + p = (B7 * 2) / B4 + else: + p = (B7 / B4) * 2 + + X1 = (p >> 8) * (p >> 8) + X1 = (X1 * 3038) >> 16 + X2 = (-7375 * p) >> 16 + if (self.debug): + print "DBG: p = %d" % (p) + print "DBG: X1 = %d" % (X1) + print "DBG: X2 = %d" % (X2) + + p = p + ((X1 + X2 + 3791) >> 4) + if (self.debug): + print "DBG: Pressure = %d Pa" % (p) + + return p + + def readAltitude(self, seaLevelPressure=101325): + "Calculates the altitude in meters" + altitude = 0.0 + pressure = float(self.readPressure()) + altitude = 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903)) + if (self.debug): + print "DBG: Altitude = %d" % (altitude) + return altitude + + return 0 + + def readMSLPressure(self, altitude): + "Calculates the mean sea level pressure" + pressure = float(self.readPressure()) + T0 = float(altitude) / 44330 + T1 = math.pow(1 - T0, 5.255) + mslpressure = pressure / T1 + return mslpressure + + +if __name__ == "__main__": + bmp = BMP085() + print str(bmp.readTemperature()) + " C" + print str(bmp.readPressure()) + " Pa" diff --git a/sensors/dht22.py b/sensors/dht22.py index 1e26189..629d79e 100644 --- a/sensors/dht22.py +++ b/sensors/dht22.py @@ -1,45 +1,51 @@ import sensor import dhtreader import time + + class DHT22(sensor.Sensor): - requiredData = ["measurement","pinNumber"] - optionalData = ["unit"] - def __init__(self,data): - dhtreader.init() - dhtreader.lastDataTime = 0 - dhtreader.lastData = (None,None) - self.sensorName = "DHT22" - self.pinNum = int(data["pinNumber"]) - if "temp" in data["measurement"].lower(): - self.valName = "Temperature" - self.valUnit = "Celsius" - self.valSymbol = "C" - if "unit" in data: - if data["unit"]=="F": - self.valUnit = "Fahrenheit" - self.valSymbol = "F" - elif "h" in data["measurement"].lower(): - self.valName = "Relative_Humidity" - self.valSymbol = "%" - self.valUnit = "% Relative Humidity" - return + requiredOptions = ["measurement", "pinNumber"] + optionalOptions = ["unit"] + + def __init__(self, data): + dhtreader.init() + dhtreader.lastDataTime = 0 + dhtreader.lastData = (None, None) + self.sensorName = "DHT22" + self.pinNum = int(data["pinNumber"]) + if "temp" in data["measurement"].lower(): + self.valName = "Temperature" + self.valUnit = "Celsius" + self.valSymbol = "C" + if "unit" in data: + if data["unit"] == "F": + self.valUnit = "Fahrenheit" + self.valSymbol = "F" + elif "h" in data["measurement"].lower(): + self.valName = "Relative_Humidity" + self.valSymbol = "%" + self.valUnit = "% Relative Humidity" + return + + def getVal(self): + tm = dhtreader.lastDataTime + if (time.time() - tm) < 2: + t, h = dhtreader.lastData + else: + tim = time.time() + try: + t, h = dhtreader.read(22, self.pinNum) + except Exception: + t, h = dhtreader.lastData + dhtreader.lastData = (t, h) + dhtreader.lastDataTime = tim + if self.valName == "Temperature": + temp = t + if self.valUnit == "Fahrenheit": + temp = temp * 1.8 + 32 + return temp + elif self.valName == "Relative_Humidity": + return h - def getVal(self): - tm = dhtreader.lastDataTime - if (time.time()-tm)<2: - t, h = dhtreader.lastData - else: - tim = time.time() - try: - t, h = dhtreader.read(22,self.pinNum) - except Exception: - t, h = dhtreader.lastData - dhtreader.lastData = (t,h) - dhtreader.lastDataTime=tim - if self.valName == "Temperature": - temp = t - if self.valUnit == "Fahrenheit": - temp = temp * 1.8 + 32 - return temp - elif self.valName == "Relative_Humidity": - return h + def getdata(self): + pass diff --git a/sensors/ds18s20.py b/sensors/ds18s20.py new file mode 100644 index 0000000..4e9ec5d --- /dev/null +++ b/sensors/ds18s20.py @@ -0,0 +1,47 @@ +#!/usr/bin/python + +import sensor + + +class DS18S20(sensor.Sensor): + requiredOptions = ["pinNumber"] + optionalOptions = ["unit"] + + def __init__(self, data): + self.sensorName = "DS18S20" + self.pinNum = int(data["pinNumber"]) + self.valUnit = "Celsius" + self.valSymbol = "C" + self.valName = "Temperature" + + # Case unit was set, and is Fahrenheit + if "unit" in data: + if data["unit"] == "F": + self.valUnit = "Fahrenheit" + self.valSymbol = "F" + + def getVal(self): + file = open('/sys/devices/w1_bus_master1/w1_master_slaves') + w1_slaves = file.readlines() + file.close() + + # Fuer jeden 1-Wire Slave aktuelle Temperatur ausgeben + for line in w1_slaves: + # 1-wire Slave extrahieren + w1_slave = line.split("\n")[0] + # 1-wire Slave Datei lesen + file = open('/sys/bus/w1/devices/' + str(w1_slave) + '/w1_slave') + file_content = file.read() + file.close() + + # Read + string_value = file_content.split("\n")[1].split(" ")[9] + temperature = float(string_value[2:]) / 1000 + + if temperature > 0: + return temperature + + return None + + def get_data(self): + pass diff --git a/sensors/mcp3008.py b/sensors/mcp3008.py index 8848f79..91b753b 100644 --- a/sensors/mcp3008.py +++ b/sensors/mcp3008.py @@ -3,64 +3,73 @@ import RPi.GPIO as GPIO import sensor + class MCP3008(sensor.Sensor): - requiredData = [] - optionalData = ["mosiPin","misoPin","csPin","clkPin"] - sharedClass = None - def __init__(self, data): - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - self.SPIMOSI = 23 - self.SPIMISO = 24 - self.SPICLK = 18 - self.SPICS = 25 - if "mosiPin" in data: - self.SPIMOSI = data["mosiPin"] - if "misoPin" in data: - self.SPIMISO = data["misoPin"] - if "clkPin" in data: - self.SPICLK = data["clkPin"] - if "csPin" in data: - self.SPICS = data["csPin"] - GPIO.setup(self.SPIMOSI, GPIO.OUT) - GPIO.setup(self.SPIMISO, GPIO.IN) - GPIO.setup(self.SPICLK, GPIO.OUT) - GPIO.setup(self.SPICS, GPIO.OUT) - if MCP3008.sharedClass == None: - MCP3008.sharedClass = self + requiredOptions = [] + optionalOptions = ["mosiPin", "misoPin", "csPin", "clkPin"] + sharedClass = None + + def __init__(self, data): + + self.SPIMOSI = 23 + self.SPIMISO = 24 + self.SPICLK = 18 + self.SPICS = 25 + + if "mosiPin" in data: + self.SPIMOSI = data["mosiPin"] + if "misoPin" in data: + self.SPIMISO = data["misoPin"] + if "clkPin" in data: + self.SPICLK = data["clkPin"] + if "csPin" in data: + self.SPICS = data["csPin"] + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(self.SPIMOSI, GPIO.OUT) + GPIO.setup(self.SPIMISO, GPIO.IN) + GPIO.setup(self.SPICLK, GPIO.OUT) + GPIO.setup(self.SPICS, GPIO.OUT) + if MCP3008.sharedClass is None: + MCP3008.sharedClass = self + + # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) + def readADC(self, adcnum): + if (adcnum > 7) or (adcnum < 0): + return -1 + + GPIO.output(self.SPICS, True) + GPIO.output(self.SPICLK, False) # start clock low + GPIO.output(self.SPICS, False) # bring CS low + + commandout = adcnum + commandout |= 0x18 # start bit + single-ended bit + commandout <<= 3 # we only need to send 5 bits here - #read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) - def readADC(self,adcnum): - if ((adcnum > 7) or (adcnum < 0)): - return -1 - GPIO.output(self.SPICS, True) + for i in range(5): + if commandout & 0x80: + GPIO.output(self.SPIMOSI, True) + else: + GPIO.output(self.SPIMOSI, False) + commandout <<= 1 + GPIO.output(self.SPICLK, True) + GPIO.output(self.SPICLK, False) - GPIO.output(self.SPICLK, False) # start clock low - GPIO.output(self.SPICS, False) # bring CS low + adcout = 0 + # read in one empty bit, one null bit and 10 ADC bits + for i in range(11): + GPIO.output(self.SPICLK, True) + GPIO.output(self.SPICLK, False) + adcout <<= 1 + if GPIO.input(self.SPIMISO): + adcout |= 0x1 - commandout = adcnum - commandout |= 0x18 # start bit + single-ended bit - commandout <<= 3 # we only need to send 5 bits here - for i in range(5): - if (commandout & 0x80): - GPIO.output(self.SPIMOSI, True) - else: - GPIO.output(self.SPIMOSI, False) - commandout <<= 1 - GPIO.output(self.SPICLK, True) - GPIO.output(self.SPICLK, False) + GPIO.output(self.SPICS, True) + return adcout - adcout = 0 - # read in one empty bit, one null bit and 10 ADC bits - for i in range(11): - GPIO.output(self.SPICLK, True) - GPIO.output(self.SPICLK, False) - adcout <<= 1 - if (GPIO.input(self.SPIMISO)): - adcout |= 0x1 + def getVal(self): + return None # not that kind of plugin, this is to be used by other plugins - GPIO.output(self.SPICS, True) - return adcout - - def getVal(self): - return None #not that kind of plugin, this is to be used by other plugins + def get_data(self): + pass diff --git a/sensors/sensor.py b/sensors/sensor.py index ae8ddf5..56c85ca 100644 --- a/sensors/sensor.py +++ b/sensors/sensor.py @@ -1,6 +1,11 @@ -class Sensor(): - def __init__(data): - raise NotImplementedError - - def getData(): - raise NotImplementedError + + +class Sensor: + requiredOptions = [] + optionalOptions = [] + + def __init__(self,data): + raise NotImplementedError + + def get_data(self): + raise NotImplementedError From f0d4282a059ec6d58c81032af24d4e48a096a515 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 15:55:36 +0200 Subject: [PATCH 2/9] New Module added DS18S20 Code refactoring --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 6f9cbe3..f7dca06 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +Latest Version +======== +- DS18S20 temperature support added (see configuration below) + + AirPi ======== @@ -145,6 +150,42 @@ FeedID=xxxxxxxxxx If you have registered with https://xively.com - you can add your API Key and Feed ID here. +## Configuration + +### DS18S20 + + +In the sensors.cfg set the pinNumber for the temperature sensor. + +``` + +[DS18S20] +filename=ds18s20 +enabled=on +pinNumber=4 +sensorName=DS18S20 + +``` + +#### Kernel-Version >= 3.18.3 +Open the /boot/config.txt + +``` +sudo vim /boot/config.txt + +``` + +and append the following + +``` +dtoverlay=w1-gpio,gpiopin=4,pullup=on + +``` + + + + + ## Running AirPi **must** be run as root. From 230c663f6813c9867712bff53465e6a31cecde01 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 16:01:48 +0200 Subject: [PATCH 3/9] New Module added DS18S20 Code refactoring --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index f7dca06..194a359 100644 --- a/README.md +++ b/README.md @@ -177,13 +177,31 @@ sudo vim /boot/config.txt and append the following +(Notice: gpiopin is your pin number, same as in the configuration file!) + ``` dtoverlay=w1-gpio,gpiopin=4,pullup=on ``` +NOW check the your kernel version: +``` +uname -r +``` + +#### Kernel-Version < 3.18.3 +If you are using an older kernel version, load the modules via + +``` +sudo modprobe w1-gpio pullup=1 && sudo modprobe w1_therm + +``` +... and add to the modules, so we dont have to load them manually after every boot +``` +sudo echo >> /etc/modules w1-gpio && sudo echo >> /etc/modules w1_therm +``` ## Running From 7b4c616651a0b1d4d558746ccea0734b4ed2c4a9 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 16:03:49 +0200 Subject: [PATCH 4/9] New Module added DS18S20 Code refactoring --- README.md | 5 +++-- helpers/exceptions.py | 6 ++++++ sensors.cfg | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 helpers/exceptions.py diff --git a/README.md b/README.md index 194a359..dca61dc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ Latest Version ======== - DS18S20 temperature support added (see configuration below) +- Code refactored AirPi @@ -177,14 +178,14 @@ sudo vim /boot/config.txt and append the following -(Notice: gpiopin is your pin number, same as in the configuration file!) +Notice: gpiopin is your pin number, same as in the configuration file! ``` dtoverlay=w1-gpio,gpiopin=4,pullup=on ``` -NOW check the your kernel version: +NOW check your kernel version: ``` uname -r ``` diff --git a/helpers/exceptions.py b/helpers/exceptions.py new file mode 100644 index 0000000..4abee24 --- /dev/null +++ b/helpers/exceptions.py @@ -0,0 +1,6 @@ +class AirPiException(Exception): + + def __init__(self, message): + self.message = message + print self.message + pass diff --git a/sensors.cfg b/sensors.cfg index 0822d3e..b4b755e 100644 --- a/sensors.cfg +++ b/sensors.cfg @@ -63,7 +63,7 @@ sensorName=ABM_713_RC [DS18S20] filename=ds18s20 -enabled=on +enabled=off pinNumber=4 sensorName=DS18S20 From 71404bb553177f15c533dbc010a22a754bcbcc3b Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 16:06:56 +0200 Subject: [PATCH 5/9] New Module added DS18S20 Code refactoring --- README.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/README.md b/README.md index dca61dc..194e332 100644 --- a/README.md +++ b/README.md @@ -185,25 +185,6 @@ dtoverlay=w1-gpio,gpiopin=4,pullup=on ``` -NOW check your kernel version: -``` -uname -r -``` - -#### Kernel-Version < 3.18.3 -If you are using an older kernel version, load the modules via - -``` -sudo modprobe w1-gpio pullup=1 && sudo modprobe w1_therm - -``` -... and add to the modules, so we dont have to load them manually after every boot - -``` -sudo echo >> /etc/modules w1-gpio && sudo echo >> /etc/modules w1_therm - -``` - ## Running From 122fdcf6ce80ed5cffb4cc2ae5fc4a89dbda7ce6 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 16:10:24 +0200 Subject: [PATCH 6/9] New Module added DS18S20 Code refactoring --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 194e332..b399861 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Latest Version ======== -- DS18S20 temperature support added (see configuration below) +- DS18S20 temperature support added (see sensor configuration below) - Code refactored @@ -151,11 +151,10 @@ FeedID=xxxxxxxxxx If you have registered with https://xively.com - you can add your API Key and Feed ID here. -## Configuration +## Sensor Configuration ### DS18S20 - In the sensors.cfg set the pinNumber for the temperature sensor. ``` @@ -185,6 +184,9 @@ dtoverlay=w1-gpio,gpiopin=4,pullup=on ``` +Thx to Enrico, a german tech blogger: +http://fluuux.de/2016/04/raspberry-pi-ds1820-temperatursensoren-auslesen/ + ## Running From 7f7e59307ad09309f3eadd8ac2faa15f898dbb4a Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 16:11:01 +0200 Subject: [PATCH 7/9] New Module added DS18S20 Code refactoring --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b399861..4f283b3 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ dtoverlay=w1-gpio,gpiopin=4,pullup=on ``` -Thx to Enrico, a german tech blogger: +Thx to Enrico, a German tech blogger: http://fluuux.de/2016/04/raspberry-pi-ds1820-temperatursensoren-auslesen/ From 5f8e78ea0f4f981d7acb3b1489a9f7f6d2c03a3d Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Sun, 8 Oct 2017 18:36:29 +0200 Subject: [PATCH 8/9] GRovestream added --- README.md | 5 ++++- airpi.py | 3 ++- outputs.cfg | 6 ++++++ outputs/grovestream.py | 27 +++++++++++++++++++++++++++ sensors.cfg | 2 +- sensors/analogue.py | 9 --------- sensors/dht22.py | 4 ++-- settings.cfg | 4 ++-- 8 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 outputs/grovestream.py diff --git a/README.md b/README.md index 4f283b3..938380a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -Latest Version +Latest Version 8.10.2017 ======== + +- GroveStream output added, since Xively is not open source neither free + - DS18S20 temperature support added (see sensor configuration below) - Code refactored diff --git a/airpi.py b/airpi.py index 285b15e..1f390c1 100644 --- a/airpi.py +++ b/airpi.py @@ -105,7 +105,8 @@ def load_from_configuration(self, configuration_name): except exceptions.AirPiException as ae: raise ae - except Exception: + except Exception as e: + print e raise exceptions.AirPiException("Did not import "+module_dir+" plugin: " + section) return inst_array diff --git a/outputs.cfg b/outputs.cfg index 6aeaa68..3613b8f 100644 --- a/outputs.cfg +++ b/outputs.cfg @@ -7,3 +7,9 @@ filename=xively enabled=off APIKey=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY FeedID=XXXXXXXXXX + +[GroveStream] +filename=grovestream +enabled=off +api_key=xxxx +comp_id=yyyy diff --git a/outputs/grovestream.py b/outputs/grovestream.py new file mode 100644 index 0000000..409540f --- /dev/null +++ b/outputs/grovestream.py @@ -0,0 +1,27 @@ +import output +import requests +import json + + +class GroveStream(output.Output): + requiredOptions = ["api_key", "comp_id"] + + def __init__(self, data): + self.APIKey = data["api_key"] + self.CompId = data["comp_id"] + + def output_data(self, data_points): + arr = [] + for i in data_points: + arr.append({"compId": self.CompId, "streamId": i["sensor"], "data": i["value"]}) + json_dump = json.dumps(arr) + try: + url = "http://grovestreams.com/api/feed?api_key="+self.APIKey + print url + response = requests.put(url, headers=None, data=json_dump) + if response.text != "": + print "GrooveStream Error: " + response.text + return False + except Exception: + return False + return True diff --git a/sensors.cfg b/sensors.cfg index b4b755e..0822d3e 100644 --- a/sensors.cfg +++ b/sensors.cfg @@ -63,7 +63,7 @@ sensorName=ABM_713_RC [DS18S20] filename=ds18s20 -enabled=off +enabled=on pinNumber=4 sensorName=DS18S20 diff --git a/sensors/analogue.py b/sensors/analogue.py index 84b6001..3bbd66b 100644 --- a/sensors/analogue.py +++ b/sensors/analogue.py @@ -36,15 +36,6 @@ def getVal(self): # Get result from adc result = self.adc.readADC(self.adcPin) - print self.adc.readADC(0) - print self.adc.readADC(1) - print self.adc.readADC(2) - print self.adc.readADC(3) - print self.adc.readADC(4) - print self.adc.readADC(5) - print self.adc.readADC(6) - print self.adc.readADC(7) - if result == 0: print "Check wiring for the " + self.sensorName + " measurement, no voltage detected on ADC input " + str(self.adcPin) return None diff --git a/sensors/dht22.py b/sensors/dht22.py index 629d79e..5080047 100644 --- a/sensors/dht22.py +++ b/sensors/dht22.py @@ -47,5 +47,5 @@ def getVal(self): elif self.valName == "Relative_Humidity": return h - def getdata(self): - pass + def getdata(self): + pass diff --git a/settings.cfg b/settings.cfg index 129375a..49f3dc0 100644 --- a/settings.cfg +++ b/settings.cfg @@ -1,4 +1,4 @@ [Main] -uploadDelay = 5 ;how long to wait before uploading again -redPin=10 +uploadDelay = 300 ;how long to wait before uploading again +redPin=21 greenPin=22 From 10f1ddb353f8f316da0312dd74bc310b5bcdc2b7 Mon Sep 17 00:00:00 2001 From: "aortega@mailbox.org" Date: Wed, 1 Nov 2017 12:35:08 +0100 Subject: [PATCH 9/9] BMP280 Senor added Bugfixes --- README.md | 4 +- sensors.cfg | 35 ++- sensors/Adafruit_I2C.py | 294 +++++++++++++------- sensors/Adafruit_Platform.py | 111 ++++++++ sensors/Adafruit_SMBus.py | 294 ++++++++++++++++++++ sensors/bmp085.py | 8 +- sensors/{bmpbackend.py => bmp085backend.py} | 0 sensors/bmp280.py | 208 ++++++++++++++ sensors/mcp3008.py | 8 +- sensors/veml6070.py | 127 +++++++++ settings.cfg | 5 +- 11 files changed, 986 insertions(+), 108 deletions(-) create mode 100644 sensors/Adafruit_Platform.py create mode 100644 sensors/Adafruit_SMBus.py rename sensors/{bmpbackend.py => bmp085backend.py} (100%) create mode 100644 sensors/bmp280.py create mode 100644 sensors/veml6070.py diff --git a/README.md b/README.md index 938380a..f143001 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ Latest Version 8.10.2017 ======== +- BMP280 sensor added + - GroveStream output added, since Xively is not open source neither free -- DS18S20 temperature support added (see sensor configuration below) +- DS18S20 sensor/temperature support added (see sensor configuration below) - Code refactored diff --git a/sensors.cfg b/sensors.cfg index 0822d3e..a9f2a5e 100644 --- a/sensors.cfg +++ b/sensors.cfg @@ -1,3 +1,20 @@ +[BMP280-temp] +filename=bmp280 +enabled=on +measurement=temp +i2cbus = 1 + +[BMP280-pres] +filename=bmp280 +enabled=on +measurement=pres +i2cbus = 1 + +[UV] +filename=veml6070 +enabled=on +i2cbus = 1 + [BMP085-temp] filename=bmp085 enabled=off @@ -15,12 +32,22 @@ altitude=40 [MCP3008] filename=mcp3008 enabled=on +mosiPin=22 +misoPin=27 +csPin=4 +clkPin=17 -[DHT22] +[DHT22-humidity] filename=dht22 -enabled=off +enabled=on measurement=humidity -pinNumber=4 +pinNumber=10 + +[DHT22-temp] +filename=dht22 +enabled=on +measurement=temp +pinNumber=10 [LDR] filename=analogue @@ -63,7 +90,7 @@ sensorName=ABM_713_RC [DS18S20] filename=ds18s20 -enabled=on +enabled=off pinNumber=4 sensorName=DS18S20 diff --git a/sensors/Adafruit_I2C.py b/sensors/Adafruit_I2C.py index 53456c7..ee8d21c 100644 --- a/sensors/Adafruit_I2C.py +++ b/sensors/Adafruit_I2C.py @@ -1,95 +1,201 @@ -#!/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: +# Copyright (c) 2014 Adafruit Industries +# Author: Tony DiCola +# Based on Adafruit_I2C.py created by Kevin Townsend. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in 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 logging +import subprocess +import Adafruit_SMBus as SMBus + +import Adafruit_Platform as Platform + + +def reverseByteOrder(data): + """DEPRECATED: See https://github.com/adafruit/Adafruit_Python_GPIO/issues/48""" + # # 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 + raise RuntimeError('reverseByteOrder is deprecated! See: https://github.com/adafruit/Adafruit_Python_GPIO/issues/48') + +def get_default_bus(): + """Return the default bus number based on the device platform. For a + Raspberry Pi either bus 0 or 1 (based on the Pi revision) will be returned. + For a Beaglebone Black the first user accessible bus, 1, will be returned. + """ + plat = Platform.platform_detect() + if plat == Platform.RASPBERRY_PI: + if Platform.pi_revision() == 1: + # Revision 1 Pi uses I2C bus 0. + return 0 + else: + # Revision 2 Pi uses I2C bus 1. + return 1 + elif plat == Platform.BEAGLEBONE_BLACK: + # Beaglebone Black has multiple I2C buses, default to 1 (P9_19 and P9_20). + return 1 + else: + raise RuntimeError('Could not determine default I2C bus for platform.') + +def get_i2c_device(address, busnum=None, i2c_interface=None, **kwargs): + """Return an I2C device for the specified address and on the specified bus. + If busnum isn't specified, the default I2C bus for the platform will attempt + to be detected. + """ + if busnum is None: + busnum = get_default_bus() + return Device(address, busnum, i2c_interface, **kwargs) + +def require_repeated_start(): + """Enable repeated start conditions for I2C register reads. This is the + normal behavior for I2C, however on some platforms like the Raspberry Pi + there are bugs which disable repeated starts unless explicitly enabled with + this function. See this thread for more details: + http://www.raspberrypi.org/forums/viewtopic.php?f=44&t=15840 + """ + plat = Platform.platform_detect() + if plat == Platform.RASPBERRY_PI: + # On the Raspberry Pi there is a bug where register reads don't send a + # repeated start condition like the kernel smbus I2C driver functions + # define. As a workaround this bit in the BCM2708 driver sysfs tree can + # be changed to enable I2C repeated starts. + subprocess.check_call('chmod 666 /sys/module/i2c_bcm2708/parameters/combined', shell=True) + subprocess.check_call('echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined', shell=True) + # Other platforms are a no-op because they (presumably) have the correct + # behavior and send repeated starts. + + +class Device(object): + """Class for communicating with an I2C device using the adafruit-pureio pure + python smbus library, or other smbus compatible I2C interface. Allows reading + and writing 8-bit, 16-bit, and byte array values to registers + on the device.""" + def __init__(self, address, busnum, i2c_interface=None): + """Create an instance of the I2C device at the specified address on the + specified I2C bus number.""" + self._address = address + if i2c_interface is None: + # Use pure python I2C interface if none is specified. + self._bus = SMBus.SMBus(busnum) + else: + # Otherwise use the provided class to create an smbus interface. + self._bus = i2c_interface(busnum) + self._logger = logging.getLogger('Adafruit_I2C.Device.Bus.{0}.Address.{1:#0X}' \ + .format(busnum, address)) + + def writeRaw8(self, value): + """Write an 8-bit value on the bus (without register).""" + value = value & 0xFF + self._bus.write_byte(self._address, value) + self._logger.debug("Wrote 0x%02X", + value) + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + value = value & 0xFF + self._bus.write_byte_data(self._address, register, value) + self._logger.debug("Wrote 0x%02X to register 0x%02X", + value, register) + + def write16(self, register, value): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + self._bus.write_word_data(self._address, register, value) + self._logger.debug("Wrote 0x%04X to register pair 0x%02X, 0x%02X", + value, register, register+1) + + def writeList(self, register, data): + """Write bytes to the specified register.""" + self._bus.write_i2c_block_data(self._address, register, data) + self._logger.debug("Wrote to register 0x%02X: %s", + register, data) + + def readList(self, register, length): + """Read a length number of bytes from the specified register. Results + will be returned as a bytearray.""" + results = self._bus.read_i2c_block_data(self._address, register, length) + self._logger.debug("Read the following from register 0x%02X: %s", + register, results) + return results + + def readRaw8(self): + """Read an 8-bit value on the bus (without register).""" + result = self._bus.read_byte(self._address) & 0xFF + self._logger.debug("Read 0x%02X", + result) + return result + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + result = self._bus.read_byte_data(self._address, register) & 0xFF + self._logger.debug("Read 0x%02X from register 0x%02X", + result, register) + return result + + def readS8(self, register): + """Read a signed byte from the specified register.""" + result = self.readU8(register) + if result > 127: + result -= 256 + return result + + def readU16(self, register, little_endian=True): + """Read an unsigned 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self._bus.read_word_data(self._address,register) & 0xFFFF + self._logger.debug("Read 0x%04X from register pair 0x%02X, 0x%02X", + result, register, register+1) + # Swap bytes if using big endian because read_word_data assumes little + # endian on ARM (little endian) systems. + if not little_endian: + result = ((result << 8) & 0xFF00) + (result >> 8) 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 + + def readS16(self, register, little_endian=True): + """Read a signed 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self.readU16(register, little_endian) + if result > 32767: + result -= 65536 + return result + + def readU16LE(self, register): + """Read an unsigned 16-bit value from the specified register, in little + endian byte order.""" + return self.readU16(register, little_endian=True) + + def readU16BE(self, register): + """Read an unsigned 16-bit value from the specified register, in big + endian byte order.""" + return self.readU16(register, little_endian=False) + + def readS16LE(self, register): + """Read a signed 16-bit value from the specified register, in little + endian byte order.""" + return self.readS16(register, little_endian=True) + + def readS16BE(self, register): + """Read a signed 16-bit value from the specified register, in big + endian byte order.""" + return self.readS16(register, little_endian=False) \ No newline at end of file diff --git a/sensors/Adafruit_Platform.py b/sensors/Adafruit_Platform.py new file mode 100644 index 0000000..f8aefb6 --- /dev/null +++ b/sensors/Adafruit_Platform.py @@ -0,0 +1,111 @@ +# Copyright (c) 2014 Adafruit Industries +# Author: Tony DiCola + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in 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 platform +import re + +# Platform identification constants. +UNKNOWN = 0 +RASPBERRY_PI = 1 +BEAGLEBONE_BLACK = 2 +MINNOWBOARD = 3 + + +def platform_detect(): + """Detect if running on the Raspberry Pi or Beaglebone Black and return the + platform type. Will return RASPBERRY_PI, BEAGLEBONE_BLACK, or UNKNOWN.""" + # Handle Raspberry Pi + pi = pi_version() + if pi is not None: + return RASPBERRY_PI + + # Handle Beaglebone Black + # TODO: Check the Beaglebone Black /proc/cpuinfo value instead of reading + # the platform. + plat = platform.platform() + if plat.lower().find('armv7l-with-debian') > -1: + return BEAGLEBONE_BLACK + elif plat.lower().find('armv7l-with-ubuntu') > -1: + return BEAGLEBONE_BLACK + elif plat.lower().find('armv7l-with-glibc2.4') > -1: + return BEAGLEBONE_BLACK + + # Handle Minnowboard + # Assumption is that mraa is installed + try: + import mraa + if mraa.getPlatformName() == 'MinnowBoard MAX': + return MINNOWBOARD + except ImportError: + pass + + # Couldn't figure out the platform, just return unknown. + return UNKNOWN + + +def pi_revision(): + """Detect the revision number of a Raspberry Pi, useful for changing + functionality like default I2C bus based on revision.""" + # Revision list available at: http://elinux.org/RPi_HardwareHistory#Board_Revision_History + with open('/proc/cpuinfo', 'r') as infile: + for line in infile: + # Match a line of the form "Revision : 0002" while ignoring extra + # info in front of the revsion (like 1000 when the Pi was over-volted). + match = re.match('Revision\s+:\s+.*(\w{4})$', line, flags=re.IGNORECASE) + if match and match.group(1) in ['0000', '0002', '0003']: + # Return revision 1 if revision ends with 0000, 0002 or 0003. + return 1 + elif match: + # Assume revision 2 if revision ends with any other 4 chars. + return 2 + # Couldn't find the revision, throw an exception. + raise RuntimeError('Could not determine Raspberry Pi revision.') + + +def pi_version(): + """Detect the version of the Raspberry Pi. Returns either 1, 2 or + None depending on if it's a Raspberry Pi 1 (model A, B, A+, B+), + Raspberry Pi 2 (model B+), or not a Raspberry Pi. + """ + # Check /proc/cpuinfo for the Hardware field value. + # 2708 is pi 1 + # 2709 is pi 2 + # 2835 is pi 3 on 4.9.x kernel + # Anything else is not a pi. + with open('/proc/cpuinfo', 'r') as infile: + cpuinfo = infile.read() + # Match a line like 'Hardware : BCM2709' + match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo, + flags=re.MULTILINE | re.IGNORECASE) + if not match: + # Couldn't find the hardware, assume it isn't a pi. + return None + if match.group(1) == 'BCM2708': + # Pi 1 + return 1 + elif match.group(1) == 'BCM2709': + # Pi 2 + return 2 + elif match.group(1) == 'BCM2835': + # Pi 3 / Pi on 4.9.x kernel + return 3 + else: + # Something else, not a pi. + return None \ No newline at end of file diff --git a/sensors/Adafruit_SMBus.py b/sensors/Adafruit_SMBus.py new file mode 100644 index 0000000..f8da6b7 --- /dev/null +++ b/sensors/Adafruit_SMBus.py @@ -0,0 +1,294 @@ +# Copyright (c) 2016 Adafruit Industries +# Author: Tony DiCola +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in 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. +from ctypes import * +from fcntl import ioctl +import struct + +# I2C C API constants (from linux kernel headers) +I2C_M_TEN = 0x0010 # this is a ten bit chip address +I2C_M_RD = 0x0001 # read data, from slave to master +I2C_M_STOP = 0x8000 # if I2C_FUNC_PROTOCOL_MANGLING +I2C_M_NOSTART = 0x4000 # if I2C_FUNC_NOSTART +I2C_M_REV_DIR_ADDR = 0x2000 # if I2C_FUNC_PROTOCOL_MANGLING +I2C_M_IGNORE_NAK = 0x1000 # if I2C_FUNC_PROTOCOL_MANGLING +I2C_M_NO_RD_ACK = 0x0800 # if I2C_FUNC_PROTOCOL_MANGLING +I2C_M_RECV_LEN = 0x0400 # length will be first received byte + +I2C_SLAVE = 0x0703 # Use this slave address +I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if + # is already in use by a driver! +I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit +I2C_FUNCS = 0x0705 # Get the adapter functionality mask +I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) +I2C_PEC = 0x0708 # != 0 to use PEC with SMBus +I2C_SMBUS = 0x0720 # SMBus transfer + + +# ctypes versions of I2C structs defined by kernel. +class i2c_msg(Structure): + _fields_ = [ + ('addr', c_uint16), + ('flags', c_uint16), + ('len', c_uint16), + ('buf', POINTER(c_uint8)) + ] + +class i2c_rdwr_ioctl_data(Structure): + _fields_ = [ + ('msgs', POINTER(i2c_msg)), + ('nmsgs', c_uint32) + ] + + +def make_i2c_rdwr_data(messages): + """Utility function to create and return an i2c_rdwr_ioctl_data structure + populated with a list of specified I2C messages. The messages parameter + should be a list of tuples which represent the individual I2C messages to + send in this transaction. Tuples should contain 4 elements: address value, + flags value, buffer length, ctypes c_uint8 pointer to buffer. + """ + # Create message array and populate with provided data. + msg_data_type = i2c_msg*len(messages) + msg_data = msg_data_type() + for i, m in enumerate(messages): + msg_data[i].addr = m[0] & 0x7F + msg_data[i].flags = m[1] + msg_data[i].len = m[2] + msg_data[i].buf = m[3] + # Now build the data structure. + data = i2c_rdwr_ioctl_data() + data.msgs = msg_data + data.nmsgs = len(messages) + return data + + +# Create an interface that mimics the Python SMBus API. +class SMBus(object): + """I2C interface that mimics the Python SMBus API but is implemented with + pure Python calls to ioctl and direct /dev/i2c device access. + """ + + def __init__(self, bus=None): + """Create a new smbus instance. Bus is an optional parameter that + specifies the I2C bus number to use, for example 1 would use device + /dev/i2c-1. If bus is not specified then the open function should be + called to open the bus. + """ + self._device = None + if bus is not None: + self.open(bus) + + def __del__(self): + """Clean up any resources used by the SMBus instance.""" + self.close() + + def __enter__(self): + """Context manager enter function.""" + # Just return this object so it can be used in a with statement, like + # with SMBus(1) as bus: + # # do stuff! + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit function, ensures resources are cleaned up.""" + self.close() + return False # Don't suppress exceptions. + + def open(self, bus): + """Open the smbus interface on the specified bus.""" + # Close the device if it's already open. + if self._device is not None: + self.close() + # Try to open the file for the specified bus. Must turn off buffering + # or else Python 3 fails (see: https://bugs.python.org/issue20074) + self._device = open('/dev/i2c-{0}'.format(bus), 'r+b', buffering=0) + # TODO: Catch IOError and throw a better error message that describes + # what's wrong (i.e. I2C may not be enabled or the bus doesn't exist). + + def close(self): + """Close the smbus connection. You cannot make any other function + calls on the bus unless open is called!""" + if self._device is not None: + self._device.close() + self._device = None + + def _select_device(self, addr): + """Set the address of the device to communicate with on the I2C bus.""" + ioctl(self._device.fileno(), I2C_SLAVE, addr & 0x7F) + + def read_byte(self, addr): + """Read a single byte from the specified device.""" + assert self._device is not None, 'Bus must be opened before operations are made against it!' + self._select_device(addr) + return ord(self._device.read(1)) + + def read_byte_data(self, addr, cmd): + """Read a single byte from the specified cmd register of the device.""" + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Build ctypes values to marshall between ioctl and Python. + reg = c_uint8(cmd) + result = c_uint8() + # Build ioctl request. + request = make_i2c_rdwr_data([ + (addr, 0, 1, pointer(reg)), # Write cmd register. + (addr, I2C_M_RD, 1, pointer(result)) # Read 1 byte as result. + ]) + # Make ioctl call and return result data. + ioctl(self._device.fileno(), I2C_RDWR, request) + return result.value + + def read_word_data(self, addr, cmd): + """Read a word (2 bytes) from the specified cmd register of the device. + Note that this will interpret data using the endianness of the processor + running Python (typically little endian)! + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Build ctypes values to marshall between ioctl and Python. + reg = c_uint8(cmd) + result = c_uint16() + # Build ioctl request. + request = make_i2c_rdwr_data([ + (addr, 0, 1, pointer(reg)), # Write cmd register. + (addr, I2C_M_RD, 2, cast(pointer(result), POINTER(c_uint8))) # Read word (2 bytes). + ]) + # Make ioctl call and return result data. + ioctl(self._device.fileno(), I2C_RDWR, request) + return result.value + + def read_block_data(self, addr, cmd): + """Perform a block read from the specified cmd register of the device. + The amount of data read is determined by the first byte send back by + the device. Data is returned as a bytearray. + """ + # TODO: Unfortunately this will require calling the low level I2C + # access ioctl to trigger a proper read_block_data. The amount of data + # returned isn't known until the device starts responding so an I2C_RDWR + # ioctl won't work. + raise NotImplementedError() + + def read_i2c_block_data(self, addr, cmd, length=32): + """Perform a read from the specified cmd register of device. Length number + of bytes (default of 32) will be read and returned as a bytearray. + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Build ctypes values to marshall between ioctl and Python. + reg = c_uint8(cmd) + result = create_string_buffer(length) + # Build ioctl request. + request = make_i2c_rdwr_data([ + (addr, 0, 1, pointer(reg)), # Write cmd register. + (addr, I2C_M_RD, length, cast(result, POINTER(c_uint8))) # Read data. + ]) + # Make ioctl call and return result data. + ioctl(self._device.fileno(), I2C_RDWR, request) + return bytearray(result.raw) # Use .raw instead of .value which will stop at a null byte! + + def write_quick(self, addr): + """Write a single byte to the specified device.""" + # What a strange function, from the python-smbus source this appears to + # just write a single byte that initiates a write to the specified device + # address (but writes no data!). The functionality is duplicated below + # but the actual use case for this is unknown. + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Build ioctl request. + request = make_i2c_rdwr_data([ + (addr, 0, 0, None), # Write with no data. + ]) + # Make ioctl call and return result data. + ioctl(self._device.fileno(), I2C_RDWR, request) + + def write_byte(self, addr, val): + """Write a single byte to the specified device.""" + assert self._device is not None, 'Bus must be opened before operations are made against it!' + self._select_device(addr) + data = bytearray(1) + data[0] = val & 0xFF + self._device.write(data) + + def write_byte_data(self, addr, cmd, val): + """Write a byte of data to the specified cmd register of the device. + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Construct a string of data to send with the command register and byte value. + data = bytearray(2) + data[0] = cmd & 0xFF + data[1] = val & 0xFF + # Send the data to the device. + self._select_device(addr) + self._device.write(data) + + def write_word_data(self, addr, cmd, val): + """Write a word (2 bytes) of data to the specified cmd register of the + device. Note that this will write the data in the endianness of the + processor running Python (typically little endian)! + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Construct a string of data to send with the command register and word value. + data = struct.pack('=BH', cmd & 0xFF, val & 0xFFFF) + # Send the data to the device. + self._select_device(addr) + self._device.write(data) + + def write_block_data(self, addr, cmd, vals): + """Write a block of data to the specified cmd register of the device. + The amount of data to write should be the first byte inside the vals + string/bytearray and that count of bytes of data to write should follow + it. + """ + # Just use the I2C block data write to write the provided values and + # their length as the first byte. + data = bytearray(len(vals)+1) + data[0] = len(vals) & 0xFF + data[1:] = vals[0:] + self.write_i2c_block_data(addr, cmd, data) + + def write_i2c_block_data(self, addr, cmd, vals): + """Write a buffer of data to the specified cmd register of the device. + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Construct a string of data to send, including room for the command register. + data = bytearray(len(vals)+1) + data[0] = cmd & 0xFF # Command register at the start. + data[1:] = vals[0:] # Copy in the block data (ugly but necessary to ensure + # the entire write happens in one transaction). + # Send the data to the device. + self._select_device(addr) + self._device.write(data) + + def process_call(self, addr, cmd, val): + """Perform a smbus process call by writing a word (2 byte) value to + the specified register of the device, and then reading a word of response + data (which is returned). + """ + assert self._device is not None, 'Bus must be opened before operations are made against it!' + # Build ctypes values to marshall between ioctl and Python. + data = create_string_buffer(struct.pack('=BH', cmd, val)) + result = c_uint16() + # Build ioctl request. + request = make_i2c_rdwr_data([ + (addr, 0, 3, cast(pointer(data), POINTER(c_uint8))), # Write data. + (addr, I2C_M_RD, 2, cast(pointer(result), POINTER(c_uint8))) # Read word (2 bytes). + ]) + # Make ioctl call and return result data. + ioctl(self._device.fileno(), I2C_RDWR, request) + # Note the python-smbus code appears to have a rather serious bug and + # does not return the result value! This is fixed below by returning it. + return result.value \ No newline at end of file diff --git a/sensors/bmp085.py b/sensors/bmp085.py index b1dff1c..3a14002 100644 --- a/sensors/bmp085.py +++ b/sensors/bmp085.py @@ -1,5 +1,5 @@ import sensor -import bmpbackend +import bmp085backend class BMP085(sensor.Sensor): @@ -7,8 +7,8 @@ def get_data(self): pass bmpClass = None - requiredData = ["measurement", "i2cbus"] - optionalData = ["altitude", "mslp", "unit"] + requiredOptions = ["measurement", "i2cbus"] + optionalOptions = ["altitude", "mslp", "unit"] def __init__(self, data): self.sensorName = "BMP085" @@ -35,7 +35,7 @@ def __init__(self, data): print "To calculate MSLP, please provide an 'altitude' config setting (in m) for the BMP085 pressure module" self.mslp = False if BMP085.bmpClass is None: - BMP085.bmpClass = bmpbackend.BMP085(bus=int(data["i2cbus"])) + BMP085.bmpClass = bmp085backend.BMP085(bus=int(data["i2cbus"])) return def getVal(self): diff --git a/sensors/bmpbackend.py b/sensors/bmp085backend.py similarity index 100% rename from sensors/bmpbackend.py rename to sensors/bmp085backend.py diff --git a/sensors/bmp280.py b/sensors/bmp280.py new file mode 100644 index 0000000..5ce7b6e --- /dev/null +++ b/sensors/bmp280.py @@ -0,0 +1,208 @@ +# coding: utf-8 +import Adafruit_I2C as I2C +import time +import sensor + +# this value is necessary to calculate the correct height above sealevel +# its also included in airport weather information ATIS named as QNH +# unit is hPa +QNH = 1020 + +# power mode +# POWER_MODE=0 # sleep mode +# POWER_MODE=1 # forced mode +# POWER_MODE=2 # forced mode +POWER_MODE = 3 # normal mode + +# temperature resolution +# OSRS_T = 0 # skipped +# OSRS_T = 1 # 16 Bit +# OSRS_T = 2 # 17 Bit +# OSRS_T = 3 # 18 Bit +# OSRS_T = 4 # 19 Bit +OSRS_T = 5 # 20 Bit + +# pressure resolution +# OSRS_P = 0 # pressure measurement skipped +# OSRS_P = 1 # 16 Bit ultra low power +# OSRS_P = 2 # 17 Bit low power +# OSRS_P = 3 # 18 Bit standard resolution +# OSRS_P = 4 # 19 Bit high resolution +OSRS_P = 5 # 20 Bit ultra high resolution + +# filter settings +# FILTER = 0 # +# FILTER = 1 # +# FILTER = 2 # +# FILTER = 3 # +FILTER = 4 # +# FILTER = 5 # +# FILTER = 6 # +# FILTER = 7 # + +# standby settings +# T_SB = 0 # 000 0,5ms +# T_SB = 1 # 001 62.5 ms +# T_SB = 2 # 010 125 ms +# T_SB = 3 # 011 250ms +T_SB = 4 # 100 500ms +# T_SB = 5 # 101 1000ms +# T_SB = 6 # 110 2000ms +# T_SB = 7 # 111 4000ms + + +CONFIG = (T_SB << 5) + (FILTER << 2) # combine bits for config +CTRL_MEAS = (OSRS_T << 5) + (OSRS_P << 2) + POWER_MODE # combine bits for ctrl_meas + +BMP280_REGISTER_DIG_T1 = 0x88 +BMP280_REGISTER_DIG_T2 = 0x8A +BMP280_REGISTER_DIG_T3 = 0x8C +BMP280_REGISTER_DIG_P1 = 0x8E +BMP280_REGISTER_DIG_P2 = 0x90 +BMP280_REGISTER_DIG_P3 = 0x92 +BMP280_REGISTER_DIG_P4 = 0x94 +BMP280_REGISTER_DIG_P5 = 0x96 +BMP280_REGISTER_DIG_P6 = 0x98 +BMP280_REGISTER_DIG_P7 = 0x9A +BMP280_REGISTER_DIG_P8 = 0x9C +BMP280_REGISTER_DIG_P9 = 0x9E +BMP280_REGISTER_CHIPID = 0xD0 +BMP280_REGISTER_VERSION = 0xD1 +BMP280_REGISTER_SOFTRESET = 0xE0 +BMP280_REGISTER_CONTROL = 0xF4 +BMP280_REGISTER_CONFIG = 0xF5 +BMP280_REGISTER_STATUS = 0xF3 +BMP280_REGISTER_TEMPDATA_MSB = 0xFA +BMP280_REGISTER_TEMPDATA_LSB = 0xFB +BMP280_REGISTER_TEMPDATA_XLSB = 0xFC +BMP280_REGISTER_PRESSDATA_MSB = 0xF7 +BMP280_REGISTER_PRESSDATA_LSB = 0xF8 +BMP280_REGISTER_PRESSDATA_XLSB = 0xF9 + + +class BMP280(sensor.Sensor): + def get_data(self): + pass + + requiredOptions = ["measurement", "i2cbus"] + optionalOptions = ["unit", "address"] + + def __init__(self, data): + + self.address = 0x76 + self.i2cbus = int(data["i2cbus"]) + + if "address" in data: + self.address = int(data["address"], 0) + + self.sensorName = "BMP280" + if "temp" in data["measurement"].lower(): + self.valName = "Temperature" + self.valUnit = "Celsius" + self.valSymbol = "C" + if "unit" in data: + if data["unit"] == "F": + self.valUnit = "Fahrenheit" + self.valSymbol = "F" + elif "alt" in data["measurement"].lower(): + self.valName = "Altitude" + self.valUnit = "m" + self.valSymbol = "m" + elif "pres" in data["measurement"].lower(): + self.valName = "Pressure" + self.valSymbol = "hPa" + self.valUnit = "Hectopascal" + + def getData(self): + i2c = I2C + device = i2c.get_i2c_device(address=self.address, busnum=self.i2cbus) + + if device.readS8(BMP280_REGISTER_CHIPID) == 0x58: # check sensor id 0x58=BMP280 + device.write8(BMP280_REGISTER_SOFTRESET, 0xB6) # reset sensor + time.sleep(0.2) # little break + device.write8(BMP280_REGISTER_CONTROL, CTRL_MEAS) # + time.sleep(0.2) # little break + device.write8(BMP280_REGISTER_CONFIG, CONFIG) # + time.sleep(0.2) + + dig_T1 = device.readU16LE(BMP280_REGISTER_DIG_T1) # read correction settings + dig_T2 = device.readS16LE(BMP280_REGISTER_DIG_T2) + dig_T3 = device.readS16LE(BMP280_REGISTER_DIG_T3) + dig_P1 = device.readU16LE(BMP280_REGISTER_DIG_P1) + dig_P2 = device.readS16LE(BMP280_REGISTER_DIG_P2) + dig_P3 = device.readS16LE(BMP280_REGISTER_DIG_P3) + dig_P4 = device.readS16LE(BMP280_REGISTER_DIG_P4) + dig_P5 = device.readS16LE(BMP280_REGISTER_DIG_P5) + dig_P6 = device.readS16LE(BMP280_REGISTER_DIG_P6) + dig_P7 = device.readS16LE(BMP280_REGISTER_DIG_P7) + dig_P8 = device.readS16LE(BMP280_REGISTER_DIG_P8) + dig_P9 = device.readS16LE(BMP280_REGISTER_DIG_P9) + + # Logic + raw_temp_msb = device.readU8(BMP280_REGISTER_TEMPDATA_MSB) # read raw temperature msb + raw_temp_lsb = device.readU8(BMP280_REGISTER_TEMPDATA_LSB) # read raw temperature lsb + raw_temp_xlsb = device.readU8(BMP280_REGISTER_TEMPDATA_XLSB) # read raw temperature xlsb + raw_press_msb = device.readU8(BMP280_REGISTER_PRESSDATA_MSB) # read raw pressure msb + raw_press_lsb = device.readU8(BMP280_REGISTER_PRESSDATA_LSB) # read raw pressure lsb + raw_press_xlsb = device.readU8(BMP280_REGISTER_PRESSDATA_XLSB) # read raw pressure xlsb + + raw_temp = (raw_temp_msb << 12) + (raw_temp_lsb << 4) + ( + raw_temp_xlsb >> 4) # combine 3 bytes msb 12 bits left, lsb 4 bits left, xlsb 4 bits right + raw_press = (raw_press_msb << 12) + (raw_press_lsb << 4) + ( + raw_press_xlsb >> 4) # combine 3 bytes msb 12 bits left, lsb 4 bits left, xlsb 4 bits right + + # the following values are from the calculation example in the datasheet + # this values can be used to check the calculation formulas + # dig_T1=27504 + # dig_T2=26435 + # dig_T3=-1000 + # dig_P1=36477 + # dig_P2=-10685 + # dig_P3=3024 + # dig_P4=2855 + # dig_P5=140 + # dig_P6=-7 + # dig_P7=15500 + # dig_P8=-14600 + # dig_P9=6000 + # t_fine=128422.2869948 + # raw_temp=519888 + # raw_press=415148 + + var1 = (raw_temp / 16384.0 - dig_T1 / 1024.0) * dig_T2 # formula for temperature from datasheet + var2 = (raw_temp / 131072.0 - dig_T1 / 8192.0) * ( + raw_temp / 131072.0 - dig_T1 / 8192.0) * dig_T3 # formula for temperature from datasheet + temp = (var1 + var2) / 5120.0 # formula for temperature from datasheet + t_fine = (var1 + var2) # need for pressure calculation + + var1 = t_fine / 2.0 - 64000.0 # formula for pressure from datasheet + var2 = var1 * var1 * dig_P6 / 32768.0 # formula for pressure from datasheet + var2 = var2 + var1 * dig_P5 * 2 # formula for pressure from datasheet + var2 = var2 / 4.0 + dig_P4 * 65536.0 # formula for pressure from datasheet + var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0 # formula for pressure from datasheet + var1 = (1.0 + var1 / 32768.0) * dig_P1 # formula for pressure from datasheet + press = 1048576.0 - raw_press # formula for pressure from datasheet + press = (press - var2 / 4096.0) * 6250.0 / var1 # formula for pressure from datasheet + var1 = dig_P9 * press * press / 2147483648.0 # formula for pressure from datasheet + var2 = press * dig_P8 / 32768.0 # formula for pressure from datasheet + press = press + (var1 + var2 + dig_P7) / 16.0 # formula for pressure from datasheet + + altitude = 44330.0 * ( + 1.0 - pow(press / (QNH * 100), (1.0 / 5.255))) # formula for altitude from airpressure + + # print( + # "temperature:{:.2f}".format(temp) + " C pressure:{:.2f}".format( + # press / 100) + " hPa altitude:{:.2f}".format( + # altitude) + " m") + return temp, press / 100, altitude + + def getVal(self): + if self.valName == "Temperature": + temp = self.getData()[0] + if self.valUnit == "Fahrenheit": + temp = temp * 1.8 + 32 + return temp + elif self.valName == "Pressure": + return self.getData()[1] + elif self.valName == "Altitude": + return self.getData()[2] diff --git a/sensors/mcp3008.py b/sensors/mcp3008.py index 91b753b..6cf2b67 100644 --- a/sensors/mcp3008.py +++ b/sensors/mcp3008.py @@ -17,13 +17,13 @@ def __init__(self, data): self.SPICS = 25 if "mosiPin" in data: - self.SPIMOSI = data["mosiPin"] + self.SPIMOSI = int(data["mosiPin"]) if "misoPin" in data: - self.SPIMISO = data["misoPin"] + self.SPIMISO = int(data["misoPin"]) if "clkPin" in data: - self.SPICLK = data["clkPin"] + self.SPICLK = int(data["clkPin"]) if "csPin" in data: - self.SPICS = data["csPin"] + self.SPICS = int(data["csPin"]) GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) diff --git a/sensors/veml6070.py b/sensors/veml6070.py new file mode 100644 index 0000000..102b1d3 --- /dev/null +++ b/sensors/veml6070.py @@ -0,0 +1,127 @@ +# https://github.com/cmur2/python-veml6070/blob/master/veml6070/veml6070.py + +import Adafruit_SMBus as smbus +import time +import sensor + +ADDR_L = 0x38 # 7bit address of the VEML6070 (write, read) +ADDR_H = 0x39 # 7bit address of the VEML6070 (read) + +RSET_240K = 240000 +RSET_270K = 270000 +RSET_300K = 300000 +RSET_600K = 600000 + +SHUTDOWN_DISABLE = 0x00 +SHUTDOWN_ENABLE = 0x01 + +INTEGRATIONTIME_1_2T = 0x00 +INTEGRATIONTIME_1T = 0x01 +INTEGRATIONTIME_2T = 0x02 +INTEGRATIONTIME_4T = 0x03 + + +class Veml6070(sensor.Sensor): + def get_data(self): + pass + + requiredOptions = ["i2cbus"] + optionalOptions = ["rset", "address"] + + def __init__(self, data): + self.sensorName = "VEML6070" + + self.bus = smbus.SMBus(int(data["i2cbus"])) + self.sensor_address = ADDR_L + self.rset = RSET_270K + self.shutdown = SHUTDOWN_DISABLE # before set_integration_time() + self.set_integration_time(INTEGRATIONTIME_1T) + self.valName = "UV" + self.valUnit = "Watt" + self.valSymbol = "W/(m*m)" + + if "address" in data: + self.sensor_address = int(data["address"], 0) + if "rset" in data: + self.rset = int(data["rset"]) + + self.disable() + + def set_integration_time(self, integration_time): + self.integration_time = integration_time + self.bus.write_byte(self.sensor_address, self.get_command_byte()) + # constant offset determined experimentally to allow sensor to readjust + time.sleep(0.2) + + def get_integration_time(self): + return self.integration_time + + def enable(self): + self.shutdown = SHUTDOWN_DISABLE + self.bus.write_byte(self.sensor_address, self.get_command_byte()) + + def disable(self): + self.shutdown = SHUTDOWN_ENABLE + self.bus.write_byte(self.sensor_address, self.get_command_byte()) + + def get_uva_light_intensity_raw(self): + self.enable() + # wait two times the refresh time to allow completion of a previous cycle with old settings (worst case) + time.sleep(self.get_refresh_time() * 2) + msb = self.bus.read_byte(self.sensor_address + (ADDR_H - ADDR_L)) + lsb = self.bus.read_byte(self.sensor_address) + self.disable() + return (msb << 8) | lsb + + def get_uva_light_intensity(self): + uv = self.get_uva_light_intensity_raw() + return uv * self.get_uva_light_sensitivity() + + def get_command_byte(self): + """ + assembles the command byte for the current state + """ + cmd = (self.shutdown & 0x01) << 0 # SD + cmd = (self.integration_time & 0x03) << 2 # IT + cmd = ((cmd | 0x02) & 0x3F) # reserved bits + return cmd + + def get_refresh_time(self): + """ + returns time needed to perform a complete measurement using current settings (in s) + """ + case_refresh_rset = { + RSET_240K: 0.1, + RSET_270K: 0.1125, + RSET_300K: 0.125, + RSET_600K: 0.25 + } + case_refresh_it = { + INTEGRATIONTIME_1_2T: 0.5, + INTEGRATIONTIME_1T: 1, + INTEGRATIONTIME_2T: 2, + INTEGRATIONTIME_4T: 4 + } + return case_refresh_rset[self.rset] * case_refresh_it[self.integration_time] + + def get_uva_light_sensitivity(self): + """ + returns UVA light sensitivity in W/(m*m)/step + """ + case_sens_rset = { + RSET_240K: 0.05, + RSET_270K: 0.05625, + RSET_300K: 0.0625, + RSET_600K: 0.125 + } + case_sens_it = { + INTEGRATIONTIME_1_2T: 0.5, + INTEGRATIONTIME_1T: 1, + INTEGRATIONTIME_2T: 2, + INTEGRATIONTIME_4T: 4 + } + return case_sens_rset[self.rset] / case_sens_it[self.integration_time] + + def getVal(self): + self.set_integration_time(INTEGRATIONTIME_2T) + return self.get_uva_light_intensity() \ No newline at end of file diff --git a/settings.cfg b/settings.cfg index 49f3dc0..42f8773 100644 --- a/settings.cfg +++ b/settings.cfg @@ -1,4 +1,7 @@ [Main] -uploadDelay = 300 ;how long to wait before uploading again +uploadDelay = 900 ;how long to wait before uploading again redPin=21 greenPin=22 +#uploadDelay = 5 +#redPin=19 +#greenPin=5 \ No newline at end of file