From 1f50273e92a47f558426b19c1d5a7370cd7ac679 Mon Sep 17 00:00:00 2001 From: Andreas Gattringer <andreas.gattringer@univie.ac.at> Date: Sat, 20 Apr 2024 23:10:05 +0200 Subject: [PATCH] rhtp and related: improved resilience --- programs/umnp-rhtp-local.py | 28 +++++++++++++++++-- programs/umnp-rhtp.py | 28 +++++++++++++++++-- .../communication/udp_communicator.py | 11 ++++++++ .../devices/network/ethernet.py | 7 +++++ .../devices/network/ethernet_w5500.py | 16 +++++++++++ umnp/microcontroller/devices/network/udp.py | 17 ++++++++++- umnp/microcontroller/measurementdevice.py | 24 ++++++++++++++++ .../sensors/lps28dfw/__init__.py | 18 ++++++++++-- .../microcontroller/sensors/sht45/__init__.py | 24 ++++++++++++++-- .../microcontroller/umock/machine/__init__.py | 5 +++- 10 files changed, 167 insertions(+), 11 deletions(-) diff --git a/programs/umnp-rhtp-local.py b/programs/umnp-rhtp-local.py index 96d6800..742e8af 100644 --- a/programs/umnp-rhtp-local.py +++ b/programs/umnp-rhtp-local.py @@ -48,7 +48,27 @@ async def aggregate_and_send( data = "{:2.2f},{:2.2f},{:2.2f},{:2.2f}".format(t, rh, p, p_t) print("Sending: '{}'".format(data)) msg = DataMessage(data, device.identifier_raw, device.device_type) - await comm.send_message(msg) + if comm.network_error: + print("Network error") + spi: machine.SPI = device.spi + if spi: + print("SPI::deinit()") + spi.deinit() + spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK) + print("SPI::new()") + device.add_spi(spi) + print("SPI::added()") + dev_mac = device.generated_mac_raw() + device.network.deactivate() + print("ETH:new()") + ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP) + print("ETH:set()") + ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS) + print("ETH:added()") + device.add_network_adapter(ether) + comm.clear_network_error() + if not comm.network_error: + await comm.send_message(msg) msg = None gc.collect() @@ -57,13 +77,17 @@ async def main(): # configure network device = MeasurementDevice(device_type=DEVICE_TYPE_RHTP) spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK) - i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA) + device.add_spi(spi) + + i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000) + device.add_i2c(i2c) dev_mac = device.generated_mac_raw() ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP) ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS) device.add_network_adapter(ether) + device.add_i2c(i2c) comm = device.create_communicator() sht45 = SHT45(i2c) diff --git a/programs/umnp-rhtp.py b/programs/umnp-rhtp.py index 1fd3e5f..e09c853 100644 --- a/programs/umnp-rhtp.py +++ b/programs/umnp-rhtp.py @@ -48,7 +48,27 @@ async def aggregate_and_send( data = "{:2.2f},{:2.2f},{:2.2f},{:2.2f}".format(t, rh, p, p_t) print("Sending: '{}'".format(data)) msg = DataMessage(data, device.identifier_raw, device.device_type) - await comm.send_message(msg) + if comm.network_error: + print("Network error") + spi: machine.SPI = device.spi + if spi: + print("SPI::deinit()") + spi.deinit() + spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK) + print("SPI::new()") + device.add_spi(spi) + print("SPI::added()") + dev_mac = device.generated_mac_raw() + device.network.deactivate() + print("ETH:new()") + ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP) + print("ETH:set()") + ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS) + print("ETH:added()") + device.add_network_adapter(ether) + comm.clear_network_error() + if not comm.network_error: + await comm.send_message(msg) msg = None gc.collect() @@ -57,13 +77,17 @@ async def main(): # configure network device = MeasurementDevice(device_type=DEVICE_TYPE_RHTP) spi = machine.SPI(0, SPI_BAUD, mosi=SPI_MOSI, miso=SPI_MISO, sck=SPI_SCK) - i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA) + device.add_spi(spi) + + i2c = machine.I2C(id=1, scl=I2C_SCL, sda=I2C_SDA, timeout=10000) + device.add_i2c(i2c) dev_mac = device.generated_mac_raw() ether = EthernetW5500(spi, ETH_CS, ETH_RST, dev_mac, ETH_USE_DHCP) ether.set_network(ETH_IP, ETH_SUBNET, ETH_GATEWAY, ETH_DNS) device.add_network_adapter(ether) + device.add_i2c(i2c) comm = device.create_communicator() sht45 = SHT45(i2c) diff --git a/umnp/microcontroller/communication/udp_communicator.py b/umnp/microcontroller/communication/udp_communicator.py index 3350839..c9346cc 100644 --- a/umnp/microcontroller/communication/udp_communicator.py +++ b/umnp/microcontroller/communication/udp_communicator.py @@ -38,6 +38,17 @@ class UDPCommunicator: self._tasks = {} self._device = device + @property + def sender(self): + return self._sender + + @property + def network_error(self): + return self._sender.error + + def clear_network_error(self): + self._sender.reset_error() + async def queue_incoming_message(self, msg, source): async with self._receive_lock: self._messages_received.append((msg, source, time.time())) diff --git a/umnp/microcontroller/devices/network/ethernet.py b/umnp/microcontroller/devices/network/ethernet.py index 8282bfd..5f38286 100644 --- a/umnp/microcontroller/devices/network/ethernet.py +++ b/umnp/microcontroller/devices/network/ethernet.py @@ -1,6 +1,7 @@ # micropython code # can't use abstract baseclass here + class EthernetAdapter: def __init__(self): pass @@ -15,3 +16,9 @@ class EthernetAdapter: def enable_dhcp(self): pass + + def reset(self): + pass + + def activate(self): + pass diff --git a/umnp/microcontroller/devices/network/ethernet_w5500.py b/umnp/microcontroller/devices/network/ethernet_w5500.py index 23bddc2..01e6b8c 100644 --- a/umnp/microcontroller/devices/network/ethernet_w5500.py +++ b/umnp/microcontroller/devices/network/ethernet_w5500.py @@ -1,4 +1,5 @@ import sys +import time from umnp.microcontroller.devices.network.ethernet import EthernetAdapter @@ -8,8 +9,11 @@ if sys.implementation.name == "micropython": # noinspection PyUnresolvedReferences import network + import umnp.protocol.compat.logging as logging + else: from umnp.microcontroller.umock import machine, network + import logging class EthernetW5500(EthernetAdapter): @@ -48,6 +52,12 @@ class EthernetW5500(EthernetAdapter): def netmask(self): return self._nic.ifconfig()[1] + def activate(self): + self._nic.active(True) + + def deactivate(self): + self._nic.active(False) + @property def ip(self): return self._nic.ifconfig()[0] @@ -55,3 +65,9 @@ class EthernetW5500(EthernetAdapter): @property def mac(self): return self._nic.config("mac") + + def reset(self): + logging.info("Resetting NIC") + self._nic.active(False) + time.sleep(0.1) + self._nic.active(True) diff --git a/umnp/microcontroller/devices/network/udp.py b/umnp/microcontroller/devices/network/udp.py index 06d5d86..ef95620 100644 --- a/umnp/microcontroller/devices/network/udp.py +++ b/umnp/microcontroller/devices/network/udp.py @@ -10,7 +10,10 @@ from umnp.microcontroller.devices.network import ( if sys.implementation.name == "micropython": # noinspection PyUnresolvedReferences import uasyncio as asyncio + import umnp.protocol.compat.logging as logging + else: + import logging import asyncio DEFAULT_UMNP_DATA_IN_PORT = 7777 DEFAULT_UMNP_COMMAND_IN_PORT = 7776 @@ -45,14 +48,26 @@ class UDPSender: self.ip = ip self.netmask = netmask self._target_port = send_to_port + self._error = False if ip and netmask: self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.broadcast_ip = calculate_broadcast_ip(ip, netmask) + @property + def error(self): + return self._error + + def reset_error(self): + self._error = False + async def broadcast(self, msg): # print("sending %s" % msg) if isinstance(msg, str): msg = msg.encode("utf-8") if self.socket: - self.socket.sendto(msg, (self.broadcast_ip, self._target_port)) + try: + self.socket.sendto(msg, (self.broadcast_ip, self._target_port)) + except OSError as e: + self._error = True + logging.error(e) diff --git a/umnp/microcontroller/measurementdevice.py b/umnp/microcontroller/measurementdevice.py index 9a3aef1..9a1ab8f 100644 --- a/umnp/microcontroller/measurementdevice.py +++ b/umnp/microcontroller/measurementdevice.py @@ -37,6 +37,8 @@ class MeasurementDevice: self._receiver = None self._communicator = None self._type = device_type + self._i2c = None + self._spi = None @property def device_type(self): @@ -46,9 +48,31 @@ class MeasurementDevice: def boot_time(self): return self._boot_time + def add_i2c(self, i2c: machine.I2C): + self._i2c = i2c + + @property + def spi(self): + return self._spi + + def add_spi(self, spi: machine.SPI): + self._spi = spi + + @property + def i2c(self): + return self._i2c + def add_network_adapter(self, adapter): self._network = adapter + def reset_network_adapter(self): + if self._network: + self._network.reset() + + @property + def network(self): + return self._network + def generated_mac_string(self) -> str: machine_id = self.identifier_raw[:6] return ":".join(f"{digit:02x}" for digit in machine_id) diff --git a/umnp/microcontroller/sensors/lps28dfw/__init__.py b/umnp/microcontroller/sensors/lps28dfw/__init__.py index 2815a9f..11e55ad 100644 --- a/umnp/microcontroller/sensors/lps28dfw/__init__.py +++ b/umnp/microcontroller/sensors/lps28dfw/__init__.py @@ -1,5 +1,6 @@ import asyncio import struct +import sys import time try: @@ -7,6 +8,15 @@ try: except ImportError: from umnp.microcontroller.umock.machine import I2C +if sys.implementation.name == "micropython": + import umnp.protocol.compat.logging as logging +else: + + def const(x): + return x + + import logging + LPS28DFW_DEFAULT_ADDRESS = const(0x5D) LPS28DFW_READ = const(0xB9) LPS28DFW_WRITE = const(0xB8) @@ -183,8 +193,12 @@ class LPS28DFW: return status & avail_mask == avail_mask, status & overrun_mask == overrun_mask async def measure(self): - p = await self.pressure() - t = await self.temperature() + try: + p = await self.pressure() + t = await self.temperature() + except OSError as e: + logging.error(f"Unable to read measurement data: {e}") + return self._nan, self._nan return p, t async def pressure(self): diff --git a/umnp/microcontroller/sensors/sht45/__init__.py b/umnp/microcontroller/sensors/sht45/__init__.py index 7b82359..40da97f 100644 --- a/umnp/microcontroller/sensors/sht45/__init__.py +++ b/umnp/microcontroller/sensors/sht45/__init__.py @@ -1,8 +1,19 @@ +import sys + try: from machine import I2C except ImportError: from umnp.microcontroller.umock.machine import I2C + +if sys.implementation.name == "micropython": + import umnp.protocol.compat.logging as logging +else: + + def const(x): + return x + + import logging import asyncio import time @@ -83,12 +94,19 @@ class SHT45: cmd = bytearray(1) cmd[0] = SHT45_HIRES - n_ack = self._i2c.writeto(self._i2c_address, cmd) + try: + n_ack = self._i2c.writeto(self._i2c_address, cmd) + except OSError as e: + logging.error(f"Unable to write to I2C bus: {e}") + return self._nan, self._nan if n_ack != 1: return temperature, rh await asyncio.sleep(SHT45_WAIT_TIME_MS / 1000) - - data = self._i2c.readfrom(self._i2c_address, 6) + try: + data = self._i2c.readfrom(self._i2c_address, 6) + except OSError as e: + logging.error(f"Unable to read from I2C bus: {e}") + return self._nan, self._nan temperature = self._translate_temperature(data[0:3]) rh = self._translate_rh(data[3:6]) diff --git a/umnp/microcontroller/umock/machine/__init__.py b/umnp/microcontroller/umock/machine/__init__.py index 4f6fadb..178916d 100644 --- a/umnp/microcontroller/umock/machine/__init__.py +++ b/umnp/microcontroller/umock/machine/__init__.py @@ -38,6 +38,9 @@ class SPI: def __init__(self, *args, **kwargs): pass + def deinit(self): + pass + def write(self, *args): pass @@ -54,7 +57,7 @@ class Pin: class I2C: - def __init__(self, id: int, scl: Pin, sda: Pin): + def __init__(self, id: int, scl: Pin, sda: Pin, timeout=500000): self._id = id self._scl = scl self._sda = sda -- GitLab