From a2a2da80c7c5dd676d6ea082f5c48195a71f1532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 07:36:29 +0200 Subject: [PATCH 01/19] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad4920d..dc30b0a 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ An effective PC client and server is [mosquitto](https://mosquitto.org/). # This repository -This contains two separate projects: +This contains three separate projects: 1. A "resilient" asynchronous non-blocking MQTT driver. 2. A means of using a cheap ESP8266 module to bring MQTT to MicroPython platforms which lack a WiFi interface. + 3. A basic network hardware controller (WLAN) which the mqtt client uses ## 1. The "resilient" driver From fd2a9dcb75350d566dd15bc4fe87014cabf89f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 15:51:19 +0200 Subject: [PATCH 02/19] Add BaseInterface as a base class for all hardware interface implementations. --- mqtt_as/interfaces/__init__.py | 79 ++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 mqtt_as/interfaces/__init__.py diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py new file mode 100644 index 0000000..86c87a8 --- /dev/null +++ b/mqtt_as/interfaces/__init__.py @@ -0,0 +1,79 @@ +from uerrno import EINPROGRESS, ETIMEDOUT +import usocket +import uasyncio as asyncio + + +async def _g(): + pass + + +type_coro = type(_g()) + + +# If a callback is passed, run it and return. +# If a coro is passed initiate it and return. +# coros are passed by name i.e. not using function call syntax. +def launch(func, tup_args): + res = func(*tup_args) + if isinstance(res, type_coro): + res = asyncio.create_task(res) + return res + + +class BaseInterface: + def __init__(self, socket=None): + # Legitimate errors while waiting on a socket. See uasyncio __init__.py open_connection(). + self.BUSY_ERRORS = [EINPROGRESS, ETIMEDOUT] + self.socket = socket or usocket # support for custom socket implementations + self._subs = [] + self._state = None + + async def connect(self): + """Serve connect request""" + return await self._connect() + + async def _connect(self): + """Hardware specific connect method""" + # return True # if connection is successful, otherwise False + raise NotImplementedError() + + async def disconnect(self): + """Serve disconnect request""" + return await self._disconnect() + + async def _disconnect(self): + """Hardware specific disconnect method""" + # return True # if connection is successful, otherwise False + raise NotImplementedError() + + async def reconnect(self): + """Serve reconnect request""" + return await self._reconnect() + + async def _reconnect(self): + """Hardware specific disconnect method""" + if await self._disconnect(): + return await self._connect() + return False + + def isconnected(self): + if self._isconnected(): + if not self._state: + # triggers if state is False or None + self._state = True + self._launch_subs(True) + else: + if self._state: + # triggers if state is True + self._state = False + self._launch_subs(False) + + def _isconnected(self): + raise NotImplementedError() + + def _launch_subs(self, state): + for cb in self._subs: + launch(cb, (state,)) + + def subscribe(self, cb): + self._subs.append(cb) From 378bc3f3fc93e0dc983e1d246b03903dda5c9f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 15:52:32 +0200 Subject: [PATCH 03/19] Add default WLAN interface --- mqtt_as/interfaces/wlan/__init__.py | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 mqtt_as/interfaces/wlan/__init__.py diff --git a/mqtt_as/interfaces/wlan/__init__.py b/mqtt_as/interfaces/wlan/__init__.py new file mode 100644 index 0000000..3485322 --- /dev/null +++ b/mqtt_as/interfaces/wlan/__init__.py @@ -0,0 +1,72 @@ +from .. import BaseInterface +from sys import platform +import network +import uasyncio as asyncio + +ESP8266 = platform == 'esp8266' +ESP32 = platform == 'esp32' +PYBOARD = platform == 'pyboard' + + +class WLAN(BaseInterface): + def __init__(self, ssid=None, wifi_pw=None): + super().__init__() + self.DEBUG = False + if platform == 'esp32' or platform == 'esp32_LoBo': + # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 + self.BUSY_ERRORS += [118, 119] # Add in weird ESP32 errors + self._ssid = ssid + self._wifi_pw = wifi_pw + # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 + self._sta_if = network.WLAN(network.STA_IF) + self._sta_if.active(True) + if platform == "esp8266": + import esp + esp.sleep_type(0) # Improve connection integrity at cost of power consumption. + + async def _connect(self): + s = self._sta_if + if ESP8266: + if s.isconnected(): # 1st attempt, already connected. + return True + s.active(True) + s.connect() # ESP8266 remembers connection. + for _ in range(60): + if s.status() != network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + break + await asyncio.sleep(1) + if s.status() == network.STAT_CONNECTING: # might hang forever awaiting dhcp lease renewal or something else + s.disconnect() + await asyncio.sleep(1) + if not s.isconnected() and self._ssid is not None and self._wifi_pw is not None: + s.connect(self._ssid, self._wifi_pw) + while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + await asyncio.sleep(1) + else: + s.active(True) + s.connect(self._ssid, self._wifi_pw) + if PYBOARD: # Doesn't yet have STAT_CONNECTING constant + while s.status() in (1, 2): + await asyncio.sleep(1) + else: + while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + await asyncio.sleep(1) + + if not s.isconnected(): + return False + # Ensure connection stays up for a few secs. + if self.DEBUG: + print('Checking WiFi integrity.') + for _ in range(5): + if not s.isconnected(): + return False # in 1st 5 secs + await asyncio.sleep(1) + if self.DEBUG: + print('Got reliable connection') + return True + + async def _disconnect(self): + self._sta_if.disconnect() + + def _isconnected(self): + return self._sta_if.isconnected() From dc761bb5b9ad96d2c576cd62a09694f5fe78cb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 15:55:28 +0200 Subject: [PATCH 04/19] Separate network interface from mqtt client --- mqtt_as/__init__.py | 0 mqtt_as/mqtt_as.py | 112 ++++++++++---------------------------------- 2 files changed, 26 insertions(+), 86 deletions(-) create mode 100644 mqtt_as/__init__.py diff --git a/mqtt_as/__init__.py b/mqtt_as/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mqtt_as/mqtt_as.py b/mqtt_as/mqtt_as.py index cccb0eb..fec95dc 100644 --- a/mqtt_as/mqtt_as.py +++ b/mqtt_as/mqtt_as.py @@ -6,43 +6,28 @@ # Various improvements contributed by Kevin Köck. import gc -import usocket as socket import ustruct as struct +# imported here to optimize RAM usage +from .interfaces import BaseInterface + gc.collect() from ubinascii import hexlify import uasyncio as asyncio gc.collect() from utime import ticks_ms, ticks_diff -from uerrno import EINPROGRESS, ETIMEDOUT gc.collect() from micropython import const from machine import unique_id -import network - -gc.collect() -from sys import platform -VERSION = (0, 6, 1) +VERSION = (0, 7, 0) # Default short delay for good SynCom throughput (avoid sleep(0) with SynCom). _DEFAULT_MS = const(20) _SOCKET_POLL_DELAY = const(5) # 100ms added greatly to publish latency -# Legitimate errors while waiting on a socket. See uasyncio __init__.py open_connection(). -if platform == 'esp32' or platform == 'esp32_LoBo': - # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 - BUSY_ERRORS = [EINPROGRESS, ETIMEDOUT, 118, 119] # Add in weird ESP32 errors -else: - BUSY_ERRORS = [EINPROGRESS, ETIMEDOUT] - -ESP8266 = platform == 'esp8266' -ESP32 = platform == 'esp32' -PYBOARD = platform == 'pyboard' -LOBO = platform == 'esp32_LoBo' - # Default "do little" coro for optional user replacement async def eliza(*_): # e.g. via set_wifi_handler(coro): see test program @@ -65,10 +50,11 @@ async def eliza(*_): # e.g. via set_wifi_handler(coro): see test program 'max_repubs': 4, 'will': None, 'subs_cb': lambda *_: None, - 'wifi_coro': eliza, + 'wifi_coro': None, 'connect_coro': eliza, 'ssid': None, 'wifi_pw': None, + 'interface': None, } @@ -111,14 +97,18 @@ def __init__(self, config): self._lw_topic = False else: self._set_last_will(*will) - # WiFi config - self._ssid = config['ssid'] # Required for ESP32 / Pyboard D. Optional ESP8266 - self._wifi_pw = config['wifi_pw'] + # Interface config + if 'interface' not in config: + # assume WLAN interface, backwards compatibility + from .interfaces.wlan import WLAN + self._interface = WLAN(config['ssid'], config['wifi_pw']) + else: + self._interface: BaseInterface = config['interface'] self._ssl = config['ssl'] self._ssl_params = config['ssl_params'] # Callbacks and coros self._cb = config['subs_cb'] - self._wifi_handler = config['wifi_coro'] + self._interface.subscribe(config['wifi_coro']) self._connect_handler = config['connect_coro'] # Network self.port = config['port'] @@ -128,8 +118,6 @@ def __init__(self, config): if self.server is None: raise ValueError('no server specified.') self._sock = None - self._sta_if = network.WLAN(network.STA_IF) - self._sta_if.active(True) self.newpid = pid_gen() self.rcv_pids = set() # PUBACK and SUBACK pids awaiting ACK response @@ -164,7 +152,7 @@ async def _as_read(self, n, sock=None): # OSError caught by superclass msg = sock.read(n - len(data)) except OSError as e: # ESP32 issues weird 119 errors here msg = None - if e.args[0] not in BUSY_ERRORS: + if e.args[0] not in self._interface.BUSY_ERRORS: raise if msg == b'': # Connection closed by host raise OSError(-1) @@ -188,7 +176,7 @@ async def _as_write(self, bytes_wr, length=0, sock=None): n = sock.write(bytes_wr) except OSError as e: # ESP32 issues weird 119 errors here n = 0 - if e.args[0] not in BUSY_ERRORS: + if e.args[0] not in self._interface.BUSY_ERRORS: raise if n: t = ticks_ms() @@ -211,12 +199,12 @@ async def _recv_len(self): sh += 7 async def _connect(self, clean): - self._sock = socket.socket() + self._sock = self._interface.socket.socket() self._sock.setblocking(False) try: self._sock.connect(self._addr) except OSError as e: - if e.args[0] not in BUSY_ERRORS: + if e.args[0] not in self._interface.BUSY_ERRORS: raise await asyncio.sleep_ms(_DEFAULT_MS) self.dprint('Connecting to broker.') @@ -271,7 +259,8 @@ async def wan_ok(self, if not self.isconnected(): # WiFi is down return False length = 32 # DNS query and response packet size - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s = self._interface.socket.socket(self._interface.socket.AF_INET, + self._interface.socket.SOCK_DGRAM) s.setblocking(False) s.connect(('8.8.8.8', 53)) await asyncio.sleep(1) @@ -458,61 +447,14 @@ def __init__(self, config): self._ping_interval = p_i self._in_connect = False self._has_connected = False # Define 'Clean Session' value to use. - if ESP8266: - import esp - esp.sleep_type(0) # Improve connection integrity at cost of power consumption. - - async def wifi_connect(self): - s = self._sta_if - if ESP8266: - if s.isconnected(): # 1st attempt, already connected. - return - s.active(True) - s.connect() # ESP8266 remembers connection. - for _ in range(60): - if s.status() != network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - break - await asyncio.sleep(1) - if s.status() == network.STAT_CONNECTING: # might hang forever awaiting dhcp lease renewal or something else - s.disconnect() - await asyncio.sleep(1) - if not s.isconnected() and self._ssid is not None and self._wifi_pw is not None: - s.connect(self._ssid, self._wifi_pw) - while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - await asyncio.sleep(1) - else: - s.active(True) - s.connect(self._ssid, self._wifi_pw) - if PYBOARD: # Doesn't yet have STAT_CONNECTING constant - while s.status() in (1, 2): - await asyncio.sleep(1) - elif LOBO: - i = 0 - while not s.isconnected(): - await asyncio.sleep(1) - i += 1 - if i >= 10: - break - else: - while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - await asyncio.sleep(1) - - if not s.isconnected(): - raise OSError - # Ensure connection stays up for a few secs. - self.dprint('Checking WiFi integrity.') - for _ in range(5): - if not s.isconnected(): - raise OSError # in 1st 5 secs - await asyncio.sleep(1) - self.dprint('Got reliable connection') async def connect(self): if not self._has_connected: - await self.wifi_connect() # On 1st call, caller handles error + if not await self.wifi_connect(): # On 1st call, caller handles error + raise OSError # Note this blocks if DNS lookup occurs. Do it once to prevent # blocking during later internet outage: - self._addr = socket.getaddrinfo(self.server, self.port)[0][-1] + self._addr = self._interface.socket.getaddrinfo(self.server, self.port)[0][-1] self._in_connect = True # Disable low level ._isconnected check clean = self._clean if self._has_connected else self._clean_init try: @@ -524,7 +466,6 @@ async def connect(self): # If we get here without error broker/LAN must be up. self._isconnected = True self._in_connect = False # Low level code can now check connectivity. - asyncio.create_task(self._wifi_handler(True)) # User handler. if not self._has_connected: self._has_connected = True # Use normal clean flag on reconnect. asyncio.create_task( @@ -578,7 +519,7 @@ async def _memory(self): def isconnected(self): if self._in_connect: # Disable low-level check during .connect() return True - if self._isconnected and not self._sta_if.isconnected(): # It's going down. + if self._isconnected and not self._interface.isconnected(): # It's going down. self._reconnect() return self._isconnected @@ -586,7 +527,6 @@ def _reconnect(self): # Schedule a reconnection if not underway. if self._isconnected: self._isconnected = False self.close() - asyncio.create_task(self._wifi_handler(False)) # User handler. # Await broker connection. async def _connection(self): @@ -601,10 +541,10 @@ async def _keep_connected(self): await asyncio.sleep(1) gc.collect() else: - self._sta_if.disconnect() + await self._interface.disconnect() await asyncio.sleep(1) try: - await self.wifi_connect() + await self._interface.connect() except OSError: continue if not self._has_connected: # User has issued the terminal .disconnect() From 67910474648430000a549ead92995d4c13cc586e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 16:03:12 +0200 Subject: [PATCH 05/19] Add comments --- mqtt_as/interfaces/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py index 86c87a8..292db81 100644 --- a/mqtt_as/interfaces/__init__.py +++ b/mqtt_as/interfaces/__init__.py @@ -57,6 +57,7 @@ async def _reconnect(self): return False def isconnected(self): + """"Checks if the interface is connected. Triggers callbacks if state changes""" if self._isconnected(): if not self._state: # triggers if state is False or None @@ -69,11 +70,14 @@ def isconnected(self): self._launch_subs(False) def _isconnected(self): + """Hardware specific isconnected method""" raise NotImplementedError() def _launch_subs(self, state): + """Private method executing all callbacks or creating asyncio tasks""" for cb in self._subs: launch(cb, (state,)) def subscribe(self, cb): + """Subscribe to interface connection state changes""" self._subs.append(cb) From 3903a4a6fdb2445c11e86dbcdff894e856be5190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 18:23:26 +0200 Subject: [PATCH 06/19] Trigger callback if state changes due to manual (dis)connect --- mqtt_as/interfaces/__init__.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py index 292db81..e82c4de 100644 --- a/mqtt_as/interfaces/__init__.py +++ b/mqtt_as/interfaces/__init__.py @@ -29,8 +29,14 @@ def __init__(self, socket=None): self._state = None async def connect(self): - """Serve connect request""" - return await self._connect() + """Serve connect request. Triggers callbacks if state changes""" + if await self._connect(): + if not self._state: + # triggers if state is False or None + self._state = True + self._launch_subs(True) + return True + return False async def _connect(self): """Hardware specific connect method""" @@ -38,8 +44,14 @@ async def _connect(self): raise NotImplementedError() async def disconnect(self): - """Serve disconnect request""" - return await self._disconnect() + """Serve disconnect request. Triggers callbacks if state changes""" + if await self._disconnect(): + if self._state: + # triggers if state is True + self._state = False + self._launch_subs(False) + return True + return False async def _disconnect(self): """Hardware specific disconnect method""" From 979fb158293740e130be51b8f8687849af509122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 21:49:32 +0200 Subject: [PATCH 07/19] Simplify state changes --- mqtt_as/interfaces/__init__.py | 43 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py index e82c4de..1f80d5f 100644 --- a/mqtt_as/interfaces/__init__.py +++ b/mqtt_as/interfaces/__init__.py @@ -31,10 +31,7 @@ def __init__(self, socket=None): async def connect(self): """Serve connect request. Triggers callbacks if state changes""" if await self._connect(): - if not self._state: - # triggers if state is False or None - self._state = True - self._launch_subs(True) + self._change_state(True) return True return False @@ -46,16 +43,13 @@ async def _connect(self): async def disconnect(self): """Serve disconnect request. Triggers callbacks if state changes""" if await self._disconnect(): - if self._state: - # triggers if state is True - self._state = False - self._launch_subs(False) + self._change_state(False) return True return False async def _disconnect(self): """Hardware specific disconnect method""" - # return True # if connection is successful, otherwise False + # return True # if disconnect is successful, otherwise False raise NotImplementedError() async def reconnect(self): @@ -63,32 +57,37 @@ async def reconnect(self): return await self._reconnect() async def _reconnect(self): - """Hardware specific disconnect method""" + """Hardware specific reconnect method""" if await self._disconnect(): return await self._connect() return False def isconnected(self): """"Checks if the interface is connected. Triggers callbacks if state changes""" - if self._isconnected(): + st = self._isconnected() + self._change_state(st) + return st + + def _isconnected(self): + """Hardware specific isconnected method""" + raise NotImplementedError() + + def _change_state(self, state): + """Private method executing all callbacks or creating asyncio tasks""" + trig = False + if state: if not self._state: # triggers if state is False or None self._state = True - self._launch_subs(True) + trig = True else: if self._state: # triggers if state is True self._state = False - self._launch_subs(False) - - def _isconnected(self): - """Hardware specific isconnected method""" - raise NotImplementedError() - - def _launch_subs(self, state): - """Private method executing all callbacks or creating asyncio tasks""" - for cb in self._subs: - launch(cb, (state,)) + trig = True + if trig: + for cb in self._subs: + launch(cb, (state,)) def subscribe(self, cb): """Subscribe to interface connection state changes""" From 14351fb87c48fee94ec06ff2f78ca8e4ad9aeebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 21:54:12 +0200 Subject: [PATCH 08/19] Simplify state changes --- mqtt_as/interfaces/__init__.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py index 1f80d5f..227fb62 100644 --- a/mqtt_as/interfaces/__init__.py +++ b/mqtt_as/interfaces/__init__.py @@ -7,7 +7,7 @@ async def _g(): pass -type_coro = type(_g()) +_type_coro = type(_g()) # If a callback is passed, run it and return. @@ -15,7 +15,7 @@ async def _g(): # coros are passed by name i.e. not using function call syntax. def launch(func, tup_args): res = func(*tup_args) - if isinstance(res, type_coro): + if isinstance(res, _type_coro): res = asyncio.create_task(res) return res @@ -73,19 +73,18 @@ def _isconnected(self): raise NotImplementedError() def _change_state(self, state): - """Private method executing all callbacks or creating asyncio tasks""" - trig = False - if state: - if not self._state: - # triggers if state is False or None - self._state = True - trig = True - else: - if self._state: - # triggers if state is True - self._state = False - trig = True - if trig: + """ + Private method executing all callbacks or creating asyncio tasks + on connection state changes + """ + st = self._state + if state and not st: + # triggers if state is False or None + self._state = True + elif not state and st: + # triggers if state is True + self._state = False + if st != state: for cb in self._subs: launch(cb, (state,)) From e35c3960b28719a89f659a7aff786103371105f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 21:57:11 +0200 Subject: [PATCH 09/19] Simplify state changes --- mqtt_as/interfaces/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mqtt_as/interfaces/__init__.py b/mqtt_as/interfaces/__init__.py index 227fb62..83ee17a 100644 --- a/mqtt_as/interfaces/__init__.py +++ b/mqtt_as/interfaces/__init__.py @@ -78,13 +78,12 @@ def _change_state(self, state): on connection state changes """ st = self._state - if state and not st: - # triggers if state is False or None - self._state = True - elif not state and st: - # triggers if state is True - self._state = False if st != state: + self._state = state + if st is None and state is False: + # not triggering disconnect cbs when interface state was unknown + # (probably disconnected on startup) + return for cb in self._subs: launch(cb, (state,)) From 17192c3880128f538af2ed60098abb6bd0ce19e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 6 Jul 2021 22:03:03 +0200 Subject: [PATCH 10/19] drop support for loboris fork completely --- mqtt_as/interfaces/wlan/__init__.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mqtt_as/interfaces/wlan/__init__.py b/mqtt_as/interfaces/wlan/__init__.py index 3485322..e4c9886 100644 --- a/mqtt_as/interfaces/wlan/__init__.py +++ b/mqtt_as/interfaces/wlan/__init__.py @@ -3,16 +3,12 @@ import network import uasyncio as asyncio -ESP8266 = platform == 'esp8266' -ESP32 = platform == 'esp32' -PYBOARD = platform == 'pyboard' - class WLAN(BaseInterface): def __init__(self, ssid=None, wifi_pw=None): super().__init__() self.DEBUG = False - if platform == 'esp32' or platform == 'esp32_LoBo': + if platform == 'esp32': # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 self.BUSY_ERRORS += [118, 119] # Add in weird ESP32 errors self._ssid = ssid @@ -26,7 +22,7 @@ def __init__(self, ssid=None, wifi_pw=None): async def _connect(self): s = self._sta_if - if ESP8266: + if platform == 'esp8266': if s.isconnected(): # 1st attempt, already connected. return True s.active(True) @@ -45,7 +41,7 @@ async def _connect(self): else: s.active(True) s.connect(self._ssid, self._wifi_pw) - if PYBOARD: # Doesn't yet have STAT_CONNECTING constant + if platform == 'pyboard': # Doesn't yet have STAT_CONNECTING constant while s.status() in (1, 2): await asyncio.sleep(1) else: From 251d9e49359e20d12ef6e90e71ea80e13e9a0561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Wed, 7 Jul 2021 06:59:35 +0200 Subject: [PATCH 11/19] split WLAN implementation per platform to reduce code size on each platform and to make the modules easier to handle. --- mqtt_as/interfaces/wlan/__init__.py | 75 ++++------------------------- mqtt_as/interfaces/wlan/esp32.py | 41 ++++++++++++++++ mqtt_as/interfaces/wlan/esp8266.py | 54 +++++++++++++++++++++ mqtt_as/interfaces/wlan/pyboard.py | 42 ++++++++++++++++ 4 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 mqtt_as/interfaces/wlan/esp32.py create mode 100644 mqtt_as/interfaces/wlan/esp8266.py create mode 100644 mqtt_as/interfaces/wlan/pyboard.py diff --git a/mqtt_as/interfaces/wlan/__init__.py b/mqtt_as/interfaces/wlan/__init__.py index e4c9886..08955f5 100644 --- a/mqtt_as/interfaces/wlan/__init__.py +++ b/mqtt_as/interfaces/wlan/__init__.py @@ -1,68 +1,11 @@ -from .. import BaseInterface from sys import platform -import network -import uasyncio as asyncio - -class WLAN(BaseInterface): - def __init__(self, ssid=None, wifi_pw=None): - super().__init__() - self.DEBUG = False - if platform == 'esp32': - # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 - self.BUSY_ERRORS += [118, 119] # Add in weird ESP32 errors - self._ssid = ssid - self._wifi_pw = wifi_pw - # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 - self._sta_if = network.WLAN(network.STA_IF) - self._sta_if.active(True) - if platform == "esp8266": - import esp - esp.sleep_type(0) # Improve connection integrity at cost of power consumption. - - async def _connect(self): - s = self._sta_if - if platform == 'esp8266': - if s.isconnected(): # 1st attempt, already connected. - return True - s.active(True) - s.connect() # ESP8266 remembers connection. - for _ in range(60): - if s.status() != network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - break - await asyncio.sleep(1) - if s.status() == network.STAT_CONNECTING: # might hang forever awaiting dhcp lease renewal or something else - s.disconnect() - await asyncio.sleep(1) - if not s.isconnected() and self._ssid is not None and self._wifi_pw is not None: - s.connect(self._ssid, self._wifi_pw) - while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - await asyncio.sleep(1) - else: - s.active(True) - s.connect(self._ssid, self._wifi_pw) - if platform == 'pyboard': # Doesn't yet have STAT_CONNECTING constant - while s.status() in (1, 2): - await asyncio.sleep(1) - else: - while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - await asyncio.sleep(1) - - if not s.isconnected(): - return False - # Ensure connection stays up for a few secs. - if self.DEBUG: - print('Checking WiFi integrity.') - for _ in range(5): - if not s.isconnected(): - return False # in 1st 5 secs - await asyncio.sleep(1) - if self.DEBUG: - print('Got reliable connection') - return True - - async def _disconnect(self): - self._sta_if.disconnect() - - def _isconnected(self): - return self._sta_if.isconnected() +if platform == "esp8266": + from .esp8266 import WLAN +elif platform == "esp32": + from .esp32 import WLAN +elif platform == "pyboard": + from .pyboard import WLAN +else: + # just try esp32 implementation. Seems most mature. + from .esp32 import WLAN diff --git a/mqtt_as/interfaces/wlan/esp32.py b/mqtt_as/interfaces/wlan/esp32.py new file mode 100644 index 0000000..475a850 --- /dev/null +++ b/mqtt_as/interfaces/wlan/esp32.py @@ -0,0 +1,41 @@ +from .. import BaseInterface +import network +import uasyncio as asyncio + + +class WLAN(BaseInterface): + def __init__(self, ssid, wifi_pw): + super().__init__() + self.DEBUG = False + # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 + self.BUSY_ERRORS += [118, 119] # Add in weird ESP32 errors + self._ssid = ssid + self._wifi_pw = wifi_pw + # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 + self._sta_if = network.WLAN(network.STA_IF) + self._sta_if.active(True) + + async def _connect(self): + s = self._sta_if + while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + await asyncio.sleep(1) + + if not s.isconnected(): + return False + # Ensure connection stays up for a few secs. + if self.DEBUG: + print('Checking WiFi integrity.') + for _ in range(5): + if not s.isconnected(): + return False # in 1st 5 secs + await asyncio.sleep(1) + if self.DEBUG: + print('Got reliable connection') + return True + + async def _disconnect(self): + self._sta_if.disconnect() + return True # not checking if really disconnected. + + def _isconnected(self): + return self._sta_if.isconnected() diff --git a/mqtt_as/interfaces/wlan/esp8266.py b/mqtt_as/interfaces/wlan/esp8266.py new file mode 100644 index 0000000..638b7a5 --- /dev/null +++ b/mqtt_as/interfaces/wlan/esp8266.py @@ -0,0 +1,54 @@ +from .. import BaseInterface +import network +import uasyncio as asyncio + + +class WLAN(BaseInterface): + def __init__(self, ssid=None, wifi_pw=None): + super().__init__() + self.DEBUG = False + self._ssid = ssid + self._wifi_pw = wifi_pw + # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 + self._sta_if = network.WLAN(network.STA_IF) + self._sta_if.active(True) + import esp + esp.sleep_type(0) # Improve connection integrity at cost of power consumption. + + async def _connect(self): + s = self._sta_if + if s.isconnected(): # 1st attempt, already connected. + return True + s.active(True) + s.connect() # ESP8266 remembers connection. + for _ in range(60): + if s.status() != network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + break + await asyncio.sleep(1) + if s.status() == network.STAT_CONNECTING: # might hang forever awaiting dhcp lease renewal or something else + s.disconnect() + await asyncio.sleep(1) + if not s.isconnected() and self._ssid is not None and self._wifi_pw is not None: + s.connect(self._ssid, self._wifi_pw) + while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + await asyncio.sleep(1) + + if not s.isconnected(): + return False + # Ensure connection stays up for a few secs. + if self.DEBUG: + print('Checking WiFi integrity.') + for _ in range(5): + if not s.isconnected(): + return False # in 1st 5 secs + await asyncio.sleep(1) + if self.DEBUG: + print('Got reliable connection') + return True + + async def _disconnect(self): + self._sta_if.disconnect() + return True # not checking if really disconnected. + + def _isconnected(self): + return self._sta_if.isconnected() diff --git a/mqtt_as/interfaces/wlan/pyboard.py b/mqtt_as/interfaces/wlan/pyboard.py new file mode 100644 index 0000000..4ec8c6e --- /dev/null +++ b/mqtt_as/interfaces/wlan/pyboard.py @@ -0,0 +1,42 @@ +from .. import BaseInterface +import network +import uasyncio as asyncio + + +class WLAN(BaseInterface): + def __init__(self, ssid, wifi_pw): + super().__init__() + self.DEBUG = False + self._ssid = ssid + self._wifi_pw = wifi_pw + # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 + self._sta_if = network.WLAN(network.STA_IF) + self._sta_if.active(True) + + async def _connect(self): + s = self._sta_if + s.active(True) + s.connect(self._ssid, self._wifi_pw) + # Pyboard doesn't yet have STAT_CONNECTING constant + while s.status() in (1, 2): + await asyncio.sleep(1) + + if not s.isconnected(): + return False + # Ensure connection stays up for a few secs. + if self.DEBUG: + print('Checking WiFi integrity.') + for _ in range(5): + if not s.isconnected(): + return False # in 1st 5 secs + await asyncio.sleep(1) + if self.DEBUG: + print('Got reliable connection') + return True + + async def _disconnect(self): + self._sta_if.disconnect() + return True # not checking if really disconnected. + + def _isconnected(self): + return self._sta_if.isconnected() From 669f2236c9815c5c3b6e6b719edc3a6847350d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Thu, 8 Jul 2021 18:53:17 +0200 Subject: [PATCH 12/19] bugfix --- mqtt_as/mqtt_as.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mqtt_as/mqtt_as.py b/mqtt_as/mqtt_as.py index fec95dc..7a3f61d 100644 --- a/mqtt_as/mqtt_as.py +++ b/mqtt_as/mqtt_as.py @@ -450,7 +450,7 @@ def __init__(self, config): async def connect(self): if not self._has_connected: - if not await self.wifi_connect(): # On 1st call, caller handles error + if not await self._interface.connect(): # On 1st call, caller handles error raise OSError # Note this blocks if DNS lookup occurs. Do it once to prevent # blocking during later internet outage: @@ -543,9 +543,7 @@ async def _keep_connected(self): else: await self._interface.disconnect() await asyncio.sleep(1) - try: - await self._interface.connect() - except OSError: + if not await self._interface.connect(): # TODO: switch to reconnect once PR final. continue if not self._has_connected: # User has issued the terminal .disconnect() self.dprint('Disconnected, exiting _keep_connected') From 566f3446876bd7e4437aedc57eae9d09eb0825b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Sat, 10 Jul 2021 06:48:49 +0200 Subject: [PATCH 13/19] WLAN: use common base class --- mqtt_as/interfaces/wlan/esp32.py | 32 ++++--------------------- mqtt_as/interfaces/wlan/esp8266.py | 32 ++++--------------------- mqtt_as/interfaces/wlan/pyboard.py | 33 ++++--------------------- mqtt_as/interfaces/wlan/wlan_base.py | 36 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 85 deletions(-) create mode 100644 mqtt_as/interfaces/wlan/wlan_base.py diff --git a/mqtt_as/interfaces/wlan/esp32.py b/mqtt_as/interfaces/wlan/esp32.py index 475a850..43a862f 100644 --- a/mqtt_as/interfaces/wlan/esp32.py +++ b/mqtt_as/interfaces/wlan/esp32.py @@ -1,41 +1,17 @@ -from .. import BaseInterface +from .wlan_base import BaseWLAN import network import uasyncio as asyncio -class WLAN(BaseInterface): +class WLAN(BaseWLAN): def __init__(self, ssid, wifi_pw): - super().__init__() - self.DEBUG = False + super().__init__(ssid, wifi_pw) # https://forum.micropython.org/viewtopic.php?f=16&t=3608&p=20942#p20942 self.BUSY_ERRORS += [118, 119] # Add in weird ESP32 errors - self._ssid = ssid - self._wifi_pw = wifi_pw - # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 - self._sta_if = network.WLAN(network.STA_IF) - self._sta_if.active(True) async def _connect(self): s = self._sta_if while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. await asyncio.sleep(1) - if not s.isconnected(): - return False - # Ensure connection stays up for a few secs. - if self.DEBUG: - print('Checking WiFi integrity.') - for _ in range(5): - if not s.isconnected(): - return False # in 1st 5 secs - await asyncio.sleep(1) - if self.DEBUG: - print('Got reliable connection') - return True - - async def _disconnect(self): - self._sta_if.disconnect() - return True # not checking if really disconnected. - - def _isconnected(self): - return self._sta_if.isconnected() + return await self._check_reliability() diff --git a/mqtt_as/interfaces/wlan/esp8266.py b/mqtt_as/interfaces/wlan/esp8266.py index 638b7a5..4370d08 100644 --- a/mqtt_as/interfaces/wlan/esp8266.py +++ b/mqtt_as/interfaces/wlan/esp8266.py @@ -1,17 +1,11 @@ -from .. import BaseInterface +from .wlan_base import BaseWLAN import network import uasyncio as asyncio -class WLAN(BaseInterface): +class WLAN(BaseWLAN): def __init__(self, ssid=None, wifi_pw=None): - super().__init__() - self.DEBUG = False - self._ssid = ssid - self._wifi_pw = wifi_pw - # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 - self._sta_if = network.WLAN(network.STA_IF) - self._sta_if.active(True) + super().__init__(ssid, wifi_pw) import esp esp.sleep_type(0) # Improve connection integrity at cost of power consumption. @@ -33,22 +27,4 @@ async def _connect(self): while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. await asyncio.sleep(1) - if not s.isconnected(): - return False - # Ensure connection stays up for a few secs. - if self.DEBUG: - print('Checking WiFi integrity.') - for _ in range(5): - if not s.isconnected(): - return False # in 1st 5 secs - await asyncio.sleep(1) - if self.DEBUG: - print('Got reliable connection') - return True - - async def _disconnect(self): - self._sta_if.disconnect() - return True # not checking if really disconnected. - - def _isconnected(self): - return self._sta_if.isconnected() + return await self._check_reliability() diff --git a/mqtt_as/interfaces/wlan/pyboard.py b/mqtt_as/interfaces/wlan/pyboard.py index 4ec8c6e..d482ecc 100644 --- a/mqtt_as/interfaces/wlan/pyboard.py +++ b/mqtt_as/interfaces/wlan/pyboard.py @@ -1,17 +1,10 @@ -from .. import BaseInterface -import network +from .wlan_base import BaseWLAN import uasyncio as asyncio -class WLAN(BaseInterface): +class WLAN(BaseWLAN): def __init__(self, ssid, wifi_pw): - super().__init__() - self.DEBUG = False - self._ssid = ssid - self._wifi_pw = wifi_pw - # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 - self._sta_if = network.WLAN(network.STA_IF) - self._sta_if.active(True) + super().__init__(ssid, wifi_pw) async def _connect(self): s = self._sta_if @@ -21,22 +14,4 @@ async def _connect(self): while s.status() in (1, 2): await asyncio.sleep(1) - if not s.isconnected(): - return False - # Ensure connection stays up for a few secs. - if self.DEBUG: - print('Checking WiFi integrity.') - for _ in range(5): - if not s.isconnected(): - return False # in 1st 5 secs - await asyncio.sleep(1) - if self.DEBUG: - print('Got reliable connection') - return True - - async def _disconnect(self): - self._sta_if.disconnect() - return True # not checking if really disconnected. - - def _isconnected(self): - return self._sta_if.isconnected() + return await self._check_reliability() diff --git a/mqtt_as/interfaces/wlan/wlan_base.py b/mqtt_as/interfaces/wlan/wlan_base.py new file mode 100644 index 0000000..1bf4c90 --- /dev/null +++ b/mqtt_as/interfaces/wlan/wlan_base.py @@ -0,0 +1,36 @@ +from .. import BaseInterface +import network +import uasyncio as asyncio + + +class BaseWLAN(BaseInterface): + def __init__(self, ssid=None, wifi_pw=None): + super().__init__() + self.DEBUG = False + self._ssid = ssid + self._wifi_pw = wifi_pw + # wifi credentials required for ESP32 / Pyboard D. Optional ESP8266 + self._sta_if = network.WLAN(network.STA_IF) + self._sta_if.active(True) + + async def _check_reliability(self): + s = self._sta_if + if not s.isconnected(): + return False + # Ensure connection stays up for a few secs. + if self.DEBUG: + print('Checking WiFi integrity.') + for _ in range(5): + if not s.isconnected(): + return False # in 1st 5 secs + await asyncio.sleep(1) + if self.DEBUG: + print('Got reliable connection') + return True + + async def _disconnect(self): + self._sta_if.disconnect() + return True # not checking if really disconnected. + + def _isconnected(self): + return self._sta_if.isconnected() From ccdfe62282d29d191f947466f6bf5a0318975a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Sat, 10 Jul 2021 08:50:35 +0200 Subject: [PATCH 14/19] WLAN: use common base class --- mqtt_as/interfaces/wlan/esp32.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mqtt_as/interfaces/wlan/esp32.py b/mqtt_as/interfaces/wlan/esp32.py index 43a862f..f16a605 100644 --- a/mqtt_as/interfaces/wlan/esp32.py +++ b/mqtt_as/interfaces/wlan/esp32.py @@ -11,6 +11,8 @@ def __init__(self, ssid, wifi_pw): async def _connect(self): s = self._sta_if + s.active(True) + s.connect(self._ssid, self._wifi_pw) while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. await asyncio.sleep(1) From bf6cf65a079d2bd22956598e59f9aa2e4832b1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Mon, 19 Jul 2021 15:12:32 +0200 Subject: [PATCH 15/19] allow backwards compatible import statements --- mqtt_as/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mqtt_as/__init__.py b/mqtt_as/__init__.py index e69de29..54f6128 100644 --- a/mqtt_as/__init__.py +++ b/mqtt_as/__init__.py @@ -0,0 +1 @@ +from .mqtt_as import MQTTClient, config # allow backwards compatible import statements From 120cb625490791596424a62bcc0d657ea7ff95c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 20 Jul 2021 12:32:56 +0200 Subject: [PATCH 16/19] add linux interface, bugfixes --- mqtt_as/interfaces/linux.py | 12 ++++++++++++ mqtt_as/mqtt_as.py | 22 +++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 mqtt_as/interfaces/linux.py diff --git a/mqtt_as/interfaces/linux.py b/mqtt_as/interfaces/linux.py new file mode 100644 index 0000000..e1b4d53 --- /dev/null +++ b/mqtt_as/interfaces/linux.py @@ -0,0 +1,12 @@ +from . import BaseInterface + + +class Linux(BaseInterface): + async def _disconnect(self): + return True # just to prevent errors we'll pretend to be disconnected. + + def _isconnected(self): + return True # always connected. + + async def _connect(self): + return True # always connected or can't do anything about it diff --git a/mqtt_as/mqtt_as.py b/mqtt_as/mqtt_as.py index 7a3f61d..c53eb94 100644 --- a/mqtt_as/mqtt_as.py +++ b/mqtt_as/mqtt_as.py @@ -6,6 +6,7 @@ # Various improvements contributed by Kevin Köck. import gc +import sys import ustruct as struct # imported here to optimize RAM usage @@ -20,7 +21,9 @@ gc.collect() from micropython import const -from machine import unique_id + +if sys.platform != 'linux': + from machine import unique_id VERSION = (0, 7, 0) @@ -35,7 +38,7 @@ async def eliza(*_): # e.g. via set_wifi_handler(coro): see test program config = { - 'client_id': hexlify(unique_id()), + 'client_id': hexlify(unique_id()) if sys.platform != 'linux' else 'linux', 'server': None, 'port': 0, 'user': '', @@ -98,17 +101,22 @@ def __init__(self, config): else: self._set_last_will(*will) # Interface config - if 'interface' not in config: - # assume WLAN interface, backwards compatibility - from .interfaces.wlan import WLAN - self._interface = WLAN(config['ssid'], config['wifi_pw']) + if 'interface' not in config or config['interface'] is None: + if sys.platform == 'linux': + from .interfaces.linux import Linux + self._interface = Linux() + else: + # assume WLAN interface, backwards compatibility + from .interfaces.wlan import WLAN + self._interface = WLAN(config['ssid'], config['wifi_pw']) else: self._interface: BaseInterface = config['interface'] self._ssl = config['ssl'] self._ssl_params = config['ssl_params'] # Callbacks and coros self._cb = config['subs_cb'] - self._interface.subscribe(config['wifi_coro']) + if config['wifi_coro']: + self._interface.subscribe(config['wifi_coro']) self._connect_handler = config['connect_coro'] # Network self.port = config['port'] From fc250be592584683803ba7304562951be7b06a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 20 Jul 2021 12:34:21 +0200 Subject: [PATCH 17/19] switch to reconnect() --- mqtt_as/interfaces/wlan/wlan_base.py | 1 + mqtt_as/mqtt_as.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mqtt_as/interfaces/wlan/wlan_base.py b/mqtt_as/interfaces/wlan/wlan_base.py index 1bf4c90..2d0a5bc 100644 --- a/mqtt_as/interfaces/wlan/wlan_base.py +++ b/mqtt_as/interfaces/wlan/wlan_base.py @@ -30,6 +30,7 @@ async def _check_reliability(self): async def _disconnect(self): self._sta_if.disconnect() + await asyncio.sleep(1) return True # not checking if really disconnected. def _isconnected(self): diff --git a/mqtt_as/mqtt_as.py b/mqtt_as/mqtt_as.py index c53eb94..afda4f8 100644 --- a/mqtt_as/mqtt_as.py +++ b/mqtt_as/mqtt_as.py @@ -549,9 +549,7 @@ async def _keep_connected(self): await asyncio.sleep(1) gc.collect() else: - await self._interface.disconnect() - await asyncio.sleep(1) - if not await self._interface.connect(): # TODO: switch to reconnect once PR final. + if not await self._interface.reconnect(): continue if not self._has_connected: # User has issued the terminal .disconnect() self.dprint('Disconnected, exiting _keep_connected') From 617c50cc9f5af6eccc47487ac722eb10398aca4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Tue, 20 Jul 2021 12:41:20 +0200 Subject: [PATCH 18/19] separate tests from library --- {mqtt_as => tests}/clean.py | 0 {mqtt_as => tests}/config.py | 0 {mqtt_as => tests}/lowpower.py | 0 {mqtt_as => tests}/main.py | 0 {mqtt_as => tests}/pubtest | 0 {mqtt_as => tests}/range.py | 0 {mqtt_as => tests}/range_ex.py | 0 {mqtt_as => tests}/tls.py | 0 {mqtt_as => tests}/tls32.py | 0 {mqtt_as => tests}/tls8266.py | 0 {mqtt_as => tests}/unclean.py | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {mqtt_as => tests}/clean.py (100%) rename {mqtt_as => tests}/config.py (100%) rename {mqtt_as => tests}/lowpower.py (100%) rename {mqtt_as => tests}/main.py (100%) rename {mqtt_as => tests}/pubtest (100%) mode change 100755 => 100644 rename {mqtt_as => tests}/range.py (100%) rename {mqtt_as => tests}/range_ex.py (100%) rename {mqtt_as => tests}/tls.py (100%) rename {mqtt_as => tests}/tls32.py (100%) rename {mqtt_as => tests}/tls8266.py (100%) rename {mqtt_as => tests}/unclean.py (100%) diff --git a/mqtt_as/clean.py b/tests/clean.py similarity index 100% rename from mqtt_as/clean.py rename to tests/clean.py diff --git a/mqtt_as/config.py b/tests/config.py similarity index 100% rename from mqtt_as/config.py rename to tests/config.py diff --git a/mqtt_as/lowpower.py b/tests/lowpower.py similarity index 100% rename from mqtt_as/lowpower.py rename to tests/lowpower.py diff --git a/mqtt_as/main.py b/tests/main.py similarity index 100% rename from mqtt_as/main.py rename to tests/main.py diff --git a/mqtt_as/pubtest b/tests/pubtest old mode 100755 new mode 100644 similarity index 100% rename from mqtt_as/pubtest rename to tests/pubtest diff --git a/mqtt_as/range.py b/tests/range.py similarity index 100% rename from mqtt_as/range.py rename to tests/range.py diff --git a/mqtt_as/range_ex.py b/tests/range_ex.py similarity index 100% rename from mqtt_as/range_ex.py rename to tests/range_ex.py diff --git a/mqtt_as/tls.py b/tests/tls.py similarity index 100% rename from mqtt_as/tls.py rename to tests/tls.py diff --git a/mqtt_as/tls32.py b/tests/tls32.py similarity index 100% rename from mqtt_as/tls32.py rename to tests/tls32.py diff --git a/mqtt_as/tls8266.py b/tests/tls8266.py similarity index 100% rename from mqtt_as/tls8266.py rename to tests/tls8266.py diff --git a/mqtt_as/unclean.py b/tests/unclean.py similarity index 100% rename from mqtt_as/unclean.py rename to tests/unclean.py From 2067e57eb64a7df40f13a618906ce03ebba5ae26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Mon, 2 Aug 2021 22:49:54 +0200 Subject: [PATCH 19/19] ESP32: fix ESP-IDF changes resulting in OSError when trying to connect an already connected WLAN interface. --- mqtt_as/interfaces/wlan/esp32.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mqtt_as/interfaces/wlan/esp32.py b/mqtt_as/interfaces/wlan/esp32.py index f16a605..4f7102d 100644 --- a/mqtt_as/interfaces/wlan/esp32.py +++ b/mqtt_as/interfaces/wlan/esp32.py @@ -12,8 +12,9 @@ def __init__(self, ssid, wifi_pw): async def _connect(self): s = self._sta_if s.active(True) - s.connect(self._ssid, self._wifi_pw) - while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. - await asyncio.sleep(1) + if not s.isconnected(): + s.connect(self._ssid, self._wifi_pw) + while s.status() == network.STAT_CONNECTING: # Break out on fail or success. Check once per sec. + await asyncio.sleep(1) return await self._check_reliability()