diff --git a/demos/atmospheric/bme280.py b/demos/atmospheric/bme280.py new file mode 100644 index 0000000..fe2f79f --- /dev/null +++ b/demos/atmospheric/bme280.py @@ -0,0 +1,297 @@ +# modified from: https://github.com/RuiSantosdotme/ESP-MicroPython/blob/master/code/WiFi/HTTP_Client_IFTTT_BME280/BME280.py +import time + +# BME280 default address. +BME280_I2CADDR = 0x77 + +# Operating Modes +BME280_OSAMPLE_1 = 1 +BME280_OSAMPLE_2 = 2 +BME280_OSAMPLE_4 = 3 +BME280_OSAMPLE_8 = 4 +BME280_OSAMPLE_16 = 5 + +# BME280 Registers + +BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers +BME280_REGISTER_DIG_T2 = 0x8A +BME280_REGISTER_DIG_T3 = 0x8C + +BME280_REGISTER_DIG_P1 = 0x8E +BME280_REGISTER_DIG_P2 = 0x90 +BME280_REGISTER_DIG_P3 = 0x92 +BME280_REGISTER_DIG_P4 = 0x94 +BME280_REGISTER_DIG_P5 = 0x96 +BME280_REGISTER_DIG_P6 = 0x98 +BME280_REGISTER_DIG_P7 = 0x9A +BME280_REGISTER_DIG_P8 = 0x9C +BME280_REGISTER_DIG_P9 = 0x9E + +BME280_REGISTER_DIG_H1 = 0xA1 +BME280_REGISTER_DIG_H2 = 0xE1 +BME280_REGISTER_DIG_H3 = 0xE3 +BME280_REGISTER_DIG_H4 = 0xE4 +BME280_REGISTER_DIG_H5 = 0xE5 +BME280_REGISTER_DIG_H6 = 0xE6 +BME280_REGISTER_DIG_H7 = 0xE7 + +BME280_REGISTER_CHIPID = 0xD0 +BME280_REGISTER_VERSION = 0xD1 +BME280_REGISTER_SOFTRESET = 0xE0 + +BME280_REGISTER_CONTROL_HUM = 0xF2 +BME280_REGISTER_CONTROL = 0xF4 +BME280_REGISTER_CONFIG = 0xF5 +BME280_REGISTER_PRESSURE_DATA = 0xF7 +BME280_REGISTER_TEMP_DATA = 0xFA +BME280_REGISTER_HUMIDITY_DATA = 0xFD + + +class Device: + """Class for communicating with an I2C device. + + Allows reading and writing 8-bit, 16-bit, and byte array values to + registers on the device.""" + + def __init__(self, address, i2c): + """Create an instance of the I2C device at the specified address using + the specified I2C interface object.""" + self._address = address + self._i2c = i2c + + def writeRaw8(self, value): + """Write an 8-bit value on the bus (without register).""" + value = value & 0xFF + self._i2c.writeto(self._address, value) + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + b = bytearray(1) + b[0] = value & 0xFF + self._i2c.writeto_mem(self._address, register, b) + + def write16(self, register, value): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + b = bytearray(2) + b[0] = value & 0xFF + b[1] = (value >> 8) & 0xFF + self.i2c.writeto_mem(self._address, register, value) + + def readRaw8(self): + """Read an 8-bit value on the bus (without register).""" + return int.from_bytes(self._i2c.readfrom(self._address, 1), 'little') \ + & 0xFF + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + return int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 1), 'little') \ + & 0xFF + + 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 = int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 2), 'little') \ + & 0xFFFF + if not little_endian: + result = ((result << 8) & 0xFF00) + (result >> 8) + return result + + 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) + + +class BME280: + def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None, + **kwargs): + # Check that mode is valid. + if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4, + BME280_OSAMPLE_8, BME280_OSAMPLE_16]: + raise ValueError( + 'Unexpected mode value {0}. Set mode to one of ' + 'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or ' + 'BME280_ULTRAHIGHRES'.format(mode)) + self._mode = mode + # Create I2C device. + if i2c is None: + raise ValueError('An I2C object is required.') + self._device = Device(address, i2c) + # Load calibration values. + self._load_calibration() + self._device.write8(BME280_REGISTER_CONTROL, 0x3F) + self.t_fine = 0 + + def _load_calibration(self): + self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1) + self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2) + self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3) + + self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1) + self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2) + self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3) + self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4) + self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5) + self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6) + self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7) + self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8) + self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9) + + self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1) + self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2) + self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3) + self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7) + + h4 = self._device.readS8(BME280_REGISTER_DIG_H4) + h4 = (h4 << 24) >> 20 + self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F) + + h5 = self._device.readS8(BME280_REGISTER_DIG_H6) + h5 = (h5 << 24) >> 20 + self.dig_H5 = h5 | ( + self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F) + + def read_raw_temp(self): + """Reads the raw (uncompensated) temperature from the sensor.""" + meas = self._mode + self._device.write8(BME280_REGISTER_CONTROL_HUM, meas) + meas = self._mode << 5 | self._mode << 2 | 1 + self._device.write8(BME280_REGISTER_CONTROL, meas) + sleep_time = 1250 + 2300 * (1 << self._mode) + + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + time.sleep_us(sleep_time) # Wait the required time + msb = self._device.readU8(BME280_REGISTER_TEMP_DATA) + lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_pressure(self): + """Reads the raw (uncompensated) pressure level from the sensor.""" + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA) + lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_humidity(self): + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA) + lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1) + raw = (msb << 8) | lsb + return raw + + def read_temperature(self): + """Get the compensated temperature in 0.01 of a degree celsius.""" + adc = self.read_raw_temp() + var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11) + var2 = (( + (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) * + self.dig_T3) >> 14 + self.t_fine = var1 + var2 + raw_temp = (self.t_fine * 5 + 128) >> 8 + temp_int = raw_temp // 100 + temp_dec = raw_temp - temp_int * 100 + return float("{0}.{1}".format(temp_int, temp_dec)) + + def read_pressure(self): + """Gets the compensated pressure in hPascals.""" + adc = self.read_raw_pressure() + var1 = self.t_fine - 128000 + var2 = var1 * var1 * self.dig_P6 + var2 = var2 + ((var1 * self.dig_P5) << 17) + var2 = var2 + (self.dig_P4 << 35) + var1 = (((var1 * var1 * self.dig_P3) >> 8) + + ((var1 * self.dig_P2) >> 12)) + var1 = (((1 << 47) + var1) * self.dig_P1) >> 33 + if var1 == 0: + return 0 + p = 1048576 - adc + p = (((p << 31) - var2) * 3125) // var1 + var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 + var2 = (self.dig_P8 * p) >> 19 + raw_pressure = ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) + p = raw_pressure // 256 + p_int = p // 100 + p_dec = p - p_int * 100 + return float("{0}.{1}".format(p_int, p_dec)) + + def read_humidity(self): + adc = self.read_raw_humidity() + # print 'Raw humidity = {0:d}'.format (adc) + h = self.t_fine - 76800 + h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) + + 16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h * + self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) * + self.dig_H2 + 8192) >> 14)) + h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4) + h = 0 if h < 0 else h + h = 419430400 if h > 419430400 else h + raw_humidity = h >> 12 + h_int = raw_humidity // 1024 + h_dec = raw_humidity * 100 // 1024 - h_int * 100 + return float("{0}.{1}".format(h_int, h_dec)) + + @property + def temperature(self): + "Return the temperature in degrees Celcius." + t = self.read_temperature() + ti = t // 100 + td = t - ti * 100 + return "{}.{:02d}C".format(ti, td) + + @property + def pressure(self): + "Return the temperature in hPa." + p = self.read_pressure() // 256 + pi = p // 100 + pd = p - pi * 100 + return "{}.{:02d}hPa".format(pi, pd) + + @property + def humidity(self): + "Return the humidity in percent." + h = self.read_humidity() + hi = h // 1024 + hd = h * 100 // 1024 - hi * 100 + return "{}.{:02d}%".format(hi, hd) diff --git a/demos/atmospheric/main.py b/demos/atmospheric/main.py new file mode 100644 index 0000000..3658fd8 --- /dev/null +++ b/demos/atmospheric/main.py @@ -0,0 +1,18 @@ +from sensor import Sensor +from bme280 import BME280 + +sensor = Sensor(sensor_name="atmospheric", + topic='sensor_data/atmospheric', + reporting_interval_sec=2, + I2CSensor=True) + +environ_sensor = BME280(i2c=sensor.i2c_bus) +sensor.register_i2c_sensor_function('temperature', + environ_sensor.read_temperature) +sensor.register_i2c_sensor_function('humidity', + environ_sensor.read_humidity) +sensor.register_i2c_sensor_function('pressure', + environ_sensor.read_pressure) + + +sensor.run() diff --git a/demos/atmospheric/sensor.py b/demos/atmospheric/sensor.py new file mode 100644 index 0000000..25cd190 --- /dev/null +++ b/demos/atmospheric/sensor.py @@ -0,0 +1,114 @@ +import time +import machine +from util import connect_network + +# TODO: add a way to register a fun +try: + import umqtt.simple as mqtt +except ImportError: + connect_network() + import mip + mip.install('umqtt.simple') + machine.reset() + +import json + +class Sensor: + def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", reporting_interval_sec=5, I2CSensor=False) -> None: + self.sensor_name = sensor_name + self.status = "initializing" + self.network = None + self.mqtt_handler = None + self.topic = topic + self.measurement_functions = dict() + self.init_functions = dict() + self.indicator_pin = machine.Pin(indicator_pin, machine.Pin.OUT) + self.reporting_interval_sec = reporting_interval_sec + self.pins = set() + self.wdt = machine.WDT(timeout=8000) + + # setup i2c bus + if I2CSensor: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), scl=machine.Pin(1)) + else: + self.i2c_bus = None + + # TODO: turn back on + try: + self.network = connect_network() + self.status = "network connection established" + except Exception as e: + self.status = "failed to establish network connection, " + "error: {}".format(e) + + machine.reset() + + mqtt_error_cnt = 0 + try: + self.mqtt_handler = mqtt.MQTTClient(sensor_name, "10.42.0.1") + mqtt_error_cnt = 0 + except Exception as e: + self.status = "failed to establish mqtt connection, error: {}".format(e) + mqtt_error_cnt += 1 + print(self.status) + + if mqtt_error_cnt > 5: + machine.reset() + + def register_analog_sensor_function(self, name, pin): + """adds a function to the sensor's measurement_functions dict""" + + # check to make sure pins unused. + if pin in self.pins: + raise ValueError(f"Pin {pin} already in use") + else: + self.pins.add(pin) + + if pin not in {26, 27, 28}: # Pins with ADC functionality on Pico W + raise ValueError(f"Pin {pin} not a valid ADC pin") + + self.measurement_functions[name] = machine.ADC(pin).read_u16 + print("{}: {}".format(name, self.measurement_functions[name]())) + + + def register_i2c_sensor_function(self, measurement_name, func): + if self.i2c_bus is None: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), + scl=machine.Pin(1)) + print("I2C bus initialized") + + self.measurement_functions[measurement_name] = func + + def measurements(self): + measurements = json.dumps( + { + "sensor": self.sensor_name, + "data": { + key: value() for key, value in + self.measurement_functions.items() + }, + } + ) + print(measurements, "\n") + return measurements + + def publish(self): + self.indicator_pin.on() + self.mqtt_handler.connect() + self.mqtt_handler.publish( + topic=bytes(self.topic, "utf-8"), + msg=self.measurements(), + qos=0 + ) + self.mqtt_handler.disconnect() + self.wdt.feed() + self.indicator_pin.off() + time.sleep(self.reporting_interval_sec) + + def run(self): + while True: + for name, func in self.measurement_functions.items(): + print(name, func()) + self.publish() + time.sleep(self.reporting_interval_sec) + diff --git a/demos/atmospheric/util.py b/demos/atmospheric/util.py new file mode 100644 index 0000000..7bd4a5f --- /dev/null +++ b/demos/atmospheric/util.py @@ -0,0 +1,58 @@ +import network +import rp2 +import machine +import utime + + +# network stuff +rp2.country("US") + + +def connect_network(max_wait=10): + """Connect to the network, return the wlan object""" + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + print('active') + wlan.connect("sensor_hub", "FourCorners") + while max_wait > 0: + + if wlan.isconnected(): + print("Connection Established") + return wlan + else: + max_wait -= 1 + print( + "waiting for connection, current status: {}".format( + wlan.status()) + ) + utime.sleep(1) + + if wlan.status() != 3: + print("Connection Failed, resetting machine") + machine.reset() + + +# other +def try_until_i2c(func): + def wrapper_try_until_runs(*args, **kwargs): + while True: + try: + return func(*args, **kwargs) + except OSError as oserr: + print(oserr) + continue + except Exception as e: + raise e + return wrapper_try_until_runs + + +def crc8(msg): + print('crc8 running') + crc = 0xFF + for byte in msg: + crc ^= byte + for _ in range(8): + crc = (crc << 1) ^ 0x07 if crc & 0x80 else crc << 1 + crc &= 0xff + final = [crc ^ 0x00] + print(final) diff --git a/main.py b/main.py index eb138a8..095fdc1 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,10 @@ from sensor import Sensor from bme280 import BME280 -from machine import WDT -# sensor = Sensor(sensor_name="soil_moisture_demo_1") -# sensor.register_analog_sensor_function("soil_moisture", 28) - -# I2C sensor example sensor = Sensor(sensor_name="atmospheric", topic='sensor_data/atmospheric', - I2CSensor=True, - wdt=WDT(timeout=7000)) + reporting_interval_sec=2, + I2CSensor=True) environ_sensor = BME280(i2c=sensor.i2c_bus) sensor.register_i2c_sensor_function('temperature', @@ -19,5 +14,5 @@ sensor.register_i2c_sensor_function('pressure', environ_sensor.read_pressure) -if __name__ == "__main__": - sensor.run() \ No newline at end of file + +sensor.run() \ No newline at end of file diff --git a/mains_and_manifests/atmospheric_main.py b/mains_and_manifests/atmospheric_main.py index eb138a8..3658fd8 100644 --- a/mains_and_manifests/atmospheric_main.py +++ b/mains_and_manifests/atmospheric_main.py @@ -1,15 +1,10 @@ from sensor import Sensor from bme280 import BME280 -from machine import WDT -# sensor = Sensor(sensor_name="soil_moisture_demo_1") -# sensor.register_analog_sensor_function("soil_moisture", 28) - -# I2C sensor example sensor = Sensor(sensor_name="atmospheric", topic='sensor_data/atmospheric', - I2CSensor=True, - wdt=WDT(timeout=7000)) + reporting_interval_sec=2, + I2CSensor=True) environ_sensor = BME280(i2c=sensor.i2c_bus) sensor.register_i2c_sensor_function('temperature', @@ -19,5 +14,5 @@ sensor.register_i2c_sensor_function('pressure', environ_sensor.read_pressure) -if __name__ == "__main__": - sensor.run() \ No newline at end of file + +sensor.run() diff --git a/mains_and_manifests/atmospheric_manifest.py b/mains_and_manifests/atmospheric_manifest.py deleted file mode 100644 index 8a866b1..0000000 --- a/mains_and_manifests/atmospheric_manifest.py +++ /dev/null @@ -1,2 +0,0 @@ -require('mqtt.simple') -package(./pico_bug) \ No newline at end of file diff --git a/analog_example.py b/mains_and_manifests/soil_moisture_main.py similarity index 100% rename from analog_example.py rename to mains_and_manifests/soil_moisture_main.py diff --git a/sensor.py b/sensor.py index 665bfd4..25cd190 100644 --- a/sensor.py +++ b/sensor.py @@ -1,6 +1,6 @@ import time import machine -from util import connect_network, _i2c_function_ +from util import connect_network # TODO: add a way to register a fun try: @@ -9,12 +9,12 @@ connect_network() import mip mip.install('umqtt.simple') - import umqtt.simple as mqtt + machine.reset() import json class Sensor: - def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", reporting_interval_sec=5, I2CSensor=False, wdt=None) -> None: + def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", reporting_interval_sec=5, I2CSensor=False) -> None: self.sensor_name = sensor_name self.status = "initializing" self.network = None @@ -25,7 +25,7 @@ def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", report self.indicator_pin = machine.Pin(indicator_pin, machine.Pin.OUT) self.reporting_interval_sec = reporting_interval_sec self.pins = set() - self.wdt = wdt + self.wdt = machine.WDT(timeout=8000) # setup i2c bus if I2CSensor: @@ -42,12 +42,18 @@ def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", report "error: {}".format(e) machine.reset() - + + mqtt_error_cnt = 0 try: self.mqtt_handler = mqtt.MQTTClient(sensor_name, "10.42.0.1") + mqtt_error_cnt = 0 except Exception as e: self.status = "failed to establish mqtt connection, error: {}".format(e) - machine.reset() + mqtt_error_cnt += 1 + print(self.status) + + if mqtt_error_cnt > 5: + machine.reset() def register_analog_sensor_function(self, name, pin): """adds a function to the sensor's measurement_functions dict""" @@ -83,6 +89,7 @@ def measurements(self): }, } ) + print(measurements, "\n") return measurements def publish(self): @@ -94,6 +101,7 @@ def publish(self): qos=0 ) self.mqtt_handler.disconnect() + self.wdt.feed() self.indicator_pin.off() time.sleep(self.reporting_interval_sec) @@ -103,5 +111,4 @@ def run(self): print(name, func()) self.publish() time.sleep(self.reporting_interval_sec) - self.wdt.feed()