class CouchHeartbeat(object): """Heartbeat object """ logger = None def __init__(self): """Standard constructor Get and hold a Python logger """ self._logger = Logger('CouchHeartbeat', Logger.INFO, "/home/pi/MVP/logs/heartbeat.log") def check(self, port): """Ping the database Should return the welcome message Throws an exception if cannot make a connection to the database Args: port: port the database is communicating on Returns: None Raises: None """ cmd = 'curl -X GET http://localhost:5984' ret = os.system(cmd) if ret == 0: self._logger.info('localhost Couch is Up') else: self._logger.error('Couch is Down') self.restart() def restart(self): """System restart (reboot) Args: None Returns: None Raises: None """ cmd = 'sudo reboot' self.logger.warning('System restart: %s' % (cmd)) os.system(cmd)
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 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 SI7021(object): def __init__(self, logger=None): self._addr = addr self._path = path self._logger = logger if logger == None: self._logger = Logger("SI7021", Logger.INFO, "/home/pi/MVP/logs/obsv.log") self._i2c = I2C(path, addr, self._logger) def calc_humidity(self, read): """Calculate relative humidity from sensor reading Args: read: the sensor value Returns: rh: calculated relative humidity Raises: None """ rh = ((125.0 * read) / 65536.0) - 6.0 return rh def calc_temp(self, read): """Calculate relative humidity from sensor reading Args: read: the sensor value Returns: tempC: calculated temperature in Centigrade Raises: None """ tempC = ((175.72 * read) / 65536.0) - 46.85 return tempC def get_tempC_prior(self): """Get the temperature from the prior humidity reading Args: None Returns: tempC: calculated temperature in Centigrade Raises: None """ msgs = self._i2c.get_data([previous_temp], 0.03, 3) if msgs == None: return None else: value = self._i2c.bytesToWord(msgs[0].data[0], msgs[0].data[1]) tempC = self.calc_temp(value) return tempC def get_humidity(self): """Get the humidity Args: None Returns: rh: calculated relative humidity Raises: None """ msgs = self._i2c.get_data([rh_no_hold], 0.03, 2) value = self._i2c.bytesToWord(msgs[0].data[0], msgs[0].data[1]) if value == None: return None else: rh = self.calc_humidity(value) return rh def get_tempC(self): """Get the temperature (new reading) Args: None Returns: tempC: calculated temperature in Centigrade Raises: None """ msgs = self._i2c.get_data([temp_no_hold], 0.03, 3) value = self._i2c.bytesToWord(msgs[0].data[0], msgs[0].data[1]) if value == None: return None else: return self.calc_temp(value) def get_rev(self): """Get the firmware revision number Args: None Returns: rev: coded revision number Raises: None """ self._logger.info("\nGet Revision") # msgs = self._i2c.get_msg([firm_rev_1_1, firm_rev_1_2], 3) msgs = self._i2c.get_data([firm_rev_1_1, firm_rev_1_2], 0.03, 3) # Need to test, may error out on some conditions if not ((msgs is None) or (msgs[0].data is None)): rev = msgs[0].data[0] if rev == 0xFF: self._logger.info("version 1.0") elif rev == 0x20: self._logger.info("version 2.0") else: self._logger.error("Unknown") else: self._logger.error("No Revision Data Available") return rev def get_id1(self): """Print the first part of the chips unique id Args: None Returns: None Raises: None """ self._logger.info("\nGet ID 1") try: msgs = self._i2c.get_data([read_id_1_1, read_id_1_2], 0.05, 4) ret = msgs[0].data for data in ret: self._logger.info("ID: " + str(hex(data))) except Exception as e: self._logger.error("Error getting msgs " + str(e)) def get_id2(self): """Print the second part of the chips unique id The device version is in SNA_3 Args: None Returns: None Raises: None """ self._logger.info("\nGet ID 2") msgs = self._i2c.get_data([read_id_2_1, read_id_2_2], 0.05, 4) ret = msgs[0].data for data in ret: self._logger.info("ID" + str(hex(data))) sna3 = msgs[0].data[0] if sna3 == 0x00: self._logger.info("Device: Engineering Sample") elif sna3 == 0xFF: self._logger.info("Device: Engineering Sample") elif sna3 == 0x14: self._logger.info("Device: SI7020") elif sna3 == 0x15: self._logger.info("Device: SI7021") else: self._logger.error("Unknown") def reset(self): """Reset the device Args: None Returns: None Raises: None """ self._logger.info("\nReset") rev_1 = self._i2c.msg_write([reset_cmd]) self._logger.info("Reset: " + str(rev_1))
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 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 CouchUtil(object): def __init__(self, logger=None): if logger == None: self._logger = Logger("LogSensor", Logger.DETAIL) else: self._logger = logger self._activity_type = "Environment_Observation" self._logger.detail("CouchUtil") self._test = False self._server = Server() self._db = self._server[db_name] def processEnv(self, row): ''' Environment specific processing Args: doc: list of attributes, should be of format: [timestamp, field, 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: json formatted record ready for the database Throws: None ''' rec = self.buildCore(row) rec['activity_type'] = row[ACTIVITY] rec['subject'] = { 'name': row[SUBJECT], 'attribute': { 'name': row[ATTRIBUTE], 'units': row[UNITS], 'value': row[VALUE] }, 'location': row[PLOT] } rec['location'] = {'field': row[FIELD]} return rec def processState(self, row): ''' State specific processing Args: doc: list of attributes, should be of format: [timestamp, field, 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: json formatted record ready for the database Throws: None ''' self._logger.detail("In State_Change") rec = self.buildCore(row) rec['activity_type'] = row[ACTIVITY] self._logger.detail("Activity: " + str(rec['activity_type'])) rec['subject'] = { 'name': row[SUBJECT], 'attribute': { 'name': row[ATTRIBUTE], 'units': row[UNITS], 'value': row[VALUE] }, 'location': row[PLOT] } self._logger.detail("Subject: " + str(rec['subject'])) rec['participant'] = {'type': 'device', 'name': row[PARTICIPANT]} rec['location'] = {'field': row[FIELD]} self._logger.detail(str(rec)) return rec def processAgro(self, row): ''' Agronomic specific processing Args: doc: list of attributes, should be of format: [timestamp, field, 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: json formatted record ready for the database Throws: None ''' self._logger.detail("In Process Agro") rec = self.buildCore(row) rec['activity_type'] = row[ACTIVITY] rec['sub-activity'] = row[PLOT] if len(row[SUBJECT]) > 0: rec['subject'] = { 'name': row[SUBJECT], 'attribute': { 'name': row[ATTRIBUTE], 'units': row[UNITS], 'value': row[VALUE] } } rec['location'] = {'field': row[FIELD], 'trial': row[TRIAL]} return rec def processPheno(self, row): ''' Phenotype specific processing Args: doc: list of attributes, should be of format: [timestamp, field, 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: json formatted record ready for the database Throws: None ''' self._logger.detail("In Process Pheno") rec = self.buildCore(row) rec['activity_type'] = row[ACTIVITY] rec['subject'] = { 'name': row[SUBJECT], 'attribute': { 'name': row[ATTRIBUTE], 'units': row[UNITS], 'value': row[VALUE] } } rec['location'] = { 'field': row[FIELD], 'trial': row[TRIAL], 'plot': row[PLOT] } return rec def buildCore(self, row): ''' Build the core of the json structure, common elements Args: row: list of activity [timestamp, field, 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: json formatted record ready for the database Throws: None ''' self._logger.detail("In buildCore") rec = {} rec['start_date'] = {'timestamp': row[TS]} if isinstance(row[PARTICIPANT], list): rec['participant'] = { 'type': row[PARTICIPANT][0], 'name': row[PARTICIPANT][1] } else: rec['participant'] = {'type': 'device', 'name': row[PARTICIPANT]} if len(row[COMMENT]) == 0: rec['status'] = { 'status': 'Complete', 'status_qualifier': row[STATUS] } else: rec['status'] = { 'status': 'Complete', 'status_qualifier': row[STATUS], 'comment': row[COMMENT] } if row[STATUS] == "Success": self._logger.detail(rec) elif row[STATUS] == "Failure": self._logger.error("Failure" + str(rec)) elif row[STATUS] == "Test": self._logger.detail(rec) else: self._logger.error("Unknown Status" + str(rec)) return rec def save(self, doc, test=False): ''' Convert activity list to json structure and save to database This is the entry point for all other functions 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: json formatted record ready for the database Throws: None ''' self._logger.detail("In saveList") # dictionary of activity types and specific processing functions proc = { 'Environment_Observation': self.processEnv, 'State_Change': self.processState, 'Agronomic_Activity': self.processAgro, 'Phenotype_Observation': self.processPheno } # add timestamp and field_id timestamp = datetime.utcnow().isoformat()[:19] doc.insert(0, env['field']['field_id']) doc.insert(0, timestamp) # Use activity type to route processing rec = proc[doc[2]](doc) self.saveRec(rec, test) def saveRec(self, rec, test=False): ''' Persist json structure to a database Args: rec: json structure Returns: id: document id rev: revision number of document Throws: None ''' # print rec id, rev = self._db.save(rec)
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