From 8a2cdd6d107ddd49ad90ca6f83f68a51ea670fc4 Mon Sep 17 00:00:00 2001 From: "Jose A. Jimenez-Berni" Date: Sat, 19 Feb 2022 07:15:10 +0100 Subject: [PATCH 1/3] Improved stability in soil sensor, timeout in Setup Mode to resume operation. --- lib/onewire.py | 8 ++++---- main.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/onewire.py b/lib/onewire.py index 0b3b35a..39eefc5 100644 --- a/lib/onewire.py +++ b/lib/onewire.py @@ -45,9 +45,9 @@ def read_bit(self): pin(1) # half of the devices don't match CRC without this line i = machine.disable_irq() pin(0) - sleep_us(1) + #sleep_us(1) pin(1) - sleep_us(1) + #sleep_us(1) value = pin() enable_irq(i) sleep_us(40) @@ -181,7 +181,7 @@ def start_convertion(self, rom=None): """ if (rom==None) and (len(self.roms)>0): rom=self.roms[0] - if rom!=None: + if rom!=None: rom = rom or self.roms[0] ow = self.ow ow.reset() @@ -197,7 +197,7 @@ def read_temp_async(self, rom=None): return None if (rom==None) and (len(self.roms)>0): rom=self.roms[0] - if rom==None: + if rom==None: return None else: ow = self.ow diff --git a/main.py b/main.py index 0df42d6..436326f 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ import network from network import LoRa, Sigfox, WLAN -from machine import I2C, RTC, Pin, SD +from machine import I2C, RTC, Pin, SD, Timer import os from OTA import WiFiOTA from sht30 import SHT30 @@ -297,15 +297,16 @@ def do_measurements(): ### Soil sensor temp = None try: - if len(ow.scan()) > 0: + ow_devices = ow.scan() + if len(ow_devices) > 0: print("Waking OWD...OK") - ow_devices = ow.scan() if len(ow_devices) > 0: ow_id = ubinascii.hexlify(ow_devices[0]).decode('ascii') else: ow_id = "None" print("OW ID: {}".format(ow_id)) temp = DS18X20(ow) + time.sleep(1) temp.start_convertion() time.sleep(1) float_values[5] = temp.read_temp_async() @@ -392,6 +393,9 @@ def do_measurements(): print_lorawan() if (p_in()==0) and (my_config_dict['node_version']!=0x01): + chrono = Timer.Chrono() + chrono.start() + last_key = chrono.read() print("CORDOVA-ET Node v{version} Type: {node_type}".format(version=__version__, node_type=my_config_dict['node_version'])) print("Entering Config Mode.") @@ -405,10 +409,12 @@ def do_measurements(): in_key = None server.init(login=('uco', 'ias_csic'), timeout=600) print(">>", end='') - while p_in()==0: + while (p_in()==0) & (chrono.read()<30): wdt.feed() char = uart.read(1) if char is not None: + chrono.reset() + chrono.start() if char == b'\r': in_key = tmp_str tmp_str = "" @@ -606,6 +612,8 @@ def do_measurements(): # * 5: Perform OTA update: [05 02 03] # * 6: Timesync message # * 7: Define air sensor type: [07 XX] # 0: None, 1: BME280, 2: SHT3x old, 3: SHT3x Rika +# * 8: Reset LoRaWAN session and force to join again: [08] +# * 9: Reset to factory defaults: [09] msg = bytearray(32) @@ -648,7 +656,7 @@ def do_measurements(): if len(in_msg) == 9: print("Got time sync!") ts_count, ts_offset = struct.unpack("Ii", bytearray(in_msg[1:])) - if ts_count == my_config_dict['sync_counter']: + if (ts_count== 0) | (ts_count == my_config_dict['sync_counter']): rtc.init(time.gmtime(time.time()+ts_offset)) t = rtc.now() print("New time is: ", rtc.now() , time.time(), ts_offset) @@ -681,6 +689,20 @@ def do_measurements(): s.bind(1) debug_info = {'irt': scan_i2c(i2c_irt), 'air': scan_i2c(i2c_air)} s.send("{}".format(debug_info)) + elif in_msg[0] == 8: + # Reset LoRaWAN session + print("Force LoRaWAN rejoin") + my_config_dict['lora_ok'] = False + save_config(my_config_dict) + wdt.init(0) + sys.exit() + elif in_msg[0] == 9: + # Reset LoRaWAN session + print("Clear configuration") + config_dict = factory_config_dict + save_config(config_dict) + wdt.init(0) + sys.exit() save_config(my_config_dict) except Exception as ex: print("Error receiving packet", ex) From c451d4f75199cb931ba5dc37596b2eb2edfa0bb3 Mon Sep 17 00:00:00 2001 From: "Jose A. Jimenez-Berni" Date: Sat, 19 Feb 2022 11:10:20 +0100 Subject: [PATCH 2/3] Node can be configured over WiFi. Added LoRa join command and improved timesync --- main.py | 204 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 122 insertions(+), 82 deletions(-) diff --git a/main.py b/main.py index 436326f..b8a0c19 100644 --- a/main.py +++ b/main.py @@ -121,6 +121,34 @@ def scan_i2c(i2c_bus): sensors=i2c_bus.scan() return sensors + +def join_lora(): + print("Joining LoRa...") + # join a network using OTAA + lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0) + + join_retry = 0 + # wait until the module has joined the network + while not lora.has_joined(): + pycom.rgbled(0x101000) # now make the LED light up yellow in colour + time.sleep(5.0) + print('Not joined yet... ', rtc.now()) + wdt.feed() + join_retry+=1 + if join_retry > MAX_JOIN_RETRY: + print("Couldn join LoRa...") + my_config_dict['lora_ok'] = False + save_config(my_config_dict) + return + + print("Connected to LoRa") + pycom.rgbled(0x000000) # now turn the LED off + wdt.feed() + lora.nvram_save() + my_config_dict['lora_ok'] = True + save_config(my_config_dict) + + def send_timesync(): print("Timesync") try: @@ -137,6 +165,8 @@ def send_timesync(): msg = bytearray(struct.pack('2L', my_config_dict['sync_counter'], my_config_dict['sync_timestamp'])) s.setblocking(True) s.send(msg) + s.setblocking(False) + parse_incoming_msg(s) lora.nvram_save() print("Done!") else: @@ -169,6 +199,7 @@ def print_help(): print("r: hard reset (looses time)") print("o: perform OTA update") print("l: reset LoRaWAN session") + print("j: join LoRaWAN server") print("t: print current time") print("TYYYY/MM/DD HH:MM:SS set current time (UTC). E.g. T20210801 10:05:00") print("VXX: set the node version, where XX is the version (01,02,03). E.g. V02 to set to pyranometer") @@ -333,6 +364,85 @@ def do_measurements(): float_values[6] = (batt.value()/4096.0)*354.8/31.6 print("Battery: {}V".format(float_values[6])) +def parse_incoming_msg(s): + try: + rx = s.recv(256) + if rx: + print("Got a packet from the cloud") + print(rx) + in_msg = bytearray(rx) + if len(in_msg) > 2: + if in_msg[0] == 1: + # Sleep time command + my_config_dict["sleep_time"] = int.from_bytes(in_msg[1:3], 'little') + print("New sleep time {}s".format(my_config_dict["sleep_time"])) + elif in_msg[0] == 5: + if in_msg == bytes([0x05, 0x02, 0x03]): + print("Performing OTA!") + pycom.rgbled(0x000011) + # Perform OTA + try: + ota.connect() + ota.update() + except Exception as ex: + print(ex) + pycom.rgbled(0x110000) + elif in_msg[0] == 6: + if len(in_msg) == 9: + print("Got time sync!") + ts_count, ts_offset = struct.unpack("Ii", bytearray(in_msg[1:])) + if (ts_count== 0) | (ts_count == my_config_dict['sync_counter']): + rtc.init(time.gmtime(time.time()+ts_offset)) + t = rtc.now() + print("New time is: ", rtc.now() , time.time(), ts_offset) + my_config_dict['sync_timestamp'] = time.time() + save_config(my_config_dict) + else: + print("Out of sync message, got {ts_count} should be {counter}".format(ts_count=ts_count, counter=my_config_dict['sync_counter'])) + + elif len(in_msg) == 2: + if in_msg[0] == 2: + # Select node type + my_config_dict['node_version'] = in_msg[1] + print("New sensor type: {}".format(my_config_dict['node_version'])) + elif len(in_msg) == 2: + if in_msg[0] == 7: + # Select air sensor type + my_config_dict['air_sensor'] = in_msg[1] + print("New sensor type: {}".format(my_config_dict['air_sensor'])) + elif len(in_msg) == 1: + if in_msg[0] == 3: + # Send version + print("Send version name...") + s.setblocking(True) + s.bind(1) + s.send(__version__ + " " + os.uname().release) + elif in_msg[0] == 4: + # Send I2C scan + print("Send I2C scan") + s.setblocking(True) + s.bind(1) + debug_info = {'irt': scan_i2c(i2c_irt), 'air': scan_i2c(i2c_air)} + s.send("{}".format(debug_info)) + elif in_msg[0] == 8: + # Reset LoRaWAN session + print("Force LoRaWAN rejoin") + my_config_dict['lora_ok'] = False + save_config(my_config_dict) + wdt.init(0) + sys.exit() + elif in_msg[0] == 9: + # Reset LoRaWAN session + print("Clear configuration") + config_dict = factory_config_dict + save_config(config_dict) + wdt.init(0) + sys.exit() + save_config(my_config_dict) + except Exception as ex: + print("Error receiving packet", ex) + + # Give some time for degubbing time.sleep(2.5) @@ -411,21 +521,21 @@ def do_measurements(): print(">>", end='') while (p_in()==0) & (chrono.read()<30): wdt.feed() - char = uart.read(1) + char = sys.stdin.read(1) if char is not None: chrono.reset() chrono.start() - if char == b'\r': + if char == '\n': in_key = tmp_str tmp_str = "" - elif char == b'\x7f': + elif char == '\x7f': if len(tmp_str)>0: tmp_str = tmp_str[:-1] print('\b \b', end='') #print('>>'+tmp_str, end='\r') else: - tmp_str += char.decode("utf-8") - print(char.decode("utf-8"), end='') + tmp_str += char + print(char, end='') if in_key is not None: print() else: @@ -455,6 +565,10 @@ def do_measurements(): save_config(my_config_dict) wdt.init(0) sys.exit() + elif in_key == 'j': + print("Join LoRaWAN") + join_lora() + print(">>", end='') elif in_key == 't': t = rtc.now() ts = '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(t[0], t[1], t[2], t[3], t[4], t[5]) @@ -525,7 +639,8 @@ def do_measurements(): print("Unknown command") print(">>", end='') in_key = None - time.sleep(0.1) + machine.idle() + time.sleep(0.2) server.deinit() wlan.deinit() pycom.rgbled(0x00000) @@ -630,82 +745,7 @@ def do_measurements(): log_to_SD() wdt.feed() #time.sleep(4) - try: - rx = s.recv(256) - if rx: - print("Got a packet from the cloud") - print(rx) - in_msg = bytearray(rx) - if len(in_msg) > 2: - if in_msg[0] == 1: - # Sleep time command - my_config_dict["sleep_time"] = int.from_bytes(in_msg[1:3], 'little') - print("New sleep time {}s".format(my_config_dict["sleep_time"])) - elif in_msg[0] == 5: - if in_msg == bytes([0x05, 0x02, 0x03]): - print("Performing OTA!") - pycom.rgbled(0x000011) - # Perform OTA - try: - ota.connect() - ota.update() - except Exception as ex: - print(ex) - pycom.rgbled(0x110000) - elif in_msg[0] == 6: - if len(in_msg) == 9: - print("Got time sync!") - ts_count, ts_offset = struct.unpack("Ii", bytearray(in_msg[1:])) - if (ts_count== 0) | (ts_count == my_config_dict['sync_counter']): - rtc.init(time.gmtime(time.time()+ts_offset)) - t = rtc.now() - print("New time is: ", rtc.now() , time.time(), ts_offset) - my_config_dict['sync_timestamp'] = time.time() - save_config(my_config_dict) - else: - print("Out of sync message, got {ts_count} should be {counter}".format(ts_count=ts_count, counter=my_config_dict['sync_counter'])) - - elif len(in_msg) == 2: - if in_msg[0] == 2: - # Select node type - my_config_dict['node_version'] = in_msg[1] - print("New sensor type: {}".format(my_config_dict['node_version'])) - elif len(in_msg) == 2: - if in_msg[0] == 7: - # Select air sensor type - my_config_dict['air_sensor'] = in_msg[1] - print("New sensor type: {}".format(my_config_dict['air_sensor'])) - elif len(in_msg) == 1: - if in_msg[0] == 3: - # Send version - print("Send version name...") - s.setblocking(True) - s.bind(1) - s.send(__version__ + " " + os.uname().release) - elif in_msg[0] == 4: - # Send I2C scan - print("Send I2C scan") - s.setblocking(True) - s.bind(1) - debug_info = {'irt': scan_i2c(i2c_irt), 'air': scan_i2c(i2c_air)} - s.send("{}".format(debug_info)) - elif in_msg[0] == 8: - # Reset LoRaWAN session - print("Force LoRaWAN rejoin") - my_config_dict['lora_ok'] = False - save_config(my_config_dict) - wdt.init(0) - sys.exit() - elif in_msg[0] == 9: - # Reset LoRaWAN session - print("Clear configuration") - config_dict = factory_config_dict - save_config(config_dict) - wdt.init(0) - sys.exit() - save_config(my_config_dict) - except Exception as ex: - print("Error receiving packet", ex) + parse_incoming_msg(s) lora.nvram_save() print("NV Save") time.sleep(4) From 4a33714898de7823ab48b05df8c40064c610bdbf Mon Sep 17 00:00:00 2001 From: "Jose A. Jimenez-Berni" Date: Sat, 19 Feb 2022 20:41:49 +0100 Subject: [PATCH 3/3] Bump to version 0.4.3 and new OTA --- OTA_VERSION.py | 2 +- config.py | 10 ++++---- main.py | 64 +++++++++++++++++++++++++++++++------------------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/OTA_VERSION.py b/OTA_VERSION.py index 6732d5a..33f9c5a 100644 --- a/OTA_VERSION.py +++ b/OTA_VERSION.py @@ -1 +1 @@ -VERSION = '1.0.2' +VERSION = '1.0.3' diff --git a/config.py b/config.py index b4793b1..5c8d574 100644 --- a/config.py +++ b/config.py @@ -8,6 +8,11 @@ ***************************** """ +# OTA Firmware Update parameters +WIFI_SSID = "MeteoNet" +WIFI_PW = "ChangeMe" +SERVER_IP = "161.111.157.183" + # Supported node versions # 0x01: Original boards without SD card (green and black) # 0x02: Pyranometer version of the node using boards v3.x @@ -20,8 +25,3 @@ # OTAA authentication params APP_EUI = "70B3D57ED001C537" - -# OTA Firmware Update parameters -WIFI_SSID = "MeteoNet" -WIFI_PW = "ChangeMe" -SERVER_IP = "161.111.157.183" diff --git a/main.py b/main.py index b8a0c19..e0bf8f1 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ """ __author__ = 'Jose A. Jimenez-Berni' -__version__ = '0.4.2' +__version__ = '0.4.3' __license__ = 'MIT' import network @@ -37,7 +37,7 @@ import status import sys -from config import WIFI_SSID, WIFI_PW, SERVER_IP, NODE_VERSION, DEBUG_MODE +from config import SERVER_IP, NODE_VERSION, DEBUG_MODE PAYLOAD_VERSION = 0x01 # Supported air temperature and humidity sensors @@ -71,7 +71,7 @@ 'dev_eui': ubinascii.hexlify(lora.mac()).decode('ascii'), 'app_eui': config.APP_EUI, 'app_key': ubinascii.hexlify(ubinascii.unhexlify((ubinascii.hexlify(sigfox.mac())+"FFFE"+ubinascii.hexlify(machine.unique_id()).decode('ascii')))).decode('ascii'), - } + 'wifi_ssid': config.WIFI_SSID, 'wifi_pw': config.WIFI_PW} # Data structure is: Ts, To, Tair, RH, Patm, Tsoil, Volt # For pyranometer board, in mV: channel_1, channel_2, channel_3, channel_4, channel_1*5.0, 0.0, Volt @@ -195,6 +195,7 @@ def print_help(): print("h: print this help") print("p: print LoRaWAN keys") print("m: test all measurements") + print("c: print config") print("g: clear config") print("r: hard reset (looses time)") print("o: perform OTA update") @@ -364,6 +365,20 @@ def do_measurements(): float_values[6] = (batt.value()/4096.0)*354.8/31.6 print("Battery: {}V".format(float_values[6])) + +# Downlink messages: +# * 1: Define duty cycle in seconds: [01 LSB MSB] +# * 2: Define node type: [02 XX] See XX values in config.py +# * 3: Send firmware version on port 1: [03] +# * 4: Send I2C bus scan on port 1: [04] +# * 5: Perform OTA update: [05 02 03] +# * 6: Timesync message +# * 7: Define air sensor type: [07 XX] # 0: None, 1: BME280, 2: SHT3x old, 3: SHT3x Rika +# * 8: Reset LoRaWAN session and force to join again: [08] +# * 9: Reset to factory defaults: [09] +# * A: Set WiFi Credentials: [10 XXXXX+YYYY]: XXXXX is the WiFi SSID, YYYY is the password +# Both encoded in Hex strings. See: https://string-functions.com/string-hex.aspx + def parse_incoming_msg(s): try: rx = s.recv(256) @@ -399,7 +414,17 @@ def parse_incoming_msg(s): save_config(my_config_dict) else: print("Out of sync message, got {ts_count} should be {counter}".format(ts_count=ts_count, counter=my_config_dict['sync_counter'])) - + elif in_msg[0] == 0x0a: + # Set WiFi Credentials + print("WiFi Settings") + try: + wifi_ssid, wifi_pw = in_msg[1:].decode('utf-8').split('+') + my_config_dict['wifi_ssid'] = wifi_ssid + my_config_dict['wifi_pw'] = wifi_pw + print("New settings: {},{}".format(wifi_ssid, wifi_pw)) + save_config(my_config_dict) + except Exception as ex: + print("Error parsing WiFi Credentials: ", ex) elif len(in_msg) == 2: if in_msg[0] == 2: # Select node type @@ -444,16 +469,7 @@ def parse_incoming_msg(s): # Give some time for degubbing -time.sleep(2.5) - -# Setup OTA -ota = WiFiOTA(WIFI_SSID, - WIFI_PW, - SERVER_IP, # Update server address - 8000, # Update server port - wdt) # WDT for long transfers - - +#time.sleep(2.5) p_in = Pin('P18', mode=Pin.IN, pull=Pin.PULL_UP) @@ -470,6 +486,12 @@ def parse_incoming_msg(s): my_config_dict = load_config() +# Setup OTA +ota = WiFiOTA(config_dict['wifi_ssid'], + config_dict['wifi_pw'], + SERVER_IP, # Update server address + 8000, # Update server port + wdt) # WDT for long transfers # Init sensor on pins according to nde version if my_config_dict['node_version']==0x01: @@ -550,6 +572,10 @@ def parse_incoming_msg(s): elif in_key == 'p': print_lorawan() print(">>", end='') + elif in_key == 'c': + print("Current configuration") + print(my_config_dict) + print(">>", end='') elif in_key == 'g': print("Clear configuration") config_dict = factory_config_dict @@ -719,16 +745,6 @@ def parse_incoming_msg(s): # Total payload size is 32 bytes # Data structure is: NODE_VERSION, PAYLOAD_VERSION, 2 BYTES, Ts, To, Tair, RH, Patm, Tsoil, Volt -# Downlink messages: -# * 1: Define duty cycle in seconds: [01 LSB MSB] -# * 2: Define node type: [02 XX] See XX values in config.py -# * 3: Send firmware version on port 1: [03] -# * 4: Send I2C bus scan on port 1: [04] -# * 5: Perform OTA update: [05 02 03] -# * 6: Timesync message -# * 7: Define air sensor type: [07 XX] # 0: None, 1: BME280, 2: SHT3x old, 3: SHT3x Rika -# * 8: Reset LoRaWAN session and force to join again: [08] -# * 9: Reset to factory defaults: [09] msg = bytearray(32)