From 0a9109a16007a5057544a2fee171cc27cd242dc8 Mon Sep 17 00:00:00 2001 From: StevenCellist Date: Thu, 1 Sep 2022 09:32:33 +0200 Subject: [PATCH] v2.6: OTA mSD updates, custom LoRa frames --- README.md | 2 +- ...ematic_Meet_je_leefomgeving_2022-09-01.pdf | Bin 44347 -> 45966 bytes ...ematic_Meet_je_leefomgeving_2022-09-01.svg | 4 +- software/_main.py | 226 ++++++++---------- software/collect_gps.py | 4 +- software/lib/SDS011.py | 15 +- software/lib/cayenneLPP.py | 165 ------------- software/lib/updateFW.py | 64 +++++ 8 files changed, 174 insertions(+), 306 deletions(-) rename Schematic_Meet_je_leefomgeving_2022-08-15.pdf => Schematic_Meet_je_leefomgeving_2022-09-01.pdf (93%) rename Schematic_Meet_je_leefomgeving_2022-08-15.svg => Schematic_Meet_je_leefomgeving_2022-09-01.svg (78%) delete mode 100644 software/lib/cayenneLPP.py create mode 100644 software/lib/updateFW.py diff --git a/README.md b/README.md index 4b413c5..7a57ed9 100644 --- a/README.md +++ b/README.md @@ -50,4 +50,4 @@ De vermoedde accuduur is drie weken, waarbij het zonnepaneel buiten beschouwing ## Schema Zie de figuur voor de opbouw van het circuit in de sensorkastjes. -![Schematic v2.5 15-08-2022](Schematic_Meet_je_leefomgeving_2022-08-15.svg) +![Schematic v2.5 15-08-2022](Schematic_Meet_je_leefomgeving_2022-09-01.svg) diff --git a/Schematic_Meet_je_leefomgeving_2022-08-15.pdf b/Schematic_Meet_je_leefomgeving_2022-09-01.pdf similarity index 93% rename from Schematic_Meet_je_leefomgeving_2022-08-15.pdf rename to Schematic_Meet_je_leefomgeving_2022-09-01.pdf index 9c2bf6a5fbdc7a44bfd798664230f82eb3a315ad..9c41ac8bc4e7022032b6962c4ea5f6e09003d420 100644 GIT binary patch delta 1909 zcmai!%WG9v6o-k{lAJa*XKX+Srl@r5KE%l+YrHs2#N#q(P;o zM#s`(x^Ps|fm5AGMv{?p5yygPMmmxX`UiCFeV+GTrK5A!`mM*e*4bz8Jm2%j>pgdt zy42<}-v>h2Wb^LHe#My(+Qpo+rj;gE4|jL2dkRbNfJ(6aT$BxdGNv7-xyl zv7+bCEDA0`9usTMyyd)QNoU}A%u@>-yBL8Jv5-LpOR7v&s-9VCWzy1E=O|XiiJ@2# zb7#ne*r!?-2a&8$nA%z&456*3^ld5C$69=I&1`_~WcRL;ovaGGX=67n?52g?TUB<7 zt+10-Wv5lQuv6O#dybT{YtPPcY5fy?x!8@%10U_Bwc*CEUk<->9-c~m?;c2s@-roP z>pErGq(d^sOG98xr-bz{j^Nq#(cyQh2Ak_tvq(&;?SfRaYmshUm$e_xGtMqWpflFW zk*`h#5gX0Jx~L!vbjoGBE>NptK*?8r~urLt7?re!&f;>!wwz zN6Y_Hr6t?Z5W5FG&8rt z_wuoRSW=b0Y>Uhjehz9A4x3QHqTsx3re=s~Y7V@8^sg8DdmIzJoHX*))u(vWNX&vi2};b}{ms+$^g>Fr7k=>kO8uy#36)x5Qh^Qm;f-u z_2KJu&2q&ZN@aX+@BKO9j6_Crr^!EIQL>OiS?Oo?&466`V~VbDPM2en1Bz0fwOIU_i8Vd``|K7jcUx2Rcww=aqhih?F@dab$q6MvOyA5Dl?9%^J bdQqztJzc%Kvbw(V;9(?%0~a-$otxdit6T@h diff --git a/Schematic_Meet_je_leefomgeving_2022-08-15.svg b/Schematic_Meet_je_leefomgeving_2022-09-01.svg similarity index 78% rename from Schematic_Meet_je_leefomgeving_2022-08-15.svg rename to Schematic_Meet_je_leefomgeving_2022-09-01.svg index dce1587..534afd2 100644 --- a/Schematic_Meet_je_leefomgeving_2022-08-15.svg +++ b/Schematic_Meet_je_leefomgeving_2022-09-01.svg @@ -1,5 +1,5 @@ -Sheet_1 \ No newline at end of file diff --git a/software/_main.py b/software/_main.py index 1b23f53..7fa46a8 100644 --- a/software/_main.py +++ b/software/_main.py @@ -1,99 +1,100 @@ #_main.py -- frozen into the firmware along all other modules except settings.py -import pycom - -pycom.heartbeat(False) -pycom.heartbeat_on_boot(False) -pycom.wifi_on_boot(False) - import time -wake_time = time.ticks_ms() +start_time = time.ticks_ms() # save current boot time +import pycom import machine -wake_reason = machine.wake_reason()[0] # tuple of (wake_reason, GPIO_list) - import settings from lib.SSD1306 import SSD1306 -i2c_bus = machine.I2C(0) # create I2C object -display = SSD1306(128, 64, i2c_bus) # initialize display (4.4 / 0.0 mA) +wake_reason = machine.wake_reason()[0] # tuple of (wake_reason, GPIO_list) + +# on first boot, disable integrated LED and WiFi +if wake_reason == machine.PWRON_WAKE: + pycom.heartbeat_on_boot(False) + pycom.wifi_on_boot(False) + +i2c_bus = machine.I2C(0) # create I2C object +display = SSD1306(128, 64, i2c_bus) # initialize display (4.4 / 0.0 mA) + +# if button was pressed, check for firmware update +if wake_reason == machine.PIN_WAKE: + from lib.updateFW import check_update + update = check_update(display) + machine.sleep(1000) # show update result for 1s on display + if update: + machine.reset() # in case of an update, reboot the device + +display.fill(0) display.text("MJLO-" + settings.NODE, 1, 1) -display.text("Hello world!", 1, 11) +display.text("FW: v2.6", 1, 11) display.show() """ This part is only executed if DEBUG == True """ if settings.DEBUG == True: - - PRINT = { - 'volt' : lambda volt : print("Accu:", volt), - 'temp' : lambda temp : print("Temp:", temp), - 'pres' : lambda pres : print("Druk:", pres), - 'humi' : lambda humi : print("Vocht:", humi), - 'volu' : lambda volu : print("Volume:", volu), - 'lx' : lambda lx : print("Licht:", lx), - 'uv' : lambda uv : print("UV:", uv), - 'voc' : lambda voc : print("VOC:", voc), - 'co2' : lambda co2 : print("CO2:", co2), - 'pm25' : lambda pm25 : print("PM2.5:", pm25), - 'pm10' : lambda pm10 : print("PM10:", pm10), - 'gps' : lambda lat, long, alt : print("NB", lat, "OL", long, "H", alt), - 'perc' : lambda perc : print("Accu%:", perc) - } - if wake_reason == machine.PIN_WAKE: # if button is pressed in DEBUG mode, enable GPS + if wake_reason == machine.PIN_WAKE: # if button is pressed in DEBUG mode, enable GPS from collect_gps import run_gps loc = run_gps(timeout = 120) - PRINT['GPS'](loc['lat'], loc['long'], loc['alt']) + print("NB", loc['lat'], "OL", loc['long'], "H", loc['alt']) from collect_sensors import run_collection values = run_collection(i2c_bus = i2c_bus, all_sensors = True, t_wake = settings.T_WAKE) - for key in values: - PRINT[key](values[key]) - - awake_time = time.ticks_ms() - wake_time # time in seconds the program has been running - push_button = machine.Pin('P23', mode = machine.Pin.IN, pull = machine.Pin.PULL_DOWN) # initialize wake-up pin - machine.pin_sleep_wakeup(['P23'], mode = machine.WAKEUP_ANY_HIGH, enable_pull = True) # set wake-up pin as trigger - machine.deepsleep(settings.T_DEBUG * 1000 - awake_time) # deepsleep for remainder of the interval time + print("Temp: " + str(values['temp']) + " C") + print("Druk: " + str(values['pres']) + " hPa") + print("Vocht: " + str(values['humi']) + " %") + print("Licht: " + str(values['lx']) + " lx") + print("UV: " + str(values['uv'])) + print("Accu: " + str(values['perc']) + " %") + print("Volume: " + str(values['volu']) + " dB") + print("VOC: " + str(values['voc']) + " Ohm") + print("CO2: " + str(values['co2']) + " ppm") + print("PM2.5: " + str(values['pm25']) + " ppm") + print("PM10: " + str(values['pm10']) + " ppm") + + push_button = machine.Pin('P2', mode = machine.Pin.IN, pull = machine.Pin.PULL_DOWN) # initialize wake-up pin + machine.pin_sleep_wakeup(['P2'], mode = machine.WAKEUP_ANY_HIGH, enable_pull = True) # set wake-up pin as trigger + machine.deepsleep((settings.T_DEBUG - 30) * 1000) # deepsleep for remainder of the interval time """ This part is only executed if DEBUG == False """ import network import socket -from lib.cayenneLPP import CayenneLPP - lora = network.LoRa(mode = network.LoRa.LORAWAN, region = network.LoRa.EU868) # create LoRa object -LORA_CNT = 0 # default LoRa frame counter +LORA_FCNT = 0 # default LoRa frame count if wake_reason == machine.RTC_WAKE or wake_reason == machine.PIN_WAKE: # if woken up from deepsleep (timer or button).. - lora.nvram_restore() # ..restore the LoRa information from nvRAM - try: - with open('/flash/counter.txt', 'r') as file: - LORA_CNT = int(file.readline()) # try to restore LoRa frame counter from deepsleep - except: - print("Failed to read counter file, reset counter to 0") + lora.nvram_restore() # ..restore LoRa information from nvRAM + LORA_FCNT = pycom.nvs_get('fcnt') # ..restore LoRa frame count from nvRAM -use_gps = False -# once a day, enable GPS (when rest(no. of messages * interval) / day < interval) (but not if the button was pressed) -if (LORA_CNT * settings.T_INTERVAL) % 86400 < settings.T_INTERVAL and wake_reason != machine.PIN_WAKE: - use_gps = True +frame = bytes([0]) # LoRa packet decoding type 0 (minimal) all_sensors = False # every FRACTION'th message or if the button was pushed, use all sensors (but not if GPS is used) -if (LORA_CNT % settings.FRACTION == 0 or wake_reason == machine.PIN_WAKE) and use_gps == False: +if LORA_FCNT % settings.FRACTION == 0 or wake_reason == machine.PIN_WAKE: all_sensors = True + frame = bytes([1]) # LoRa packet decoding type 1 (all sensors) + +use_gps = False +# once a day, enable GPS (but not if the button was pressed) +if LORA_FCNT % int(86400 / settings.T_INTERVAL) == 0 and wake_reason != machine.PIN_WAKE: + use_gps = True + all_sensors = False + frame = bytes([2]) # LoRa packet decoding type 2 (use GPS) LORA_SF = settings.SF_LOW # if GPS or all sensors are used, send on high SF (don't let precious power go to waste) if use_gps == True or all_sensors == True: LORA_SF = settings.SF_HIGH -lora.sf(LORA_SF) # set SF for this uplink -LORA_DR = 12 - LORA_SF # calculate DR for this SF +lora.sf(LORA_SF) # set SF for this uplink +LORA_DR = 12 - LORA_SF # calculate DR for this SF -s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) # create a LoRa socket (blocking) -s.setsockopt(socket.SOL_LORA, socket.SO_DR, LORA_DR) # set the LoRaWAN data rate +s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) # create a LoRa socket (blocking) +s.setsockopt(socket.SOL_LORA, socket.SO_DR, LORA_DR) # set the LoRaWAN data rate # join the network upon first-time wake -if LORA_CNT == 0: +if LORA_FCNT == 0: import secret if settings.LORA_MODE == 'OTAA': @@ -102,93 +103,64 @@ mode = network.LoRa.ABP lora.join(activation = mode, auth = secret.auth(settings.LORA_MODE, settings.NODE), dr = LORA_DR) - # don't need to wait for has_joined(): when joining the network, GPS is enabled next which takes much longer - -# create Cayenne-formatted LoRa message (payload either 41 or 42 bytes so stick to 42 either way) -lpp = CayenneLPP(size = 42, sock = s) - -LPP_ADD = { # routine for adding data to Cayenne message - 'volt' : lambda volt : lpp.add_analog_input(volt, channel = 0), # 4 (2) bits - 'temp' : lambda temp : lpp.add_temperature(temp, channel = 1), # 4 (2) bits - 'pres' : lambda pres : lpp.add_barometric_pressure(pres, channel = 2), # 4 (2) bits - 'humi' : lambda humi : lpp.add_relative_humidity(humi, channel = 3), # 3 (1) bits - 'volu' : lambda volu : lpp.add_relative_humidity(volu, channel = 4), # 3 (1) bits - 'lx' : lambda lx : lpp.add_luminosity(lx, channel = 5), # 4 (2) bits - 'uv' : lambda uv : lpp.add_luminosity(uv, channel = 6), # 4 (2) bits - 'voc' : lambda voc : lpp.add_luminosity(voc, channel = 7), # 4 (2) bits - 'co2' : lambda co2 : lpp.add_luminosity(co2, channel = 8), # 4 (2) bits - 'pm25' : lambda pm25 : lpp.add_barometric_pressure(pm25, channel = 9), # 4 (2) bits - 'pm10' : lambda pm10 : lpp.add_barometric_pressure(pm10, channel = 10), # 4 (2) bits - 'gps' : lambda lat, long, alt : lpp.add_gps(lat, long, alt, channel = 11), # 11 (3/3/3) bits - 'perc' : lambda perc : lora.set_battery_level(perc), # set level for MAC command -} - -DISPLAY_TEXT = { # routine for displaying text on OLED display - 'volt' : lambda volt : None, - 'temp' : lambda temp : display.text("Temp: " + str(round(temp, 1)) + " C", 1, 1), - 'pres' : lambda pres : display.text("Druk: " + str(round(pres, 1)) + " hPa", 1, 11), - 'humi' : lambda humi : display.text("Vocht: " + str(round(humi, 1)) + " %", 1, 21), - 'lx' : lambda lx : display.text("Licht: " + str(int(lx)) + " lx", 1, 31), - 'uv' : lambda uv : display.text("UV: " + str(int(uv)), 1, 41), - - 'volu' : lambda volu : display.text("Volume: " + str(int(volu)) + " dB", 1, 1), - 'voc' : lambda voc : display.text("VOC: " + str(int(voc)) + " Ohm", 1, 11), - 'co2' : lambda co2 : display.text("CO2: " + str(int(co2)) + " ppm", 1, 21), - 'pm25' : lambda pm25 : display.text("PM2.5: " + str(pm25) + " ppm", 1, 31), - 'pm10' : lambda pm10 : display.text("PM10: " + str(pm10) + " ppm", 1, 41), - - 'perc' : lambda perc : display.text("Accu: " + str(int(perc)) + " %", 1, 54), -} + # don't need to wait for has_joined(): GPS takes much longer to start -display.poweroff() +# run sensor routine +from collect_sensors import run_collection +values = run_collection(i2c_bus = i2c_bus, all_sensors = all_sensors, t_wake = settings.T_WAKE) + +def pack(value, precision, size = 2): + value = int(value / precision) # round to precision + value = max(0, min(value, 2**(8*size))) # stay in range 0 .. int.max_size + return value.to_bytes(size, 'big') # pack to bytes -# run gps routine if enabled and add values to Cayenne message -if use_gps: +# add the sensor values that are always measured (frame is now 1 + 14 = 15 bytes) +frame += pack(values['volt'], 0.001) + pack(values['temp'] + 25, 0.01) + pack(values['pres'], 0.1) \ + + pack(values['humi'], 0.5, size = 1) + pack(values['volu'], 0.5, size = 1) \ + + pack(values['lx'], 1) + pack(values['uv'], 1) + pack(values['voc'], 1) + +if all_sensors == True: + # add extra sensor values (frame is now 1 + 14 + 6 = 21 bytes) + frame += pack(values['co2'], 1) + pack(values['pm25'], 0.1) + pack(values['pm10'], 0.1) + +if use_gps == True: + # run gps routine from collect_gps import run_gps loc = run_gps() - LPP_ADD['gps'](loc['lat'], loc['long'], loc['alt']) -# run sensor routine and add values to Cayenne message -from collect_sensors import run_collection -values = run_collection(i2c_bus = i2c_bus, all_sensors = all_sensors, t_wake = settings.T_WAKE) -for key in values: - LPP_ADD[key](values[key]) - -# send Cayenne message and reset payload -lpp.send(reset_payload = True) + # add gps values (frame is now 1 + 14 + 9 = 24 bytes) + frame += pack(loc['lat'] + 180, 0.0000001, size = 4) + pack(loc['long'] + 90, 0.0000001, size = 4) \ + + pack(loc['alt'], 0.1, size = 1) -# store LoRa context in non-volatile RAM (should be using wear leveling) +# send LoRa message and store LoRa context + frame count in non-volatile RAM (should be using wear leveling) +s.send(frame) lora.nvram_save() - -# store LoRa frame counter to flash (should be VERY r/w resistant >> RTC memory, millions of cycles) -with open('/flash/counter.txt', 'w') as file: - file.write(str(LORA_CNT + 1)) +pycom.nvs_set('fcnt', LORA_FCNT) # write all values to display in two series -display.poweron() display.fill(0) -DISPLAY_TEXT['temp'](values['temp']) -DISPLAY_TEXT['pres'](values['pres']) -DISPLAY_TEXT['humi'](values['humi']) -DISPLAY_TEXT['lx'](values['lx']) -DISPLAY_TEXT['uv'](values['uv']) -DISPLAY_TEXT['perc'](values['perc']) +display.text("Temp: " + str(round(values['temp'], 1)) + " C", 1, 1), +display.text("Druk: " + str(round(values['pres'], 1)) + " hPa", 1, 11), +display.text("Vocht: " + str(round(values['humi'], 1)) + " %", 1, 21), +display.text("Licht: " + str(int(values['lx'])) + " lx", 1, 31), +display.text("UV: " + str(int(values['uv'])), 1, 41), +display.text("Accu: " + str(int(values['perc'])) + " %", 1, 54), display.show() machine.sleep(settings.T_DISPLAY * 1000) display.fill(0) -DISPLAY_TEXT['volu'](values['volu']) -DISPLAY_TEXT['voc'](values['voc']) -DISPLAY_TEXT['perc'](values['perc']) +display.text("Volume: " + str(int(values['volu'])) + " dB", 1, 1), +display.text("VOC: " + str(int(values['voc'])) + " Ohm", 1, 11), if all_sensors == True: - DISPLAY_TEXT['co2'](values['co2']) - DISPLAY_TEXT['pm25'](values['pm25']) - DISPLAY_TEXT['pm10'](values['pm10']) + display.text("CO2: " + str(int(values['co2'])) + " ppm", 1, 21), + display.text("PM2.5: " + str(values['pm25']) + " ppm", 1, 31), + display.text("PM10: " + str(values['pm10']) + " ppm", 1, 41), +display.text("Accu: " + str(int(values['perc'])) + " %", 1, 54), display.show() machine.sleep(settings.T_DISPLAY * 1000) display.poweroff() # set up for deepsleep -awake_time = time.ticks_ms() - wake_time # time in seconds the program has been running -push_button = machine.Pin('P23', mode = machine.Pin.IN, pull = machine.Pin.PULL_DOWN) # initialize wake-up pin -machine.pin_sleep_wakeup(['P23'], mode = machine.WAKEUP_ANY_HIGH, enable_pull = True) # set wake-up pin as trigger -machine.deepsleep(settings.T_INTERVAL * 1000 - awake_time) # deepsleep for remainder of the interval time \ No newline at end of file +awake_time = time.ticks_diff(time.ticks_ms(), start_time) # time in milliseconds the program has been running +push_button = machine.Pin('P2', mode = machine.Pin.IN, pull = machine.Pin.PULL_DOWN) # initialize wake-up pin +machine.pin_sleep_wakeup(['P2'], mode = machine.WAKEUP_ANY_HIGH, enable_pull = True) # set wake-up pin as trigger +machine.deepsleep(settings.T_INTERVAL * 1000 - awake_time) # deepsleep for remainder of the interval time diff --git a/software/collect_gps.py b/software/collect_gps.py index 8e26464..1ede56b 100644 --- a/software/collect_gps.py +++ b/software/collect_gps.py @@ -11,7 +11,7 @@ def run_gps(timeout = 120): gps_en.value(0) # enable GPS power gps = MicropyGPS() # create GPS object - com = machine.UART(2, pins = ('P3', 'P4'), baudrate = 9600) # GPS communication + com = machine.UART(2, pins = ('P3', 'P11'), baudrate = 9600)# GPS communication t1 = time.time() while gps.latitude == gps.longitude == 0 and time.time() - t1 < timeout: # timeout if no fix after .. @@ -26,4 +26,4 @@ def run_gps(timeout = 120): values["lat"] = gps.latitude values["long"] = gps.longitude values["alt"] = gps.alt - return values \ No newline at end of file + return values diff --git a/software/lib/SDS011.py b/software/lib/SDS011.py index 7810fbf..807f22a 100644 --- a/software/lib/SDS011.py +++ b/software/lib/SDS011.py @@ -11,10 +11,9 @@ 7 DATA6 ID byte 2 8 Checksum Low byte of sum of DATA bytes 9 Tail '\xab' - """ -import ustruct as struct +import struct _SDS011_CMDS = {'SET': b'\x01', 'GET': b'\x00', @@ -51,7 +50,7 @@ def make_command(self, cmd, mode, param): def get_response(self, command_ID): # try for 120 bytes (0.2) second to get a response from sensor (typical response time 12~33 bytes) - for _ in range(240): + for _ in range(120): try: header = self._uart.read(1) if header == b'\xaa': @@ -63,9 +62,9 @@ def get_response(self, command_ID): self.process_measurement(packet) return True else: - print("Response did not match command ID", command_ID) + pass except Exception as e: - print('Problem attempting to read:', e) + pass return False def wake(self): @@ -94,13 +93,11 @@ def query(self): def process_measurement(self, packet): try: - *data, checksum, tail = struct.unpack('h', value)) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_luminosity(self, value, channel = 5): - # Resolution: 1 lux, unsigned. - if self.is_within_size_limit(ILLUMINANCE_SENSOR[1]): - value = int(value) # precision is 1 - self.payload = (self.payload + - bytes([channel]) + - ILLUMINANCE_SENSOR[0] + - struct.pack('>H', value)) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_temperature(self, value, channel = 7): - # Resolution: 0.1 degrees Celsius, signed. - if self.is_within_size_limit(TEMPERATURE_SENSOR[1]): - value = int(value * 10) # precision is 0.1 - self.payload = (self.payload + - bytes([channel]) + - TEMPERATURE_SENSOR[0] + - struct.pack('>h', value)) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_relative_humidity(self, value, channel = 8): - # Resolution: 0.5 %, signed. - if self.is_within_size_limit(HUMIDITY_SENSOR[1]): - value = int(value * 2) # precision is 0.5 - self.payload = (self.payload + - bytes([channel]) + - HUMIDITY_SENSOR[0] + - struct.pack('>B', value)) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_barometric_pressure(self, value, channel = 10): - # Resolution: 0.1 hPa, unsigned. - if self.is_within_size_limit(BAROMETER[1]): - value = int(value * 10) # precision is 0.1 - self.payload = (self.payload + - bytes([channel]) + - BAROMETER[0] + - struct.pack('>H', value)) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_gps(self, lat, lon, alt, channel = 12): - """ - Resolution: - 0.0001 deg for the latitude and longitute, signed. - 0.01 meters for the altitude, signed. - """ - - if self.is_within_size_limit(GPS[1]): - lat = int(lat * 10000) # precision is 0.0001 for lat and lon - lon = int(lon * 10000) - alt = int(alt * 100) # precision is 0.01 for altitude - self.payload = (self.payload + - bytes([channel]) + - GPS[0] + - struct.pack('>l', lat)[1:4] + - struct.pack('>l', lon)[1:4] + - struct.pack('>l', alt)[1:4]) - else: - raise Exception('payload too big: size exceeds the limit!') - - def add_generic(self, lpp_id, values, channel = 13, data_size = 1, - is_signed = True, precision = 1): - """ - Adding an generic sensor reading to the payload - - Args: - channel: The channel of the payload. - lpp_id: The LPP id of the sensor (IPSO id - 3200). - data_size: The total number of bytes for the payload. - is_signed: Show whether we use signed (True) or unsigned (False) encoding. - precision: The precision of the sensor reading (e.g. 0.01, 1, 0.5). - values: The data to be encoded, either a scalar or a list. - """ - - if self.is_within_size_limit(data_size): - - # determining the encoding - enc = '' - if is_signed: - enc = '>l' - else: - enc = '>L' - - # updating the payload - self.payload = self.payload + bytes([channel]) + bytes([lpp_id]) - values = int(values / precision) - self.payload = self.payload + struct.pack(enc, values)[-data_size:] - - else: - raise Exception('payload too big: size exceeds the limit!') diff --git a/software/lib/updateFW.py b/software/lib/updateFW.py new file mode 100644 index 0000000..02f2578 --- /dev/null +++ b/software/lib/updateFW.py @@ -0,0 +1,64 @@ +import machine +import pycom +import os +import time +import secret + +BLOCKSIZE = const(4096) +APPIMG = secret.filename # firmware filename + +def check_update(display): + display.fill(0) + try: + sd = machine.SD() # initialize SD card + display.text("Found SD card", 1, 1) + display.show() + except: + display.text("No SD card", 1, 1) + display.show() + return False + + try: + os.mount(sd, '/sd') # mount SD card + display.text("Mounted SD card", 1, 11) + display.show() + except: + display.text("Could not mount", 1, 11) + display.show() + return False + + try: + with open(APPIMG, "rb") as f: # open new firmware file + display.text("New firmware!", 1, 21) + filesize = int(os.stat(APPIMG)[6] / 1000) # calculate filesize in kB + display.text("Size: " + str(filesize) + " kB", 1, 31) + display.show() + buffer = bytearray(BLOCKSIZE) + mv = memoryview(buffer) + size = 0 + last_prog = 0 + pycom.ota_start() # start Over The Air update + while True: + chunk = f.readinto(buffer) # read chunk + if chunk > 0: + pycom.ota_write(mv[:chunk]) + size += chunk + prog = int(size / filesize / 10) # calculate progress (in %) + if prog != last_prog: # if % has changed, update display + display.text("Progress: {:>3}%".format(last_prog), 1, 41, col = 0) + display.text("Progress: {:>3}%".format(prog), 1, 41) + display.show() + last_prog = prog # update old value + else: + break + display.text("Rebooting...", 1, 51) + display.show() + pycom.ota_finish() # finish Over The Air update + os.umount('/sd') + sd.deinit() + return True + except: + display.text("Update failed", 1, 41) + display.show() + + return False