aboutsummaryrefslogtreecommitdiff
path: root/firmware
diff options
context:
space:
mode:
Diffstat (limited to 'firmware')
-rw-r--r--firmware/.gitignore2
-rw-r--r--firmware/bme680.py229
-rw-r--r--firmware/boot.py7
-rw-r--r--firmware/esp8266-20191220-v1.12.binbin0 -> 619828 bytes
-rw-r--r--firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08bin0 -> 6820 bytes
-rw-r--r--firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9abin0 -> 15882 bytes
-rw-r--r--firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7bin0 -> 120199 bytes
-rw-r--r--firmware/false/selfcheck/b26dae3201984cbbecf1047ed3665c561642f1d99a4ae03cb9e4d5131
-rw-r--r--firmware/firmware.org13
-rwxr-xr-xfirmware/flash.sh17
-rw-r--r--firmware/main.py126
m---------firmware/microhomie0
-rw-r--r--firmware/microhomie-esp8266-v3.0.2.binbin0 -> 605256 bytes
-rw-r--r--firmware/settings.py.example10
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
new file mode 100644
index 0000000..e46d257
--- /dev/null
+++ b/firmware/esp8266-20191220-v1.12.bin
Binary files differ
diff --git a/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08 b/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08
new file mode 100644
index 0000000..e81a5c1
--- /dev/null
+++ b/firmware/false/http/9/3/1/1/7/9311713fa1827ce3764dc9c5b5903a68caebb462cf09cc39fb974b08
Binary files differ
diff --git a/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a b/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a
new file mode 100644
index 0000000..57a0aeb
--- /dev/null
+++ b/firmware/false/http/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a
Binary files differ
diff --git a/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7 b/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7
new file mode 100644
index 0000000..06e84c4
--- /dev/null
+++ b/firmware/false/http/d/6/9/f/e/d69fe23937fd9c8b66e0f663d79796d234be74bd1b95f05ba7e878e7
Binary files differ
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
new file mode 100644
index 0000000..8d8bd13
--- /dev/null
+++ b/firmware/microhomie-esp8266-v3.0.2.bin
Binary files differ
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"