class CO2_stat(object): """Code associated with the thermostat controller""" def __init__(self): self.logger = Logger("CO2_stat") self.logger.debug("initialize CO2 controller object") self._co2 = SCD30(self.logger) self._fan = Fan(self.logger) def check(self, co2=None, test=False): """Adjust the fan depending upon the CO2 Args: temp: optional test CO2 Returns: None Raises: None """ target_co2 = TARGET_CO2 if co2 == None: co2, temp, rh = self._co2.get_data() msg = "{} {} {} {}".format("CO2:", co2, " Target CO2:", target_co2) # Get target temperature from file self.logger.info(msg) if co2 > target_co2: self._fan.set(Fan.ON) else: self._fan.set(Fan.OFF)
class RF_Sender: def __init__(self, logger=None, pin=11): self._pin = pin self._protocol = 1 self._length = 444 self._logger = logger if self._logger == None: self._logger = Logger('RF_Send', Logger.INFO) self._logger.debug("Initialize RF Sender on channel {}".format(pin)) self._rfdevice = RFDevice(pin) self._rfdevice.enable_tx() def send(self, code, protocol, length): self._rfdevice.tx_code(code, protocol, length) print("TX Pin: {}, Code: {}, Protocol: {}, Length: {}".format( self._pin, code, protocol, length)) def set_on(self, pin): self.send(code[pin]['On'], protocol, length) def set_off(self, pin): self.send(code[pin]['Off'], protocol, length) def cleanup(self): self._rfdevice.cleanup()
class Humidistat(object): """Code associated with the thermostat controller""" def __init__(self): self._logger = Logger("Humidistat") self._logger.setLevel(Logger.INFO) self._logger.debug("initialize Humidistat object") self._co2 = SCD30() self._humidifier = Humidifier() self._target_rh = 80 def check(self, rh=None, test=False): """Adjust the fhumidifier depending upon the rh Args: temp: optional test rh Returns: None Raises: None """ if rh == None: co2, temp, rh = self._co2.get_data() msg = "{} {} {} {}".format("Humidity:", rh, " Target Humidity:", self._target_rh) self._logger.info(msg) if rh > self._target_rh: self._humidifier.set(Humidifier.OFF) else: self._humidifier.set(Humidifier.ON)
class Thermostat(object): """Code associated with the thermostat controller""" def __init__(self): self._logger = Logger("Thermostat", lvl=Logger.INFO, file="/home/pi/MVP/logs/state.log") self._logger.debug("initialize Thermostat object") self._temp = SI7021(self._logger) self._fan = Fan(self._logger) def check(self, temp=None): """Adjust the fan depending upon the temperature Args: temp: optional test temperature Returns: None Raises: None """ if temp == None: temp = self._temp.get_tempC() # Get target temperature from file target_temp = env['thermostat']['targetTemp'] msg = "{} {} {} {}".format("Temp:", temp, " Target Temp:", target_temp) self._logger.debug(msg) if temp > target_temp: self._fan.set(Fan.ON) else: self._fan.set(Fan.OFF)
class Template(object): def __init__(self, logger=None): """Initialize the object Args: logger: existing logger to use Returns: rh: calculated relative humidity Raises: None """ if logger == None: self._logger = Logger("Template", Logger.INFO, "/home/pi/MVP/logs/template.log") def doSomething(self): """Function to do something Args: none: Returns: thing: Raises: None """ self._logger.debug("In doSomething") thing = True self._logger.debug("{}: {}".format("thing", thing)) return thing
class OneWireTemp(object): one_temp = {0: "Ambient", 1: "Reservoir", 2: "Box", 3: "Top"} def __init__(self, logger=None): """Create sensor object Args: None Returns: None Raises: None """ self._logger = logger if logger == None: self._logger = Logger("OneWireTemp", Logger.INFO) self._logger.debug("Initialize OneWireTemp") # flag for testing self._test = False def read_temp_raw(self, x): """Read sensor buffer Args: x: number of the sensor Returns: lines: lines read Raises: None """ device_folder = glob.glob(base_dir + '28*')[x] device_file = device_folder + '/w1_slave' self._logger.debug("{} {}, {} {}, {} {}".format( "In read temp raw-Device:", x, "Device Folder:", device_folder, "Device File:", device_file)) f = open(device_file, 'r') lines = f.readlines() f.close() return lines def getTempC(self, x): """Read sensor buffer Args: x: number of the sensor Returns: temp_c: temperature reading Raises: None """ lines = self.read_temp_raw(x) while lines[0].strip()[-3:] != 'YES': time.sleep(0.2) lines = read_temp_raw() equals_pos = lines[1].find('t=') if equals_pos != -1: temp_string = lines[1][equals_pos + 2:] temp_c = float(temp_string) / 1000.0 # temp_f = temp_c * 9.0 / 5.0 + 32.0 # return temp_c, temp_f return temp_c
class Pump(object): def __init__(self, logger=None): self._rf = RF_Sender() self._logger = logger if self._logger == None: self._logger = Logger('Pump', Logger.INFO, "/home/pi/MVP/logs/obsv.log") self._persist = Persistence(self._logger) def __del__(self): ''' Don't call GPIO.cleanup() as need to leave pin state as is ''' pass def on(self, test=False): "Check state and turn on if needed" self._rf.set_on(pumpPin, ) self.log_state("On", test) self._logger.debug('Pump turned ON') def off(self, test=False): '''Check state and turn off if needed''' self._rf.set_off(pumpPin) self.log_state("Off", test) self._logger.debug('Pump turned Off') def log_state(self, value, test=False): """ Create Environment Observation """ status_qualifier = 'Success' if test: status_qualifier = 'Test' self._persist.save([ 'State_Change', '', 'Reservoir', 'Pump', 'State', value, 'Pump', 'state', status_qualifier, '' ])
class Pump: def __init__(self, logger=None): '''Initialize the object''' self.solenoidPin = Relay4 if logger==None: self._logger = Logger('Solenoid', Logger.INFO) else: self._logger = logger self._relay=Relay(self._logger) self._couch=CouchUtil(self._logger) self._test = False self.activity_type = 'State_Change' def on(self): self._relay.set_off(self.solenoidPin) self._logger.debug("{}".format("Open Solenoid")) self.logState("Open") def off(self): self._relay.set_on(self.solenoidPin) self._logger.debug("{}".format("Close Solenoid")) self.logState("Closed") def getState(self): state=self._relay.get_state(self.solenoidPin) if state==0: return "On" else: return "Off" def logState(self, value): status_qualifier='Success' if self._test: status_qualifier='Test' self._couch.saveList(['State_Change', '', 'Pump', 'Reservoir', 'State', value, 'state', 'Solenoid', status_qualifier, ''])
class AppendUtil(object): def __init__(self, logger=None): """Record optional sensor data Args: lvl: Logging level Returns: None Raises: None """ if logger == None: self._logger = Logger("AppendUtil", Logger.DETAIL) else: self._logger = logger self._sheet_name = '1Mtlr_-yqwdEwXFEcaLoEP01aDN7vvOosDOEerFpDkaI' self._scope = ['https://www.googleapis.com/auth/spreadsheets'] self._l_util = ListUtil(self._logger) self._s_util = SheetUtil(self._sheet_name, self._scope, self._logger) self._logger.debug("Initialized AppendUtil") def append(self, rec, append_range): #print(rec) self._s_util.append(append_range, rec) def save(self, doc): ''' This is the entry function into the object Lookup the range in the dictionary before appending Save list to Google Sheet Args: self doc - formatted list to save (append) Returns: None Throws: None ''' self._logger.detail("In save") name = doc[0] self._logger.debug('%s: %s' % ('Rec Type', name)) rec = self._l_util.build(doc) # dictionary of sheet tabs range = { 'Environment_Observation': 'Environment!A1', 'State_Change': 'State!A1', 'Agronomic_Activity': 'Agronomic!A1', 'Phenotype_Observation': 'Phenotype!A1' } # Lookup range for record type - note this is a lookup on a list of lists append_range = range[name] self._logger.debug('%s: %s' % ('Range', append_range)) self._s_util.append(append_range, rec)
class Light(object): def __init__(self, logger=None): self._logger = logger if self._logger == None: self._logger = Logger('Light', Logger.INFO, "/home/pi/MVP/logs/obsv.log") self._logger.debug("Initialize RF Light") self._rf = RF_Sender(self._logger) self._persist = Persistence(self._logger) def __del__(self): self._rf.cleanup() def set_on(self, test=False): "Check state and turn on if needed" self._rf.set_on(lightPin, ) self.log_state("On", test) self._logger.debug('Light turned ON') def set_off(self, test=False): '''Check state and turn off if needed''' self._rf.set_off(lightPin) self.log_state("Off", test) self._logger.debug('Light turned Off') def log_state(self, value, test=False): """ Create Environment Observation """ status_qualifier = 'Success' if test: status_qualifier = 'Test' self._persist.save([ 'State_Change', '', 'Top', 'Lights', 'State', value, 'Boolean', 'Light', status_qualifier, '' ]) def check(self): env = Environ(self._logger) if env._state: self.set_on(test) else: self.set_off(test)
class StartUp(object): def __init__(self, logger=None): if logger == None: self._logger = Logger("StartUp", Logger.INFO) else: self._logger = logger self._logger.debug("StartUp Initialized") def check(self, test=False): """Main function to run what needs to be done at restart Args: test: flag for testing system Returns: None: Raises: None """ light_state = "Unknown" try: light_state = self.checkLight() except Exception as e: self._logger.error(e) self.checkPump() def checkPump(self): """Make sure the pump is turned off Don't check, just turn off If no pump in installed, this will just flip the GPIO Args: test: flag for testing system Returns: state: (should be Off) Raises: None """ self._logger.debug("In checkPump") pump_state = "Unknown" try: pump_state = self.pumpOff() self._logger.debug("{}: {}".format("Pump State", pump_state)) except Exception as e: self._logger.error(e) def pumpOff(self): """Make sure the pump is turned off Args: test: flag for testing system Returns: state: (should be Off) Raises: None """ from Pump import Pump pump = Pump() self._logger.debug("In pumpOff") p = Pump(self._logger) p.off() msg = "Pump is turned off" self._logger.debug(msg) return "Off" def checkLight(self): """Check if lights should be on or off Args: test: flag for testing system Returns: None Raises: None """ # Move import here so can trap if fails # couchdb library does not seem to load when initially starting self._logger.debug("In checkLight") import Light # Get times from env and split into components s = env['lights']['On'] e = env['lights']['Off'] state = self.determineState(s, e) l = Light.Light(self._logger) if state: l.set_on(test) return "On" else: l.set_off(test) return "Off" def determineState(self, start, end): ''' Determine if lights should be on or off''' s = start.split(':') e = end.split(':') # Munge date into times t = datetime.now() st = t.replace(hour=int(s[0]), minute=int(s[1]), second=int(s[2])) et = t.replace(hour=int(e[0]), minute=int(e[1]), second=int(e[2])) if st > et: # Night Light - roll to next day when lights go off et += timedelta(days=1) msg = "{} {} {} {}".format("Start Time: ", st, "End Time: ", et) self._logger.debug(msg) if (st < datetime.now()) and (et > datetime.now()): msg = "Lights should be On" self._logger.debug(msg) return True else: msg = "Lights should be Off" self._logger.debug(msg) return False
class I2C(object): def __init__(self, path, addr, logger=None): self._path = path self._addr = addr self._i2c = pI2C(self._path) self._logger = logger if logger == None: self._logger = Logger("SCD30", Logger.INFO) self._logger.debug("initialize I2C object") def __exit__(self, exc_type, exc_value, traceback): self._i2c.close() def msg_write(self, cmds): """Write to sensor Args: cmds: commands to send Returns: msgs: basically returns the cmds, since nothing altered Raises: None """ self._logger.debug("In Msg Write") for cmd in cmds: self._logger.detail("{}, {}".format("Cmd: ", cmd)) msgs = [self._i2c.Message(cmds)] try: self._logger.detail("Transfer") self._i2c.transfer(self._addr, msgs) msb = msgs[0].data[0] self._logger.detail("{}, {}".format("MSB: ", hex(msb))) return msgs except Exception as e: self._logger.error(str(e)) return None def msg_read(self, size, cmds=None): """Read existing data Args: cmds: addresses to read from - optional for some sensors (SI7021) size: size of byte array for receiving data Returns: msgs: message package, last one should hold data Raises: None """ self._logger.detail("{}, {}, {}, {}".format("Msg Read - size: ", size, " cmds: ", cmds)) sz = self._i2c.Message(bytearray([0x00 for x in range(size)]), read=True) msgs = [sz] # print("C " + str(type(cmds))) if cmds is not None: # print("Cmds " + str(cmds)) rd = self._i2c.Message(cmds) msgs = [rd, sz] try: self._i2c.transfer(self._addr, msgs) return msgs except Exception as e: self._logger.error(str(e)) return None def get_data(self, cmd, sleep, size, read=None): '''Combine sending of command and reading Some sensors default the read to the prior command and don't specify a read address ''' self._logger.debug("{}, {}, {}, {}, {}, {}".format( "In Get Data-cmd: ", cmd, " sleep: ", sleep, " size: ", size)) self.msg_write(cmd) time.sleep(sleep) msgs = self.msg_read(size, read) if msgs == None: return None else: for msg in msgs: self._logger.detail("-") for dt in msg.data: self._logger.detail("Dt " + str(dt)) self._logger.debug("Data " + str(msgs[0].data[0]) + " " + str(msgs[0].data[1])) value = self.bytesToWord(msgs[0].data[0], msgs[0].data[1]) return msgs def bytesToWord(self, high, low): """Convert two byte buffers into a single word value shift the first byte into the work high position then add the low byte Args: high: byte to move to high position of word low: byte to place in low position of word Returns: word: the final value Raises: None """ self._logger.debug("{}, {}, {}, {}".format("In Bytes To Word-high: ", high, " Low: ", low)) word = (high << 8) + low return word
class Reservoir: FULL = 0 EMPTY = 1 OK = 2 vol_per_mm = 0.3785 # estimate 1 gal per 10 mm, this is reservoir specific vol_per_sec = 0.3785 # estimate 100 ml per sec, this is reservoir specific def __init__(self): '''Get distances for determining reservoir levels''' self.res = {'full': full_ec, 'empty': empty_ec, 'timeout': timeout} self._activity_type = 'Agronomic_Activity' self._logger = Logger('LogReservoir', Logger.INFO) self._persist = Persistence(self._logger) self._logger.detail("Reservoir Initialized") # flag for testing self._test = False def getStatus(self): "Logic for calling the reservoir full" self._logger.detail("In getStatus") ec = self.getEC() if ec <= full_ec: self._logger.debug("{}, {}, {:10.1f}".format( "Reservoir Full", EC, ec)) return Reservoir.FULL, ec elif ec >= empty_ec: self._logger.debug("{}, {}, {:10.1f}".format( "Reservoir Empty", EC, ec)) return Reservoir.EMPTY, ec else: self._logger.debug("{}, {}, {:10.1f}".format( "Reservoir not Empty", EC, ec)) return Reservoir.OK, ec def isFull(self): self._logger.detail("In isFull") status, ec = self.getStatus() if status == Reservoir.FULL: return True else: return False def getEC(self): '''Get EC reading''' self._logger.detail("In getEC") snsr = EC(self._logger) return snsr.getEC() def fill(self, test=False): ''' Routine to control re-filling of the reservoir''' self._logger.detail("{}".format("In Fill")) start_ec = self.getEC() start_t = time.time() pump = Pump() pump.on() # Loop till filled or times out while (not self.isFull()) and self.isFilling(start_t): self._logger.detail("{}".format("In Fill Loop")) self._logger.detail("{}".format("Exit Fill Loop, close solenoid")) # Close valve pump.off() # Calculate amount filled stop_t = time.time() stop_ec = self.getEC() dif_t = stop_t - start_t volume = dif_t * self.vol_per_sec self._logger.detail("{}".format("Exit Fill")) return volume def isFilling(self, start_time, test=False): '''Check that actually filling: the distance is actually changing''' start_ec = self.getEC() self._logger.detail("{} {}".format("Filling, Start EC:", start_ec)) time.sleep(fill_time) # Check for level change first, else will never get to this logic till timeout end_ec = self.getEC() change = start_ec - end_ec if end_ec < start_ec: # need to see at least a 5mm change self._logger.detail("{} {} {} {} {} {}".format( "Still Filling, change:", change, "Start", start_ec, "End", end_ec)) return True else: self._logger.detail("{} {} {} {} {} {}".format( "Not Filling, no change:", change, "Start", start_ec, "End", end_ec)) return False # Check for timeout stop_time = time.time() if stop_time - start_time > self.res['timeout']: self._logger.detail("{}".format("Timeout")) return False else: return True def checkReservoir(self): '''Check condition of reservoir and fill if necessary''' self._logger.detail("{}".format("Check Reservoir")) status, ec = self.getStatus() self._logger.debug("{} {} {} {}".format("Status:", status, "EC", ec)) # Is full, log state if status == Reservoir.FULL: self._logger.detail("{}".format("Status: Full")) self._logger.info("{} {} {} {}".format("EC:", ec, "Full level:", self.res['full'], "Empty:", self.res['empty'])) return True else: # Needs filling self._logger.debug("{}".format("Status: Filling")) volume = self.fill() if volume > 0: # Filled, log volume self.logState(volume, 'Success') return True else: # Failure self._logger.debug("{}".format("Status: Failure")) level = 'Empty' if status == '2': level = 'Ok' self._logger.error("{}".format("Failure to fill Reservoir")) self._logger.error("{} {} {} {}".format( "EC:", ec, "Full level:", self.res['full'], "Empty:", self.res['empty'])) self.logState(volume, 'Failure') return False def logState(self, value, status_qualifier): if self._test: status_qualifier = 'Test' txt = { 'Volume': value, 'full_level': self.res['full'], 'empty_level': self.res['empty'], 'status': 'Full' } self._persist.save([ 'State_Change', '', 'Nutrient', 'Reservoir', 'Volume', value, 'Liter', 'Solenoid', status_qualifier, '' ]) self._logger.info(txt)
class ListUtil(object): def __init__(self, logger=None): if logger == None: self._logger = Logger("ListUtil", Logger.DETAIL) else: self._logger = logger self._logger.detail("Initialize ListUtil") def build(self, doc): ''' Convert activity list to list of lists Args: doc: list of attributes, should be of format: [activity_name, trial, plot, subject, attribute, value, units, participant, status_qualifier, comment] participant may be a device string, or a list: ['person':'hmw'] Returns: rec: list formatted record ready for the spreadsheet Throws: None ''' # add timestamp and field_id timestamp = datetime.utcnow() ts_str = timestamp.isoformat()[:19] t_str = ts_str.replace('T', ' ') doc.insert(0, env['field']['field_id']) doc.insert(0, t_str) # append date, week of trial start = env['trials'][0]['start_date'] st = datetime.strptime(start, '%Y-%m-%dT%H:%M:%S') dif = timestamp - st # Week of trial wk = int(math.ceil(dif.days / 7)) # date portion doc.append(t_str[:10]) # time portion doc.append(t_str[-8:]) # weeks of trial doc.append(wk) # create binned timestamp - time is 20 minute group ts2 = timestamp.replace(minute=(int(math.floor(timestamp.minute / 20))), second=0) ts2_str = ts2.isoformat()[:19] doc.append(ts2_str) # fix participant if isinstance(doc[PARTICIPANT], list): p_type = doc[PARTICIPANT][0] p_name = doc[PARTICIPANT][1] del doc[PARTICIPANT] doc.insert(PARTICIPANT, p_name) doc.insert(PARTICIPANT, p_type) else: doc.insert(PARTICIPANT, 'device') # Remove 'Field' from Environment & State # 'Plot' is used for Location of sensor self._logger.debug(doc[2]) if doc[2] == 'Environment_Observation': del doc[3] if doc[2] == 'State_Change': del doc[3] # convert to list of lists doc2 = [[el] for el in doc] self._logger.debug(doc2) return doc2
class Fan(object): """Code associated with the exhaust fan""" ON = 1 OFF = 0 target_temp = 0 def __init__(self, logger=None): self._logger = logger if logger == None: self._logger = Logger("Relay", Logger.INFO) self._logger.debug("initialize Fan object") self._relay = Relay(self._logger) self.fan_relay = FAN_PIN self._persist = Persistence(self._logger) # flag for testing self._test = False def set(self, state): """Set the fan to state Args: state: condition from other source Returns: None Raises: None """ self._logger.debug("In set_state") prior = self._relay.get_state(self.fan_relay) self._relay.set_state(self.fan_relay, state) current = self._relay.get_state(self.fan_relay) if prior != current: self.log_state(state) def set_fan_on(self): """Turn the fan on Args: None Returns: None Raises: None """ self._logger.debug("In set_fan_on") self.set(ON, test) def set_fan_off(self): """Turn the fan off Args: None Returns: None Raises: None """ self._logger.debug("In set_fan_off") self.set(OFF, test) def log_state(self, value): """Send state change to database Args: value: state change (numeric value) test: flag for testing Returns: None Raises: None """ state = 'Off' if value > 0: state = 'On' status_qualifier = 'Success' if self._test: status_qualifier = 'Test' self._persist.save([ 'State_Change', '', 'Side', 'Fan', 'State', state, 'Boolean', 'Fan', status_qualifier, '' ]) self._logger.debug("{}, {:1}, {} {}".format( "Fan State Change: Value: ", value, " Status Qualifier: ", status_qualifier)) def getState(self): return self._relay.get_state(self.fan_relay)
class SheetUtil(object): def __init__(self, sheet_name, scope, logger=None): if logger == None: self._logger = Logger("SheetUtil", Logger.DETAIL) else: self._logger = logger self._logger.debug("Initialize SheetUtil") self._sheet_name = sheet_name self._scope = scope # The ID and range of a sample spreadsheet. self._sheet = self.open_sheet() self._logger.debug("%s: %s" % ('Opened', sheet_name)) def open_sheet(self): ''' Args: self Returns: sheet - sheet service Throws: None ''' creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( CRED, self._scope) creds = flow.run_local_server(port=0) # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('sheets', 'v4', credentials=creds) # Call the Sheets API sheet = service.spreadsheets() return sheet def getData(self, range_name): ''' Read data range from a sheet Args: self range: where to get data from, includes tab Returns: values: data from sheet Throws: None ''' result = self._sheet.values().get(spreadsheetId=self._sheet_name, range=range_name).execute() values = result.get('values', []) return values def append(self, range_name, values): ''' Append data to a range Args: self range_name - where to put the data values - what to add Returns: None Throws: None ''' # Append Test resource = {"majorDimension": "COLUMNS", "values": values} result = self._sheet.values().append( spreadsheetId=self._sheet_name, range=range_name, body=resource, valueInputOption="USER_ENTERED").execute()
class LogShroom(object): def __init__(self, logger=None): """Create sensor object Args: None Returns: None Raises: None """ self._logger = logger if logger == None: self._logger = Logger("LogShroom", Logger.INFO, file="/home/pi/MVP/logs/obsv.log") self._logger.debug("Initialize LogShroom") self._couch = CouchUtil(self._logger) # flag for testing self._test = False def log(self): """Routine to log all sensors Args: None Returns: None Raises: None """ self.getSCD() def getSCD(self): """Record CO2 sensor (scd30) Generates co2, temperature and relative humidity Args: None Returns: None Raises: None """ from scd30 import SCD30 self._scd = SCD30(self._logger) self._logger.debug("In SCD30") try: co2, temp, rh = self._scd.get_data() status = 'Success' if self._test: status = 'Test' self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'CO2', "{:10.1f}".format(co2), 'ppm', 'scd30', status, '' ]) self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'Temperature', "{:10.1f}".format(temp), 'Centigrade', 'scd30', status, '' ]) self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'Humidity', "{:10.1f}".format(rh), 'Percent', 'scd30', status, '' ]) self._logger.info("{} {:6.1f}, {} {:3.1f}, {} {:3.1f}".format( "EnvObsv-CO2:", co2, "Temp", temp, "Humidity:", rh)) except Exception as e: status = 'Failure' if self._test: status = 'Test' self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'CO2', '', 'ppm', 'scd30', status, str(e) ]) self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'Temperature', '', 'Centigrde', 'scd30', status, '' ]) self._couch.saveList([ 'Environment_Observation', '', 'Top', 'Air', 'Humidity', '', 'Percent', 'scd30', status, '' ]) self._logger.debug("{} {}".format("EnvObsv-SCD30 Error:", e))
class LogSensorsExtra(object): def __init__(self, lvl=Logger.INFO): """Record optional sensor data Args: lvl: Logging level Returns: None Raises: None """ self._logger = Logger("LogSensor-Extra", lvl, file="/home/pi/MVP/logs/obsv.log") self._activity_type = "Environment_Observation" self._test = False self._persist = Persistence(self._logger) def getOneWire(self, test=False): """Loop OneWire temperature sensors Assumes there are four Args: test: flag for testing Returns: None Raises: None """ self._logger.debug("In getOneWire") from OneWireTemp import OneWireTemp for sensor in OneWireTemp.one_temp: self.logOneWire(sensor, OneWireTemp.one_temp[sensor]) def logOneWire(self, sensor, name, test=False): """Record OneWire temperature sensor Args: sensor: number of the sensor name: name of the sensor test: flag for testing Returns: None Raises: None """ self._logger.debug("In logOneWire") from OneWireTemp import OneWireTemp try: ow = OneWireTemp() temp = ow.getTempC(sensor) status_qualifier = 'Success' if self._test: status_qualifier = 'Test' rec = [ self._activity_type, '', name, 'Air', 'Temperature', "{:10.1f}".format(temp), 'Centigrade', 'DS18B20-' + str(sensor), status_qualifier, '' ] self._persist.save(rec) self._logger.info("{}, {}, {:10.1f}".format( name, status_qualifier, temp)) except Exception as e: status_qualifier = 'Failure' if test: status_qualifier = 'Test' rec = [ self._activity_type, '', name, 'Air', 'Temperature', '', 'Centigrade', 'DS18B20-' + str(sensor), status_qualifier, str(e) ] self._persist.save(rec) self._logger.error("{}, {}, {}".format(name, status_qualifier, e)) def getLux(self, test=False): """Record LUX sensor (TSL2561) Args: test: flag for testing Returns: None Raises: None """ from TSL2561 import TSL2561 lx = TSL2561() self._logger.info("TSL2561 - LUX") try: lux = lx.getLux() status_qualifier = 'Success' if test: status_qualifier = 'Test' rec = [ self._activity_type, '', 'Canopy', 'Light', 'LUX', "{:3.1f}".format(lux), 'lux', 'TSL2561', status_qualifier, '' ] self._persist.save(rec) self._logger.info("{}, {}, {:10.1f}".format( "LUX", status_qualifier, lux)) except Exception as e: status_qualifier = 'Failure' if test: status_qualifier = 'Test' rec = [ self._activity_type, '', 'Canopy', 'Light', 'LUX', '', 'lux', 'TSL2561', status_qualifier, str(e) ] self._persist.save(rec) self._logger.error("{}, {}, {}".format("LUX", status_qualifier, e)) def getEC(self, test=False): """Record EC sensor (EC - ADC reading) Args: test: flag for testing Returns: None Raises: None """ from EC import EC self._logger.info("EC") try: s = EC() ec = s.getEC() status_qualifier = 'Success' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("EC", status_qualifier, ec)) rec = [ self._activity_type, '', 'Reservoir', 'Nutrient', 'EC', "{:3.1f}".format(ec), 'EC', 'EC', status_qualifier, '' ] self._persist.save(rec) self._logger.info("{}, {}, {:10.1f}".format( "EC", status_qualifier, ec)) except Exception as e: status_qualifier = 'Failure' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("EC", status_qualifier, ec)) rec = [ self._activity_type, '', 'Reservoir', 'Nutrient', 'EC', '', 'EC', 'EC', status_qualifier, str(e) ] self._persist.save(rec) self._logger.error("{}, {}, {}".format("EC CCS811", status_qualifier, e)) def getCO2_NDIR(self, test=False): """Record CO2 sensor (NDIR) Args: test: flag for testing Returns: None Raises: None """ from NDIR import Sensor self._logger.info("CO2 - NDIR") try: sensor = Sensor() sensor.begin() co2 = sensor.getCO2() status_qualifier = 'Success' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("CO2 Canopy", status_qualifier, co2)) rec = [ self._activity_type, '', 'Canopy', 'Air', 'CO2', "{:3.1f}".format(co2), 'ppm', 'MH-Z16-NDIR', status_qualifier, '' ] self._persist.save(rec) self._logger.debug("{}, {}, {:10.1f}".format( "CO2", status_qualifier, co2)) except Exception as e: status_qualifier = 'Failure' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("CO2 Canopy", status_qualifier, co2)) rec = [ self._activity_type, '', 'Canopy', 'Air', 'CO2', '', 'ppm', 'MH-Z16-NDIR', status_qualifier, str(e) ] self._persist.save(rec) self._logger.error("{}, {}, {}".format("CO2 NDIR", status_qualifier, e)) def getCO2_CCS811(self, test=False): """Record CO2 sensor (CCS811) Args: test: flag for testing Returns: None Raises: None """ from CCS811 import CCS811 self._logger.info("CO2 CCS811") try: sensor = CCS811(SLAVE) co2 = sensor.get_co2() status_qualifier = 'Success' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("CO2 Canopy", status_qualifier, co2)) rec = [ self._activity_type, '', 'Canopy', 'Air', 'CO2', "{:3.1f}".format(co2), 'ppm', 'CCS811', str(e) ] self._persist.save(rec) self._logger.debug("{}, {}, {:10.1f}".format( "CCS811 - CO2", status_qualifier, co2)) except Exception as e: status_qualifier = 'Failure' if test: status_qualifier = 'Test' print("{}, {}, {:10.1f}".format("CO2 Canopy", status_qualifier, co2)) rec = [ self._activity_type, '', 'Canopy', 'Air', 'CO2', '', 'ppm', 'CCS811', status_qualifier, str(e) ] self._persist.save(rec) self._logger.error("{}, {}, {}".format("CO2 CCS811", status_qualifier, e)) def getSCD(self): """Record CO2 sensor (scd30) Generates co2, temperature and relative humidity Args: None Returns: None Raises: None """ from scd30 import SCD30 self._scd = SCD30(self._logger) self._logger.debug("In SCD30") try: co2, temp, rh = self._scd.get_data() status = 'Success' if self._test: status = 'Test' c_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'CO2', "{:10.1f}".format(co2), 'ppm', 'scd30', status, '' ] t_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'Temperature', "{:10.1f}".format(temp), 'Centigrade', 'scd30', status, '' ] h_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'Humidity', "{:10.1f}".format(rh), 'Percent', 'scd30', status, '' ] self._persist.save(c_rec) self._persist.save(t_rec) self._persist.save(h_rec) self._logger.info("{} {:6.1f}, {} {:3.1f}, {} {:3.1f}".format( "EnvObsv-CO2:", co2, "Temp", temp, "Humidity:", rh)) except Exception as e: status = 'Failure' if self._test: status = 'Test' c_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'CO2', '', 'ppm', 'scd30', status, str(e) ] t_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'Temperature', '', 'Centigrde', 'scd30', status, '' ] h_rec = [ 'Environment_Observation', '', 'Top', 'Air', 'Humidity', '', 'Percent', 'scd30', status, '' ] self._persist.save(c_rec) self._persist.save(t_rec) self._persist.save(h_rec) self._logger.debug("{} {}".format("EnvObsv-SCD30 Error:", e)) def log(self): '''Log extra sensors Uncomment desired sensors Imports are in the function to avoid loading unnecessary code ''' #self.getOneWire() self.getLux() self.getEC() self.getCO2_NDIR() #self.getCO2_CCS811() self.getSCD()
class SCD30: CMD_START_PERIODIC_MEASUREMENT = 0x0010 CMD_STOP_PERIODIC_MEASUREMENT = 0x0104 CMD_READ_MEASUREMENT = 0x0300 CMD_GET_DATA_READY = 0x0202 CMD_SET_MEASUREMENT_INTERVAL = 0x4600 CMD_SET_TEMPERATURE_OFFSET = 0x5403 CMD_SET_ALTITUDE = 0x5102 WORD_LEN = 2 COMMAND_LEN = 2 MAX_BUFFER_WORDS = 24 STATUS_FAIL = 1 STATUS_OK = 0 CO2_DATA_INDEX = 0 TEMP_DATA_INDEX = 1 HUMIDITY_DATA_INDEX = 2 def __init__(self, logger=None): """Create sensor object Args: None Returns: None Raises: None """ self._addr = addr self._path = path self._logger = logger if logger == None: self._logger = Logger("SCD30", Logger.INFO) self._i2c = I2C(path, addr, self._logger) self._logger.debug("initialize SCD30") self.start_periodic_measurement() def start_periodic_measurement(self): """Start sensor to generate data (about every 2 seconds Args: self: test: flag for test logic Returns: None Raises: None """ # command 0x0010, altitude 0x03eb, crc_check 0x87 # altitude set to 1003 mb #msgs = [0x00,0x10, 0x03, 0xeb, 0x87] # Altitude is currently set to 0 self._logger.debug("In Start Periodic Measurment") msgs = [0x00,0x10, 0x00, 0x00, 0x81] self._i2c.msg_write(msgs) # TESTE ADN WORKS! def stop_periodic_measurement(self): """Stop automatic data gathering Args: None Returns: None Raises: None """ self._logger.debug("In Stop Periodic Measurment") msgs = [0x01,0x14] self._i2c.msg_write(msgs) # return self._i2c.msg_write(self.CMD_STOP_PERIODIC_MEASUREMENT pass def read_measurement(self): """Read data Args: self: test: Returns: CO2 Temp Relative Humidity Raises: None """ self._logger.debug("In Read Measurment") self._i2c.msg_write([0x03, 0x00]) msgs = self._i2c.msg_read(18) data3 = self.bytes_to_value(msgs[0].data) return data3[self.CO2_DATA_INDEX], data3[self.TEMP_DATA_INDEX], data3[self.HUMIDITY_DATA_INDEX] def bytes_to_value(self, byte_array): """Convert array of byte values into three float values Args: self byte_array: array of data from I2C sensor test Returns: data: array of float values (CO2, Temp, RH) Raises: None """ self._logger.debug("In Bytes To Value") # Array for value bytes (exclude crc check byte) bytes_buf = [0]*12 # 2 words for each co2, temperature, humidity l = len(bytes_buf) ld = len(byte_array) # array for word conversion - two words per value word_buf = [0]*int(l/2) # final data structure - one place per value data = [0]*int(len(word_buf)/2) # Load bytes_buffer, strip crc bytes y = 0 for x in range(0, ld, 6): # print("x: " + str(x) + " y: " + str(y)) bytes_buf[y] = byte_array[x] bytes_buf[y+1] = byte_array[x+1] bytes_buf[y+2] = byte_array[x+3] bytes_buf[y+3] = byte_array[x+4] y += 4 self._logger.detail("bytes_buf: " + str(bytes_buf)) # Convert sensor data reads to physical value per Sensirion specification # Load buffer with values # Cast 4 bytes to one unsigned 32 bit integer # Cast unsigned 32 bit integer to 32 bit float # Convert bytes to words for i in range(len(word_buf)): word_buf[i] = (bytes_buf[i*2] << 8) | bytes_buf[i*2+1] self._logger.detail(str(hex(bytes_buf[i])) + " " + str(hex(bytes_buf[i+1]))) self._logger.detail(hex(word_buf[i])) #convert words to int32 data[self.CO2_DATA_INDEX] = (word_buf[0] << 16) | word_buf[1] data[self.TEMP_DATA_INDEX] = (word_buf[2] << 16) | word_buf[3] data[self.HUMIDITY_DATA_INDEX] = (word_buf[4] << 16) | word_buf[5] #Convert int32 data to float32 floatData = numpy.array(data, dtype=numpy.int32) data = floatData.view('float32') self._logger.detail("CO2: " + str(data[self.CO2_DATA_INDEX])) self._logger.detail("Temp: " + str(data[self.TEMP_DATA_INDEX])) self._logger.detail("RH: " + str(data[self.HUMIDITY_DATA_INDEX])) return data # interval : (u16 integer) def set_measurement_interval(self, interval_sec): """Set frequency of automatic data collectoin Args: self interval_sec: time in seconds test Returns: None Raises: None """ self._logger.debug("In Set Measurment Interval") if interval_sec < 2 or interval_sec > 1800: return self.STATUS_FAIL # Need to finish this so value is 32 word split to two 16 words # Calculate crc value for last word # [Cmd MSB, Cmd LSB, Interval MSB, Interval LSB, CRC] # msb, lsb = convert_word(interval_sec, test) # crc = calc_crc(msb, lsb, test) # msg = [0x46,0x10, msb, lsb, crc] msgs = [0x46,0x10, 0x00, 0x02, 0xE3] self._i2c.msg_write(msgs) def get_data_ready(self): """Check if have fresh data from periodic update Args: self test Returns: ready flag Raises: None """ self._logger.debug("In Get Data Ready") self._i2c.msg_write([0x02, 0x02]) msgs = self._i2c.msg_read(3) #for msg in msgs: # self._logger.detail("Msg: " + str(msg)) # for d in msg.data: # self._logger.detail("Data: " + str(hex(d))) #self._logger.detail("Data: " + str(msgs[0].data[1])) return msgs[0].data[1] # Strange behaviour def set_temperature_offset(self, temperature_offset): """Temperature compensation offset Args: self temperature offset test Returns: None Raises: None """ self._logger.debug("In Set Temperature Offset") # Need to finish this so value is 32 word split to two 16 words # Calculate crc value for last word # [Cmd MSB, Cmd LSB, Interval MSB, Interval LSB, CRC] # msb, lsb = convert_word(interval_sec, test) # crc = calc_crc(msb, lsb, test) # msg = [0x54,0x03, msb, lsb, crc] msgs = [0x54,0x03, 0x00, 0x02, 0xE3] self._i2c.msg_write(msgs) # TESTE ADN WORKS! def set_altitude(self, altitude): """Altitude compensation Args: self altitude: uint16, height over sea level in [m] above 0 test Returns: None Raises: None """ self._logger.debug("In Set Altitude") # Need to finish this so value is 32 word split to two 16 words # Calculate crc value for last word # [Cmd MSB, Cmd LSB, Interval MSB, Interval LSB, CRC] # msb, lsb = convert_word(interval_sec, test) # crc = calc_crc(msb, lsb, test) # msg = [0x51,0x02, msb, lsb, crc] msgs = [0x51,0x02, 0x00, 0x02, 0xE3] self._i2c.msg_write(msgs) # TESTE ADN WORKS! def get_configured_address(self, test=False): """Altitude compensation Args: self test Returns: self._addr: address of the sensor Raises: None """ self._logger.debug("In Get Configured Address") return self._addr def get_data(self): """High level logic to simply get data Args: self test Returns: co2: co2 value temp: temperature value rh: relative humidity value Raises: NameError: if error in logic (I2C Problem) """ self._logger.debug("In Get Data") for x in range(0, 4): # Only give four reste tries before giving up try: while True: # Test if data is ready if self.get_data_ready(): # fetch data co2, temp, rh = self.read_measurement() self._logger.detail("CO2: " + str(co2)) self._logger.detail("Temp: " + str(temp)) self._logger.detail("RH: " + str(rh)) return co2, temp, rh time.sleep(1) # try reset to see if can recover from errors except Exception as e: self._logger.error("{}, {}".format("Data Ready Err: ", e)) self.__init__() time.sleep(2) # Give up if cannot fix the problems raise NameError("Too Many Failures")
class Relay(object): def __init__(self, logger=None): self._logger = logger if logger == None: self._logger = Logger("Relay", Logger.INFO) self._logger.debug("Initialize Relay") GPIO.setwarnings(False) if not GPIO.getmode() == GPIO.BOARD: GPIO.setmode(GPIO.BOARD) GPIO.setup(Relay1, GPIO.OUT) GPIO.setup(Relay2, GPIO.OUT) GPIO.setup(Relay3, GPIO.OUT) GPIO.setup(Relay4, GPIO.OUT) def __del__(self): ''' Don't GPIO.cleanup() as need to leave pin state alone ''' pass def set_state(self, pin, state): '''Change state if different''' msg = "{}, {}, {}".format("Current ", state, GPIO.input(pin)) self._logger.debug(msg) if state == ON and not GPIO.input(pin): self.set_on(pin) msg = "{} {} {}".format("Pin:", pin, " On") self._logger.debug(msg) elif state == OFF and GPIO.input(pin): self.set_off(pin) msg = "{} {} {}".format("Pin:", pin, " Off") self._logger.debug(msg) else: msg = "{} {} {}".format("Pin:", pin, " No Change") self._logger.debug(msg) def get_state(self, pin): '''Get the current state of the pin''' state = GPIO.input(pin) self._logger.debug("State: " + str(state)) return state def set_off(self, pin): GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.LOW) self._logger.debug("Set Off") def set_on(self, pin): GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.HIGH) self._logger.debug("Set On") def watch_change(self, pin, callback): GPIO.setup(pin, GPIO.IN) # only react to going high GPIO.add_event_detect(pin, GPIO.RISING, callback)
class Humidifier(object): """Code associated with the Humidifier""" ON = 1 OFF = 0 target_rh = 0 def __init__(self, logger=None): self._logger = logger if logger == None: self._logger = Logger("Humidifier", Logger.INFO) self._logger.debug("initialize Fan object") self._relay = Relay(self._logger) self._pin = 29 self._couch = CouchUtil(self._logger) # flag for if in testing self._test = False def set(self, state): """Set the humidifier to state Args: state: condition from other source Returns: None Raises: None """ self._logger.debug("In set_state") self._relay.set_state(self._pin, state) def set_on(self): """Turn the humidifier on Args: None Returns: None Raises: None """ self._logger.debug("In set_on") self.set(ON, test) def set_off(self): """Turn the fan off Args: None Returns: None Raises: None """ self._logger.debug("In set_off") self.set(OFF, test) def get_state(self): return self._relay.get_state(self._pin) def log_state(self, value): """Send state change to database Args: value: state change test: flag for testing Returns: None Raises: None """ status_qualifier = 'Success' if self._test: status_qualifier = 'Test' self._couch.saveList([ 'State_Change', '', 'Side', 'Fan', 'State', value, 'state', 'Fan', status_qualifier, '' ])