diff options
author | Blaise Thompson <blaise@untzag.com> | 2021-03-08 13:21:37 -0600 |
---|---|---|
committer | Blaise Thompson <blaise@untzag.com> | 2021-03-08 13:21:37 -0600 |
commit | 751e664a28892fd92694abf3d821d068013e9453 (patch) | |
tree | 8a9a62bbe3bd6fcbbd7be411c38b366f3f008b83 /firmware |
initial commit
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/.gitignore | 2 | ||||
-rw-r--r-- | firmware/bme680.py | 229 | ||||
-rw-r--r-- | firmware/boot.py | 7 | ||||
-rw-r--r-- | firmware/esp8266-20191220-v1.12.bin | bin | 0 -> 619828 bytes | |||
-rw-r--r-- | firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08 | bin | 0 -> 6820 bytes | |||
-rw-r--r-- | firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a | bin | 0 -> 15882 bytes | |||
-rw-r--r-- | firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7 | bin | 0 -> 120199 bytes | |||
-rw-r--r-- | firmware/false/selfcheck/b26dae3201984cbbecf1047ed3665c561642f1d99a4ae03cb9e4d513 | 1 | ||||
-rw-r--r-- | firmware/firmware.org | 13 | ||||
-rwxr-xr-x | firmware/flash.sh | 17 | ||||
-rw-r--r-- | firmware/main.py | 126 | ||||
m--------- | firmware/microhomie | 0 | ||||
-rw-r--r-- | firmware/microhomie-esp8266-v3.0.2.bin | bin | 0 -> 605256 bytes | |||
-rw-r--r-- | firmware/settings.py.example | 10 |
14 files changed, 405 insertions, 0 deletions
diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 0000000..cd7d6eb --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1,2 @@ +*.mpy +settings.py diff --git a/firmware/bme680.py b/firmware/bme680.py new file mode 100644 index 0000000..3dfe753 --- /dev/null +++ b/firmware/bme680.py @@ -0,0 +1,229 @@ +# Spaces, comments and some functions have been removed from the original file to save memory +# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py +import time +import math +from micropython import const +from ubinascii import hexlify as hex +try: + import struct +except ImportError: + import ustruct as struct +_BME680_CHIPID = const(0x61) +_BME680_REG_CHIPID = const(0xD0) +_BME680_BME680_COEFF_ADDR1 = const(0x89) +_BME680_BME680_COEFF_ADDR2 = const(0xE1) +_BME680_BME680_RES_HEAT_0 = const(0x5A) +_BME680_BME680_GAS_WAIT_0 = const(0x64) +_BME680_REG_SOFTRESET = const(0xE0) +_BME680_REG_CTRL_GAS = const(0x71) +_BME680_REG_CTRL_HUM = const(0x72) +_BME280_REG_STATUS = const(0xF3) +_BME680_REG_CTRL_MEAS = const(0x74) +_BME680_REG_CONFIG = const(0x75) +_BME680_REG_PAGE_SELECT = const(0x73) +_BME680_REG_MEAS_STATUS = const(0x1D) +_BME680_REG_PDATA = const(0x1F) +_BME680_REG_TDATA = const(0x22) +_BME680_REG_HDATA = const(0x25) +_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16) +_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127) +_BME680_RUNGAS = const(0x10) +_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, + 2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0, + 2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0, + 2147483647.0) +_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0, + 64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0, + 500000.0, 250000.0, 125000.0) +def _read24(arr): + ret = 0.0 + for b in arr: + ret *= 256.0 + ret += float(b & 0xFF) + return ret +class Adafruit_BME680: + def __init__(self, *, refresh_rate=10): + self._write(_BME680_REG_SOFTRESET, [0xB6]) + time.sleep(0.005) + chip_id = self._read_byte(_BME680_REG_CHIPID) + if chip_id != _BME680_CHIPID: + raise RuntimeError('Failed 0x%x' % chip_id) + self._read_calibration() + self._write(_BME680_BME680_RES_HEAT_0, [0x73]) + self._write(_BME680_BME680_GAS_WAIT_0, [0x65]) + self.sea_level_pressure = 1013.25 + self._pressure_oversample = 0b011 + self._temp_oversample = 0b100 + self._humidity_oversample = 0b010 + self._filter = 0b010 + self._adc_pres = None + self._adc_temp = None + self._adc_hum = None + self._adc_gas = None + self._gas_range = None + self._t_fine = None + self._last_reading = 0 + self._min_refresh_time = 1000 / refresh_rate + @property + def pressure_oversample(self): + return _BME680_SAMPLERATES[self._pressure_oversample] + @pressure_oversample.setter + def pressure_oversample(self, sample_rate): + if sample_rate in _BME680_SAMPLERATES: + self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate) + else: + raise RuntimeError("Invalid") + @property + def humidity_oversample(self): + return _BME680_SAMPLERATES[self._humidity_oversample] + @humidity_oversample.setter + def humidity_oversample(self, sample_rate): + if sample_rate in _BME680_SAMPLERATES: + self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate) + else: + raise RuntimeError("Invalid") + @property + def temperature_oversample(self): + return _BME680_SAMPLERATES[self._temp_oversample] + @temperature_oversample.setter + def temperature_oversample(self, sample_rate): + if sample_rate in _BME680_SAMPLERATES: + self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate) + else: + raise RuntimeError("Invalid") + @property + def filter_size(self): + return _BME680_FILTERSIZES[self._filter] + @filter_size.setter + def filter_size(self, size): + if size in _BME680_FILTERSIZES: + self._filter = _BME680_FILTERSIZES[size] + else: + raise RuntimeError("Invalid") + @property + def temperature(self): + self._perform_reading() + calc_temp = (((self._t_fine * 5) + 128) / 256) + return calc_temp / 100 + @property + def pressure(self): + self._perform_reading() + var1 = (self._t_fine / 2) - 64000 + var2 = ((var1 / 4) * (var1 / 4)) / 2048 + var2 = (var2 * self._pressure_calibration[5]) / 4 + var2 = var2 + (var1 * self._pressure_calibration[4] * 2) + var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536) + var1 = (((((var1 / 4) * (var1 / 4)) / 8192) * + (self._pressure_calibration[2] * 32) / 8) + + ((self._pressure_calibration[1] * var1) / 2)) + var1 = var1 / 262144 + var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768 + calc_pres = 1048576 - self._adc_pres + calc_pres = (calc_pres - (var2 / 4096)) * 3125 + calc_pres = (calc_pres / var1) * 2 + var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096 + var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192 + var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072 + calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16) + return calc_pres # Pa + @property + def humidity(self): + self._perform_reading() + temp_scaled = ((self._t_fine * 5) + 128) / 256 + var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) - + ((temp_scaled * self._humidity_calibration[2]) / 200)) + var2 = (self._humidity_calibration[1] * + (((temp_scaled * self._humidity_calibration[3]) / 100) + + (((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) / + 64) / 100) + 16384)) / 1024 + var3 = var1 * var2 + var4 = self._humidity_calibration[5] * 128 + var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16 + var5 = ((var3 / 16384) * (var3 / 16384)) / 1024 + var6 = (var4 * var5) / 2 + calc_hum = (((var3 + var6) / 1024) * 1000) / 4096 + calc_hum /= 1000 + if calc_hum > 100: + calc_hum = 100 + if calc_hum < 0: + calc_hum = 0 + return calc_hum + @property + def altitude(self): + pressure = self.pressure + return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903)) + @property + def gas(self): + self._perform_reading() + var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536 + var2 = ((self._adc_gas * 32768) - 16777216) + var1 + var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512 + calc_gas_res = (var3 + (var2 / 2)) / var2 + return int(calc_gas_res) + def _perform_reading(self): + if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1) + < self._min_refresh_time): + return + self._write(_BME680_REG_CONFIG, [self._filter << 2]) + self._write(_BME680_REG_CTRL_MEAS, + [(self._temp_oversample << 5)|(self._pressure_oversample << 2)]) + self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample]) + self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS]) + ctrl = self._read_byte(_BME680_REG_CTRL_MEAS) + ctrl = (ctrl & 0xFC) | 0x01 + self._write(_BME680_REG_CTRL_MEAS, [ctrl]) + new_data = False + while not new_data: + data = self._read(_BME680_REG_MEAS_STATUS, 15) + new_data = data[0] & 0x80 != 0 + time.sleep(0.005) + self._last_reading = time.ticks_ms() + self._adc_pres = _read24(data[2:5]) / 16 + self._adc_temp = _read24(data[5:8]) / 16 + self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0] + self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64) + self._gas_range = data[14] & 0x0F + var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2) + var2 = (var1 * self._temp_calibration[1]) / 2048 + var3 = ((var1 / 2) * (var1 / 2)) / 4096 + var3 = (var3 * self._temp_calibration[2] * 16) / 16384 + self._t_fine = int(var2 + var3) + def _read_calibration(self): + coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25) + coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16) + coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39]))) + coeff = [float(i) for i in coeff] + self._temp_calibration = [coeff[x] for x in [23, 0, 1]] + self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]] + self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]] + self._gas_calibration = [coeff[x] for x in [25, 24, 26]] + self._humidity_calibration[1] *= 16 + self._humidity_calibration[1] += self._humidity_calibration[0] % 16 + self._humidity_calibration[0] /= 16 + self._heat_range = (self._read_byte(0x02) & 0x30) / 16 + self._heat_val = self._read_byte(0x00) + self._sw_err = (self._read_byte(0x04) & 0xF0) / 16 + def _read_byte(self, register): + return self._read(register, 1)[0] + def _read(self, register, length): + raise NotImplementedError() + def _write(self, register, values): + raise NotImplementedError() +class BME680_I2C(Adafruit_BME680): + def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10): + self._i2c = i2c + self._address = address + self._debug = debug + super().__init__(refresh_rate=refresh_rate) + def _read(self, register, length): + result = bytearray(length) + self._i2c.readfrom_mem_into(self._address, register & 0xff, result) + if self._debug: + print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result])) + return result + def _write(self, register, values): + if self._debug: + print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values])) + for value in values: + self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF])) + register += 1 diff --git a/firmware/boot.py b/firmware/boot.py new file mode 100644 index 0000000..18ffbcf --- /dev/null +++ b/firmware/boot.py @@ -0,0 +1,7 @@ +import esp +esp.osdebug(None) +#import uos, machine +#uos.dupterm(None, 1) # disable REPL on UART(0) +import gc +from machine import RTC +gc.collect() diff --git a/firmware/esp8266-20191220-v1.12.bin b/firmware/esp8266-20191220-v1.12.bin Binary files differnew file mode 100644 index 0000000..e46d257 --- /dev/null +++ b/firmware/esp8266-20191220-v1.12.bin diff --git a/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08 b/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08 Binary files differnew file mode 100644 index 0000000..e81a5c1 --- /dev/null +++ b/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08 diff --git a/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a b/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a Binary files differnew file mode 100644 index 0000000..57a0aeb --- /dev/null +++ b/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a diff --git a/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7 b/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7 Binary files differnew file mode 100644 index 0000000..06e84c4 --- /dev/null +++ b/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7 diff --git a/firmware/false/selfcheck/b26dae3201984cbbecf1047ed3665c561642f1d99a4ae03cb9e4d513 b/firmware/false/selfcheck/b26dae3201984cbbecf1047ed3665c561642f1d99a4ae03cb9e4d513 new file mode 100644 index 0000000..03c7d50 --- /dev/null +++ b/firmware/false/selfcheck/b26dae3201984cbbecf1047ed3665c561642f1d99a4ae03cb9e4d513 @@ -0,0 +1 @@ +{"key":"/home/blaise/miniconda3/envs/micropython","last_check":"2020-12-10T22:34:28Z","pypi_version":"20.3.1"}
\ No newline at end of file diff --git a/firmware/firmware.org b/firmware/firmware.org new file mode 100644 index 0000000..9a5fbd6 --- /dev/null +++ b/firmware/firmware.org @@ -0,0 +1,13 @@ +#+TITLE: firmware: network of CO2 sensors +* commands used to flash device +esptool.py --port /dev/ttyUSB0 erase_flash +esptool.py -p /dev/ttyUSB0 --baud 450800 --chip esp8266 write_flash 0x00000 esp8266-20191220-v1.12.bin +(use rshell to upload main.py, settings.py) +./flash.sh +* commands used to read from sensor +i2c=I2C(scl=Pin(4), sda={␛[␛[KPin(5), freq=10000) +i2c.writeto(104, "\x22\x00\x08\x2A".encode()) +i2c.readfrom(104, 4) +* commands used to interact with device +conda activate micropython +rshell diff --git a/firmware/flash.sh b/firmware/flash.sh new file mode 100755 index 0000000..56287b3 --- /dev/null +++ b/firmware/flash.sh @@ -0,0 +1,17 @@ +# flash micropython +read -p "bring GPIO0 low, reset device, and press enter" +esptool.py --port /dev/ttyUSB0 erase_flash +read -p "bring GPIO0 low, reset device, and press enter" +esptool.py -p /dev/ttyUSB0 --baud 450800 --chip esp8266 write_flash 0x00000 microhomie-esp8266-v3.0.2.bin +read -p "reset device and press enter" +# upload files +echo "sleeping" +sleep 5 +echo "putting files on device" +ampy -p /dev/ttyUSB0 put main.py +python -m mpy_cross settings.py +ampy -p /dev/ttyUSB0 put settings.mpy +python -m mpy_cross bme680.py +ampy -p /dev/ttyUSB0 put bme680.mpy +read -p "reset device and press enter" +echo "done!" diff --git a/firmware/main.py b/firmware/main.py new file mode 100644 index 0000000..37ce957 --- /dev/null +++ b/firmware/main.py @@ -0,0 +1,126 @@ +import settings + +import sys +from machine import Pin, I2C, WDT +import network +import time +import struct + +import mqtt_as +mqtt_as.MQTT_base.DEBUG = True + + +from bme680 import * + +from homie.constants import FALSE, TRUE, BOOLEAN, FLOAT, STRING +from homie.device import HomieDevice +from homie.node import HomieNode +from homie.property import HomieNodeProperty + +from uasyncio import get_event_loop, sleep_ms + +class BME680(HomieNode): + + def __init__(self, name="bme680", device=None): + super().__init__(id="bme680", name=name, type="sensor") + self.wdt = WDT() + self.device = device + self.i2c = I2C(scl=Pin(5), sda=Pin(4)) + self.bme680 = BME680_I2C(i2c=self.i2c) + self.temperature = HomieNodeProperty( + id="temperature", + name="temperature", + unit="°C", + settable=False, + datatype=FLOAT, + default=0, + ) + self.add_property(self.temperature) + self.humidity = HomieNodeProperty( + id="humidity", + name="humidity", + unit="%", + settable=False, + datatype=FLOAT, + default=0, + ) + self.add_property(self.humidity) + self.pressure = HomieNodeProperty( + id="pressure", + name="pressure", + unit="Pa", + settable=False, + datatype=FLOAT, + default=0, + ) + self.add_property(self.pressure) + self.gas = HomieNodeProperty( + id="voc", + name="voc", + unit="ohm", + settable=False, + datatype=FLOAT, + default=0, + ) + self.add_property(self.gas) + self.uptime = HomieNodeProperty( + id="uptime", + name="uptime", + settable=False, + datatype=STRING, + default="PT0S" + ) + self.add_property(self.uptime) + loop = get_event_loop() + loop.create_task(self.update_data()) + self.led = Pin(0, Pin.OUT) + self.online_led = Pin(12, Pin.OUT) + self.online_led.off() + self.last_online = time.time() + self.start = time.time() + + async def update_data(self): + while True: + if self.device.mqtt.isconnected(): + self.last_online = time.time() + self.online_led.on() + self.led.value(0) # illuminate onboard LED + self.temperature.data = str(self.bme680.temperature) + self.humidity.data = str(self.bme680.humidity) + self.pressure.data = str(self.bme680.pressure) + self.gas.data = str(self.bme680.gas) + self.uptime.data = self.get_uptime() + self.led.value(1) # onboard LED off + for _ in range(15): + self.wdt.feed() + await sleep_ms(1000) + else: + self.online_led.off() + if time.time() - self.last_online < 60: + self.wdt.feed() + await sleep_ms(1000) + + def get_uptime(self): + diff = int(time.time() - self.start) + out = "PT" + # hours + if diff // 3600: + out += str(diff // 3600) + "H" + diff %= 3600 + # minutes + if diff // 60: + out += str(diff // 60) + "M" + diff %= 60 + # seconds + out += str(diff) + "S" + return out + +def main(): + # homie + print("homie main") + homie = HomieDevice(settings) + homie.add_node(BME680(device=homie)) + homie.run_forever() + +if __name__ == "__main__": + main() diff --git a/firmware/microhomie b/firmware/microhomie new file mode 160000 +Subproject 117e81ff76b529673e8081e2aa68183e9ebaa8e diff --git a/firmware/microhomie-esp8266-v3.0.2.bin b/firmware/microhomie-esp8266-v3.0.2.bin Binary files differnew file mode 100644 index 0000000..8d8bd13 --- /dev/null +++ b/firmware/microhomie-esp8266-v3.0.2.bin diff --git a/firmware/settings.py.example b/firmware/settings.py.example new file mode 100644 index 0000000..b603a0a --- /dev/null +++ b/firmware/settings.py.example @@ -0,0 +1,10 @@ +WIFI_SSID = "ssid" +WIFI_PASSWORD = "password" + +MQTT_BROKER = "mqtt.chem.wisc.edu" +MQTT_USERNAME = "username" +MQTT_PASSWORD = "password" + +WDT_DELAY = 1_000_000 + +DEVICE_ID = "id" |