From 8f1f8974147438533a8ecdbe374793138cbb1fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E4=B9=89=E6=B7=B1?= <1371033826@qq.com> Date: Fri, 27 Aug 2021 22:32:03 +0800 Subject: [PATCH 001/289] Update adafruit_minimqtt.py --- adafruit_minimqtt/adafruit_minimqtt.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1c4a0d26..36acd283 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -64,6 +64,13 @@ _default_sock = None # pylint: disable=invalid-name _fake_context = None # pylint: disable=invalid-name +# Override default len() method +len_overrided = len +def len(object): + if isinstance(object, str): + return len_overrided(object.encode('utf-8')) + else: + return len_overrided(object) class MMQTTException(Exception): """MiniMQTT Exception class.""" From 3980b5a41ebd2b701a5c404a23897f2057d1e77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E4=B9=89=E6=B7=B1?= <1371033826@qq.com> Date: Sat, 28 Aug 2021 07:43:03 +0800 Subject: [PATCH 002/289] Update adafruit_minimqtt.py --- adafruit_minimqtt/adafruit_minimqtt.py | 31 ++++++++++---------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 36acd283..8131fe69 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -64,13 +64,6 @@ _default_sock = None # pylint: disable=invalid-name _fake_context = None # pylint: disable=invalid-name -# Override default len() method -len_overrided = len -def len(object): - if isinstance(object, str): - return len_overrided(object.encode('utf-8')) - else: - return len_overrided(object) class MMQTTException(Exception): """MiniMQTT Exception class.""" @@ -188,7 +181,7 @@ def __init__( randint(0, int(time.monotonic() * 100) % 1000), randint(0, 99) ) # generated client_id's enforce spec.'s length rules - if len(self.client_id) > 23 or not self.client_id: + if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: raise ValueError("MQTT Client ID must be between 1 and 23 bytes") # LWT @@ -457,16 +450,16 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): var_header[6] = clean_session << 1 # Set up variable header and remaining_length - remaining_length = 12 + len(self.client_id) + remaining_length = 12 + len(self.client_id.encode("utf-8")) if self._username: - remaining_length += 2 + len(self._username) + 2 + len(self._password) + remaining_length += 2 + len(self._username.encode("utf-8")) + 2 + len(self._password.encode("utf-8")) var_header[6] |= 0xC0 if self.keep_alive: assert self.keep_alive < MQTT_TOPIC_LENGTH_LIMIT var_header[7] |= self.keep_alive >> 8 var_header[8] |= self.keep_alive & 0x00FF if self._lw_topic: - remaining_length += 2 + len(self._lw_topic) + 2 + len(self._lw_msg) + remaining_length += 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg.encode("utf-8")) var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[6] |= self._lw_retain << 5 @@ -583,7 +576,7 @@ def publish(self, topic, msg, retain=False, qos=0): pass else: raise MMQTTException("Invalid message data type.") - if len(msg) > MQTT_MSG_MAX_SZ: + if len(msg.encode("utf-8")) > MQTT_MSG_MAX_SZ: raise MMQTTException("Message size larger than %d bytes." % MQTT_MSG_MAX_SZ) assert ( 0 <= qos <= 1 @@ -593,10 +586,10 @@ def publish(self, topic, msg, retain=False, qos=0): pub_hdr_fixed = bytearray([0x30 | retain | qos << 1]) # variable header = 2-byte Topic length (big endian) - pub_hdr_var = bytearray(struct.pack(">H", len(topic))) + pub_hdr_var = bytearray(struct.pack(">H", len(topic.encode("utf-8")))) pub_hdr_var.extend(topic.encode("utf-8")) # Topic name - remaining_length = 2 + len(msg) + len(topic) + remaining_length = 2 + len(msg.encode("utf-8")) + len(topic.encode("utf-8")) if qos > 0: # packet identifier where QoS level is 1 or 2. [3.3.2.2] remaining_length += 2 @@ -675,7 +668,7 @@ def subscribe(self, topic, qos=0): topics.append((t, q)) # Assemble packet packet_length = 2 + (2 * len(topics)) + (1 * len(topics)) - packet_length += sum(len(topic) for topic, qos in topics) + packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) packet_length_byte = packet_length.to_bytes(1, "big") self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") @@ -683,7 +676,7 @@ def subscribe(self, topic, qos=0): packet = MQTT_SUB + packet_length_byte + packet_id_bytes # attaching topic and QOS level to the packet for t, q in topics: - topic_size = len(t).to_bytes(2, "big") + topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") packet += topic_size + t.encode() + qos_byte if self.logger: @@ -724,13 +717,13 @@ def unsubscribe(self, topic): ) # Assemble packet packet_length = 2 + (2 * len(topics)) - packet_length += sum(len(topic) for topic in topics) + packet_length += sum(len(topic.encode("utf-8")) for topic in topics) packet_length_byte = packet_length.to_bytes(1, "big") self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") packet = MQTT_UNSUB + packet_length_byte + packet_id_bytes for t in topics: - topic_size = len(t).to_bytes(2, "big") + topic_size = len(t.encode("utf-8")).to_bytes(2, "big") packet += topic_size + t.encode() if self.logger: for t in topics: @@ -921,7 +914,7 @@ def _send_str(self, string): :param str string: String to write to the socket. """ - self._sock.send(struct.pack("!H", len(string))) + self._sock.send(struct.pack("!H", len(string.encode("utf-8")))) if isinstance(string, str): self._sock.send(str.encode(string, "utf-8")) else: From df63c8f40fce16923865130225a1f9b49468fa05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E4=B9=89=E6=B7=B1?= <1371033826@qq.com> Date: Sat, 28 Aug 2021 08:00:06 +0800 Subject: [PATCH 003/289] Update adafruit_minimqtt.py --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 8131fe69..ddea7cb5 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -576,7 +576,7 @@ def publish(self, topic, msg, retain=False, qos=0): pass else: raise MMQTTException("Invalid message data type.") - if len(msg.encode("utf-8")) > MQTT_MSG_MAX_SZ: + if len(msg) > MQTT_MSG_MAX_SZ: raise MMQTTException("Message size larger than %d bytes." % MQTT_MSG_MAX_SZ) assert ( 0 <= qos <= 1 @@ -589,7 +589,7 @@ def publish(self, topic, msg, retain=False, qos=0): pub_hdr_var = bytearray(struct.pack(">H", len(topic.encode("utf-8")))) pub_hdr_var.extend(topic.encode("utf-8")) # Topic name - remaining_length = 2 + len(msg.encode("utf-8")) + len(topic.encode("utf-8")) + remaining_length = 2 + len(msg) + len(topic.encode("utf-8")) if qos > 0: # packet identifier where QoS level is 1 or 2. [3.3.2.2] remaining_length += 2 From a2032c06ee3e6e416d766339da038e84d6e01e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E4=B9=89=E6=B7=B1?= <1371033826@qq.com> Date: Sat, 28 Aug 2021 08:14:39 +0800 Subject: [PATCH 004/289] Update adafruit_minimqtt.py --- adafruit_minimqtt/adafruit_minimqtt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ddea7cb5..35470c35 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -459,7 +459,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): var_header[7] |= self.keep_alive >> 8 var_header[8] |= self.keep_alive & 0x00FF if self._lw_topic: - remaining_length += 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg.encode("utf-8")) + remaining_length += 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[6] |= self._lw_retain << 5 @@ -914,10 +914,11 @@ def _send_str(self, string): :param str string: String to write to the socket. """ - self._sock.send(struct.pack("!H", len(string.encode("utf-8")))) if isinstance(string, str): + self._sock.send(struct.pack("!H", len(string.encode("utf-8")))) self._sock.send(str.encode(string, "utf-8")) else: + self._sock.send(struct.pack("!H", len(string))) self._sock.send(string) @staticmethod From 2a9a71d1290bd7575aba93ec5615a3ad432c50aa Mon Sep 17 00:00:00 2001 From: dherrada Date: Fri, 5 Nov 2021 14:49:30 -0400 Subject: [PATCH 005/289] Disabled unspecified-encoding pylint check Signed-off-by: dherrada --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index e78bad2f..cfd1c414 100644 --- a/.pylintrc +++ b/.pylintrc @@ -55,7 +55,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 63a73ccc9ba424aa258305d723e574f6b1716553 Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 9 Nov 2021 13:31:14 -0500 Subject: [PATCH 006/289] Updated readthedocs file Signed-off-by: dherrada --- .readthedocs.yaml | 15 +++++++++++++++ .readthedocs.yml | 7 ------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..95ec2184 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +python: + version: "3.6" + install: + - requirements: docs/requirements.txt + - requirements: requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 49dcab30..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -python: - version: 3 -requirements_file: docs/requirements.txt From 8c30e51fb90b5332315853a88ef125527011860f Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 23 Nov 2021 13:15:03 -0600 Subject: [PATCH 007/289] update rtd py version --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 95ec2184..13351121 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.6" + version: "3.7" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 9afb9ebb7c168608c9862df7f96202803b512936 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 21 Dec 2021 18:28:48 -0600 Subject: [PATCH 008/289] code format --- adafruit_minimqtt/adafruit_minimqtt.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 93f4d0a2..876f09da 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -450,14 +450,21 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): # Set up variable header and remaining_length remaining_length = 12 + len(self.client_id.encode("utf-8")) if self._username: - remaining_length += 2 + len(self._username.encode("utf-8")) + 2 + len(self._password.encode("utf-8")) + remaining_length += ( + 2 + + len(self._username.encode("utf-8")) + + 2 + + len(self._password.encode("utf-8")) + ) var_header[6] |= 0xC0 if self.keep_alive: assert self.keep_alive < MQTT_TOPIC_LENGTH_LIMIT var_header[7] |= self.keep_alive >> 8 var_header[8] |= self.keep_alive & 0x00FF if self._lw_topic: - remaining_length += 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) + remaining_length += ( + 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) + ) var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[6] |= self._lw_retain << 5 From 1bdaca883529c4612dba790518a190904993c7c5 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Fri, 7 Jan 2022 17:46:57 -0400 Subject: [PATCH 009/289] feat: add support for binary messages --- adafruit_minimqtt/adafruit_minimqtt.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 876f09da..db2b46ac 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -126,6 +126,7 @@ class MQTT: :param int keep_alive: KeepAlive interval between the broker and the MiniMQTT client. :param socket socket_pool: A pool of socket resources available for the given radio. :param ssl_context: SSL context for long-lived SSL connections. + :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. """ @@ -141,12 +142,14 @@ def __init__( keep_alive=60, socket_pool=None, ssl_context=None, + use_binary_mode=False, ): self._socket_pool = socket_pool self._ssl_context = ssl_context self._sock = None self._backwards_compatible_sock = False + self._use_binary_mode = use_binary_mode self.keep_alive = keep_alive self._user_data = None @@ -839,8 +842,9 @@ def _wait_for_msg(self, timeout=0.1): pid = pid[0] << 0x08 | pid[1] sz -= 0x02 # read message contents - msg = self._sock_exact_recv(sz) - self._handle_on_message(self, topic, str(msg, "utf-8")) + raw_msg = self._sock_exact_recv(sz) + msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") + self._handle_on_message(self, topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") struct.pack_into("!H", pkt, 2, pid) From f60ea2c5e8406abd4905fb00651d7c6d00460984 Mon Sep 17 00:00:00 2001 From: dherrada Date: Thu, 13 Jan 2022 16:27:30 -0500 Subject: [PATCH 010/289] First part of patch Signed-off-by: dherrada --- .../PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/release.yml | 8 ++++---- .readthedocs.yaml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md index 71ef8f89..8de294e6 100644 --- a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md @@ -4,7 +4,7 @@ Thank you for contributing! Before you submit a pull request, please read the following. -Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html +Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 860cc368..140d2d90 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.7 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.x" - name: Versions run: | python3 --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d0015a6..a65e5def 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: "3.x" - name: Versions run: | python3 --version @@ -67,7 +67,7 @@ jobs: echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - name: Set up Python if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 13351121..f8b28912 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.7" + version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 62bb44d191b33175f81fe8b24c855ec50df1432b Mon Sep 17 00:00:00 2001 From: Randy Hudson Date: Sat, 22 Jan 2022 13:28:04 -0500 Subject: [PATCH 011/289] Added missing blank lines before :param blocks in docstrings --- adafruit_minimqtt/adafruit_minimqtt.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index db2b46ac..1927f943 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -75,6 +75,7 @@ class MMQTTException(Exception): # Legacy ESP32SPI Socket API def set_socket(sock, iface=None): """Legacy API for setting the socket and network interface. + :param sock: socket object. :param iface: internet interface object @@ -116,6 +117,7 @@ def wrap_socket(self, socket, server_hostname=None): class MQTT: """MQTT Client for CircuitPython. + :param str broker: MQTT Broker URL or IP Address. :param int port: Optional port definition, defaults to 8883. :param str username: Username for broker authentication. @@ -209,6 +211,7 @@ def __init__( # pylint: disable=too-many-branches def _get_connect_socket(self, host, port, *, timeout=1): """Obtains a new socket and connects to a broker. + :param str host: Desired broker hostname :param int port: Desired broker port :param int timeout: Desired socket timeout @@ -409,6 +412,7 @@ def _handle_on_message(self, client, topic, message): def username_pw_set(self, username, password=None): """Set client's username and an optional password. + :param str username: Username to use with your MQTT broker. :param str password: Password to use with your MQTT broker. @@ -422,6 +426,7 @@ def username_pw_set(self, username, password=None): # pylint: disable=too-many-branches, too-many-statements, too-many-locals def connect(self, clean_session=True, host=None, port=None, keep_alive=None): """Initiates connection with the MQTT Broker. + :param bool clean_session: Establishes a persistent session. :param str host: Hostname or IP address of the remote broker. :param int port: Network port of the remote broker. @@ -563,6 +568,7 @@ def ping(self): # pylint: disable=too-many-branches, too-many-statements def publish(self, topic, msg, retain=False, qos=0): """Publishes a message to a topic provided. + :param str topic: Unique topic identifier. :param str,int,float,bytes msg: Data to send to the broker. :param bool retain: Whether the message is saved by the broker. @@ -706,6 +712,7 @@ def subscribe(self, topic, qos=0): def unsubscribe(self, topic): """Unsubscribes from a MQTT topic. + :param str,list topic: Unique MQTT topic identifier string or list. """ @@ -754,6 +761,7 @@ def unsubscribe(self, topic): def reconnect(self, resub_topics=True): """Attempts to reconnect to the MQTT broker. + :param bool resub_topics: Resubscribe to previously subscribed topics. """ @@ -777,6 +785,7 @@ def loop(self, timeout=1): """Non-blocking message loop. Use this method to check incoming subscription messages. Returns response codes of any messages received. + :param int timeout: Socket timeout, in seconds. """ @@ -884,6 +893,7 @@ def _sock_exact_recv(self, bufsize): terms of the minimum size of the buffer, which could be 1 byte. This is a wrapper for socket recv() to ensure that no less than the expected number of bytes is returned or trigger a timeout exception. + :param int bufsize: number of bytes to receive """ @@ -920,6 +930,7 @@ def _sock_exact_recv(self, bufsize): def _send_str(self, string): """Encodes a string and sends it to a socket. + :param str string: String to write to the socket. """ @@ -933,6 +944,7 @@ def _send_str(self, string): @staticmethod def _valid_topic(topic): """Validates if topic provided is proper MQTT topic format. + :param str topic: Topic identifier """ @@ -948,6 +960,7 @@ def _valid_topic(topic): @staticmethod def _valid_qos(qos_level): """Validates if the QoS level is supported by this library + :param int qos_level: Desired QoS level. """ @@ -968,6 +981,7 @@ def is_connected(self): # Logging def enable_logger(self, logger, log_level=20): """Enables library logging provided a logger object. + :param logger: A python logger pacakge. :param log_level: Numeric value of a logging level, defaults to INFO. From 1e28429946e643a9af46d542c374565f0d1e6697 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 24 Jan 2022 16:46:17 -0500 Subject: [PATCH 012/289] Updated docs link, updated python docs link, updated setup.py --- README.rst | 4 ++-- docs/conf.py | 4 ++-- docs/index.rst | 2 +- setup.py | 2 -- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index c3db2aa3..b04d6990 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Introduction ============ .. image:: https://readthedocs.org/projects/adafruit-circuitpython-minimqtt/badge/?version=latest - :target: https://circuitpython.readthedocs.io/projects/minimqtt/en/latest/ + :target: https://docs.circuitpython.org/projects/minimqtt/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg @@ -58,7 +58,7 @@ for usage examples for this library. Documentation ============= -API documentation for this library can be found on `Read the Docs `_. +API documentation for this library can be found on `Read the Docs `_. Contributing ============ diff --git a/docs/conf.py b/docs/conf.py index 7dfe9601..b1282160 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,8 +28,8 @@ intersphinx_mapping = { - "python": ("https://docs.python.org/3.4", None), - "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), + "python": ("https://docs.python.org/3", None), + "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index 9208b989..cc67740e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,7 +30,7 @@ Table of Contents :caption: Other Links Download - CircuitPython Reference Documentation + CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat Adafruit Learning System diff --git a/setup.py b/setup.py index 696d4b2d..6d31ffd7 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,6 @@ "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", ], # What does your project relate to? keywords="adafruit blinka circuitpython micropython minimqtt mqtt, client, socket", From 141853b005d75b87d0944a472fd267f2630d9524 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 1 Feb 2022 19:06:01 +0100 Subject: [PATCH 013/289] Fix typo --- examples/minimqtt_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 0a4ca3fa..2f1c49b5 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -68,7 +68,7 @@ def publish(mqtt_client, userdata, topic, pid): def message(client, topic, message): - # Method callled when a client's subscribed feed has a new value. + # Method called when a client's subscribed feed has a new value. print("New message on topic {0}: {1}".format(topic, message)) From 64012bfbc387614f01d64a75f6578cea0134c7bc Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 10 Feb 2022 10:06:30 -0500 Subject: [PATCH 014/289] Consolidate Documentation sections of README --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index b04d6990..a89f5fc9 100644 --- a/README.rst +++ b/README.rst @@ -60,14 +60,11 @@ Documentation API documentation for this library can be found on `Read the Docs `_. +For information on building library documentation, please check out `this guide `_. + Contributing ============ Contributions are welcome! Please read our `Code of Conduct `_ before contributing to help this project stay welcoming. - -Documentation -============= - -For information on building library documentation, please check out `this guide `_. From 58be6148c035a02b9ffd1f0c0155cab2aeb74e00 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 14 Feb 2022 15:35:02 -0500 Subject: [PATCH 015/289] Fixed readthedocs build Signed-off-by: dherrada --- .readthedocs.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f8b28912..33c2a610 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,12 @@ # Required version: 2 +build: + os: ubuntu-20.04 + tools: + python: "3" + python: - version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 51a573e795e368d7577ee9074ea96e3b55e550a1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 18 Feb 2022 20:57:22 -0500 Subject: [PATCH 016/289] Minor updates to documentation May also fix the error resulting in #102 by re-running the CI. --- adafruit_minimqtt/adafruit_minimqtt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1927f943..8519af03 100755 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -338,7 +338,7 @@ def will_set(self, topic=None, payload=None, qos=0, retain=False): """Sets the last will and testament properties. MUST be called before `connect()`. :param str topic: MQTT Broker topic. - :param int,float,str payload: Last will disconnection payload. + :param int|float|str payload: Last will disconnection payload. payloads of type int & float are converted to a string. :param int qos: Quality of Service level, defaults to zero. Conventional options are ``0`` (send at most once), ``1`` @@ -368,7 +368,7 @@ def add_topic_callback(self, mqtt_topic, callback_method): """Registers a callback_method for a specific MQTT topic. :param str mqtt_topic: MQTT topic identifier. - :param function callback_method: Name of callback method. + :param function callback_method: The callback method. """ if mqtt_topic is None or callback_method is None: raise ValueError("MQTT topic and callback method must both be defined.") @@ -570,7 +570,7 @@ def publish(self, topic, msg, retain=False, qos=0): """Publishes a message to a topic provided. :param str topic: Unique topic identifier. - :param str,int,float,bytes msg: Data to send to the broker. + :param str|int|float|bytes msg: Data to send to the broker. :param bool retain: Whether the message is saved by the broker. :param int qos: Quality of Service level for the message, defaults to zero. @@ -653,7 +653,7 @@ def subscribe(self, topic, qos=0): """Subscribes to a topic on the MQTT Broker. This method can subscribe to one topics or multiple topics. - :param str,tuple,list topic: Unique MQTT topic identifier string. If + :param str|tuple|list topic: Unique MQTT topic identifier string. If this is a `tuple`, then the tuple should contain topic identifier string and qos level integer. If this is a `list`, then @@ -713,7 +713,7 @@ def subscribe(self, topic, qos=0): def unsubscribe(self, topic): """Unsubscribes from a MQTT topic. - :param str,list topic: Unique MQTT topic identifier string or list. + :param str|list topic: Unique MQTT topic identifier string or list. """ topics = None From 163b369016a1656f82c6f422a995cc67603d5537 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 27 Feb 2022 13:51:47 +0100 Subject: [PATCH 017/289] Update permission --- adafruit_minimqtt/__init__.py | 0 adafruit_minimqtt/adafruit_minimqtt.py | 0 adafruit_minimqtt/matcher.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 adafruit_minimqtt/__init__.py mode change 100755 => 100644 adafruit_minimqtt/adafruit_minimqtt.py mode change 100755 => 100644 adafruit_minimqtt/matcher.py diff --git a/adafruit_minimqtt/__init__.py b/adafruit_minimqtt/__init__.py old mode 100755 new mode 100644 diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py old mode 100755 new mode 100644 diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py old mode 100755 new mode 100644 From ae6e668bb49f572a696e08ab04798a9f32550d17 Mon Sep 17 00:00:00 2001 From: dgriswold Date: Sun, 6 Mar 2022 16:40:45 -0500 Subject: [PATCH 018/289] add debug logging to recieved messages. --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 8519af03..5117832c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -853,6 +853,10 @@ def _wait_for_msg(self, timeout=0.1): # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") + if self.logger: + self.logger.debug( + "Receiving SUBSCRIBE \nTopic: %s\nMsg: %s\n", topic, raw_msg + ) self._handle_on_message(self, topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") From 3cee7088fe3cd37156bb143e152a62f5e5d12f60 Mon Sep 17 00:00:00 2001 From: dgriswold Date: Mon, 7 Mar 2022 11:30:56 -0500 Subject: [PATCH 019/289] change self.logger from trueness to not None. --- adafruit_minimqtt/adafruit_minimqtt.py | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5117832c..71553675 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -237,11 +237,11 @@ def _get_connect_socket(self, host, port, *, timeout=1): "ssl_context must be set before using adafruit_mqtt for secure MQTT." ) - if self.logger and port == MQTT_TLS_PORT: + if self.logger is not None and port == MQTT_TLS_PORT: self.logger.info( "Establishing a SECURE SSL connection to {0}:{1}".format(host, port) ) - elif self.logger: + elif self.logger is not None: self.logger.info( "Establishing an INSECURE connection to {0}:{1}".format(host, port) ) @@ -348,7 +348,7 @@ def will_set(self, topic=None, payload=None, qos=0, retain=False): :param bool retain: Specifies if the payload is to be retained when it is published. """ - if self.logger: + if self.logger is not None: self.logger.debug("Setting last will properties") self._valid_qos(qos) if self._is_connected: @@ -440,7 +440,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): if keep_alive: self.keep_alive = keep_alive - if self.logger: + if self.logger is not None: self.logger.debug("Attempting to establish MQTT connection...") # Get a new socket @@ -494,7 +494,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): fixed_header.append(remaining_length) fixed_header.append(0x00) - if self.logger: + if self.logger is not None: self.logger.debug("Sending CONNECT to broker...") self.logger.debug( "Fixed Header: %s\nVariable Header: %s", fixed_header, var_header @@ -512,7 +512,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): else: self._send_str(self._username) self._send_str(self._password) - if self.logger: + if self.logger is not None: self.logger.debug("Receiving CONNACK packet from broker") while True: op = self._wait_for_msg() @@ -535,7 +535,7 @@ def disconnect(self): try: self._sock.send(MQTT_DISCONNECT) except RuntimeError as e: - if self.logger: + if self.logger is not None: self.logger.warning("Unable to send DISCONNECT packet: {}".format(e)) if self.logger is not None: self.logger.debug("Closing socket") @@ -551,7 +551,7 @@ def ping(self): Returns response codes of any messages received while waiting for PINGRESP. """ self.is_connected() - if self.logger: + if self.logger is not None: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive @@ -622,7 +622,7 @@ def publish(self, topic, msg, retain=False, qos=0): else: pub_hdr_fixed.append(remaining_length) - if self.logger: + if self.logger is not None: self.logger.debug( "Sending PUBLISH\nTopic: %s\nMsg: %s\ \nQoS: %d\nRetain? %r", @@ -693,7 +693,7 @@ def subscribe(self, topic, qos=0): topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") packet += topic_size + t.encode() + qos_byte - if self.logger: + if self.logger is not None: for t, q in topics: self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) self._sock.send(packet) @@ -740,11 +740,11 @@ def unsubscribe(self, topic): for t in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") packet += topic_size + t.encode() - if self.logger: + if self.logger is not None: for t in topics: self.logger.debug("UNSUBSCRIBING from topic %s", t) self._sock.send(packet) - if self.logger: + if self.logger is not None: self.logger.debug("Waiting for UNSUBACK...") while True: op = self._wait_for_msg() @@ -765,13 +765,13 @@ def reconnect(self, resub_topics=True): :param bool resub_topics: Resubscribe to previously subscribed topics. """ - if self.logger: + if self.logger is not None: self.logger.debug("Attempting to reconnect with MQTT broker") self.connect() - if self.logger: + if self.logger is not None: self.logger.debug("Reconnected with broker") if resub_topics: - if self.logger: + if self.logger is not None: self.logger.debug( "Attempting to resubscribe to previously subscribed topics." ) @@ -828,7 +828,7 @@ def _wait_for_msg(self, timeout=0.1): # If we get here, it means that there is nothing to be received return None if res[0] == MQTT_PINGRESP: - if self.logger: + if self.logger is not None: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: @@ -853,7 +853,7 @@ def _wait_for_msg(self, timeout=0.1): # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") - if self.logger: + if self.logger is not None: self.logger.debug( "Receiving SUBSCRIBE \nTopic: %s\nMsg: %s\n", topic, raw_msg ) @@ -911,7 +911,7 @@ def _sock_exact_recv(self, bufsize): # This will timeout with socket timeout (not keepalive timeout) rc = self._sock.recv(bufsize) if not rc: - if self.logger: + if self.logger is not None: self.logger.debug("_sock_exact_recv timeout") # If no bytes waiting, raise same exception as socketpool raise OSError(errno.ETIMEDOUT) From 38e822b3fe0805bbe36d35608266de67464cc3bd Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 28 Mar 2022 15:52:04 -0400 Subject: [PATCH 020/289] Update Black to latest. Signed-off-by: Kattni Rembor --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b9fadc5..7467c1df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool From a0c5de2bb84bc87ebab30e81921720d37ad5d8e7 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Thu, 21 Apr 2022 15:00:27 -0400 Subject: [PATCH 021/289] Updated gitignore Signed-off-by: evaherrada --- .gitignore | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 9647e712..544ec4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,47 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries # -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT +# Do not include files and directories created by your personal work environment, such as the IDE +# you use, except for those already listed here. Pull requests including changes to this file will +# not be accepted. + +# This .gitignore file contains rules for files generated by working with CircuitPython libraries, +# including building Sphinx, testing with pip, and creating a virual environment, as well as the +# MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. + +# If you find that there are files being generated on your machine that should not be included in +# your git commit, you should create a .gitignore_global file on your computer to include the +# files created by your personal setup. To do so, follow the two steps below. + +# First, create a file called .gitignore_global somewhere convenient for you, and add rules for +# the files you want to exclude from git commits. + +# Second, configure Git to use the exclude file for all Git repositories by running the +# following via commandline, replacing "path/to/your/" with the actual path to your newly created +# .gitignore_global file: +# git config --global core.excludesfile path/to/your/.gitignore_global + +# CircuitPython-specific files *.mpy -.idea + +# Python-specific files __pycache__ -_build *.pyc + +# Sphinx build-specific files +_build + +# This file results from running `pip -e install .` in a local repository +*.egg-info + +# Virtual environment-specific files .env -bundles + +# MacOS-specific files *.DS_Store -.eggs -dist -**/*.egg-info + +# IDE-specific files +.idea +.vscode +*~ From 101f1752e3de1b6ebf9bd606d27970e412d153e2 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Fri, 22 Apr 2022 15:58:59 -0400 Subject: [PATCH 022/289] Patch: Replaced discord badge image --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a89f5fc9..b48de7ca 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/minimqtt/en/latest/ :alt: Documentation Status -.. image:: https://img.shields.io/discord/327254708534116352.svg +.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From c4517bbbde70e2dc7fdb1a1fcfe120d1b6b2f530 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 24 Apr 2022 14:04:54 -0500 Subject: [PATCH 023/289] change discord badge --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b48de7ca..956e717a 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/minimqtt/en/latest/ :alt: Documentation Status -.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg +.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From 4b21b4d1b2fec532a7f3395fbe2495f8cbae8378 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 15 May 2022 12:50:03 -0400 Subject: [PATCH 024/289] Patch .pre-commit-config.yaml --- .pre-commit-config.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7467c1df..33436064 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,40 +3,40 @@ # SPDX-License-Identifier: Unlicense repos: -- repo: https://github.com/python/black + - repo: https://github.com/python/black rev: 22.3.0 hooks: - - id: black -- repo: https://github.com/fsfe/reuse-tool - rev: v0.12.1 + - id: black + - repo: https://github.com/fsfe/reuse-tool + rev: v0.14.0 hooks: - - id: reuse -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - id: reuse + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/pycqa/pylint + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/pycqa/pylint rev: v2.11.1 hooks: - - id: pylint + - id: pylint name: pylint (library code) types: [python] args: - --disable=consider-using-f-string exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint + - id: pylint name: pylint (example code) description: Run pylint rules on "examples/*.py" files types: [python] files: "^examples/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - id: pylint name: pylint (test code) description: Run pylint rules on "tests/*.py" files types: [python] files: "^tests/" args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - --disable=missing-docstring,consider-using-f-string,duplicate-code From ca1c6fb1ad31be03d4125b688cdf6d6780ae4bf4 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:55 -0400 Subject: [PATCH 025/289] Increase min lines similarity Signed-off-by: Alec Delaney --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index cfd1c414..f006a4a9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -252,7 +252,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=4 +min-similarity-lines=12 [BASIC] From ed8ae39b791534fec345ac60f6f321f1e187bee0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:23 -0400 Subject: [PATCH 026/289] Switch to inclusive terminology Signed-off-by: Alec Delaney --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index f006a4a9..f7729712 100644 --- a/.pylintrc +++ b/.pylintrc @@ -9,11 +9,11 @@ # run arbitrary code extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignore-list. They should be base names, not # paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The +# Add files or directories matching the regex patterns to the ignore-list. The # regex matches against base names, not paths. ignore-patterns= From c3fe3f7f9503250b2112e9ff89c76d0116ebf326 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 30 May 2022 14:25:04 -0400 Subject: [PATCH 027/289] Set language to "en" for documentation Signed-off-by: Alec Delaney --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b1282160..32f24d4a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From 5f783d5fe46b7067f0bc172773c6cb1a04c6d013 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 7 Jun 2022 15:34:40 -0400 Subject: [PATCH 028/289] Added cp.org link to index.rst --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index cc67740e..eb6a361a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,8 @@ Table of Contents .. toctree:: :caption: Other Links - Download + Download from GitHub + Download Library Bundle CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat From 895fced8115f89239918b8c2cb3b7c863606ccaa Mon Sep 17 00:00:00 2001 From: evaherrada Date: Fri, 22 Jul 2022 13:59:02 -0400 Subject: [PATCH 029/289] Changed .env to .venv in README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 956e717a..42d19d6c 100644 --- a/README.rst +++ b/README.rst @@ -45,8 +45,8 @@ To install in a virtual environment in your current project: .. code-block:: shell mkdir project-name && cd project-name - python3 -m venv .env - source .env/bin/activate + python3 -m venv .venv + source .venv/bin/activate pip3 install adafruit-circuitpython-minimqtt Usage Example From 9097a5183cd2a35ad9005a0ecc6e4f034bd764ee Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 2 Aug 2022 17:00:50 -0400 Subject: [PATCH 030/289] Added Black formatting badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 42d19d6c..2ca1660e 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,10 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT/actions/ :alt: Build Status +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code Style: Black + MQTT Client library for CircuitPython. Dependencies From d22fd9eb4da94d1e52c0567db71f481047725337 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 8 Aug 2022 22:05:55 -0400 Subject: [PATCH 031/289] Switched to pyproject.toml --- .github/workflows/build.yml | 23 +++++++-------- .github/workflows/release.yml | 17 ++++++----- optional_requirements.txt | 3 ++ pyproject.toml | 46 +++++++++++++++++++++++++++++ requirements.txt | 2 +- setup.py | 55 ----------------------------------- 6 files changed, 70 insertions(+), 76 deletions(-) create mode 100644 optional_requirements.txt create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 140d2d90..22f6582d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,8 @@ jobs: pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - name: Pre-commit hooks run: | pre-commit run --all-files @@ -57,24 +59,19 @@ jobs: with: name: bundles path: ${{ github.workspace }}/bundles/ - - name: Check For docs folder - id: need-docs - run: | - echo ::set-output name=docs::$( find . -wholename './docs' ) - name: Build docs - if: contains(steps.need-docs.outputs.docs, 'docs') working-directory: docs run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal + pip install --upgrade build twine + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + done; + python -m build twine check dist/* - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a65e5def..d1b4f8d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,25 +61,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Set up Python - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install --upgrade build twine - name: Build and publish - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') env: TWINE_USERNAME: ${{ secrets.pypi_username }} TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | - python setup.py sdist + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + done; + python -m build twine upload dist/* diff --git a/optional_requirements.txt b/optional_requirements.txt new file mode 100644 index 00000000..d4e27c4d --- /dev/null +++ b/optional_requirements.txt @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7ab7adbb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +[build-system] +requires = [ + "setuptools", + "wheel", +] + +[project] +name = "adafruit-circuitpython-minimqtt" +description = "MQTT client library for CircuitPython" +version = "0.0.0-auto.0" +readme = "README.rst" +authors = [ + {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} +] +urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT"} +keywords = [ + "adafruit", + "blinka", + "circuitpython", + "micropython", + "minimqtt", + "mqtt,", + "client,", + "socket", +] +license = {text = "MIT"} +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Embedded Systems", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +dynamic = ["dependencies", "optional-dependencies"] + +[tool.setuptools] +packages = ["adafruit_minimqtt"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} +optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} diff --git a/requirements.txt b/requirements.txt index 17a850d4..7a984a47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries # # SPDX-License-Identifier: Unlicense diff --git a/setup.py b/setup.py deleted file mode 100644 index 6d31ffd7..00000000 --- a/setup.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""A setuptools based setup module. - -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from setuptools import setup, find_packages - -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="adafruit-circuitpython-minimqtt", - use_scm_version=True, - setup_requires=["setuptools_scm"], - description="MQTT client library for CircuitPython", - long_description=long_description, - long_description_content_type="text/x-rst", - # The project's main homepage. - url="https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT", - # Author details - author="Adafruit Industries", - author_email="circuitpython@adafruit.com", - install_requires=["Adafruit-Blinka"], - # Choose your license - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Hardware", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], - # What does your project relate to? - keywords="adafruit blinka circuitpython micropython minimqtt mqtt, client, socket", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, - # CHANGE `py_modules=['...']` TO `packages=['...']` - packages=["adafruit_minimqtt"], -) From 67e0adeea9342787fbe715bd1776a8df983ad66b Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 9 Aug 2022 12:03:54 -0400 Subject: [PATCH 032/289] Add setuptools-scm to build system requirements Signed-off-by: Alec Delaney --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 7ab7adbb..6800385a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ requires = [ "setuptools", "wheel", + "setuptools-scm", ] [project] From 8f5749b9662e8491e978f991c684a010620595fb Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 18:09:15 -0400 Subject: [PATCH 033/289] Update version string --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 71553675..7ec73be8 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -32,7 +32,7 @@ from micropython import const from .matcher import MQTTMatcher -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git" # Client-specific variables diff --git a/pyproject.toml b/pyproject.toml index 6800385a..3d88672c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ requires = [ [project] name = "adafruit-circuitpython-minimqtt" description = "MQTT client library for CircuitPython" -version = "0.0.0-auto.0" +version = "0.0.0+auto.0" readme = "README.rst" authors = [ {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} From 75ca1c5b556989f5cc74fefc332a211cbedb0ad2 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 21:09:15 -0400 Subject: [PATCH 034/289] Fix version strings in workflow files --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22f6582d..cb2f60e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: run: | pip install --upgrade build twine for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; done; python -m build twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1b4f8d9..f3a0325b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; done; python -m build twine upload dist/* From 2c4c39a9be404b6185861f8dba105f772330ee66 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 17 Aug 2022 19:01:31 +0200 Subject: [PATCH 035/289] allow to set socket timeout fixes #112 --- adafruit_minimqtt/adafruit_minimqtt.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ec73be8..8d11f555 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -129,6 +129,7 @@ class MQTT: :param socket socket_pool: A pool of socket resources available for the given radio. :param ssl_context: SSL context for long-lived SSL connections. :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. + :param int socket_timeout: socket timeout, in seconds """ @@ -145,6 +146,7 @@ def __init__( socket_pool=None, ssl_context=None, use_binary_mode=False, + socket_timeout=1, ): self._socket_pool = socket_pool @@ -152,6 +154,7 @@ def __init__( self._sock = None self._backwards_compatible_sock = False self._use_binary_mode = use_binary_mode + self._socket_timeout = socket_timeout self.keep_alive = keep_alive self._user_data = None @@ -209,12 +212,12 @@ def __init__( self.on_unsubscribe = None # pylint: disable=too-many-branches - def _get_connect_socket(self, host, port, *, timeout=1): + def _get_connect_socket(self, host, port, *, timeout): """Obtains a new socket and connects to a broker. :param str host: Desired broker hostname :param int port: Desired broker port - :param int timeout: Desired socket timeout + :param int timeout: Desired socket timeout in seconds """ # For reconnections - check if we're using a socket already and close it if self._sock: @@ -444,7 +447,9 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): self.logger.debug("Attempting to establish MQTT connection...") # Get a new socket - self._sock = self._get_connect_socket(self.broker, self.port) + self._sock = self._get_connect_socket( + self.broker, self.port, timeout=self._socket_timeout + ) # Fixed Header fixed_header = bytearray([0x10]) From 5f35f2b9c0104c0b033428333f958d8e37451a56 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 17 Aug 2022 20:04:14 +0200 Subject: [PATCH 036/289] suppress the too-many-lines pylint error the module length grew above 1k lines --- adafruit_minimqtt/adafruit_minimqtt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 8d11f555..36672d71 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -6,6 +6,8 @@ # Modified Work Copyright (c) 2019 Bradley Beach, esp32spi_mqtt # Modified Work Copyright (c) 2012-2019 Roger Light and others, Paho MQTT Python +# pylint: disable=too-many-lines + """ `adafruit_minimqtt` ================================================================================ From aac193664bf085f478aefa66cc1c89d5277e2e89 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 16:48:35 +0200 Subject: [PATCH 037/289] add the default value back --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 36672d71..4705fbc4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -214,12 +214,12 @@ def __init__( self.on_unsubscribe = None # pylint: disable=too-many-branches - def _get_connect_socket(self, host, port, *, timeout): + def _get_connect_socket(self, host, port, *, timeout=1): """Obtains a new socket and connects to a broker. :param str host: Desired broker hostname :param int port: Desired broker port - :param int timeout: Desired socket timeout in seconds + :param int timeout: Desired socket timeout, in seconds """ # For reconnections - check if we're using a socket already and close it if self._sock: From 53708f6668ab664e0c49b53c0034b80222f5bff2 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 16:59:28 +0200 Subject: [PATCH 038/289] try to explain what socket timeout means --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 4705fbc4..fa00f553 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -131,7 +131,7 @@ class MQTT: :param socket socket_pool: A pool of socket resources available for the given radio. :param ssl_context: SSL context for long-lived SSL connections. :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. - :param int socket_timeout: socket timeout, in seconds + :param int socket_timeout: How often to check socket state for read/write/connect operations, in seconds. """ From cb4b3d1b54f4f7f5d268d1e14d55775e0704d8dd Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 17:06:45 +0200 Subject: [PATCH 039/289] wrap the line --- adafruit_minimqtt/adafruit_minimqtt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index fa00f553..56673aad 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -131,7 +131,8 @@ class MQTT: :param socket socket_pool: A pool of socket resources available for the given radio. :param ssl_context: SSL context for long-lived SSL connections. :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. - :param int socket_timeout: How often to check socket state for read/write/connect operations, in seconds. + :param int socket_timeout: How often to check socket state for read/write/connect operations, + in seconds. """ From d478ccabe2f9d72cc0d927982404d5f3646c43fe Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 17 Aug 2022 21:43:36 +0200 Subject: [PATCH 040/289] avoid endless loop when waiting for data from broker fixes #115 --- adafruit_minimqtt/adafruit_minimqtt.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 56673aad..9b1d54a2 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -128,6 +128,7 @@ class MQTT: :param str client_id: Optional client identifier, defaults to a unique, generated string. :param bool is_ssl: Sets a secure or insecure connection with the broker. :param int keep_alive: KeepAlive interval between the broker and the MiniMQTT client. + :param int recv_timeout: receive timeout, in seconds. :param socket socket_pool: A pool of socket resources available for the given radio. :param ssl_context: SSL context for long-lived SSL connections. :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. @@ -146,6 +147,7 @@ def __init__( client_id=None, is_ssl=True, keep_alive=60, + recv_timeout=10, socket_pool=None, ssl_context=None, use_binary_mode=False, @@ -160,6 +162,7 @@ def __init__( self._socket_timeout = socket_timeout self.keep_alive = keep_alive + self._recv_timeout = recv_timeout self._user_data = None self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM @@ -522,6 +525,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): self._send_str(self._password) if self.logger is not None: self.logger.debug("Receiving CONNACK packet from broker") + stamp = time.monotonic() while True: op = self._wait_for_msg() if op == 32: @@ -535,6 +539,12 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): self.on_connect(self, self._user_data, result, rc[2]) return result + if op is None: + if time.monotonic() - stamp > self._recv_timeout: + raise MMQTTException( + f"No data received from broker for {self._recv_timeout} seconds." + ) + def disconnect(self): """Disconnects the MiniMQTT client from the MQTT broker.""" self.is_connected() @@ -645,6 +655,7 @@ def publish(self, topic, msg, retain=False, qos=0): if qos == 0 and self.on_publish is not None: self.on_publish(self, self._user_data, topic, self._pid) if qos == 1: + stamp = time.monotonic() while True: op = self._wait_for_msg() if op == 0x40: @@ -657,6 +668,12 @@ def publish(self, topic, msg, retain=False, qos=0): self.on_publish(self, self._user_data, topic, rcv_pid) return + if op is None: + if time.monotonic() - stamp > self._recv_timeout: + raise MMQTTException( + f"No data received from broker for {self._recv_timeout} seconds." + ) + def subscribe(self, topic, qos=0): """Subscribes to a topic on the MQTT Broker. This method can subscribe to one topics or multiple topics. @@ -705,6 +722,7 @@ def subscribe(self, topic, qos=0): for t, q in topics: self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) self._sock.send(packet) + stamp = time.monotonic() while True: op = self._wait_for_msg() if op == 0x90: @@ -718,6 +736,12 @@ def subscribe(self, topic, qos=0): self._subscribed_topics.append(t) return + if op is None: + if time.monotonic() - stamp > self._recv_timeout: + raise MMQTTException( + f"No data received from broker for {self._recv_timeout} seconds." + ) + def unsubscribe(self, topic): """Unsubscribes from a MQTT topic. @@ -755,6 +779,7 @@ def unsubscribe(self, topic): if self.logger is not None: self.logger.debug("Waiting for UNSUBACK...") while True: + stamp = time.monotonic() op = self._wait_for_msg() if op == 176: rc = self._sock_exact_recv(3) @@ -767,6 +792,12 @@ def unsubscribe(self, topic): self._subscribed_topics.remove(t) return + if op is None: + if time.monotonic() - stamp > self._recv_timeout: + raise MMQTTException( + f"No data received from broker for {self._recv_timeout} seconds." + ) + def reconnect(self, resub_topics=True): """Attempts to reconnect to the MQTT broker. From b00ab42e88477d46ce8f7fd84291cc94a6f889f4 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 18:09:59 +0200 Subject: [PATCH 041/289] check socket timeout against receive timeout --- adafruit_minimqtt/adafruit_minimqtt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 9b1d54a2..9f96eb35 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -159,10 +159,13 @@ def __init__( self._sock = None self._backwards_compatible_sock = False self._use_binary_mode = use_binary_mode + + if recv_timeout <= socket_timeout: + raise MMQTTException("recv_timeout must be strictly greater than socket_timeout") self._socket_timeout = socket_timeout + self._recv_timeout = recv_timeout self.keep_alive = keep_alive - self._recv_timeout = recv_timeout self._user_data = None self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM From c0085aa5c365e29e9984de9f0517d64b76acdc6d Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 18:16:34 +0200 Subject: [PATCH 042/289] apply black --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 9f96eb35..d1d8a56c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -161,7 +161,9 @@ def __init__( self._use_binary_mode = use_binary_mode if recv_timeout <= socket_timeout: - raise MMQTTException("recv_timeout must be strictly greater than socket_timeout") + raise MMQTTException( + "recv_timeout must be strictly greater than socket_timeout" + ) self._socket_timeout = socket_timeout self._recv_timeout = recv_timeout From fa8a33ff110519633e61cce6062ddd7d5c0b665d Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 19:43:42 +0200 Subject: [PATCH 043/289] wrap the last exception when reporting connect failure fixes #113 --- adafruit_minimqtt/adafruit_minimqtt.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 56673aad..9d56d737 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -258,6 +258,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): sock = None retry_count = 0 + last_exception = None while retry_count < 5 and sock is None: retry_count += 1 @@ -274,15 +275,20 @@ def _get_connect_socket(self, host, port, *, timeout=1): try: sock.connect((connect_host, port)) - except MemoryError: + except MemoryError as exc: sock.close() sock = None - except OSError: + last_exception = exc + except OSError as exc: sock.close() sock = None + last_exception = exc if sock is None: - raise RuntimeError("Repeated socket failures") + if last_exception: + raise RuntimeError("Repeated socket failures") from last_exception + else: + raise RuntimeError("Repeated socket failures") self._backwards_compatible_sock = not hasattr(sock, "recv_into") return sock From 4c16b42924adf7d981992c8005e3e1dea98bbc69 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 18 Aug 2022 19:45:45 +0200 Subject: [PATCH 044/289] fix pylint --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 9d56d737..2222bd08 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -287,8 +287,8 @@ def _get_connect_socket(self, host, port, *, timeout=1): if sock is None: if last_exception: raise RuntimeError("Repeated socket failures") from last_exception - else: - raise RuntimeError("Repeated socket failures") + + raise RuntimeError("Repeated socket failures") self._backwards_compatible_sock = not hasattr(sock, "recv_into") return sock From 6f1cbb0ce802f437ee259dcf763e2042800a7bc8 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 22 Aug 2022 21:36:33 -0400 Subject: [PATCH 045/289] Keep copyright up to date in documentation --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 32f24d4a..cc825c05 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,7 @@ import os import sys +import datetime sys.path.insert(0, os.path.abspath("..")) @@ -42,7 +43,8 @@ # General information about the project. project = "Adafruit MiniMQTT Library" -copyright = "2019 Brent Rubell" +current_year = str(datetime.datetime.now().year) +copyright = current_year + " Brent Rubell" author = "Brent Rubell" # The version info for the project you're documenting, acts as replacement for From c993271f7dd27956e9013f60bd8a0443b4561626 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 23 Aug 2022 17:26:22 -0400 Subject: [PATCH 046/289] Use year duration range for copyright attribution --- docs/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index cc825c05..84fade74 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,8 +43,14 @@ # General information about the project. project = "Adafruit MiniMQTT Library" +creation_year = "2019" current_year = str(datetime.datetime.now().year) -copyright = current_year + " Brent Rubell" +year_duration = ( + current_year + if current_year == creation_year + else creation_year + " - " + current_year +) +copyright = year_duration + " Brent Rubell" author = "Brent Rubell" # The version info for the project you're documenting, acts as replacement for From ff0958fed52432f989b016e38e293b1f2d300d05 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 30 Aug 2022 21:56:58 +0200 Subject: [PATCH 047/289] make connect retries configurable fixes #14 --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d62779cc..ccd713d5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -152,6 +152,7 @@ def __init__( ssl_context=None, use_binary_mode=False, socket_timeout=1, + connect_retries=5, ): self._socket_pool = socket_pool @@ -166,6 +167,7 @@ def __init__( ) self._socket_timeout = socket_timeout self._recv_timeout = recv_timeout + self._connect_retries = connect_retries self.keep_alive = keep_alive self._user_data = None @@ -267,7 +269,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): sock = None retry_count = 0 last_exception = None - while retry_count < 5 and sock is None: + while retry_count < self._connect_retries and sock is None: retry_count += 1 try: From a47b298f7184771ce314bb421341c1e8477e3281 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 30 Aug 2022 21:59:32 +0200 Subject: [PATCH 048/289] update doc --- adafruit_minimqtt/adafruit_minimqtt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ccd713d5..801605fc 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -134,6 +134,7 @@ class MQTT: :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. :param int socket_timeout: How often to check socket state for read/write/connect operations, in seconds. + :param int connect_retries: How many times to try to connect to broker before giving up. """ From 6ca84875fd6e539914eb099e4be8c221b53fa7c7 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Wed, 31 Aug 2022 00:14:41 -0400 Subject: [PATCH 049/289] Make MQTT__init__ arguments keyword-only --- adafruit_minimqtt/adafruit_minimqtt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 801605fc..01fcd3ac 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -141,6 +141,7 @@ class MQTT: # pylint: disable=too-many-arguments,too-many-instance-attributes, not-callable, invalid-name, no-member def __init__( self, + *, broker, port=None, username=None, From cf39fbcfe9eb909fbe5ee445b741b0a8c8118429 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 09:29:24 +0100 Subject: [PATCH 050/289] loop wait_for_msg until nothing returned --- adafruit_minimqtt/adafruit_minimqtt.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 801605fc..96831220 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -855,8 +855,17 @@ def loop(self, timeout=1): rcs = self.ping() return rcs self._sock.settimeout(timeout) - rc = self._wait_for_msg() - return [rc] if rc else None + + responses = [] + while True: + rc = self._wait_for_msg() + if rc == None: + break + else: + responses.append(rc) + + return responses if responses else None + def _wait_for_msg(self, timeout=0.1): """Reads and processes network events.""" From dde29b5460c34f997e1543290efbeb5817e8afe2 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 09:34:15 +0100 Subject: [PATCH 051/289] ignore EAGAIN error (occurs when timeout is 0) --- adafruit_minimqtt/adafruit_minimqtt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 96831220..d021e042 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -882,6 +882,9 @@ def _wait_for_msg(self, timeout=0.1): if error.errno == errno.ETIMEDOUT: # raised by a socket timeout if 0 bytes were present return None + if error.errno == errno.EAGAIN: + # there is no data available right now, try again later + return None raise MMQTTException from error # Block while we parse the rest of the response From feb8c2ee10e08314efb20ca3c5657ff4c6c0177f Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 10:22:24 +0100 Subject: [PATCH 052/289] added a timeout and syntax better matching ping() --- adafruit_minimqtt/adafruit_minimqtt.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d021e042..990ebdb4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -854,17 +854,24 @@ def loop(self, timeout=1): ) rcs = self.ping() return rcs - self._sock.settimeout(timeout) - responses = [] + stamp = time.monotonic() + self._sock.settimeout(timeout) + rcs = [] while True: rc = self._wait_for_msg() if rc == None: break + if time.monotonic() - stamp > self._recv_timeout: + if self.logger is not None: + self.logger.debug( + f"Loop timed out, message queue not empty after {self._recv_timeout}s" + ) + break else: - responses.append(rc) + rcs.append(rc) - return responses if responses else None + return rcs if rcs else None def _wait_for_msg(self, timeout=0.1): From e5a3f10078d3a794e43d86076516bcb3f5efe173 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 10:25:26 +0100 Subject: [PATCH 053/289] default timeout set to 0, for true non-blocking mode --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 990ebdb4..ce57552e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -834,7 +834,7 @@ def reconnect(self, resub_topics=True): feed = subscribed_topics.pop() self.subscribe(feed) - def loop(self, timeout=1): + def loop(self, timeout=0): """Non-blocking message loop. Use this method to check incoming subscription messages. Returns response codes of any messages received. From ca12a3cb25aa47d2680633e51394ee2d0dcd6535 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 11:07:26 +0100 Subject: [PATCH 054/289] trying to fix commit checks --- adafruit_minimqtt/adafruit_minimqtt.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ce57552e..f09021c7 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -857,10 +857,10 @@ def loop(self, timeout=0): stamp = time.monotonic() self._sock.settimeout(timeout) - rcs = [] + responses = [] while True: rc = self._wait_for_msg() - if rc == None: + if rc is None: break if time.monotonic() - stamp > self._recv_timeout: if self.logger is not None: @@ -868,11 +868,9 @@ def loop(self, timeout=0): f"Loop timed out, message queue not empty after {self._recv_timeout}s" ) break - else: - rcs.append(rc) - - return rcs if rcs else None + responses.append(rc) + return responses if responses else None def _wait_for_msg(self, timeout=0.1): """Reads and processes network events.""" From 430f9f64849a3e9dfd377ff060c40a7447123938 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 11:20:47 +0100 Subject: [PATCH 055/289] disable pylint too-many-return-statements --- adafruit_minimqtt/adafruit_minimqtt.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index f09021c7..351828fa 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -835,6 +835,7 @@ def reconnect(self, resub_topics=True): self.subscribe(feed) def loop(self, timeout=0): + # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to check incoming subscription messages. Returns response codes of any messages received. @@ -842,6 +843,7 @@ def loop(self, timeout=0): :param int timeout: Socket timeout, in seconds. """ + if self._timestamp == 0: self._timestamp = time.monotonic() current_time = time.monotonic() @@ -857,10 +859,11 @@ def loop(self, timeout=0): stamp = time.monotonic() self._sock.settimeout(timeout) - responses = [] + rcs = [] + while True: rc = self._wait_for_msg() - if rc is None: + if rc is None: break if time.monotonic() - stamp > self._recv_timeout: if self.logger is not None: @@ -868,11 +871,13 @@ def loop(self, timeout=0): f"Loop timed out, message queue not empty after {self._recv_timeout}s" ) break - responses.append(rc) + rcs.append(rc) - return responses if responses else None + return rcs if rcs else None def _wait_for_msg(self, timeout=0.1): + # pylint: disable = too-many-return-statements + """Reads and processes network events.""" # CPython socket module contains a timeout attribute if hasattr(self._socket_pool, "timeout"): From 9a316d3cfd58a7dc73880f2c65e99f37ab1a9fe0 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Mon, 5 Sep 2022 11:27:25 +0100 Subject: [PATCH 056/289] run black --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 351828fa..78b0a954 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -869,7 +869,7 @@ def loop(self, timeout=0): if self.logger is not None: self.logger.debug( f"Loop timed out, message queue not empty after {self._recv_timeout}s" - ) + ) break rcs.append(rc) From b97e2ebf03923763719232126c30481fbf7c203d Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Tue, 6 Sep 2022 14:09:01 +0100 Subject: [PATCH 057/289] was still getting the MemoryError, propagating timeout=0 --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 78b0a954..d950152a 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -862,7 +862,7 @@ def loop(self, timeout=0): rcs = [] while True: - rc = self._wait_for_msg() + rc = self._wait_for_msg(timeout) if rc is None: break if time.monotonic() - stamp > self._recv_timeout: From e9590268bfc11844b4f9ba683b133ab005cbedd8 Mon Sep 17 00:00:00 2001 From: Tim Newsome Date: Sat, 17 Sep 2022 20:09:03 -0700 Subject: [PATCH 058/289] _wait_for_msg(): accept EAGAIN in addition to ETIMEDOUT You get EAGAIN when passing a timeout value of 0 and there is no data in the buffer. --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 801605fc..2a5889fd 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -870,7 +870,7 @@ def _wait_for_msg(self, timeout=0.1): try: res = self._sock_exact_recv(1) except OSError as error: - if error.errno == errno.ETIMEDOUT: + if error.errno in (errno.ETIMEDOUT, errno.EAGAIN): # raised by a socket timeout if 0 bytes were present return None raise MMQTTException from error From 8a65c4e60d3a1a37a3e7214feed93da31a6ab4c3 Mon Sep 17 00:00:00 2001 From: Calum Cuthill Date: Tue, 4 Oct 2022 11:11:54 +0100 Subject: [PATCH 059/289] correcting duplicate EAGAIN handling --- adafruit_minimqtt/adafruit_minimqtt.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 279e6500..743fdbc4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -892,9 +892,6 @@ def _wait_for_msg(self, timeout=0.1): if error.errno in (errno.ETIMEDOUT, errno.EAGAIN): # raised by a socket timeout if 0 bytes were present return None - if error.errno == errno.EAGAIN: - # there is no data available right now, try again later - return None raise MMQTTException from error # Block while we parse the rest of the response From 972142096e20f64f0232311b098b1c10d4f4f581 Mon Sep 17 00:00:00 2001 From: BiffoBear Date: Sun, 16 Oct 2022 07:41:50 +0300 Subject: [PATCH 060/289] Refactored old is_connected to _connected to maintain error checking purpose. Created new is_connected that returns a bool. --- adafruit_minimqtt/adafruit_minimqtt.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 743fdbc4..f98d3a62 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -561,7 +561,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): def disconnect(self): """Disconnects the MiniMQTT client from the MQTT broker.""" - self.is_connected() + self._connected() if self.logger is not None: self.logger.debug("Sending DISCONNECT packet to broker") try: @@ -582,7 +582,7 @@ def ping(self): there is an active network connection. Returns response codes of any messages received while waiting for PINGRESP. """ - self.is_connected() + self._connected() if self.logger is not None: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) @@ -607,7 +607,7 @@ def publish(self, topic, msg, retain=False, qos=0): :param int qos: Quality of Service level for the message, defaults to zero. """ - self.is_connected() + self._connected() self._valid_topic(topic) if "+" in topic or "#" in topic: raise MMQTTException("Publish topic can not contain wildcards.") @@ -703,7 +703,7 @@ def subscribe(self, topic, qos=0): (send at least once), or ``2`` (send exactly once). """ - self.is_connected() + self._connected() topics = None if isinstance(topic, tuple): topic, qos = topic @@ -1046,7 +1046,7 @@ def _valid_qos(qos_level): else: raise MMQTTException("QoS must be an integer.") - def is_connected(self): + def _connected(self): """Returns MQTT client session status as True if connected, raises a `MMQTTException` if `False`. """ @@ -1054,6 +1054,15 @@ def is_connected(self): raise MMQTTException("MiniMQTT is not connected.") return self._is_connected + def is_connected(self): + """Returns MQTT client session status as True if connected, False + if not. + """ + try: + return self._connected() + except MMQTTException: + return False + # Logging def enable_logger(self, logger, log_level=20): """Enables library logging provided a logger object. From 8097b80d4a93f0380d1755fb932a2250d80a84c1 Mon Sep 17 00:00:00 2001 From: BiffoBear Date: Tue, 1 Nov 2022 18:11:46 +0700 Subject: [PATCH 061/289] Made requested changes. --- adafruit_minimqtt/adafruit_minimqtt.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index f98d3a62..7901205f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1050,18 +1050,14 @@ def _connected(self): """Returns MQTT client session status as True if connected, raises a `MMQTTException` if `False`. """ - if self._sock is None or self._is_connected is False: - raise MMQTTException("MiniMQTT is not connected.") - return self._is_connected + if not self.is_connected(): + raise MQTTException("MiniMQTT is not connected") def is_connected(self): """Returns MQTT client session status as True if connected, False if not. """ - try: - return self._connected() - except MMQTTException: - return False + return self._is_connected and self._sock is not None # Logging def enable_logger(self, logger, log_level=20): From 5c4d43b91668fcfa8cfcaa17d0494c581a9c2eb2 Mon Sep 17 00:00:00 2001 From: BiffoBear Date: Tue, 1 Nov 2022 18:29:13 +0700 Subject: [PATCH 062/289] Fixed a typo. --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7901205f..d654fd7a 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1051,7 +1051,7 @@ def _connected(self): a `MMQTTException` if `False`. """ if not self.is_connected(): - raise MQTTException("MiniMQTT is not connected") + raise MMQTTException("MiniMQTT is not connected") def is_connected(self): """Returns MQTT client session status as True if connected, False From f04e9a2da59de4f2c7b1672f1c1465045b1c6f81 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:02:50 -0400 Subject: [PATCH 063/289] Switching to composite actions --- .github/workflows/build.yml | 67 +---------------------- .github/workflows/release.yml | 88 ------------------------------ .github/workflows/release_gh.yml | 14 +++++ .github/workflows/release_pypi.yml | 14 +++++ 4 files changed, 30 insertions(+), 153 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/release_gh.yml create mode 100644 .github/workflows/release_pypi.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb2f60e3..041a337c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,68 +10,5 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - - name: Library version - run: git describe --dirty --always --tags - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - pip install --upgrade build twine - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; - done; - python -m build - twine check dist/* + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f3a0325b..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: Release Actions - -on: - release: - types: [published] - -jobs: - upload-release-assets: - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Upload Release Assets - # the 'official' actions version does not yet support dynamically - # supplying asset names to upload. @csexton's version chosen based on - # discussion in the issue below, as its the simplest to implement and - # allows for selecting files with a pattern. - # https://github.com/actions/upload-release-asset/issues/4 - #uses: actions/upload-release-asset@v1.0.1 - uses: csexton/release-asset-action@master - with: - pattern: "bundles/*" - github-token: ${{ secrets.GITHUB_TOKEN }} - - upload-pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Set up Python - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - python -m pip install --upgrade pip - pip install --upgrade build twine - - name: Build and publish - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - env: - TWINE_USERNAME: ${{ secrets.pypi_username }} - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: | - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; - done; - python -m build - twine upload dist/* diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml new file mode 100644 index 00000000..041a337c --- /dev/null +++ b/.github/workflows/release_gh.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml new file mode 100644 index 00000000..041a337c --- /dev/null +++ b/.github/workflows/release_pypi.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main From 521fc3a74b876fddb48d5c15aded8fa9a253be82 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:47:00 -0400 Subject: [PATCH 064/289] Updated pylint version to 2.13.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33436064..4c437101 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.11.1 + rev: v2.13.0 hooks: - id: pylint name: pylint (library code) From 49ae1eafbaa70dda9b14eed2489bb1a3c3a65c59 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 08:15:21 -0400 Subject: [PATCH 065/289] Update pylint to 2.15.5 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c437101..0e5fccc2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.13.0 + rev: v2.15.5 hooks: - id: pylint name: pylint (library code) From 33b2e1e7108f41dc089801654dc96973b78cd64c Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 09:12:46 -0400 Subject: [PATCH 066/289] Fix release CI files --- .github/workflows/release_gh.yml | 14 +++++++++----- .github/workflows/release_pypi.yml | 15 ++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index 041a337c..b8aa8d62 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -2,13 +2,17 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: GitHub Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run GitHub Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-gh@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index 041a337c..65775b7b 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -2,13 +2,18 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: PyPI Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run PyPI Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-pypi@main + with: + pypi-username: ${{ secrets.pypi_username }} + pypi-password: ${{ secrets.pypi_password }} From d68d43a23bccaf209396cd6a9dbdc5b4909ca81a Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 18:34:33 -0400 Subject: [PATCH 067/289] Update .pylintrc for v2.15.5 --- .pylintrc | 45 ++++----------------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/.pylintrc b/.pylintrc index f7729712..40208c39 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # # SPDX-License-Identifier: Unlicense @@ -26,7 +26,7 @@ jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint.extensions.no_self_use # Pickle collected data for later comparisons. persistent=yes @@ -54,8 +54,8 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding +# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call +disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -225,12 +225,6 @@ max-line-length=100 # Maximum number of lines in a module max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no @@ -257,38 +251,22 @@ min-similarity-lines=12 [BASIC] -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - # Regular expression matching correct class names # class-rgx=[A-Z_][a-zA-Z0-9]+$ class-rgx=[A-Z_][a-zA-Z0-9_]+$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ @@ -296,9 +274,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # ones are exempt. docstring-min-length=-1 -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ @@ -309,21 +284,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -339,9 +305,6 @@ no-docstring-rgx=^_ # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ From 3eb88f060048cac52f28bd6709bf188eb1122e80 Mon Sep 17 00:00:00 2001 From: Louis King Date: Wed, 16 Nov 2022 13:35:14 +0000 Subject: [PATCH 068/289] Issue #126 Added bytearray('\x00') to the list of expected "noop" returns from the socket. --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d654fd7a..6387678c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -896,7 +896,7 @@ def _wait_for_msg(self, timeout=0.1): # Block while we parse the rest of the response self._sock.settimeout(timeout) - if res in [None, b""]: + if res in [None, b"", bytearray(b'\x00')]: # If we get here, it means that there is nothing to be received return None if res[0] == MQTT_PINGRESP: From 52d6af61b59eb8446fd1de9d5b4f042428b1bde1 Mon Sep 17 00:00:00 2001 From: Louis King Date: Wed, 16 Nov 2022 13:52:09 +0000 Subject: [PATCH 069/289] Changed from bytearray syntax to b"\x00" Fixed linting errors using Black --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6387678c..a71615f5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -896,7 +896,7 @@ def _wait_for_msg(self, timeout=0.1): # Block while we parse the rest of the response self._sock.settimeout(timeout) - if res in [None, b"", bytearray(b'\x00')]: + if res in [None, b"", b"\x00"]: # If we get here, it means that there is nothing to be received return None if res[0] == MQTT_PINGRESP: From 9f4c8fd6ed7475810d34dce6a025f790d8ba6e55 Mon Sep 17 00:00:00 2001 From: Flavio Fernandes Date: Sun, 20 Nov 2022 18:35:23 -0500 Subject: [PATCH 070/289] MQTT connect with an empty username should have the proper header Connect needs to distinguish None from an empty string when populating and sending CONNECT to the MQTT broker. Fixes #129 Signed-off-by: Flavio Fernandes --- adafruit_minimqtt/adafruit_minimqtt.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a71615f5..9740c274 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -482,7 +482,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): # Set up variable header and remaining_length remaining_length = 12 + len(self.client_id.encode("utf-8")) - if self._username: + if self._username is not None: remaining_length += ( 2 + len(self._username.encode("utf-8")) @@ -532,9 +532,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): # [MQTT-3.1.3-11] self._send_str(self._lw_topic) self._send_str(self._lw_msg) - if self._username is None: - self._username = None - else: + if self._username is not None: self._send_str(self._username) self._send_str(self._password) if self.logger is not None: From 27696bb54bca8e110c07a3dfdf2e6f1316bdf9ce Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 1 Sep 2022 20:16:31 -0400 Subject: [PATCH 071/289] Add .venv to .gitignore Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 544ec4a6..db3d5387 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _build # Virtual environment-specific files .env +.venv # MacOS-specific files *.DS_Store From 6a6bfe47f0b10405de38e87941ff565befe234d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Sat, 3 Dec 2022 20:40:19 +0100 Subject: [PATCH 072/289] make _wait_for_msg() more readable --- adafruit_minimqtt/adafruit_minimqtt.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index b01a0780..445dbdfd 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -47,6 +47,7 @@ # MQTT Commands MQTT_PINGREQ = b"\xc0\0" MQTT_PINGRESP = const(0xD0) +MQTT_PUBLISH = const(0x30) MQTT_SUB = b"\x82" MQTT_UNSUB = b"\xA2" MQTT_DISCONNECT = b"\xe0\0" @@ -879,7 +880,9 @@ def loop(self, timeout=0): def _wait_for_msg(self, timeout=0.1): # pylint: disable = too-many-return-statements - """Reads and processes network events.""" + """Reads and processes network events. + Return the packet type or None if there is nothing to be received. + """ # CPython socket module contains a timeout attribute if hasattr(self._socket_pool, "timeout"): try: @@ -909,8 +912,11 @@ def _wait_for_msg(self, timeout=0.1): "Unexpected PINGRESP returned from broker: {}.".format(sz) ) return MQTT_PINGRESP - if res[0] & 0xF0 != 0x30: + + if res[0] & 0xF0 != MQTT_PUBLISH: return res[0] + + # Handle only the PUBLISH packet type from now on. sz = self._recv_len() # topic length MSB & LSB topic_len = self._sock_exact_recv(2) @@ -923,12 +929,13 @@ def _wait_for_msg(self, timeout=0.1): pid = self._sock_exact_recv(2) pid = pid[0] << 0x08 | pid[1] sz -= 0x02 + # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") if self.logger is not None: self.logger.debug( - "Receiving SUBSCRIBE \nTopic: %s\nMsg: %s\n", topic, raw_msg + "Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg ) self._handle_on_message(self, topic, msg) if res[0] & 0x06 == 0x02: @@ -937,6 +944,7 @@ def _wait_for_msg(self, timeout=0.1): self._sock.send(pkt) elif res[0] & 6 == 4: assert 0 + return res[0] def _recv_len(self): From 2d7491d5c9787ab304b77eb82d3bb6fa98bf0893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Sat, 3 Dec 2022 21:03:10 +0100 Subject: [PATCH 073/289] avoid the reserved bits --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 445dbdfd..eca7ff0c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -903,7 +903,7 @@ def _wait_for_msg(self, timeout=0.1): if res in [None, b"", b"\x00"]: # If we get here, it means that there is nothing to be received return None - if res[0] == MQTT_PINGRESP: + if res[0] & 0xF0 == MQTT_PINGRESP: if self.logger is not None: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] From 4e38f9ee96cfee6a997a8bcadf207f35fa370700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Sat, 3 Dec 2022 21:41:41 +0100 Subject: [PATCH 074/289] check topic length against remaining length --- adafruit_minimqtt/adafruit_minimqtt.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index eca7ff0c..c79ae15b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -921,6 +921,12 @@ def _wait_for_msg(self, timeout=0.1): # topic length MSB & LSB topic_len = self._sock_exact_recv(2) topic_len = (topic_len[0] << 8) | topic_len[1] + + if topic_len > sz - 2: + raise MMQTTException( + f"Topic length {topic_len} in PUBLISH packet exceeds remaining length {sz} - 2" + ) + topic = self._sock_exact_recv(topic_len) topic = str(topic, "utf-8") sz -= topic_len + 2 From 94589a86635d1406f23863a8f9d033abd2633192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Sun, 4 Dec 2022 12:05:34 +0100 Subject: [PATCH 075/289] remove duplicate initialization --- adafruit_minimqtt/adafruit_minimqtt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index c79ae15b..367181c1 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -211,7 +211,6 @@ def __init__( # LWT self._lw_topic = None self._lw_qos = 0 - self._lw_topic = None self._lw_msg = None self._lw_retain = False From 29e60e9a8e050803675a70cb14ba9fc52b845dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 17:10:23 +0100 Subject: [PATCH 076/289] use const for the packet type mask --- adafruit_minimqtt/adafruit_minimqtt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 367181c1..b2e6aea1 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -52,6 +52,8 @@ MQTT_UNSUB = b"\xA2" MQTT_DISCONNECT = b"\xe0\0" +MQTT_PKT_TYPE_MASK = const(0xF0) + # Variable CONNECT header [MQTT 3.1.2] MQTT_HDR_CONNECT = bytearray(b"\x04MQTT\x04\x02\0\0") @@ -902,7 +904,7 @@ def _wait_for_msg(self, timeout=0.1): if res in [None, b"", b"\x00"]: # If we get here, it means that there is nothing to be received return None - if res[0] & 0xF0 == MQTT_PINGRESP: + if res[0] & MQTT_PKT_TYPE_MASK == MQTT_PINGRESP: if self.logger is not None: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] @@ -912,7 +914,7 @@ def _wait_for_msg(self, timeout=0.1): ) return MQTT_PINGRESP - if res[0] & 0xF0 != MQTT_PUBLISH: + if res[0] & MQTT_PKT_TYPE_MASK != MQTT_PUBLISH: return res[0] # Handle only the PUBLISH packet type from now on. From b76d6bbfbd6a78d48b661f9e8892e8756260524a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 17:11:14 +0100 Subject: [PATCH 077/289] use MQTT_PUBLISH instead of literal --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index b2e6aea1..99b0c7e0 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -632,7 +632,7 @@ def publish(self, topic, msg, retain=False, qos=0): ), "Quality of Service Level 2 is unsupported by this library." # fixed header. [3.3.1.2], [3.3.1.3] - pub_hdr_fixed = bytearray([0x30 | retain | qos << 1]) + pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) # variable header = 2-byte Topic length (big endian) pub_hdr_var = bytearray(struct.pack(">H", len(topic.encode("utf-8")))) From d60ab23c375f86a74c7ddaa55018f20c9d5c1563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 18:06:36 +0100 Subject: [PATCH 078/289] remove duplicate _sock_exact_recv() definition --- adafruit_minimqtt/adafruit_minimqtt.py | 29 -------------------------- 1 file changed, 29 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 00bace2b..1e341af7 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -313,35 +313,6 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, traceback): self.deinit() - def _sock_exact_recv(self, bufsize): - """Reads _exact_ number of bytes from the connected socket. Will only return - string with the exact number of bytes requested. - - The semantics of native socket receive is that it returns no more than the - specified number of bytes (i.e. max size). However, it makes no guarantees in - terms of the minimum size of the buffer, which could be 1 byte. This is a - wrapper for socket recv() to ensure that no less than the expected number of - bytes is returned or trigger a timeout exception. - - :param int bufsize: number of bytes to receive - """ - stamp = time.monotonic() - rc = self._sock.recv(bufsize) - to_read = bufsize - len(rc) - assert to_read >= 0 - read_timeout = self.keep_alive - while to_read > 0: - recv = self._sock.recv(to_read) - to_read -= len(recv) - rc += recv - if time.monotonic() - stamp > read_timeout: - raise MMQTTException( - "Unable to receive {} bytes within {} seconds.".format( - to_read, read_timeout - ) - ) - return rc - def deinit(self): """De-initializes the MQTT client and disconnects from the mqtt broker.""" self.disconnect() From c334c8157dddc06f67b85b9c299b49d0b29f3031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 19:43:38 +0100 Subject: [PATCH 079/289] finish partial reads in CPython fixes #131 --- adafruit_minimqtt/adafruit_minimqtt.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1e341af7..d0862515 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -956,15 +956,29 @@ def _sock_exact_recv(self, bufsize): bytes is returned or trigger a timeout exception. :param int bufsize: number of bytes to receive - + :return: byte array """ + stamp = time.monotonic() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) - self._sock.recv_into(rc, bufsize) - else: # ESP32SPI Impl. - stamp = time.monotonic() + mv = memoryview(rc) + recv = self._sock.recv_into(rc, bufsize) + to_read = bufsize - recv + assert to_read >= 0 read_timeout = self.keep_alive + mv = mv[recv:] + while to_read > 0: + recv = self._sock.recv_into(mv, to_read) + to_read -= recv + mv = mv[recv:] + if time.monotonic() - stamp > read_timeout: + raise MMQTTException( + "Unable to receive {} bytes within {} seconds.".format( + to_read, read_timeout + ) + ) + else: # ESP32SPI Impl. # This will timeout with socket timeout (not keepalive timeout) rc = self._sock.recv(bufsize) if not rc: From f0fd46514b5104cd67baaf78c2b96d63d9a921b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Tue, 6 Dec 2022 09:55:42 +0100 Subject: [PATCH 080/289] rename variable for better readability --- adafruit_minimqtt/adafruit_minimqtt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d0862515..0304cae5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -963,15 +963,15 @@ def _sock_exact_recv(self, bufsize): # CPython/Socketpool Impl. rc = bytearray(bufsize) mv = memoryview(rc) - recv = self._sock.recv_into(rc, bufsize) - to_read = bufsize - recv + recv_len = self._sock.recv_into(rc, bufsize) + to_read = bufsize - recv_len assert to_read >= 0 read_timeout = self.keep_alive - mv = mv[recv:] + mv = mv[recv_len:] while to_read > 0: - recv = self._sock.recv_into(mv, to_read) - to_read -= recv - mv = mv[recv:] + recv_len = self._sock.recv_into(mv, to_read) + to_read -= recv_len + mv = mv[recv_len:] if time.monotonic() - stamp > read_timeout: raise MMQTTException( "Unable to receive {} bytes within {} seconds.".format( From c0a902a26e3e35888a66244394e0863a4088f072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Wed, 7 Dec 2022 11:19:32 +0100 Subject: [PATCH 081/289] use f-strings for the exceptions pylint will like the code more --- adafruit_minimqtt/adafruit_minimqtt.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 0304cae5..5b1cc08e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -974,9 +974,7 @@ def _sock_exact_recv(self, bufsize): mv = mv[recv_len:] if time.monotonic() - stamp > read_timeout: raise MMQTTException( - "Unable to receive {} bytes within {} seconds.".format( - to_read, read_timeout - ) + f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) else: # ESP32SPI Impl. # This will timeout with socket timeout (not keepalive timeout) @@ -997,9 +995,7 @@ def _sock_exact_recv(self, bufsize): rc += recv if time.monotonic() - stamp > read_timeout: raise MMQTTException( - "Unable to receive {} bytes within {} seconds.".format( - to_read, read_timeout - ) + f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) return rc From ebd5796f71e2ccfeacdd08ac19d1c8305346b73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Fri, 23 Dec 2022 10:35:08 +0100 Subject: [PATCH 082/289] allow to set user data in the init method fixes #135 --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 00bace2b..a74a0681 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -138,6 +138,7 @@ class MQTT: :param int socket_timeout: How often to check socket state for read/write/connect operations, in seconds. :param int connect_retries: How many times to try to connect to broker before giving up. + :param class user_data: arbitrary data to pass as a second argument to the callbacks. """ @@ -158,6 +159,7 @@ def __init__( use_binary_mode=False, socket_timeout=1, connect_retries=5, + user_data=None, ): self._socket_pool = socket_pool @@ -175,7 +177,7 @@ def __init__( self._connect_retries = connect_retries self.keep_alive = keep_alive - self._user_data = None + self._user_data = user_data self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 From efe37e09d5531b140ff7b98aa2f698552e9ecc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 26 Dec 2022 17:16:24 +0100 Subject: [PATCH 083/289] add type hints --- adafruit_minimqtt/matcher.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 5d641ccb..10d784c7 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -11,6 +11,13 @@ * Author(s): Yoch (https://github.com/yoch) """ +try: + from typing import Dict, Any +except ImportError: + pass + +from collections.abc import Callable, Iterator + class MQTTMatcher: """Intended to manage topic filters including wildcards. @@ -27,14 +34,14 @@ class Node: __slots__ = "children", "content" - def __init__(self): - self.children = {} + def __init__(self) -> None: + self.children: Dict[str, MQTTMatcher.Node] = {} self.content = None - def __init__(self): + def __init__(self) -> None: self._root = self.Node() - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Callable[..., Any]) -> None: """Add a topic filter :key to the prefix tree and associate it to :value""" node = self._root @@ -42,7 +49,7 @@ def __setitem__(self, key, value): node = node.children.setdefault(sym, self.Node()) node.content = value - def __getitem__(self, key): + def __getitem__(self, key: str) -> Callable[..., Any]: """Retrieve the value associated with some topic filter :key""" try: node = self._root @@ -54,7 +61,7 @@ def __getitem__(self, key): except KeyError: raise KeyError(key) from None - def __delitem__(self, key): + def __delitem__(self, key: str) -> None: """Delete the value associated with some topic filter :key""" lst = [] try: @@ -71,13 +78,13 @@ def __delitem__(self, key): break del parent.children[k] - def iter_match(self, topic): + def iter_match(self, topic: str) -> Iterator[Callable[..., Any]]: """Return an iterator on all values associated with filters that match the :topic""" lst = topic.split("/") normal = not topic.startswith("$") - def rec(node, i=0): + def rec(node: MQTTMatcher.Node, i: int = 0) -> Iterator[Callable[..., Any]]: if i == len(lst): if node.content is not None: yield node.content From 4f76bb1593dcedc10d84b02fdc7b21bb02be84c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 26 Dec 2022 17:17:14 +0100 Subject: [PATCH 084/289] apply isort --- adafruit_minimqtt/matcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 10d784c7..4a546c65 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -12,7 +12,7 @@ """ try: - from typing import Dict, Any + from typing import Any, Dict except ImportError: pass From 20965e3f90ca03e80b74f9e02cc84055b9358a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Fri, 6 Jan 2023 20:52:34 +0100 Subject: [PATCH 085/289] use Callable, Iterator from typing --- adafruit_minimqtt/matcher.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 4a546c65..83025918 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -12,12 +12,10 @@ """ try: - from typing import Any, Dict + from typing import Any, Dict, Callable, Iterator except ImportError: pass -from collections.abc import Callable, Iterator - class MQTTMatcher: """Intended to manage topic filters including wildcards. From 1ea551c2c06609003c78c982fe011e885e9ef104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Fri, 6 Jan 2023 20:53:20 +0100 Subject: [PATCH 086/289] apply isort --- adafruit_minimqtt/matcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 83025918..cf693044 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -12,7 +12,7 @@ """ try: - from typing import Any, Dict, Callable, Iterator + from typing import Any, Callable, Dict, Iterator except ImportError: pass From 4ae6820093cadae4da0bbb96658a296155ca3ebe Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:07:46 +0100 Subject: [PATCH 087/289] do not hard-code TLS port value --- adafruit_minimqtt/adafruit_minimqtt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 00bace2b..1f3f832f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -124,7 +124,8 @@ class MQTT: """MQTT Client for CircuitPython. :param str broker: MQTT Broker URL or IP Address. - :param int port: Optional port definition, defaults to 8883. + :param int port: Optional port definition, defaults to MQTT_TLS_PORT if is_ssl is set, + MQTT_TCP_PORT otherwise. :param str username: Username for broker authentication. :param str password: Password for broker authentication. :param network_manager: NetworkManager object, such as WiFiManager from ESPSPI_WiFiManager. @@ -252,7 +253,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): if not isinstance(port, int): raise RuntimeError("Port must be an integer") - if port == 8883 and not self._ssl_context: + if port == MQTT_TLS_PORT and not self._ssl_context: raise RuntimeError( "ssl_context must be set before using adafruit_mqtt for secure MQTT." ) @@ -282,7 +283,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): continue connect_host = addr_info[-1][0] - if port == 8883: + if port == MQTT_TLS_PORT: sock = self._ssl_context.wrap_socket(sock, server_hostname=host) connect_host = host sock.settimeout(timeout) From 79e58f64fae4e9824463bc9bdc73beab56229a73 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:10:44 +0100 Subject: [PATCH 088/289] improve wording --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1f3f832f..c53ad9bd 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -124,7 +124,7 @@ class MQTT: """MQTT Client for CircuitPython. :param str broker: MQTT Broker URL or IP Address. - :param int port: Optional port definition, defaults to MQTT_TLS_PORT if is_ssl is set, + :param int port: Optional port definition, defaults to MQTT_TLS_PORT if is_ssl is True, MQTT_TCP_PORT otherwise. :param str username: Username for broker authentication. :param str password: Password for broker authentication. From ae9f5caab70084fe34271bdd66096f06fbae9f85 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:41:49 +0100 Subject: [PATCH 089/289] use correct terminology for enable_logger() also improve the documentation --- adafruit_minimqtt/adafruit_minimqtt.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 00bace2b..36a5f10d 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1074,14 +1074,15 @@ def is_connected(self): return self._is_connected and self._sock is not None # Logging - def enable_logger(self, logger, log_level=20): - """Enables library logging provided a logger object. + def enable_logger(self, log_pkg, log_level=20): + """Enables library logging by getting logger named "log" from the specified logging package + and setting its log level. - :param logger: A python logger pacakge. + :param log_pkg: A Python logging package. :param log_level: Numeric value of a logging level, defaults to INFO. """ - self.logger = logger.getLogger("log") + self.logger = log_pkg.getLogger("log") self.logger.setLevel(log_level) def disable_logger(self): From 5b440f5c422099c7a86dcd36d9f76318263f26ce Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:48:09 +0100 Subject: [PATCH 090/289] return the logger --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 36a5f10d..83b31cc3 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1080,11 +1080,13 @@ def enable_logger(self, log_pkg, log_level=20): :param log_pkg: A Python logging package. :param log_level: Numeric value of a logging level, defaults to INFO. - + :return logger object """ self.logger = log_pkg.getLogger("log") self.logger.setLevel(log_level) + return self.logger + def disable_logger(self): """Disables logging.""" if not self.logger: From a5284239401326785cfd24bcb31ce99140985ff2 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:52:14 +0100 Subject: [PATCH 091/289] allow to specify logger name --- adafruit_minimqtt/adafruit_minimqtt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 83b31cc3..4314aa42 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1074,15 +1074,16 @@ def is_connected(self): return self._is_connected and self._sock is not None # Logging - def enable_logger(self, log_pkg, log_level=20): - """Enables library logging by getting logger named "log" from the specified logging package + def enable_logger(self, log_pkg, log_level=20, logger_name="log"): + """Enables library logging by getting logger from the specified logging package and setting its log level. :param log_pkg: A Python logging package. :param log_level: Numeric value of a logging level, defaults to INFO. + :param logger_name: name of the logger, defaults to "log". :return logger object """ - self.logger = log_pkg.getLogger("log") + self.logger = log_pkg.getLogger(logger_name) self.logger.setLevel(log_level) return self.logger From b24bb81dec512405bd98969e8ae1d1416f3d03e1 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:56:18 +0100 Subject: [PATCH 092/289] field list should end with a blank line --- adafruit_minimqtt/adafruit_minimqtt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 4314aa42..f71b23ef 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1082,6 +1082,7 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): :param log_level: Numeric value of a logging level, defaults to INFO. :param logger_name: name of the logger, defaults to "log". :return logger object + """ self.logger = log_pkg.getLogger(logger_name) self.logger.setLevel(log_level) From 9d66d9f553b8814be9149dc13e19075c4f5c2c7e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 8 Jan 2023 22:59:26 +0100 Subject: [PATCH 093/289] another blank line --- adafruit_minimqtt/adafruit_minimqtt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index f71b23ef..6132b54f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1081,6 +1081,7 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): :param log_pkg: A Python logging package. :param log_level: Numeric value of a logging level, defaults to INFO. :param logger_name: name of the logger, defaults to "log". + :return logger object """ From 8f517100fc35c59c5b488e9d82ab247ef4db9d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Thu, 12 Jan 2023 17:50:12 +0100 Subject: [PATCH 094/289] use MMQTTException instead of assert for the negative read length --- adafruit_minimqtt/adafruit_minimqtt.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5b1cc08e..10effeaf 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -965,7 +965,11 @@ def _sock_exact_recv(self, bufsize): mv = memoryview(rc) recv_len = self._sock.recv_into(rc, bufsize) to_read = bufsize - recv_len - assert to_read >= 0 + if to_read < 0: + raise MMQTTException( + f"negative number of bytes to read: " + f"{to_read} = {bufsize} - {recv_len}" + ) read_timeout = self.keep_alive mv = mv[recv_len:] while to_read > 0: From be9c198ddf76f72b746bf2d958102d2c8a7538a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Thu, 12 Jan 2023 17:56:43 +0100 Subject: [PATCH 095/289] try different format --- adafruit_minimqtt/adafruit_minimqtt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 10effeaf..4fa0b90a 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -966,10 +966,7 @@ def _sock_exact_recv(self, bufsize): recv_len = self._sock.recv_into(rc, bufsize) to_read = bufsize - recv_len if to_read < 0: - raise MMQTTException( - f"negative number of bytes to read: " - f"{to_read} = {bufsize} - {recv_len}" - ) + raise MMQTTException(f"negative number of bytes to read: {to_read}") read_timeout = self.keep_alive mv = mv[recv_len:] while to_read > 0: From 716aea131642360133abb051cde1d32e33085ba1 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 12 Jan 2023 21:27:22 +0100 Subject: [PATCH 096/289] remove unused doc reference to network manager --- adafruit_minimqtt/adafruit_minimqtt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a1125b6b..065d845b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -128,7 +128,6 @@ class MQTT: MQTT_TCP_PORT otherwise. :param str username: Username for broker authentication. :param str password: Password for broker authentication. - :param network_manager: NetworkManager object, such as WiFiManager from ESPSPI_WiFiManager. :param str client_id: Optional client identifier, defaults to a unique, generated string. :param bool is_ssl: Sets a secure or insecure connection with the broker. :param int keep_alive: KeepAlive interval between the broker and the MiniMQTT client. From 64ddb5c888fd39e0a3b901a9d2cc73dc6390a207 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 12 Jan 2023 21:33:23 +0100 Subject: [PATCH 097/289] use f-strings where possible --- adafruit_minimqtt/adafruit_minimqtt.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a1125b6b..341362d1 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -206,8 +206,8 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - self.client_id = "cpy{0}{1}".format( - randint(0, int(time.monotonic() * 100) % 1000), randint(0, 99) + self.client_id = ( + f"cpy{randint(0, int(time.monotonic() * 100) % 1000)}{randint(0, 99)}" ) # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: @@ -261,13 +261,9 @@ def _get_connect_socket(self, host, port, *, timeout=1): ) if self.logger is not None and port == MQTT_TLS_PORT: - self.logger.info( - "Establishing a SECURE SSL connection to {0}:{1}".format(host, port) - ) + self.logger.info(f"Establishing a SECURE SSL connection to {host}:{port}") elif self.logger is not None: - self.logger.info( - "Establishing an INSECURE connection to {0}:{1}".format(host, port) - ) + self.logger.info(f"Establishing an INSECURE connection to {host}:{port}") addr_info = self._socket_pool.getaddrinfo( host, port, 0, self._socket_pool.SOCK_STREAM @@ -543,7 +539,7 @@ def disconnect(self): self._sock.send(MQTT_DISCONNECT) except RuntimeError as e: if self.logger is not None: - self.logger.warning("Unable to send DISCONNECT packet: {}".format(e)) + self.logger.warning(f"Unable to send DISCONNECT packet: {e}") if self.logger is not None: self.logger.debug("Closing socket") self._sock.close() @@ -598,7 +594,7 @@ def publish(self, topic, msg, retain=False, qos=0): else: raise MMQTTException("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: - raise MMQTTException("Message size larger than %d bytes." % MQTT_MSG_MAX_SZ) + raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") assert ( 0 <= qos <= 1 ), "Quality of Service Level 2 is unsupported by this library." @@ -881,9 +877,7 @@ def _wait_for_msg(self, timeout=0.1): self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: - raise MMQTTException( - "Unexpected PINGRESP returned from broker: {}.".format(sz) - ) + raise MMQTTException(f"Unexpected PINGRESP returned from broker: {sz}.") return MQTT_PINGRESP if res[0] & MQTT_PKT_TYPE_MASK != MQTT_PUBLISH: From 414debb11879edbc77ecc7023a46ed2d8aa96288 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 16 Jan 2023 18:28:59 +0100 Subject: [PATCH 098/289] remove Callable in type hints --- adafruit_minimqtt/matcher.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index cf693044..141a4f05 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -12,7 +12,7 @@ """ try: - from typing import Any, Callable, Dict, Iterator + from typing import Dict except ImportError: pass @@ -39,7 +39,7 @@ def __init__(self) -> None: def __init__(self) -> None: self._root = self.Node() - def __setitem__(self, key: str, value: Callable[..., Any]) -> None: + def __setitem__(self, key: str, value) -> None: """Add a topic filter :key to the prefix tree and associate it to :value""" node = self._root @@ -47,7 +47,7 @@ def __setitem__(self, key: str, value: Callable[..., Any]) -> None: node = node.children.setdefault(sym, self.Node()) node.content = value - def __getitem__(self, key: str) -> Callable[..., Any]: + def __getitem__(self, key: str): """Retrieve the value associated with some topic filter :key""" try: node = self._root @@ -76,13 +76,13 @@ def __delitem__(self, key: str) -> None: break del parent.children[k] - def iter_match(self, topic: str) -> Iterator[Callable[..., Any]]: + def iter_match(self, topic: str): """Return an iterator on all values associated with filters that match the :topic""" lst = topic.split("/") normal = not topic.startswith("$") - def rec(node: MQTTMatcher.Node, i: int = 0) -> Iterator[Callable[..., Any]]: + def rec(node: MQTTMatcher.Node, i: int = 0): if i == len(lst): if node.content is not None: yield node.content From 52c52c3dd034da92b6b409cbcb82b1841e9cdf0e Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 19 Jan 2023 23:39:55 -0500 Subject: [PATCH 099/289] Add upload url to release action Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .github/workflows/release_gh.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index b8aa8d62..9acec601 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -16,3 +16,4 @@ jobs: uses: adafruit/workflows-circuitpython-libs/release-gh@main with: github-token: ${{ secrets.GITHUB_TOKEN }} + upload-url: ${{ github.event.release.upload_url }} From 7cab7d87b7821094ed15fd91c6d8058ff0741263 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 14 Jan 2023 22:19:38 +0100 Subject: [PATCH 100/289] exponential backoff for (re)connect fixes #10 --- adafruit_minimqtt/adafruit_minimqtt.py | 197 ++++++++++++++++++++----- 1 file changed, 160 insertions(+), 37 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 9af31d9b..0635e1d8 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -77,6 +77,10 @@ class MMQTTException(Exception): # pass +class TemporaryError(Exception): + """Temporary error class used for handling reconnects.""" + + # Legacy ESP32SPI Socket API def set_socket(sock, iface=None): """Legacy API for setting the socket and network interface. @@ -137,12 +141,13 @@ class MQTT: :param bool use_binary_mode: Messages are passed as bytearray instead of string to callbacks. :param int socket_timeout: How often to check socket state for read/write/connect operations, in seconds. - :param int connect_retries: How many times to try to connect to broker before giving up. + :param int connect_retries: How many times to try to connect to the broker before giving up + on connect or reconnect. Exponential backoff will be used for the retries. :param class user_data: arbitrary data to pass as a second argument to the callbacks. """ - # pylint: disable=too-many-arguments,too-many-instance-attributes, not-callable, invalid-name, no-member + # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements, not-callable, invalid-name, no-member def __init__( self, *, @@ -174,7 +179,6 @@ def __init__( ) self._socket_timeout = socket_timeout self._recv_timeout = recv_timeout - self._connect_retries = connect_retries self.keep_alive = keep_alive self._user_data = user_data @@ -184,6 +188,13 @@ def __init__( self._timestamp = 0 self.logger = None + self._reconnect_attempt = 0 + self._reconnect_timeout = float(0) + self._reconnect_maximum_backoff = 32 + if connect_retries <= 0: + raise MMQTTException("connect_retries must be positive") + self._reconnect_attempts_max = connect_retries + self.broker = broker self._username = username self._password = password @@ -268,39 +279,37 @@ def _get_connect_socket(self, host, port, *, timeout=1): host, port, 0, self._socket_pool.SOCK_STREAM )[0] - sock = None - retry_count = 0 - last_exception = None - while retry_count < self._connect_retries and sock is None: - retry_count += 1 + try: + sock = self._socket_pool.socket(addr_info[0], addr_info[1]) + except OSError as exc: + # Do not consider this for back-off. + if self.logger is not None: + self.logger.warning( + f"Failed to create socket for host {addr_info[0]} and port {addr_info[1]}" + ) + raise TemporaryError from exc - try: - sock = self._socket_pool.socket(addr_info[0], addr_info[1]) - except OSError: - continue + connect_host = addr_info[-1][0] + if port == MQTT_TLS_PORT: + sock = self._ssl_context.wrap_socket(sock, server_hostname=host) + connect_host = host + sock.settimeout(timeout) - connect_host = addr_info[-1][0] - if port == MQTT_TLS_PORT: - sock = self._ssl_context.wrap_socket(sock, server_hostname=host) - connect_host = host - sock.settimeout(timeout) + last_exception = None + try: + sock.connect((connect_host, port)) + except MemoryError as exc: + sock.close() + if self.logger is not None: + self.logger.warning(f"Failed to allocate memory for connect: {exc}") + # Do not consider this for back-off. + raise TemporaryError from exc + except OSError as exc: + sock.close() + last_exception = exc - try: - sock.connect((connect_host, port)) - except MemoryError as exc: - sock.close() - sock = None - last_exception = exc - except OSError as exc: - sock.close() - sock = None - last_exception = exc - - if sock is None: - if last_exception: - raise RuntimeError("Repeated socket failures") from last_exception - - raise RuntimeError("Repeated socket failures") + if last_exception: + raise last_exception self._backwards_compatible_sock = not hasattr(sock, "recv_into") return sock @@ -418,8 +427,66 @@ def username_pw_set(self, username, password=None): if password is not None: self._password = password - # pylint: disable=too-many-branches, too-many-statements, too-many-locals def connect(self, clean_session=True, host=None, port=None, keep_alive=None): + """Initiates connection with the MQTT Broker. Will perform exponential back-off + on connect failures. + + :param bool clean_session: Establishes a persistent session. + :param str host: Hostname or IP address of the remote broker. + :param int port: Network port of the remote broker. + :param int keep_alive: Maximum period allowed for communication + within single connection attempt, in seconds. + + """ + + last_exception = None + backoff = False + for i in range(0, self._reconnect_attempts_max): + if i > 0: + if backoff: + self._recompute_reconnect_backoff() + else: + self._reset_reconnect_backoff() + if self.logger is not None: + self.logger.debug( + f"Attempting to connect to MQTT broker (attempt #{self._reconnect_attempt})" + ) + + try: + ret = self._connect( + clean_session=clean_session, + host=host, + port=port, + keep_alive=keep_alive, + ) + self._reset_reconnect_backoff() + return ret + except TemporaryError as e: + if self.logger is not None: + self.logger.warning(f"temporary error when connecting: {e}") + backoff = False + except OSError as e: + last_exception = e + if self.logger is not None: + self.logger.info(f"failed to connect: {e}") + backoff = True + except MMQTTException as e: + last_exception = e + if self.logger is not None: + self.logger.info(f"MMQT error: {e}") + backoff = True + + if self._reconnect_attempts_max > 1: + exc_msg = "Repeated connect failures" + else: + exc_msg = "Connect failure" + if last_exception: + raise MMQTTException(exc_msg) from last_exception + + raise MMQTTException(exc_msg) + + # pylint: disable=too-many-branches, too-many-statements, too-many-locals + def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): """Initiates connection with the MQTT Broker. :param bool clean_session: Establishes a persistent session. @@ -438,6 +505,12 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): if self.logger is not None: self.logger.debug("Attempting to establish MQTT connection...") + if self._reconnect_attempt > 0: + self.logger.debug( + f"Sleeping for {self._reconnect_timeout:.3} seconds due to connect back-off" + ) + time.sleep(self._reconnect_timeout) + # Get a new socket self._sock = self._get_connect_socket( self.broker, self.port, timeout=self._socket_timeout @@ -492,7 +565,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): fixed_header.append(0x00) if self.logger is not None: - self.logger.debug("Sending CONNECT to broker...") + self.logger.debug("Sending CONNECT packet to broker...") self.logger.debug( "Fixed Header: %s\nVariable Header: %s", fixed_header, var_header ) @@ -521,6 +594,7 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): result = rc[0] & 1 if self.on_connect is not None: self.on_connect(self, self._user_data, result, rc[2]) + return result if op is None: @@ -782,15 +856,62 @@ def unsubscribe(self, topic): f"No data received from broker for {self._recv_timeout} seconds." ) + def _recompute_reconnect_backoff(self): + """ + Recompute the reconnection timeout. The self._reconnect_timeout will be used + in self._connect() to perform the actual sleep. + + """ + self._reconnect_attempt = self._reconnect_attempt + 1 + self._reconnect_timeout = 2**self._reconnect_attempt + if self.logger is not None: + # pylint: disable=consider-using-f-string + self.logger.debug( + "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) + ) + + if self._reconnect_timeout > self._reconnect_maximum_backoff: + if self.logger is not None: + self.logger.debug( + f"Truncating reconnect timeout to {self._reconnect_maximum_backoff} seconds" + ) + self._reconnect_timeout = float(self._reconnect_maximum_backoff) + + # Add a sub-second jitter. + # Even truncated timeout should have jitter added to it. This is why it is added here. + jitter = randint(0, 1000) / 1000 + if self.logger is not None: + # pylint: disable=consider-using-f-string + self.logger.debug( + "adding jitter {:.2f} to {:.2f} seconds".format( + jitter, self._reconnect_timeout + ) + ) + self._reconnect_timeout += jitter + + def _reset_reconnect_backoff(self): + """ + Reset reconnect back-off to the initial state. + + """ + if self.logger is not None: + self.logger.debug("Resetting reconnect backoff") + self._reconnect_attempt = 0 + self._reconnect_timeout = float(0) + def reconnect(self, resub_topics=True): """Attempts to reconnect to the MQTT broker. + Return the value from connect() if successful. Will disconnect first if already connected. + Will perform exponential back-off on connect failures. - :param bool resub_topics: Resubscribe to previously subscribed topics. + :param bool resub_topics: Whether to resubscribe to previously subscribed topics. """ + if self.logger is not None: self.logger.debug("Attempting to reconnect with MQTT broker") - self.connect() + + ret = self.connect() if self.logger is not None: self.logger.debug("Reconnected with broker") if resub_topics: @@ -804,6 +925,8 @@ def reconnect(self, resub_topics=True): feed = subscribed_topics.pop() self.subscribe(feed) + return ret + def loop(self, timeout=0): # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to From dbb9aeb81d0fb62505cd70a8a67c59f3939c7f6a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 14 Feb 2023 21:00:41 +0100 Subject: [PATCH 101/289] add missing logger check --- adafruit_minimqtt/adafruit_minimqtt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 0635e1d8..57de22da 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -506,9 +506,10 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): self.logger.debug("Attempting to establish MQTT connection...") if self._reconnect_attempt > 0: - self.logger.debug( - f"Sleeping for {self._reconnect_timeout:.3} seconds due to connect back-off" - ) + if self.logger is not None: + self.logger.debug( + f"Sleeping for {self._reconnect_timeout:.3} seconds due to connect back-off" + ) time.sleep(self._reconnect_timeout) # Get a new socket From 9aff9f47af6cfea7944e6f55b414247c26b04f0e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 14 Feb 2023 22:10:30 +0100 Subject: [PATCH 102/289] add basic back-off test --- .gitignore | 3 +++ tests/backoff_test.py | 59 +++++++++++++++++++++++++++++++++++++++++++ tox.ini | 11 ++++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/backoff_test.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index db3d5387..342b4999 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ _build .idea .vscode *~ + +# tox local cache +.tox diff --git a/tests/backoff_test.py b/tests/backoff_test.py new file mode 100644 index 00000000..ed66dd5f --- /dev/null +++ b/tests/backoff_test.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""exponential back-off tests""" + +import socket +import ssl +import time +from unittest import TestCase, main +from unittest.mock import call, patch + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +class ExpBackOff(TestCase): + """basic exponential back-off test""" + + connect_times = [] + + # pylint: disable=unused-argument + def fake_connect(self, arg): + """connect() replacement that records the call times and always raises OSError""" + self.connect_times.append(time.monotonic()) + raise OSError("this connect failed") + + def test_failing_connect(self) -> None: + """test that exponential back-off is used when connect() always raises OSError""" + # use RFC 1918 address to avoid dealing with IPv6 in the call list below + host = "172.40.0.3" + port = 1883 + + with patch.object(socket.socket, "connect") as mock_method: + mock_method.side_effect = self.fake_connect + + connect_retries = 3 + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + connect_retries=connect_retries, + ) + print("connecting") + with self.assertRaises(MQTT.MMQTTException) as context: + mqtt_client.connect() + self.assertTrue("Repeated connect failures" in str(context.exception)) + + mock_method.assert_called() + calls = [call((host, port)) for _ in range(0, connect_retries)] + mock_method.assert_has_calls(calls) + + print(f"connect() call times: {self.connect_times}") + for i in range(1, connect_retries): + assert self.connect_times[i] >= 2**i + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..04f4809a --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: MIT + +[tox] +envlist = py39 + +[testenv] +changedir = {toxinidir}/tests +deps = pytest==6.2.5 +commands = pytest From 7468a0941ff207fbec25c7fa655fdefd57171317 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 5 Feb 2023 12:41:09 +0100 Subject: [PATCH 103/289] use NullHandler by default fixes #146 --- adafruit_minimqtt/adafruit_minimqtt.py | 120 +++++++++++-------------- requirements.txt | 1 + 2 files changed, 53 insertions(+), 68 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 57de22da..b8838694 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -31,7 +31,10 @@ import struct import time from random import randint + +import adafruit_logging as logging from micropython import const + from .matcher import MQTTMatcher __version__ = "0.0.0+auto.0" @@ -186,7 +189,7 @@ def __init__( self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 self._timestamp = 0 - self.logger = None + self._init_logger() self._reconnect_attempt = 0 self._reconnect_timeout = float(0) @@ -270,9 +273,9 @@ def _get_connect_socket(self, host, port, *, timeout=1): "ssl_context must be set before using adafruit_mqtt for secure MQTT." ) - if self.logger is not None and port == MQTT_TLS_PORT: + if port == MQTT_TLS_PORT: self.logger.info(f"Establishing a SECURE SSL connection to {host}:{port}") - elif self.logger is not None: + else: self.logger.info(f"Establishing an INSECURE connection to {host}:{port}") addr_info = self._socket_pool.getaddrinfo( @@ -352,8 +355,7 @@ def will_set(self, topic=None, payload=None, qos=0, retain=False): :param bool retain: Specifies if the payload is to be retained when it is published. """ - if self.logger is not None: - self.logger.debug("Setting last will properties") + self.logger.debug("Setting last will properties") self._valid_qos(qos) if self._is_connected: raise MMQTTException("Last Will should only be called before connect().") @@ -502,8 +504,7 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): if keep_alive: self.keep_alive = keep_alive - if self.logger is not None: - self.logger.debug("Attempting to establish MQTT connection...") + self.logger.debug("Attempting to establish MQTT connection...") if self._reconnect_attempt > 0: if self.logger is not None: @@ -565,11 +566,9 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): fixed_header.append(remaining_length) fixed_header.append(0x00) - if self.logger is not None: - self.logger.debug("Sending CONNECT packet to broker...") - self.logger.debug( - "Fixed Header: %s\nVariable Header: %s", fixed_header, var_header - ) + self.logger.debug("Sending CONNECT to broker...") + self.logger.debug(f"Fixed Header: {fixed_header}") + self.logger.debug(f"Variable Header: {var_header}") self._sock.send(fixed_header) self._sock.send(var_header) # [MQTT-3.1.3-4] @@ -581,8 +580,7 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): if self._username is not None: self._send_str(self._username) self._send_str(self._password) - if self.logger is not None: - self.logger.debug("Receiving CONNACK packet from broker") + self.logger.debug("Receiving CONNACK packet from broker") stamp = time.monotonic() while True: op = self._wait_for_msg() @@ -607,15 +605,12 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): def disconnect(self): """Disconnects the MiniMQTT client from the MQTT broker.""" self._connected() - if self.logger is not None: - self.logger.debug("Sending DISCONNECT packet to broker") + self.logger.debug("Sending DISCONNECT packet to broker") try: self._sock.send(MQTT_DISCONNECT) except RuntimeError as e: - if self.logger is not None: - self.logger.warning(f"Unable to send DISCONNECT packet: {e}") - if self.logger is not None: - self.logger.debug("Closing socket") + self.logger.warning(f"Unable to send DISCONNECT packet: {e}") + self.logger.debug("Closing socket") self._sock.close() self._is_connected = False self._subscribed_topics = [] @@ -628,8 +623,7 @@ def ping(self): Returns response codes of any messages received while waiting for PINGRESP. """ self._connected() - if self.logger is not None: - self.logger.debug("Sending PINGREQ") + self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive stamp = time.monotonic() @@ -699,15 +693,14 @@ def publish(self, topic, msg, retain=False, qos=0): else: pub_hdr_fixed.append(remaining_length) - if self.logger is not None: - self.logger.debug( - "Sending PUBLISH\nTopic: %s\nMsg: %s\ - \nQoS: %d\nRetain? %r", - topic, - msg, - qos, - retain, - ) + self.logger.debug( + "Sending PUBLISH\nTopic: %s\nMsg: %s\ + \nQoS: %d\nRetain? %r", + topic, + msg, + qos, + retain, + ) self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) @@ -777,9 +770,8 @@ def subscribe(self, topic, qos=0): topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") packet += topic_size + t.encode() + qos_byte - if self.logger is not None: - for t, q in topics: - self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) + for t, q in topics: + self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) self._sock.send(packet) stamp = time.monotonic() while True: @@ -831,12 +823,10 @@ def unsubscribe(self, topic): for t in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") packet += topic_size + t.encode() - if self.logger is not None: - for t in topics: - self.logger.debug("UNSUBSCRIBING from topic %s", t) + for t in topics: + self.logger.debug("UNSUBSCRIBING from topic %s", t) self._sock.send(packet) - if self.logger is not None: - self.logger.debug("Waiting for UNSUBACK...") + self.logger.debug("Waiting for UNSUBACK...") while True: stamp = time.monotonic() op = self._wait_for_msg() @@ -909,17 +899,13 @@ def reconnect(self, resub_topics=True): """ - if self.logger is not None: - self.logger.debug("Attempting to reconnect with MQTT broker") - - ret = self.connect() - if self.logger is not None: - self.logger.debug("Reconnected with broker") + self.logger.debug("Attempting to reconnect with MQTT broker") + self.connect() + self.logger.debug("Reconnected with broker") if resub_topics: - if self.logger is not None: - self.logger.debug( - "Attempting to resubscribe to previously subscribed topics." - ) + self.logger.debug( + "Attempting to resubscribe to previously subscribed topics." + ) subscribed_topics = self._subscribed_topics.copy() self._subscribed_topics = [] while subscribed_topics: @@ -938,16 +924,16 @@ def loop(self, timeout=0): """ + self.logger.debug(f"waiting for messages for {timeout} seconds") if self._timestamp == 0: self._timestamp = time.monotonic() current_time = time.monotonic() if current_time - self._timestamp >= self.keep_alive: self._timestamp = 0 # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server - if self.logger is not None: - self.logger.debug( - "KeepAlive period elapsed - requesting a PINGRESP from the server..." - ) + self.logger.debug( + "KeepAlive period elapsed - requesting a PINGRESP from the server..." + ) rcs = self.ping() return rcs @@ -960,10 +946,9 @@ def loop(self, timeout=0): if rc is None: break if time.monotonic() - stamp > self._recv_timeout: - if self.logger is not None: - self.logger.debug( - f"Loop timed out, message queue not empty after {self._recv_timeout}s" - ) + self.logger.debug( + f"Loop timed out, message queue not empty after {self._recv_timeout}s" + ) break rcs.append(rc) @@ -996,8 +981,7 @@ def _wait_for_msg(self, timeout=0.1): # If we get here, it means that there is nothing to be received return None if res[0] & MQTT_PKT_TYPE_MASK == MQTT_PINGRESP: - if self.logger is not None: - self.logger.debug("Got PINGRESP") + self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: raise MMQTTException(f"Unexpected PINGRESP returned from broker: {sz}.") @@ -1029,10 +1013,7 @@ def _wait_for_msg(self, timeout=0.1): # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") - if self.logger is not None: - self.logger.debug( - "Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg - ) + self.logger.debug("Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) self._handle_on_message(self, topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") @@ -1101,8 +1082,7 @@ def _sock_exact_recv(self, bufsize): # This will timeout with socket timeout (not keepalive timeout) rc = self._sock.recv(bufsize) if not rc: - if self.logger is not None: - self.logger.debug("_sock_exact_recv timeout") + self.logger.debug("_sock_exact_recv timeout") # If no bytes waiting, raise same exception as socketpool raise OSError(errno.ETIMEDOUT) # If any bytes waiting, try to read them all, @@ -1187,6 +1167,7 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): :return logger object """ + # pylint: disable=attribute-defined-outside-init self.logger = log_pkg.getLogger(logger_name) self.logger.setLevel(log_level) @@ -1194,6 +1175,9 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): def disable_logger(self): """Disables logging.""" - if not self.logger: - raise MMQTTException("Can not disable logger, no logger found.") - self.logger = None + self._init_logger() + + def _init_logger(self): + """Initializes logger to use NullHandler, i.e. no logging will be done.""" + self.logger = logging.getLogger("") + self.logger.addHandler(logging.NullHandler()) diff --git a/requirements.txt b/requirements.txt index 7a984a47..598c926a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +adafruit-circuitpython-logging From 033a5a13c4f7e042e6aec107e8c87b216608d8b5 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 5 Feb 2023 22:49:19 +0100 Subject: [PATCH 104/289] document the new dependency --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 2ca1660e..077966c9 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,7 @@ Dependencies This driver depends on: * `Adafruit CircuitPython `_ +* `Adafruit CircuitPython Logging `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading From 6d8b72a1e47be5073b6107dd03451d5d0fe202aa Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 14 Feb 2023 23:32:25 +0100 Subject: [PATCH 105/289] avoid importing logging use fake logger instead --- README.rst | 1 - adafruit_minimqtt/adafruit_minimqtt.py | 19 +++++++++++++++---- requirements.txt | 1 - 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 077966c9..2ca1660e 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,6 @@ Dependencies This driver depends on: * `Adafruit CircuitPython `_ -* `Adafruit CircuitPython Logging `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index b8838694..a1ba3911 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -32,7 +32,6 @@ import time from random import randint -import adafruit_logging as logging from micropython import const from .matcher import MQTTMatcher @@ -127,6 +126,19 @@ def wrap_socket(self, socket, server_hostname=None): return _FakeSSLSocket(socket, self._iface.TLS_MODE) +class NullLogger: + """Fake logger class that does not do anything""" + + # pylint: disable=unused-argument + def nothing(self, msg: str, *args) -> None: + """no action""" + pass + + def __init__(self): + for log_level in ["debug", "info", "warning", "error", "critical"]: + setattr(NullLogger, log_level, self.nothing) + + class MQTT: """MQTT Client for CircuitPython. @@ -1178,6 +1190,5 @@ def disable_logger(self): self._init_logger() def _init_logger(self): - """Initializes logger to use NullHandler, i.e. no logging will be done.""" - self.logger = logging.getLogger("") - self.logger.addHandler(logging.NullHandler()) + """Initializes logger to NullLogger, i.e. no logging will be done.""" + self.logger = NullLogger() diff --git a/requirements.txt b/requirements.txt index 598c926a..7a984a47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,3 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -adafruit-circuitpython-logging From f0720840b6dc8d08dc4cb985ace037024a8175d0 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 14 Feb 2023 23:41:26 +0100 Subject: [PATCH 106/289] fix rebase --- adafruit_minimqtt/adafruit_minimqtt.py | 66 +++++++++++--------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a1ba3911..d5143554 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -298,10 +298,9 @@ def _get_connect_socket(self, host, port, *, timeout=1): sock = self._socket_pool.socket(addr_info[0], addr_info[1]) except OSError as exc: # Do not consider this for back-off. - if self.logger is not None: - self.logger.warning( - f"Failed to create socket for host {addr_info[0]} and port {addr_info[1]}" - ) + self.logger.warning( + f"Failed to create socket for host {addr_info[0]} and port {addr_info[1]}" + ) raise TemporaryError from exc connect_host = addr_info[-1][0] @@ -315,8 +314,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): sock.connect((connect_host, port)) except MemoryError as exc: sock.close() - if self.logger is not None: - self.logger.warning(f"Failed to allocate memory for connect: {exc}") + self.logger.warning(f"Failed to allocate memory for connect: {exc}") # Do not consider this for back-off. raise TemporaryError from exc except OSError as exc: @@ -461,10 +459,10 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): self._recompute_reconnect_backoff() else: self._reset_reconnect_backoff() - if self.logger is not None: - self.logger.debug( - f"Attempting to connect to MQTT broker (attempt #{self._reconnect_attempt})" - ) + + self.logger.debug( + f"Attempting to connect to MQTT broker (attempt #{self._reconnect_attempt})" + ) try: ret = self._connect( @@ -476,18 +474,15 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): self._reset_reconnect_backoff() return ret except TemporaryError as e: - if self.logger is not None: - self.logger.warning(f"temporary error when connecting: {e}") + self.logger.warning(f"temporary error when connecting: {e}") backoff = False except OSError as e: last_exception = e - if self.logger is not None: - self.logger.info(f"failed to connect: {e}") + self.logger.info(f"failed to connect: {e}") backoff = True except MMQTTException as e: last_exception = e - if self.logger is not None: - self.logger.info(f"MMQT error: {e}") + self.logger.info(f"MMQT error: {e}") backoff = True if self._reconnect_attempts_max > 1: @@ -519,10 +514,9 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): self.logger.debug("Attempting to establish MQTT connection...") if self._reconnect_attempt > 0: - if self.logger is not None: - self.logger.debug( - f"Sleeping for {self._reconnect_timeout:.3} seconds due to connect back-off" - ) + self.logger.debug( + f"Sleeping for {self._reconnect_timeout:.3} seconds due to connect back-off" + ) time.sleep(self._reconnect_timeout) # Get a new socket @@ -867,29 +861,26 @@ def _recompute_reconnect_backoff(self): """ self._reconnect_attempt = self._reconnect_attempt + 1 self._reconnect_timeout = 2**self._reconnect_attempt - if self.logger is not None: - # pylint: disable=consider-using-f-string - self.logger.debug( - "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) - ) + # pylint: disable=consider-using-f-string + self.logger.debug( + "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) + ) if self._reconnect_timeout > self._reconnect_maximum_backoff: - if self.logger is not None: - self.logger.debug( - f"Truncating reconnect timeout to {self._reconnect_maximum_backoff} seconds" - ) + self.logger.debug( + f"Truncating reconnect timeout to {self._reconnect_maximum_backoff} seconds" + ) self._reconnect_timeout = float(self._reconnect_maximum_backoff) # Add a sub-second jitter. # Even truncated timeout should have jitter added to it. This is why it is added here. jitter = randint(0, 1000) / 1000 - if self.logger is not None: - # pylint: disable=consider-using-f-string - self.logger.debug( - "adding jitter {:.2f} to {:.2f} seconds".format( - jitter, self._reconnect_timeout - ) + # pylint: disable=consider-using-f-string + self.logger.debug( + "adding jitter {:.2f} to {:.2f} seconds".format( + jitter, self._reconnect_timeout ) + ) self._reconnect_timeout += jitter def _reset_reconnect_backoff(self): @@ -897,8 +888,7 @@ def _reset_reconnect_backoff(self): Reset reconnect back-off to the initial state. """ - if self.logger is not None: - self.logger.debug("Resetting reconnect backoff") + self.logger.debug("Resetting reconnect backoff") self._reconnect_attempt = 0 self._reconnect_timeout = float(0) @@ -912,7 +902,7 @@ def reconnect(self, resub_topics=True): """ self.logger.debug("Attempting to reconnect with MQTT broker") - self.connect() + ret = self.connect() self.logger.debug("Reconnected with broker") if resub_topics: self.logger.debug( From dd56de3a535031706afc7003ea7169b40dd5e3ca Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 10:59:21 +0100 Subject: [PATCH 107/289] get rid of separate logger init function also document the self.logger --- adafruit_minimqtt/adafruit_minimqtt.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d5143554..31700699 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -201,7 +201,9 @@ def __init__( self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 self._timestamp = 0 - self._init_logger() + self.logger = NullLogger() + """An optional logging attribute that can be set with with a Logger + to enable debug logging.""" self._reconnect_attempt = 0 self._reconnect_timeout = float(0) @@ -1177,8 +1179,4 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): def disable_logger(self): """Disables logging.""" - self._init_logger() - - def _init_logger(self): - """Initializes logger to NullLogger, i.e. no logging will be done.""" self.logger = NullLogger() From d9d5cceb39d7ed54710e22af737940d258dd1023 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 11:03:53 +0100 Subject: [PATCH 108/289] remove trailing whitespace --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 31700699..2d74dd92 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -202,7 +202,7 @@ def __init__( self._pid = 0 self._timestamp = 0 self.logger = NullLogger() - """An optional logging attribute that can be set with with a Logger + """An optional logging attribute that can be set with with a Logger to enable debug logging.""" self._reconnect_attempt = 0 From 4d003eb709dcc4286d7781159513a4ee66b22889 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 12 Jan 2023 22:11:01 +0100 Subject: [PATCH 109/289] allow to use any port as TLS port fixes #140 --- adafruit_minimqtt/adafruit_minimqtt.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2d74dd92..240bd510 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -222,11 +222,12 @@ def __init__( self.port = MQTT_TCP_PORT if is_ssl: + self._is_ssl = True self.port = MQTT_TLS_PORT if port: self.port = port - # define client identifer + # define client identifier if client_id: # user-defined client_id MAY allow client_id's > 23 bytes or # non-alpha-numeric characters @@ -282,12 +283,12 @@ def _get_connect_socket(self, host, port, *, timeout=1): if not isinstance(port, int): raise RuntimeError("Port must be an integer") - if port == MQTT_TLS_PORT and not self._ssl_context: + if self._is_ssl and not self._ssl_context: raise RuntimeError( "ssl_context must be set before using adafruit_mqtt for secure MQTT." ) - if port == MQTT_TLS_PORT: + if self._is_ssl: self.logger.info(f"Establishing a SECURE SSL connection to {host}:{port}") else: self.logger.info(f"Establishing an INSECURE connection to {host}:{port}") @@ -306,7 +307,7 @@ def _get_connect_socket(self, host, port, *, timeout=1): raise TemporaryError from exc connect_host = addr_info[-1][0] - if port == MQTT_TLS_PORT: + if self._is_ssl: sock = self._ssl_context.wrap_socket(sock, server_hostname=host) connect_host = host sock.settimeout(timeout) From e95d9f285afc57af4022d9e1780939875554ccde Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 12 Jan 2023 23:04:35 +0100 Subject: [PATCH 110/289] fixup the examples --- examples/cpython/minimqtt_adafruitio_cpython.py | 3 +-- examples/ethernet/minimqtt_simpletest_eth.py | 6 +++++- .../minimqtt_adafruitio_native_networking.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 7eb4f5fb..18bfd251 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -46,8 +46,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - port=1883, + broker="io.adafruit.com", username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=socket, diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index c585cf78..3e91c8fc 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -67,8 +67,12 @@ def publish(client, userdata, topic, pid): MQTT.set_socket(socket, eth) # Set up a MiniMQTT Client +# NOTE: We'll need to connect insecurely for ethernet configurations. client = MQTT.MQTT( - broker=secrets["broker"], username=secrets["user"], password=secrets["pass"] + broker=secrets["broker"], + username=secrets["user"], + password=secrets["pass"], + is_ssl=False, ) # Connect callback handlers to client diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index a4f5ecaa..21661d31 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -62,7 +62,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], + broker="io.adafruit.com", port=secrets["port"], username=secrets["aio_username"], password=secrets["aio_key"], From 831420a5fa4394d66afdd0322b1f1978fa6154e4 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 20:33:26 +0100 Subject: [PATCH 111/289] change the behavior - insecure is default also add tests --- adafruit_minimqtt/adafruit_minimqtt.py | 11 ++- tests/test_port_ssl.py | 132 +++++++++++++++++++++++++ tox.ini | 11 +++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 tests/test_port_ssl.py create mode 100644 tox.ini diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 240bd510..519aae2f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -171,7 +171,7 @@ def __init__( username=None, password=None, client_id=None, - is_ssl=True, + is_ssl=None, keep_alive=60, recv_timeout=10, socket_pool=None, @@ -220,9 +220,14 @@ def __init__( ): # [MQTT-3.1.3.5] raise MMQTTException("Password length is too large.") + # The connection will be insecure unless is_ssl is set to True. + # If the port is not specified, the security will be set based on the is_ssl parameter. + # If the port is specified, the is_ssl parameter will be honored. self.port = MQTT_TCP_PORT - if is_ssl: - self._is_ssl = True + if is_ssl is None: + is_ssl = False + self._is_ssl = is_ssl + if self._is_ssl: self.port = MQTT_TLS_PORT if port: self.port = port diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py new file mode 100644 index 00000000..9bbb36f7 --- /dev/null +++ b/tests/test_port_ssl.py @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""tests that verify the connect behavior w.r.t. port number and TLS""" + +import socket +import ssl +from unittest import TestCase, main +from unittest.mock import Mock, call, patch + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +class PortSslSetup(TestCase): + """This class contains tests that verify how host/port and TLS is set for connect(). + These tests assume that there is no MQTT broker running on the hosts/ports they connect to. + """ + + def test_default_port(self) -> None: + """verify default port value and that TLS is not used""" + host = "127.0.0.1" + port = 1883 + + with patch.object(socket.socket, "connect") as connect_mock: + ssl_context = ssl.create_default_context() + mqtt_client = MQTT.MQTT( + broker=host, + socket_pool=socket, + ssl_context=ssl_context, + connect_retries=1, + ) + + ssl_mock = Mock() + ssl_context.wrap_socket = ssl_mock + + with self.assertRaises(OSError): + expected_port = port + mqtt_client.connect() + + ssl_mock.assert_not_called() + connect_mock.assert_called() + # Assuming the repeated calls will have the same arguments. + connect_mock.assert_has_calls( + [call((host, expected_port))] + ) + + def test_connect_override(self): + """Test that connect() can override host and port.""" + host = "127.0.0.1" + port = 1883 + + with patch.object(socket.socket, "connect") as connect_mock: + connect_mock.side_effect = OSError("artificial error") + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + connect_retries=1, + ) + + with self.assertRaises(OSError): + expected_host = "127.0.0.2" + expected_port = 1884 + self.assertNotEqual( + expected_port, port, "port override should differ" + ) + self.assertNotEqual( + expected_host, host, "host override should differ" + ) + mqtt_client.connect(host=expected_host, port=expected_port) + + connect_mock.assert_called() + # Assuming the repeated calls will have the same arguments. + connect_mock.assert_has_calls( + [call((expected_host, expected_port))] + ) + + def test_tls_port(self) -> None: + """verify that when is_ssl=True is set, the default port is 8883 + and the socket is TLS wrapped. Also test that the TLS port can be overridden.""" + host = "127.0.0.1" + + for port in [None, 8884]: + if port is None: + expected_port = 8883 + else: + expected_port = port + with self.subTest(): + ssl_mock = Mock() + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + is_ssl=True, + ssl_context=ssl_mock, + connect_retries=1, + ) + + socket_mock = Mock() + connect_mock = Mock(side_effect=OSError) + socket_mock.connect = connect_mock + ssl_mock.wrap_socket = Mock(return_value=socket_mock) + + with self.assertRaises(RuntimeError): + mqtt_client.connect() + + ssl_mock.wrap_socket.assert_called() + + connect_mock.assert_called() + # Assuming the repeated calls will have the same arguments. + connect_mock.assert_has_calls([call((host, expected_port))]) + + def test_tls_without_ssl_context(self) -> None: + """verify that when is_ssl=True is set, the code will check that ssl_context is not None""" + host = "127.0.0.1" + + mqtt_client = MQTT.MQTT( + broker=host, + socket_pool=socket, + is_ssl=True, + ssl_context=None, + connect_retries=1, + ) + + with self.assertRaises(RuntimeError) as context: + mqtt_client.connect() + self.assertTrue("ssl_context must be set" in str(context)) + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..6a9584b3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: MIT + +[tox] +envlist = py39 + +[testenv] +changedir = {toxinidir}/tests +deps = pytest==6.2.5 +commands = pytest -v From f304d7f621b1f5fb54c77c85495924818518b04e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 20:52:06 +0100 Subject: [PATCH 112/289] fix tests --- tests/test_port_ssl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 9bbb36f7..3fdf6a3b 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -34,7 +34,7 @@ def test_default_port(self) -> None: ssl_mock = Mock() ssl_context.wrap_socket = ssl_mock - with self.assertRaises(OSError): + with self.assertRaises(MQTT.MMQTTException): expected_port = port mqtt_client.connect() @@ -59,7 +59,7 @@ def test_connect_override(self): connect_retries=1, ) - with self.assertRaises(OSError): + with self.assertRaises(MQTT.MMQTTException): expected_host = "127.0.0.2" expected_port = 1884 self.assertNotEqual( @@ -102,7 +102,7 @@ def test_tls_port(self) -> None: socket_mock.connect = connect_mock ssl_mock.wrap_socket = Mock(return_value=socket_mock) - with self.assertRaises(RuntimeError): + with self.assertRaises(MQTT.MMQTTException): mqtt_client.connect() ssl_mock.wrap_socket.assert_called() From 5f3fc076f5fd9ba43d3a698b71687fce76719a73 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 20:59:28 +0100 Subject: [PATCH 113/289] apply black --- tests/test_port_ssl.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 3fdf6a3b..8474b56d 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -41,9 +41,7 @@ def test_default_port(self) -> None: ssl_mock.assert_not_called() connect_mock.assert_called() # Assuming the repeated calls will have the same arguments. - connect_mock.assert_has_calls( - [call((host, expected_port))] - ) + connect_mock.assert_has_calls([call((host, expected_port))]) def test_connect_override(self): """Test that connect() can override host and port.""" @@ -62,19 +60,13 @@ def test_connect_override(self): with self.assertRaises(MQTT.MMQTTException): expected_host = "127.0.0.2" expected_port = 1884 - self.assertNotEqual( - expected_port, port, "port override should differ" - ) - self.assertNotEqual( - expected_host, host, "host override should differ" - ) + self.assertNotEqual(expected_port, port, "port override should differ") + self.assertNotEqual(expected_host, host, "host override should differ") mqtt_client.connect(host=expected_host, port=expected_port) connect_mock.assert_called() # Assuming the repeated calls will have the same arguments. - connect_mock.assert_has_calls( - [call((expected_host, expected_port))] - ) + connect_mock.assert_has_calls([call((expected_host, expected_port))]) def test_tls_port(self) -> None: """verify that when is_ssl=True is set, the default port is 8883 From ed4d2ae75f9174e7fc2fbfb0bfe44cb0d1312a31 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 21:04:10 +0100 Subject: [PATCH 114/289] use TLS when possible --- examples/cpython/minimqtt_adafruitio_cpython.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 18bfd251..dbe62bdb 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -1,8 +1,10 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import time import socket +import ssl +import time + import adafruit_minimqtt.adafruit_minimqtt as MQTT ### Secrets File Setup ### @@ -50,6 +52,8 @@ def message(client, topic, message): username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=socket, + is_ssl=True, + ssl_context=ssl.create_default_context() ) # Setup the callback methods above From 768c046daa53253b7caf5615a426ad48f7f417b7 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 15 Feb 2023 21:21:49 +0100 Subject: [PATCH 115/289] apply black --- examples/cpython/minimqtt_adafruitio_cpython.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index dbe62bdb..3667329b 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -53,7 +53,7 @@ def message(client, topic, message): password=secrets["aio_key"], socket_pool=socket, is_ssl=True, - ssl_context=ssl.create_default_context() + ssl_context=ssl.create_default_context(), ) # Setup the callback methods above From de3d09233a444fb447ee560535d272781d0f7e52 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 16 Feb 2023 21:32:20 +0100 Subject: [PATCH 116/289] _recv_into() is not used anymore --- adafruit_minimqtt/adafruit_minimqtt.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2d74dd92..97abf58b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1040,16 +1040,6 @@ def _recv_len(self): return n sh += 7 - def _recv_into(self, buf, size=0): - """Backwards-compatible _recv_into implementation.""" - if self._backwards_compatible_sock: - size = len(buf) if size == 0 else size - b = self._sock.recv(size) - read_size = len(b) - buf[:read_size] = b - return read_size - return self._sock.recv_into(buf, size) - def _sock_exact_recv(self, bufsize): """Reads _exact_ number of bytes from the connected socket. Will only return string with the exact number of bytes requested. From 5646d548de81bf2d4c88e7c5ed310ca94fdf0614 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 16 Feb 2023 23:21:45 +0100 Subject: [PATCH 117/289] handle variable payload in SUBACK --- adafruit_minimqtt/adafruit_minimqtt.py | 61 ++++++++++++++++---------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2d74dd92..90d3a74c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -784,22 +784,34 @@ def subscribe(self, topic, qos=0): stamp = time.monotonic() while True: op = self._wait_for_msg() - if op == 0x90: - rc = self._sock_exact_recv(4) - assert rc[1] == packet[2] and rc[2] == packet[3] - if rc[3] == 0x80: - raise MMQTTException("SUBACK Failure!") - for t, q in topics: - if self.on_subscribe is not None: - self.on_subscribe(self, self._user_data, t, q) - self._subscribed_topics.append(t) - return - if op is None: if time.monotonic() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) + else: + if op == 0x90: + rc = self._sock_exact_recv(3) + # Check packet identifier. + assert rc[1] == packet[2] and rc[2] == packet[3] + remaining_len = rc[0] - 2 + assert remaining_len > 0 + rc = self._sock_exact_recv(remaining_len) + for i in range(0, remaining_len): + if rc[i] not in [0, 1, 2]: + raise MMQTTException( + f"SUBACK Failure for topic {topics[i][0]}: {hex(rc[i])}" + ) + + for t, q in topics: + if self.on_subscribe is not None: + self.on_subscribe(self, self._user_data, t, q) + self._subscribed_topics.append(t) + return + + raise MMQTTException( + f"invalid message received as response to SUBSCRIBE: {hex(op)}" + ) def unsubscribe(self, topic): """Unsubscribes from a MQTT topic. @@ -838,22 +850,26 @@ def unsubscribe(self, topic): while True: stamp = time.monotonic() op = self._wait_for_msg() - if op == 176: - rc = self._sock_exact_recv(3) - assert rc[0] == 0x02 - # [MQTT-3.32] - assert rc[1] == packet_id_bytes[0] and rc[2] == packet_id_bytes[1] - for t in topics: - if self.on_unsubscribe is not None: - self.on_unsubscribe(self, self._user_data, t, self._pid) - self._subscribed_topics.remove(t) - return - if op is None: if time.monotonic() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) + else: + if op == 176: + rc = self._sock_exact_recv(3) + assert rc[0] == 0x02 + # [MQTT-3.32] + assert rc[1] == packet_id_bytes[0] and rc[2] == packet_id_bytes[1] + for t in topics: + if self.on_unsubscribe is not None: + self.on_unsubscribe(self, self._user_data, t, self._pid) + self._subscribed_topics.remove(t) + return + + raise MMQTTException( + f"invalid message received as response to UNSUBSCRIBE: {hex(op)}" + ) def _recompute_reconnect_backoff(self): """ @@ -992,6 +1008,7 @@ def _wait_for_msg(self, timeout=0.1): return MQTT_PINGRESP if res[0] & MQTT_PKT_TYPE_MASK != MQTT_PUBLISH: + self.logger.debug(f"Got message type: {hex(res[0])}") return res[0] # Handle only the PUBLISH packet type from now on. From c3a080b96516e6552fd4d2d21d6806c7a61a543c Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:19:21 +0100 Subject: [PATCH 118/289] add some type annotations --- adafruit_minimqtt/adafruit_minimqtt.py | 126 +++++++++++++++---------- 1 file changed, 74 insertions(+), 52 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 81630e4a..07d4093c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -32,6 +32,11 @@ import time from random import randint +try: + from typing import List, Tuple, Type, Union +except ImportError: + pass + from micropython import const from .matcher import MQTTMatcher @@ -84,7 +89,7 @@ class TemporaryError(Exception): # Legacy ESP32SPI Socket API -def set_socket(sock, iface=None): +def set_socket(sock, iface=None) -> None: """Legacy API for setting the socket and network interface. :param sock: socket object. @@ -100,7 +105,7 @@ def set_socket(sock, iface=None): class _FakeSSLSocket: - def __init__(self, socket, tls_mode): + def __init__(self, socket, tls_mode) -> None: self._socket = socket self._mode = tls_mode self.settimeout = socket.settimeout @@ -117,10 +122,10 @@ def connect(self, address): class _FakeSSLContext: - def __init__(self, iface): + def __init__(self, iface) -> None: self._iface = iface - def wrap_socket(self, socket, server_hostname=None): + def wrap_socket(self, socket, server_hostname=None) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument return _FakeSSLSocket(socket, self._iface.TLS_MODE) @@ -134,7 +139,7 @@ def nothing(self, msg: str, *args) -> None: """no action""" pass - def __init__(self): + def __init__(self) -> None: for log_level in ["debug", "info", "warning", "error", "critical"]: setattr(NullLogger, log_level, self.nothing) @@ -166,21 +171,21 @@ class MQTT: def __init__( self, *, - broker, - port=None, - username=None, - password=None, - client_id=None, - is_ssl=None, - keep_alive=60, - recv_timeout=10, + broker: str, + port: Union[int, None] = None, + username: Union[str, None] = None, + password: Union[str, None] = None, + client_id: Union[str, None] = None, + is_ssl: Union[bool, None] = None, + keep_alive: int = 60, + recv_timeout: int = 10, socket_pool=None, ssl_context=None, - use_binary_mode=False, - socket_timeout=1, - connect_retries=5, + use_binary_mode: bool = False, + socket_timeout: int = 1, + connect_retries: int = 5, user_data=None, - ): + ) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context @@ -253,7 +258,7 @@ def __init__( self._lw_retain = False # List of subscribed topics, used for tracking - self._subscribed_topics = [] + self._subscribed_topics: List[str] = [] self._on_message_filtered = MQTTMatcher() # Default topic callback methods @@ -265,7 +270,7 @@ def __init__( self.on_unsubscribe = None # pylint: disable=too-many-branches - def _get_connect_socket(self, host, port, *, timeout=1): + def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): """Obtains a new socket and connects to a broker. :param str host: Desired broker hostname @@ -338,20 +343,20 @@ def _get_connect_socket(self, host, port, *, timeout=1): def __enter__(self): return self - def __exit__(self, exception_type, exception_value, traceback): + def __exit__(self, exception_type, exception_value, traceback) -> None: self.deinit() - def deinit(self): + def deinit(self) -> None: """De-initializes the MQTT client and disconnects from the mqtt broker.""" self.disconnect() @property - def mqtt_msg(self): + def mqtt_msg(self) -> Tuple[int, int]: """Returns maximum MQTT payload and topic size.""" return self._msg_size_lim, MQTT_TOPIC_LENGTH_LIMIT @mqtt_msg.setter - def mqtt_msg(self, msg_size): + def mqtt_msg(self, msg_size: int) -> None: """Sets the maximum MQTT message payload size. :param int msg_size: Maximum MQTT payload size. @@ -388,7 +393,7 @@ def will_set(self, topic=None, payload=None, qos=0, retain=False): self._lw_msg = payload self._lw_retain = retain - def add_topic_callback(self, mqtt_topic, callback_method): + def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: """Registers a callback_method for a specific MQTT topic. :param str mqtt_topic: MQTT topic identifier. @@ -398,7 +403,7 @@ def add_topic_callback(self, mqtt_topic, callback_method): raise ValueError("MQTT topic and callback method must both be defined.") self._on_message_filtered[mqtt_topic] = callback_method - def remove_topic_callback(self, mqtt_topic): + def remove_topic_callback(self, mqtt_topic: str) -> None: """Removes a registered callback method. :param str mqtt_topic: MQTT topic identifier string. @@ -421,10 +426,10 @@ def on_message(self): return self._on_message @on_message.setter - def on_message(self, method): + def on_message(self, method) -> None: self._on_message = method - def _handle_on_message(self, client, topic, message): + def _handle_on_message(self, client, topic: str, message: str): matched = False if topic is not None: for callback in self._on_message_filtered.iter_match(topic): @@ -434,7 +439,7 @@ def _handle_on_message(self, client, topic, message): if not matched and self.on_message: # regular on_message self.on_message(client, topic, message) - def username_pw_set(self, username, password=None): + def username_pw_set(self, username: str, password: Union[str, None] = None) -> None: """Set client's username and an optional password. :param str username: Username to use with your MQTT broker. @@ -447,7 +452,13 @@ def username_pw_set(self, username, password=None): if password is not None: self._password = password - def connect(self, clean_session=True, host=None, port=None, keep_alive=None): + def connect( + self, + clean_session: bool = True, + host: Union[str, None] = None, + port: Union[int, None] = None, + keep_alive: Union[int, None] = None, + ) -> int: """Initiates connection with the MQTT Broker. Will perform exponential back-off on connect failures. @@ -503,7 +514,13 @@ def connect(self, clean_session=True, host=None, port=None, keep_alive=None): raise MMQTTException(exc_msg) # pylint: disable=too-many-branches, too-many-statements, too-many-locals - def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): + def _connect( + self, + clean_session: bool = True, + host: Union[str, None] = None, + port: Union[int, None] = None, + keep_alive: Union[int, None] = None, + ) -> int: """Initiates connection with the MQTT Broker. :param bool clean_session: Establishes a persistent session. @@ -616,7 +633,7 @@ def _connect(self, clean_session=True, host=None, port=None, keep_alive=None): f"No data received from broker for {self._recv_timeout} seconds." ) - def disconnect(self): + def disconnect(self) -> None: """Disconnects the MiniMQTT client from the MQTT broker.""" self._connected() self.logger.debug("Sending DISCONNECT packet to broker") @@ -631,7 +648,7 @@ def disconnect(self): if self.on_disconnect is not None: self.on_disconnect(self, self._user_data, 0) - def ping(self): + def ping(self) -> list[int]: """Pings the MQTT Broker to confirm if the broker is alive or if there is an active network connection. Returns response codes of any messages received while waiting for PINGRESP. @@ -651,7 +668,13 @@ def ping(self): return rcs # pylint: disable=too-many-branches, too-many-statements - def publish(self, topic, msg, retain=False, qos=0): + def publish( + self, + topic: str, + msg: Union[str, int, float, bytes], + retain: bool = False, + qos: int = 0, + ) -> None: """Publishes a message to a topic provided. :param str topic: Unique topic identifier. @@ -740,7 +763,7 @@ def publish(self, topic, msg, retain=False, qos=0): f"No data received from broker for {self._recv_timeout} seconds." ) - def subscribe(self, topic, qos=0): + def subscribe(self, topic: str, qos: int = 0) -> None: """Subscribes to a topic on the MQTT Broker. This method can subscribe to one topics or multiple topics. @@ -807,7 +830,7 @@ def subscribe(self, topic, qos=0): f"No data received from broker for {self._recv_timeout} seconds." ) - def unsubscribe(self, topic): + def unsubscribe(self, topic: str) -> None: """Unsubscribes from a MQTT topic. :param str|list topic: Unique MQTT topic identifier string or list. @@ -861,7 +884,7 @@ def unsubscribe(self, topic): f"No data received from broker for {self._recv_timeout} seconds." ) - def _recompute_reconnect_backoff(self): + def _recompute_reconnect_backoff(self) -> None: """ Recompute the reconnection timeout. The self._reconnect_timeout will be used in self._connect() to perform the actual sleep. @@ -891,7 +914,7 @@ def _recompute_reconnect_backoff(self): ) self._reconnect_timeout += jitter - def _reset_reconnect_backoff(self): + def _reset_reconnect_backoff(self) -> None: """ Reset reconnect back-off to the initial state. @@ -900,7 +923,7 @@ def _reset_reconnect_backoff(self): self._reconnect_attempt = 0 self._reconnect_timeout = float(0) - def reconnect(self, resub_topics=True): + def reconnect(self, resub_topics: bool = True) -> int: """Attempts to reconnect to the MQTT broker. Return the value from connect() if successful. Will disconnect first if already connected. Will perform exponential back-off on connect failures. @@ -924,13 +947,13 @@ def reconnect(self, resub_topics=True): return ret - def loop(self, timeout=0): + def loop(self, timeout: float = 0) -> Union[list[int], None]: # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to check incoming subscription messages. Returns response codes of any messages received. - :param int timeout: Socket timeout, in seconds. + :param float timeout: Socket timeout, in seconds. """ @@ -964,7 +987,7 @@ def loop(self, timeout=0): return rcs if rcs else None - def _wait_for_msg(self, timeout=0.1): + def _wait_for_msg(self, timeout: float = 0.1) -> Union[int, None]: # pylint: disable = too-many-return-statements """Reads and processes network events. @@ -1004,7 +1027,7 @@ def _wait_for_msg(self, timeout=0.1): sz = self._recv_len() # topic length MSB & LSB topic_len = self._sock_exact_recv(2) - topic_len = (topic_len[0] << 8) | topic_len[1] + topic_len = int((topic_len[0] << 8) | topic_len[1]) if topic_len > sz - 2: raise MMQTTException( @@ -1034,11 +1057,10 @@ def _wait_for_msg(self, timeout=0.1): return res[0] - def _recv_len(self): + def _recv_len(self) -> int: """Unpack MQTT message length.""" n = 0 sh = 0 - b = bytearray(1) while True: b = self._sock_exact_recv(1)[0] n |= (b & 0x7F) << sh @@ -1046,7 +1068,7 @@ def _recv_len(self): return n sh += 7 - def _sock_exact_recv(self, bufsize): + def _sock_exact_recv(self, bufsize: int) -> bytearray: """Reads _exact_ number of bytes from the connected socket. Will only return string with the exact number of bytes requested. @@ -1100,7 +1122,7 @@ def _sock_exact_recv(self, bufsize): ) return rc - def _send_str(self, string): + def _send_str(self, string: str) -> None: """Encodes a string and sends it to a socket. :param str string: String to write to the socket. @@ -1114,7 +1136,7 @@ def _send_str(self, string): self._sock.send(string) @staticmethod - def _valid_topic(topic): + def _valid_topic(topic: str) -> None: """Validates if topic provided is proper MQTT topic format. :param str topic: Topic identifier @@ -1130,7 +1152,7 @@ def _valid_topic(topic): raise MMQTTException("Topic length is too large.") @staticmethod - def _valid_qos(qos_level): + def _valid_qos(qos_level: int) -> None: """Validates if the QoS level is supported by this library :param int qos_level: Desired QoS level. @@ -1142,21 +1164,21 @@ def _valid_qos(qos_level): else: raise MMQTTException("QoS must be an integer.") - def _connected(self): + def _connected(self) -> None: """Returns MQTT client session status as True if connected, raises a `MMQTTException` if `False`. """ if not self.is_connected(): raise MMQTTException("MiniMQTT is not connected") - def is_connected(self): + def is_connected(self) -> bool: """Returns MQTT client session status as True if connected, False if not. """ return self._is_connected and self._sock is not None # Logging - def enable_logger(self, log_pkg, log_level=20, logger_name="log"): + def enable_logger(self, log_pkg, log_level: int = 20, logger_name: str = "log"): """Enables library logging by getting logger from the specified logging package and setting its log level. @@ -1173,6 +1195,6 @@ def enable_logger(self, log_pkg, log_level=20, logger_name="log"): return self.logger - def disable_logger(self): + def disable_logger(self) -> None: """Disables logging.""" self.logger = NullLogger() From d30c0f946188689031d007767c1b9e8d82c8aa8b Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:33:07 +0100 Subject: [PATCH 119/289] remove unused Type --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 07d4093c..a942fcc6 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -33,7 +33,7 @@ from random import randint try: - from typing import List, Tuple, Type, Union + from typing import List, Tuple, Union except ImportError: pass From ab88d0f3781add50aa82f5d56a575a1d3cb414db Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:47:57 +0100 Subject: [PATCH 120/289] yet more type annotations --- adafruit_minimqtt/adafruit_minimqtt.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a942fcc6..37ad0dd8 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -205,7 +205,7 @@ def __init__( self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 - self._timestamp = 0 + self._timestamp: float = 0 self.logger = NullLogger() """An optional logging attribute that can be set with with a Logger to enable debug logging.""" @@ -364,7 +364,13 @@ def mqtt_msg(self, msg_size: int) -> None: if msg_size < MQTT_MSG_MAX_SZ: self._msg_size_lim = msg_size - def will_set(self, topic=None, payload=None, qos=0, retain=False): + def will_set( + self, + topic: Union[str, None] = None, + payload: Union[int, float, str, None] = None, + qos: int = 0, + retain: bool = False, + ) -> None: """Sets the last will and testament properties. MUST be called before `connect()`. :param str topic: MQTT Broker topic. From 59ffe3411641325a8ede30260f30750103edb136 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:50:42 +0100 Subject: [PATCH 121/289] use separate variable to avoid type complaints --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 37ad0dd8..b8d8d8f1 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1032,8 +1032,8 @@ def _wait_for_msg(self, timeout: float = 0.1) -> Union[int, None]: # Handle only the PUBLISH packet type from now on. sz = self._recv_len() # topic length MSB & LSB - topic_len = self._sock_exact_recv(2) - topic_len = int((topic_len[0] << 8) | topic_len[1]) + topic_len_buf = self._sock_exact_recv(2) + topic_len = int((topic_len_buf[0] << 8) | topic_len_buf[1]) if topic_len > sz - 2: raise MMQTTException( From 06f167429be2b297a0e0b21e16867bf85866fa49 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:53:22 +0100 Subject: [PATCH 122/289] another case of separate variable --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index b8d8d8f1..f588abc5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1045,8 +1045,8 @@ def _wait_for_msg(self, timeout: float = 0.1) -> Union[int, None]: sz -= topic_len + 2 pid = 0 if res[0] & 0x06: - pid = self._sock_exact_recv(2) - pid = pid[0] << 0x08 | pid[1] + pid_buf = self._sock_exact_recv(2) + pid = pid_buf[0] << 0x08 | pid_buf[1] sz -= 0x02 # read message contents From ee0cd3cd7d7bb614e0d4b77cf2972d6e8986e9d6 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 25 Feb 2023 10:54:22 +0100 Subject: [PATCH 123/289] another separate var declaration --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index f588abc5..a566d351 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1040,8 +1040,8 @@ def _wait_for_msg(self, timeout: float = 0.1) -> Union[int, None]: f"Topic length {topic_len} in PUBLISH packet exceeds remaining length {sz} - 2" ) - topic = self._sock_exact_recv(topic_len) - topic = str(topic, "utf-8") + topic_buf = self._sock_exact_recv(topic_len) + topic = str(topic_buf, "utf-8") sz -= topic_len + 2 pid = 0 if res[0] & 0x06: From aa0ffcdd0e0257cd5ed7bd0e5f8816a4554ba911 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Feb 2023 20:48:44 +0100 Subject: [PATCH 124/289] fix the return type in docstring --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a566d351..ab8ba670 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1076,7 +1076,7 @@ def _recv_len(self) -> int: def _sock_exact_recv(self, bufsize: int) -> bytearray: """Reads _exact_ number of bytes from the connected socket. Will only return - string with the exact number of bytes requested. + bytearray with the exact number of bytes requested. The semantics of native socket receive is that it returns no more than the specified number of bytes (i.e. max size). However, it makes no guarantees in From 5134596809507a82de69e0eb3ce97c4cb987458c Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Feb 2023 20:52:54 +0100 Subject: [PATCH 125/289] replace most of Union with Optional --- adafruit_minimqtt/adafruit_minimqtt.py | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ab8ba670..337d2724 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -33,7 +33,7 @@ from random import randint try: - from typing import List, Tuple, Union + from typing import List, Optional, Tuple, Union except ImportError: pass @@ -172,11 +172,11 @@ def __init__( self, *, broker: str, - port: Union[int, None] = None, - username: Union[str, None] = None, - password: Union[str, None] = None, - client_id: Union[str, None] = None, - is_ssl: Union[bool, None] = None, + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + client_id: Optional[str] = None, + is_ssl: Optional[bool] = None, keep_alive: int = 60, recv_timeout: int = 10, socket_pool=None, @@ -366,8 +366,8 @@ def mqtt_msg(self, msg_size: int) -> None: def will_set( self, - topic: Union[str, None] = None, - payload: Union[int, float, str, None] = None, + topic: Optional[str] = None, + payload: Optional[int, float, str] = None, qos: int = 0, retain: bool = False, ) -> None: @@ -445,7 +445,7 @@ def _handle_on_message(self, client, topic: str, message: str): if not matched and self.on_message: # regular on_message self.on_message(client, topic, message) - def username_pw_set(self, username: str, password: Union[str, None] = None) -> None: + def username_pw_set(self, username: str, password: Optional[str] = None) -> None: """Set client's username and an optional password. :param str username: Username to use with your MQTT broker. @@ -461,9 +461,9 @@ def username_pw_set(self, username: str, password: Union[str, None] = None) -> N def connect( self, clean_session: bool = True, - host: Union[str, None] = None, - port: Union[int, None] = None, - keep_alive: Union[int, None] = None, + host: Optional[str] = None, + port: Optional[int] = None, + keep_alive: Optional[int] = None, ) -> int: """Initiates connection with the MQTT Broker. Will perform exponential back-off on connect failures. @@ -523,9 +523,9 @@ def connect( def _connect( self, clean_session: bool = True, - host: Union[str, None] = None, - port: Union[int, None] = None, - keep_alive: Union[int, None] = None, + host: Optional[str] = None, + port: Optional[int] = None, + keep_alive: Optional[int] = None, ) -> int: """Initiates connection with the MQTT Broker. @@ -953,7 +953,7 @@ def reconnect(self, resub_topics: bool = True) -> int: return ret - def loop(self, timeout: float = 0) -> Union[list[int], None]: + def loop(self, timeout: float = 0) -> Optional[list[int]]: # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to check incoming subscription messages. @@ -993,7 +993,7 @@ def loop(self, timeout: float = 0) -> Union[list[int], None]: return rcs if rcs else None - def _wait_for_msg(self, timeout: float = 0.1) -> Union[int, None]: + def _wait_for_msg(self, timeout: float = 0.1) -> Optional[int]: # pylint: disable = too-many-return-statements """Reads and processes network events. From 2a319a1fb402964dee40af687c8ecad13e828e7a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Feb 2023 20:55:26 +0100 Subject: [PATCH 126/289] return one Union back --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 337d2724..11c9ceeb 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -367,7 +367,7 @@ def mqtt_msg(self, msg_size: int) -> None: def will_set( self, topic: Optional[str] = None, - payload: Optional[int, float, str] = None, + payload: Optional[Union[int, float, str]] = None, qos: int = 0, retain: bool = False, ) -> None: From be521a8cb1c515b50903c9c409bbb283d4dd7c32 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Feb 2023 20:58:36 +0100 Subject: [PATCH 127/289] more variable splitting --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 11c9ceeb..248501e1 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -756,8 +756,8 @@ def publish( if op == 0x40: sz = self._sock_exact_recv(1) assert sz == b"\x02" - rcv_pid = self._sock_exact_recv(2) - rcv_pid = rcv_pid[0] << 0x08 | rcv_pid[1] + rcv_pid_buf = self._sock_exact_recv(2) + rcv_pid = rcv_pid_buf[0] << 0x08 | rcv_pid_buf[1] if self._pid == rcv_pid: if self.on_publish is not None: self.on_publish(self, self._user_data, topic, rcv_pid) From 84ddaa18f89c77d6ef16a31e9a6613f25897f414 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Feb 2023 21:06:53 +0100 Subject: [PATCH 128/289] add type hints to __exit()__ --- adafruit_minimqtt/adafruit_minimqtt.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 248501e1..4d6e5080 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -33,7 +33,12 @@ from random import randint try: - from typing import List, Optional, Tuple, Union + from typing import List, Optional, Tuple, Type, Union +except ImportError: + pass + +try: + from types import TracebackType except ImportError: pass @@ -343,7 +348,12 @@ def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): def __enter__(self): return self - def __exit__(self, exception_type, exception_value, traceback) -> None: + def __exit__( + self, + exception_type: Optional[Type[BaseException]], + exception_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: self.deinit() def deinit(self) -> None: From 98c94a27177e97ccb1b63272f1f333272ae47140 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:05:54 -0400 Subject: [PATCH 129/289] Updating examples to use settings.toml Updating the esp32spi and native networking examples to use settings.toml --- .../esp32spi/minimqtt_adafruitio_esp32spi.py | 25 ++++++------- .../minimqtt_adafruitio_native_networking.py | 36 +++++++++---------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 7141e8bc..7edddcb8 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -1,25 +1,23 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio from digitalio import DigitalInOut import neopixel from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +aio_username = os.getenv('aio_username') +aio_key = os.getenv('aio_key') # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -46,15 +44,14 @@ # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) # status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = secrets["aio_username"] + "/feeds/photocell" +photocell_feed = aio_username + "/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = secrets["aio_username"] + "/feeds/onoff" +onoff_feed = aio_username + "/feeds/onoff" ### Code ### @@ -81,7 +78,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -wifi.connect() +esp.connect_AP(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) print("Connected!") # Initialize MQTT interface with the esp interface @@ -90,8 +87,8 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - username=secrets["aio_username"], - password=secrets["aio_key"], + username=aio_username, + password=aio_key, ) # Setup the callback methods above diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 21661d31..7e42abcc 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -1,38 +1,34 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import ssl import socketpool import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# Set your Adafruit IO Username and Key in secrets.py + +# Set your Adafruit IO Username, Key and Port in settings.toml # (visit io.adafruit.com if you need to create an account, # or if you need your Adafruit IO key.) -aio_username = secrets["aio_username"] -aio_key = secrets["aio_key"] +aio_username = os.getenv('aio_username') +aio_key = os.getenv('aio_key') +aio_port = os.getenv('port') -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!" % secrets["ssid"]) +print("Connecting to %s" % os.getenv('CIRCUITPY_WIFI_SSID')) +wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) +print("Connected to %s!" % os.getenv('CIRCUITPY_WIFI_SSID')) ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = secrets["aio_username"] + "/feeds/photocell" +photocell_feed = aio_username + "/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = secrets["aio_username"] + "/feeds/onoff" +onoff_feed = aio_username + "/feeds/onoff" ### Code ### @@ -63,9 +59,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - port=secrets["port"], - username=secrets["aio_username"], - password=secrets["aio_key"], + port=aio_port, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl.create_default_context(), ) From e5f953009fd3b96184a910b1559a7a768fb55bd5 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:10:23 -0400 Subject: [PATCH 130/289] black --- examples/esp32spi/minimqtt_adafruitio_esp32spi.py | 7 ++++--- .../minimqtt_adafruitio_native_networking.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 7edddcb8..68bf3dd6 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -16,8 +16,8 @@ # with your WiFi credentials. Add your Adafruit IO username and key as well. # DO NOT share that file or commit it into Git or other source control. -aio_username = os.getenv('aio_username') -aio_key = os.getenv('aio_key') +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -55,6 +55,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): @@ -78,7 +79,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -esp.connect_AP(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) +esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") # Initialize MQTT interface with the esp interface diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 7e42abcc..a4bce577 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -15,13 +15,15 @@ # Set your Adafruit IO Username, Key and Port in settings.toml # (visit io.adafruit.com if you need to create an account, # or if you need your Adafruit IO key.) -aio_username = os.getenv('aio_username') -aio_key = os.getenv('aio_key') -aio_port = os.getenv('port') +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") +aio_port = os.getenv("port") -print("Connecting to %s" % os.getenv('CIRCUITPY_WIFI_SSID')) -wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) -print("Connected to %s!" % os.getenv('CIRCUITPY_WIFI_SSID')) +print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") +) +print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed @@ -32,6 +34,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): From 1ad80850f76afb792b0cc7a822d61d2caebc5659 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:19:47 -0400 Subject: [PATCH 131/289] updating simpletest --- examples/minimqtt_simpletest.py | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 2f1c49b5..17927443 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -1,30 +1,29 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import ssl import socketpool import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# Set your Adafruit IO Username and Key in secrets.py + +# Set your Adafruit IO Username and Key in settings.toml # (visit io.adafruit.com if you need to create an account, # or if you need your Adafruit IO key.) -aio_username = secrets["aio_username"] -aio_key = secrets["aio_key"] - -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!" % secrets["ssid"]) +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") +aio_broker = os.getenv("broker") +aio_port = os.getenv("port") + +print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") +) +print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Topic Setup ### @@ -36,6 +35,7 @@ # Use this topic if you'd like to connect to io.adafruit.com # mqtt_topic = secrets["aio_username"] + '/feeds/temperature' + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name @@ -77,10 +77,10 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - port=secrets["port"], - username=secrets["aio_username"], - password=secrets["aio_key"], + broker=aio_broker, + port=aio_port, + username=os.getenv("CIRCUITPY_WIFI_SSID"), + password=os.getenv("CIRCUITPY_WIFI_PASSWORD"), socket_pool=pool, ssl_context=ssl.create_default_context(), ) From 466120471e538dd37b183e30033263eee6a11aa7 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:33:26 -0400 Subject: [PATCH 132/289] updating pub sub examples --- .../minimqtt_pub_sub_blocking_esp32spi.py | 28 ++++++++++--------- .../minimqtt_pub_sub_nonblocking_esp32spi.py | 22 +++++++-------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index 734816ea..7f19067a 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -1,25 +1,23 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio from digitalio import DigitalInOut import neopixel from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -46,15 +44,15 @@ # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) # status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = secrets["user"] + "/feeds/testfeed" +default_topic = aio_username + "/feeds/testfeed" ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): @@ -81,7 +79,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -wifi.connect() +esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") # Initialize MQTT interface with the esp interface @@ -89,7 +87,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], username=secrets["user"], password=secrets["pass"] + broker=os.getenv("broker"), username=aio_username, password=aio_key ) # Setup the callback methods above @@ -109,7 +107,11 @@ def message(client, topic, message): mqtt_client.loop() except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) - wifi.reset() + esp.reset() + time.sleep(1) + esp.connect_AP( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") + ) mqtt_client.reconnect() continue time.sleep(1) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 6ff98d27..525e01b9 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -1,25 +1,23 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio from digitalio import DigitalInOut import neopixel from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -46,12 +44,12 @@ # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) # status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = secrets["user"] + "/feeds/testfeed" +default_topic = aio_username + "/feeds/testfeed" + ### Code ### # Define callback methods which are called when events occur @@ -80,7 +78,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -wifi.connect() +esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") # Initialize MQTT interface with the esp interface @@ -88,7 +86,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], username=secrets["user"], password=secrets["pass"] + broker=os.getenv("broker"), username=aio_username, password=aio_key ) # Setup the callback methods above From 8212c464ddf75d3fa5d3232937a675436c90f501 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:50:37 -0400 Subject: [PATCH 133/289] updating mqtt_client --- examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py | 2 +- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 2 +- examples/minimqtt_simpletest.py | 6 ++---- .../minimqtt_adafruitio_native_networking.py | 7 +++---- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index 7f19067a..a4e508c9 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -87,7 +87,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), username=aio_username, password=aio_key + broker="io.adafruit.com", username=aio_username, password=aio_key ) # Setup the callback methods above diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 525e01b9..9bf3755b 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -86,7 +86,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), username=aio_username, password=aio_key + broker="io.adafruit.com", username=aio_username, password=aio_key ) # Setup the callback methods above diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 17927443..2faa8067 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -16,8 +16,6 @@ # or if you need your Adafruit IO key.) aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") -aio_broker = os.getenv("broker") -aio_port = os.getenv("port") print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) wifi.radio.connect( @@ -77,8 +75,8 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=aio_broker, - port=aio_port, + broker="io.adafruit.com", + port=8883, username=os.getenv("CIRCUITPY_WIFI_SSID"), password=os.getenv("CIRCUITPY_WIFI_PASSWORD"), socket_pool=pool, diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index a4bce577..d4e892ba 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -17,7 +17,6 @@ # or if you need your Adafruit IO key.) aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") -aio_port = os.getenv("port") print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) wifi.radio.connect( @@ -62,9 +61,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - port=aio_port, - username=aio_username, - password=aio_key, + port=8883, + username=os.getenv("CIRCUITPY_WIFI_SSID"), + password=os.getenv("CIRCUITPY_WIFI_PASSWORD"), socket_pool=pool, ssl_context=ssl.create_default_context(), ) From eca04848c226b396a9355a9bdce7e0b7265dda4d Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 25 Apr 2023 11:52:50 -0400 Subject: [PATCH 134/289] fixing simpletest --- examples/minimqtt_simpletest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 2faa8067..397cbbe9 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -31,7 +31,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = secrets["aio_username"] + '/feeds/temperature' +# mqtt_topic = aio_username + '/feeds/temperature' ### Code ### @@ -77,8 +77,8 @@ def message(client, topic, message): mqtt_client = MQTT.MQTT( broker="io.adafruit.com", port=8883, - username=os.getenv("CIRCUITPY_WIFI_SSID"), - password=os.getenv("CIRCUITPY_WIFI_PASSWORD"), + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl.create_default_context(), ) From 5ee757dffba2cd359795143c3cdbf5c1f9cac23a Mon Sep 17 00:00:00 2001 From: Zane Bauman Date: Thu, 4 May 2023 16:40:00 -0400 Subject: [PATCH 135/289] docs: add examples using cert/key pair --- .../minimqtt_adafruitio_native_networking.py | 11 ++++++++++- .../minimqtt_pub_sub_blocking_native_networking.py | 11 ++++++++++- ..._sub_blocking_topic_callbacks_native_networking.py | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 21661d31..b16aaf80 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -36,6 +36,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): @@ -59,6 +60,14 @@ def message(client, topic, message): # Create a socket pool pool = socketpool.SocketPool(wifi.radio) +ssl_context = ssl.create_default_context() + +# If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the +# ssl context by uncommenting the line below +# ssl_context.load_cert_chain( +# certfile=secrets['device_cert_path'], +# keyfile=secrets['device_key_path'] +# ) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( @@ -67,7 +76,7 @@ def message(client, topic, message): username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=pool, - ssl_context=ssl.create_default_context(), + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 58dbc7f7..4016433d 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -32,6 +32,7 @@ # Setup a feed named `testfeed` for publishing. default_topic = secrets["aio_username"] + "/feeds/testfeed" + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name @@ -59,6 +60,14 @@ def message(client, topic, message): # Create a socket pool pool = socketpool.SocketPool(wifi.radio) +ssl_context = ssl.create_default_context() + +# If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the +# ssl context by uncommenting the line below +# ssl_context.load_cert_chain( +# certfile=secrets['device_cert_path'], +# keyfile=secrets['device_key_path'] +# ) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( @@ -67,7 +76,7 @@ def message(client, topic, message): username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=pool, - ssl_context=ssl.create_default_context(), + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 2a2eddf3..433a4376 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -29,6 +29,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): @@ -66,6 +67,14 @@ def on_message(client, topic, message): # Create a socket pool pool = socketpool.SocketPool(wifi.radio) +ssl_context = ssl.create_default_context() + +# If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the +# ssl context by uncommenting the line below +# ssl_context.load_cert_chain( +# certfile=secrets['device_cert_path'], +# keyfile=secrets['device_key_path'] +# ) # Set up a MiniMQTT Client client = MQTT.MQTT( @@ -74,7 +83,7 @@ def on_message(client, topic, message): username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=pool, - ssl_context=ssl.create_default_context(), + ssl_context=ssl_context, ) # Setup the callback methods above From 877f7bd6537ec10066fdc0f456409fdbcca444bf Mon Sep 17 00:00:00 2001 From: Zane Bauman Date: Fri, 5 May 2023 07:55:48 -0400 Subject: [PATCH 136/289] docs: add more context --- .../minimqtt_adafruitio_native_networking.py | 8 +++++--- .../minimqtt_pub_sub_blocking_native_networking.py | 8 +++++--- ..._pub_sub_blocking_topic_callbacks_native_networking.py | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index b16aaf80..75720b31 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -63,10 +63,12 @@ def message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the line below +# ssl context by uncommenting the lines below and adding the following keys to the "secrets" +# dictionary in your secrets.py file: +# "device_cert_path" - Path to the Device Certificate +# "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets['device_cert_path'], -# keyfile=secrets['device_key_path'] +# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 4016433d..b296eacc 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -63,10 +63,12 @@ def message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the line below +# ssl context by uncommenting the lines below and adding the following keys to the "secrets" +# dictionary in your secrets.py file: +# "device_cert_path" - Path to the Device Certificate +# "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets['device_cert_path'], -# keyfile=secrets['device_key_path'] +# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 433a4376..f38b627d 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -70,10 +70,12 @@ def on_message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the line below +# ssl context by uncommenting the lines below and adding the following keys to the "secrets" +# dictionary in your secrets.py file: +# "device_cert_path" - Path to the Device Certificate +# "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets['device_cert_path'], -# keyfile=secrets['device_key_path'] +# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] # ) # Set up a MiniMQTT Client From b6a336ab2b69be3256d8e667bc050982049b9bb6 Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Tue, 9 May 2023 20:26:25 -0400 Subject: [PATCH 137/289] Update pre-commit hooks Signed-off-by: Tekktrik --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e5fccc2..70ade69d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,21 +4,21 @@ repos: - repo: https://github.com/python/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: v0.14.0 + rev: v1.1.2 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.15.5 + rev: v2.17.4 hooks: - id: pylint name: pylint (library code) From c6a0f53e1c2b2505b920d6c1ebf0fcb7c914d1d6 Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Wed, 10 May 2023 22:38:50 -0400 Subject: [PATCH 138/289] Run pre-commit --- adafruit_minimqtt/adafruit_minimqtt.py | 1 - examples/cellular/minimqtt_adafruitio_cellular.py | 1 + examples/cellular/minimqtt_simpletest_cellular.py | 1 + examples/cpython/minimqtt_adafruitio_cpython.py | 1 + examples/cpython/minimqtt_simpletest_cpython.py | 1 + examples/esp32spi/minimqtt_adafruitio_esp32spi.py | 1 + examples/esp32spi/minimqtt_certificate_esp32spi.py | 1 + examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py | 1 + .../minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py | 1 + examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 1 + examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py | 1 + examples/esp32spi/minimqtt_simpletest_esp32spi.py | 1 + examples/ethernet/minimqtt_adafruitio_eth.py | 1 + examples/ethernet/minimqtt_simpletest_eth.py | 1 + examples/minimqtt_simpletest.py | 1 + 15 files changed, 14 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 0a3e7311..7f96ea9d 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -191,7 +191,6 @@ def __init__( connect_retries: int = 5, user_data=None, ) -> None: - self._socket_pool = socket_pool self._ssl_context = ssl_context self._sock = None diff --git a/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index d83b410b..5f1c4d11 100755 --- a/examples/cellular/minimqtt_adafruitio_cellular.py +++ b/examples/cellular/minimqtt_adafruitio_cellular.py @@ -36,6 +36,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/cellular/minimqtt_simpletest_cellular.py b/examples/cellular/minimqtt_simpletest_cellular.py index 24bd33ac..57b11676 100644 --- a/examples/cellular/minimqtt_simpletest_cellular.py +++ b/examples/cellular/minimqtt_simpletest_cellular.py @@ -38,6 +38,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 3667329b..7ebcf0fe 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -25,6 +25,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/cpython/minimqtt_simpletest_cpython.py b/examples/cpython/minimqtt_simpletest_cpython.py index f0d71a09..551fa0b7 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -25,6 +25,7 @@ # Use this topic if you'd like to connect to io.adafruit.com # mqtt_topic = secrets["aio_username"] + "/feeds/temperature" + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 7141e8bc..53242b66 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -58,6 +58,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index 50f002eb..f3e2d791 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -59,6 +59,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index 734816ea..8c323ba1 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -55,6 +55,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index 60b4504f..5094e4b4 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -50,6 +50,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 6ff98d27..8a5ce8ba 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -53,6 +53,7 @@ # Setup a feed named `testfeed` for publishing. default_topic = secrets["user"] + "/feeds/testfeed" + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index 6409cdd0..b98d92b5 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -21,6 +21,7 @@ # ------------- MQTT Topic Setup ------------- # mqtt_topic = "test/topic" + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index 254253dc..96a71474 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -57,6 +57,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 753bf473..5c114cee 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -34,6 +34,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index 3e91c8fc..16846d5c 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -33,6 +33,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 2f1c49b5..9defad6f 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -36,6 +36,7 @@ # Use this topic if you'd like to connect to io.adafruit.com # mqtt_topic = secrets["aio_username"] + '/feeds/temperature' + ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name From b094348620be01ddea1fc3bdfdebc9bd5687d862 Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Thu, 11 May 2023 00:30:13 -0400 Subject: [PATCH 139/289] Linted per pre-commit --- adafruit_minimqtt/matcher.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 141a4f05..c14a3514 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -70,11 +70,10 @@ def __delitem__(self, key: str) -> None: node.content = None except KeyError: raise KeyError(key) from None - else: # cleanup - for parent, k, node in reversed(lst): - if node.children or node.content is not None: - break - del parent.children[k] + for parent, k, node in reversed(lst): + if node.children or node.content is not None: + break + del parent.children[k] def iter_match(self, topic: str): """Return an iterator on all values associated with filters From 40b90968fb744ebaffce6b09ca27b5a7e747bbff Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Sun, 14 May 2023 13:00:32 -0400 Subject: [PATCH 140/289] Update .pylintrc, fix jQuery for docs Signed-off-by: Tekktrik --- .pylintrc | 2 +- docs/conf.py | 1 + docs/requirements.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 40208c39..f945e920 100644 --- a/.pylintrc +++ b/.pylintrc @@ -396,4 +396,4 @@ min-public-methods=1 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception diff --git a/docs/conf.py b/docs/conf.py index 84fade74..ddab6d36 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinxcontrib.jquery", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.todo", diff --git a/docs/requirements.txt b/docs/requirements.txt index 88e67331..797aa045 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense sphinx>=4.0.0 +sphinxcontrib-jquery From 5f743abbe7de731b2966bddad39cd667793be275 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Jun 2023 22:41:51 +0200 Subject: [PATCH 141/289] loop() timeout parameter should be absolute --- adafruit_minimqtt/adafruit_minimqtt.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7f96ea9d..2f448475 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -980,11 +980,10 @@ def reconnect(self, resub_topics: bool = True) -> int: def loop(self, timeout: float = 0) -> Optional[list[int]]: # pylint: disable = too-many-return-statements - """Non-blocking message loop. Use this method to - check incoming subscription messages. - Returns response codes of any messages received. + """Non-blocking message loop. Use this method to check for incoming messages. + Returns list of response codes of any messages received or None. - :param float timeout: Socket timeout, in seconds. + :param float timeout: timeout to wait for a message, in seconds. """ @@ -1002,23 +1001,21 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: return rcs stamp = time.monotonic() - self._sock.settimeout(timeout) rcs = [] while True: - rc = self._wait_for_msg(timeout) - if rc is None: - break - if time.monotonic() - stamp > self._recv_timeout: + rc = self._wait_for_msg() + if rc is not None: + rcs.append(rc) + if time.monotonic() - stamp > timeout: self.logger.debug( - f"Loop timed out, message queue not empty after {self._recv_timeout}s" + f"Loop timed out, message queue empty after {timeout} seconds" ) break - rcs.append(rc) return rcs if rcs else None - def _wait_for_msg(self, timeout: float = 0.1) -> Optional[int]: + def _wait_for_msg(self) -> Optional[int]: # pylint: disable = too-many-return-statements """Reads and processes network events. @@ -1039,8 +1036,6 @@ def _wait_for_msg(self, timeout: float = 0.1) -> Optional[int]: return None raise MMQTTException from error - # Block while we parse the rest of the response - self._sock.settimeout(timeout) if res in [None, b"", b"\x00"]: # If we get here, it means that there is nothing to be received return None From 9cd7392cff89711d6e4a13bb335481b5a332565a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Jun 2023 22:47:27 +0200 Subject: [PATCH 142/289] adjust the log message --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2f448475..1c9fc39f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1008,9 +1008,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: if rc is not None: rcs.append(rc) if time.monotonic() - stamp > timeout: - self.logger.debug( - f"Loop timed out, message queue empty after {timeout} seconds" - ) + self.logger.debug(f"Loop timed out after {timeout} seconds") break return rcs if rcs else None From 4e5e22eefdc8c41b761085c2c8141977011b120d Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Jun 2023 22:49:24 +0200 Subject: [PATCH 143/289] adjust the doc --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1c9fc39f..6b974ddc 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -983,7 +983,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: """Non-blocking message loop. Use this method to check for incoming messages. Returns list of response codes of any messages received or None. - :param float timeout: timeout to wait for a message, in seconds. + :param float timeout: return after this timeout, in seconds. """ From 4f57b20bfc360ef82ba5e2fc40eeb120965adeac Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 22 Jun 2023 15:10:37 +0200 Subject: [PATCH 144/289] bump to 3 seconds for stability --- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 8a5ce8ba..30611384 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -109,4 +109,4 @@ def message(client, topic, message): print("Sending photocell value: %d" % photocell_val) mqtt_client.publish(default_topic, photocell_val) photocell_val += 1 - time.sleep(0.5) + time.sleep(3) From 0794d69018d27325cd7baf25bd265e32cc35f54e Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 7 Jul 2023 10:53:52 -0400 Subject: [PATCH 145/289] Updating examples for settings dot toml Retested examples using a Feather ESP32-S2 for native and a Metro M7 for esp32spi --- .../esp32spi/minimqtt_simpletest_esp32spi.py | 1 + examples/minimqtt_simpletest.py | 58 ++++++++++++------- .../minimqtt_adafruitio_native_networking.py | 16 ++--- ...mqtt_pub_sub_blocking_native_networking.py | 38 ++++++------ ...cking_topic_callbacks_native_networking.py | 44 +++++++------- 5 files changed, 83 insertions(+), 74 deletions(-) diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index 254253dc..96a71474 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -57,6 +57,7 @@ ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 397cbbe9..5bc6524f 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -2,39 +2,57 @@ # SPDX-License-Identifier: MIT import os -import ssl -import socketpool -import wifi +import board +import busio +from digitalio import DigitalInOut +from adafruit_esp32spi import adafruit_esp32spi +import adafruit_esp32spi.adafruit_esp32spi_socket as socket import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Set your Adafruit IO Username and Key in settings.toml -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") -print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) -wifi.radio.connect( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") -) -print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) + +print("Connecting to AP...") +while not esp.is_connected: + try: + esp.connect_AP( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") + ) + except RuntimeError as e: + print("could not connect to AP, retrying: ", e) + continue +print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) ### Topic Setup ### # MQTT Topic # Use this topic if you'd like to connect to a standard MQTT broker -mqtt_topic = "test/topic" +# mqtt_topic = "test/topic" # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = aio_username + '/feeds/temperature' - +mqtt_topic = aio_username + "/feeds/temperature" ### Code ### + + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): @@ -66,21 +84,17 @@ def publish(mqtt_client, userdata, topic, pid): def message(client, topic, message): - # Method called when a client's subscribed feed has a new value. print("New message on topic {0}: {1}".format(topic, message)) -# Create a socket pool -pool = socketpool.SocketPool(wifi.radio) +socket.set_interface(esp) +MQTT.set_socket(socket, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - port=8883, username=aio_username, password=aio_key, - socket_pool=pool, - ssl_context=ssl.create_default_context(), ) # Connect callback handlers to mqtt_client diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index d4e892ba..9e7c89e3 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -18,11 +18,11 @@ aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") -print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") wifi.radio.connect( os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") ) -print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) +print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed @@ -39,7 +39,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed) + print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(onoff_feed) @@ -52,7 +52,7 @@ def disconnected(client, userdata, rc): def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Create a socket pool @@ -61,9 +61,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - port=8883, - username=os.getenv("CIRCUITPY_WIFI_SSID"), - password=os.getenv("CIRCUITPY_WIFI_PASSWORD"), + port=1883, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl.create_default_context(), ) @@ -83,7 +83,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d..." % photocell_val) + print(f"Sending photocell value: {photocell_val}...") mqtt_client.publish(photocell_feed, photocell_val) print("Sent!") photocell_val += 1 diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 58dbc7f7..bbcab99e 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -1,36 +1,34 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import ssl import socketpool import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# Set your Adafruit IO Username and Key in secrets.py + +# Set your Adafruit IO Username, Key and Port in settings.toml # (visit io.adafruit.com if you need to create an account, # or if you need your Adafruit IO key.) -aio_username = secrets["aio_username"] -aio_key = secrets["aio_key"] +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!" % secrets["ssid"]) +print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") +) +print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = secrets["aio_username"] + "/feeds/testfeed" +default_topic = aio_username + "/feeds/testfeed" + ### Code ### # Define callback methods which are called when events occur @@ -62,10 +60,10 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - port=secrets["port"], - username=secrets["aio_username"], - password=secrets["aio_key"], + broker="io.adafruit.com", + port=1883, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl.create_default_context(), ) diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 2a2eddf3..7f18f55b 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -1,34 +1,32 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import ssl import socketpool import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# Set your Adafruit IO Username and Key in secrets.py + +# Set your Adafruit IO Username, Key and Port in settings.toml # (visit io.adafruit.com if you need to create an account, # or if you need your Adafruit IO key.) -aio_username = secrets["aio_username"] -aio_key = secrets["aio_key"] +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!" % secrets["ssid"]) +print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") +) +print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Code ### + # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): @@ -56,7 +54,7 @@ def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value print("Battery level: {}v".format(message)) - # client.remove_topic_callback(secrets["aio_username"] + "/feeds/device.batterylevel") + # client.remove_topic_callback(aio_username + "/feeds/device.batterylevel") def on_message(client, topic, message): @@ -69,10 +67,10 @@ def on_message(client, topic, message): # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=secrets["broker"], - port=secrets["port"], - username=secrets["aio_username"], - password=secrets["aio_key"], + broker="io.adafruit.com", + port=1883, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl.create_default_context(), ) @@ -83,16 +81,14 @@ def on_message(client, topic, message): client.on_subscribe = subscribe client.on_unsubscribe = unsubscribe client.on_message = on_message -client.add_topic_callback( - secrets["aio_username"] + "/feeds/device.batterylevel", on_battery_msg -) +client.add_topic_callback(aio_username + "/feeds/device.batterylevel", on_battery_msg) # Connect the client to the MQTT broker. print("Connecting to MQTT broker...") client.connect() # Subscribe to all notifications on the device group -client.subscribe(secrets["aio_username"] + "/groups/device", 1) +client.subscribe(aio_username + "/groups/device", 1) # Start a blocking message loop... # NOTE: NO code below this loop will execute From e8532e043252e0e9e178231d8d0898e57a43c565 Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 7 Jul 2023 11:06:09 -0400 Subject: [PATCH 146/289] black --- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 6 +++--- .../minimqtt_pub_sub_blocking_native_networking.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 9bf3755b..d0ae3feb 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -57,7 +57,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to MQTT broker! Listening for topic changes on %s" % default_topic) + print(f"Connected to MQTT broker! Listening for topic changes on {default_topic}") # Subscribe to all changes on the default_topic feed. client.subscribe(default_topic) @@ -73,7 +73,7 @@ def message(client, topic, message): :param str topic: The topic of the feed with a new value. :param str message: The new value """ - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Connect to WiFi @@ -103,7 +103,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d" % photocell_val) + print(f"Sending photocell value: {photocell_val}") mqtt_client.publish(default_topic, photocell_val) photocell_val += 1 time.sleep(0.5) diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index bbcab99e..384d4df1 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -36,7 +36,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to MQTT broker! Listening for topic changes on %s" % default_topic) + print(f"Connected to MQTT broker! Listening for topic changes on {default_topic}") # Subscribe to all changes on the default_topic feed. client.subscribe(default_topic) @@ -52,7 +52,7 @@ def message(client, topic, message): :param str topic: The topic of the feed with a new value. :param str message: The new value """ - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Create a socket pool From 91a0f13048cebcc3b39150cebd61a938e2e9b79b Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 7 Jul 2023 11:24:36 -0400 Subject: [PATCH 147/289] black --- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 2 +- .../minimqtt_pub_sub_blocking_native_networking.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index d0ae3feb..758d805e 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT import os diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 384d4df1..55684dcf 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT import os From 1da654e232d2b8e23a5dae806ac529bda4d3fa2b Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 7 Jul 2023 11:25:50 -0400 Subject: [PATCH 148/289] black --- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 2 +- .../minimqtt_pub_sub_blocking_native_networking.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 758d805e..d0ae3feb 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT import os diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 55684dcf..384d4df1 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT import os From 6b96ab3c5cc97b21a307419275f10a73c157e5ad Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 7 Jul 2023 12:52:58 -0500 Subject: [PATCH 149/289] merge main and code format --- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 1 - .../minimqtt_pub_sub_blocking_native_networking.py | 1 - 2 files changed, 2 deletions(-) diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 77e84bf9..d0ae3feb 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -51,7 +51,6 @@ default_topic = aio_username + "/feeds/testfeed" - ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 569a58c6..0ba76dfb 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -30,7 +30,6 @@ default_topic = aio_username + "/feeds/testfeed" - ### Code ### # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name From 6798689ba1b7596933cec08be5e6122d2763d777 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 24 Jul 2023 23:06:24 +0200 Subject: [PATCH 150/289] add basic loop() test --- tests/test_loop.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/test_loop.py diff --git a/tests/test_loop.py b/tests/test_loop.py new file mode 100644 index 00000000..4ee3fa65 --- /dev/null +++ b/tests/test_loop.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""loop() tests""" + +import random +import socket +import ssl +import time +from unittest import TestCase, main +from unittest.mock import patch + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +class Loop(TestCase): + """basic loop() test""" + + connect_times = [] + INITIAL_RCS_VAL = 42 + rcs_val = INITIAL_RCS_VAL + + def fake_wait_for_msg(self): + """_wait_for_msg() replacement. Sleeps for 1 second and returns an integer.""" + time.sleep(1) + retval = self.rcs_val + self.rcs_val += 1 + return retval + + def test_loop_basic(self) -> None: + """ + test that loop() returns only after the specified timeout, regardless whether + _wait_for_msg() returned repeatedly within that timeout. + """ + + host = "172.40.0.3" + port = 1883 + + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + ) + + with patch.object(mqtt_client, "_wait_for_msg") as mock_method: + mock_method.side_effect = self.fake_wait_for_msg + + time_before = time.time() + timeout = random.randint(3, 8) + rcs = mqtt_client.loop(timeout=timeout) + time_after = time.time() + + assert time_after - time_before >= timeout + mock_method.assert_called() + + # Check the return value. + assert rcs is not None + assert len(rcs) > 1 + expected_rc = self.INITIAL_RCS_VAL + for ret_code in rcs: + assert ret_code == expected_rc + expected_rc += 1 + + +if __name__ == "__main__": + main() From 7eb02215635a84c96ebfceef752ec85632787ba8 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 25 Jul 2023 09:20:42 +0200 Subject: [PATCH 151/289] use monotonic time --- tests/test_loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 4ee3fa65..80ffc227 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -47,10 +47,10 @@ def test_loop_basic(self) -> None: with patch.object(mqtt_client, "_wait_for_msg") as mock_method: mock_method.side_effect = self.fake_wait_for_msg - time_before = time.time() + time_before = time.monotonic() timeout = random.randint(3, 8) rcs = mqtt_client.loop(timeout=timeout) - time_after = time.time() + time_after = time.monotonic() assert time_after - time_before >= timeout mock_method.assert_called() From 0297a4dc65144b3e54a9b70b04ccce0d6c774f4c Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 25 Aug 2023 22:25:39 +0200 Subject: [PATCH 152/289] check is_connected in loop() --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6b974ddc..7e4f1826 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -986,7 +986,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: :param float timeout: return after this timeout, in seconds. """ - + self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") if self._timestamp == 0: self._timestamp = time.monotonic() From 324940d748dd4b4b0b460ab27800e2af1aa117db Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 25 Aug 2023 22:37:58 +0200 Subject: [PATCH 153/289] fix test --- tests/test_loop.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 80ffc227..e5b0748c 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -44,8 +44,11 @@ def test_loop_basic(self) -> None: ssl_context=ssl.create_default_context(), ) - with patch.object(mqtt_client, "_wait_for_msg") as mock_method: - mock_method.side_effect = self.fake_wait_for_msg + with patch.object(mqtt_client, "_wait_for_msg") as wait_for_msg_mock, \ + patch.object(mqtt_client, "is_connected") as is_connected_mock: + + wait_for_msg_mock.side_effect = self.fake_wait_for_msg + is_connected_mock.side_effect = lambda: True time_before = time.monotonic() timeout = random.randint(3, 8) @@ -53,7 +56,7 @@ def test_loop_basic(self) -> None: time_after = time.monotonic() assert time_after - time_before >= timeout - mock_method.assert_called() + wait_for_msg_mock.assert_called() # Check the return value. assert rcs is not None From 52a04c3b1180327a067163b7211deb3aa959791c Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 25 Aug 2023 22:49:48 +0200 Subject: [PATCH 154/289] add test --- tests/test_loop.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index e5b0748c..6014c010 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -44,8 +44,11 @@ def test_loop_basic(self) -> None: ssl_context=ssl.create_default_context(), ) - with patch.object(mqtt_client, "_wait_for_msg") as wait_for_msg_mock, \ - patch.object(mqtt_client, "is_connected") as is_connected_mock: + with patch.object( + mqtt_client, "_wait_for_msg" + ) as wait_for_msg_mock, patch.object( + mqtt_client, "is_connected" + ) as is_connected_mock: wait_for_msg_mock.side_effect = self.fake_wait_for_msg is_connected_mock.side_effect = lambda: True @@ -66,6 +69,22 @@ def test_loop_basic(self) -> None: assert ret_code == expected_rc expected_rc += 1 + def test_loop_is_connected(self): + """ + loop() should throw MMQTTException if not connected + """ + mqtt_client = MQTT.MQTT( + broker="127.0.0.1", + port=1883, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + ) + + with self.assertRaises(MQTT.MMQTTException) as context: + mqtt_client.loop(timeout=1) + + assert "not connected" in str(context.exception) + if __name__ == "__main__": main() From 6b345de31ef37bdb0d297b856ece0e890acab9c4 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 25 Aug 2023 22:58:50 +0200 Subject: [PATCH 155/289] apply black --- tests/test_loop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 6014c010..6a762fde 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -49,7 +49,6 @@ def test_loop_basic(self) -> None: ) as wait_for_msg_mock, patch.object( mqtt_client, "is_connected" ) as is_connected_mock: - wait_for_msg_mock.side_effect = self.fake_wait_for_msg is_connected_mock.side_effect = lambda: True From e7c26859c3cda61e1f942f7ad4079e5014cd2dbf Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 18 Sep 2023 16:24:03 -0500 Subject: [PATCH 156/289] "fix rtd theme " --- docs/conf.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ddab6d36..eb5696d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -100,19 +100,10 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 61dbdf0cf8568dd7debd67a3212ad22cd45bbc57 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 25 Oct 2023 22:59:36 +0200 Subject: [PATCH 157/289] no need to pass self to _handle_on_message() --- adafruit_minimqtt/adafruit_minimqtt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6b974ddc..cbeb8f6c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -444,15 +444,15 @@ def on_message(self): def on_message(self, method) -> None: self._on_message = method - def _handle_on_message(self, client, topic: str, message: str): + def _handle_on_message(self, topic: str, message: str): matched = False if topic is not None: for callback in self._on_message_filtered.iter_match(topic): - callback(client, topic, message) # on_msg with callback + callback(self, topic, message) # on_msg with callback matched = True if not matched and self.on_message: # regular on_message - self.on_message(client, topic, message) + self.on_message(self, topic, message) def username_pw_set(self, username: str, password: Optional[str] = None) -> None: """Set client's username and an optional password. @@ -1072,7 +1072,7 @@ def _wait_for_msg(self) -> Optional[int]: raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") self.logger.debug("Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) - self._handle_on_message(self, topic, msg) + self._handle_on_message(topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") struct.pack_into("!H", pkt, 2, pid) From 690007e87e489a2bafd2b7875e341e253f0312fc Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 30 Oct 2023 22:40:19 +0100 Subject: [PATCH 158/289] use time.monotonic_ns() when available otherwise fall back to time.monotonic() however only if forced fixes #176 --- adafruit_minimqtt/adafruit_minimqtt.py | 69 ++++++++++++++++++-------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cbeb8f6c..f15980d0 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -169,10 +169,12 @@ class MQTT: :param int connect_retries: How many times to try to connect to the broker before giving up on connect or reconnect. Exponential backoff will be used for the retries. :param class user_data: arbitrary data to pass as a second argument to the callbacks. + :param bool use_imprecise_time: on boards without time.monotonic_ns() one has to set + this to True in order to operate correctly over more than 24 days or so """ - # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements, not-callable, invalid-name, no-member + # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements,not-callable,invalid-name,no-member,too-many-locals def __init__( self, *, @@ -190,6 +192,7 @@ def __init__( socket_timeout: int = 1, connect_retries: int = 5, user_data=None, + use_imprecise_time: Optional[bool] = None, ) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context @@ -197,6 +200,20 @@ def __init__( self._backwards_compatible_sock = False self._use_binary_mode = use_binary_mode + self.use_monotonic_ns = False + try: + time.monotonic_ns() + self.use_monotonic_ns = True + except AttributeError: + if use_imprecise_time: + self.use_monotonic_ns = False + else: + raise MMQTTException( # pylint: disable=raise-missing-from + "time.monotonic_ns() is not available. " + "Will use imprecise time however only if the" + "use_imprecise_time argument is set to True." + ) + if recv_timeout <= socket_timeout: raise MMQTTException( "recv_timeout must be strictly greater than socket_timeout" @@ -248,9 +265,8 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - self.client_id = ( - f"cpy{randint(0, int(time.monotonic() * 100) % 1000)}{randint(0, 99)}" - ) + time_int = int(self.get_monotonic_time() * 100) % 1000 + self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: raise ValueError("MQTT Client ID must be between 1 and 23 bytes") @@ -273,6 +289,17 @@ def __init__( self.on_subscribe = None self.on_unsubscribe = None + def get_monotonic_time(self) -> float: + """ + Provide monotonic time in seconds. Based on underlying implementation + this might result in imprecise time, that will result in the library + not being able to operate if running contiguously for more than 24 days or so. + """ + if self.use_monotonic_ns: + return time.monotonic_ns() / 1000000000 + + return time.monotonic() + # pylint: disable=too-many-branches def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): """Obtains a new socket and connects to a broker. @@ -627,7 +654,7 @@ def _connect( self._send_str(self._username) self._send_str(self._password) self.logger.debug("Receiving CONNACK packet from broker") - stamp = time.monotonic() + stamp = self.get_monotonic_time() while True: op = self._wait_for_msg() if op == 32: @@ -643,7 +670,7 @@ def _connect( return result if op is None: - if time.monotonic() - stamp > self._recv_timeout: + if self.get_monotonic_time() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -672,13 +699,13 @@ def ping(self) -> list[int]: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive - stamp = time.monotonic() + stamp = self.get_monotonic_time() rc, rcs = None, [] while rc != MQTT_PINGRESP: rc = self._wait_for_msg() if rc: rcs.append(rc) - if time.monotonic() - stamp > ping_timeout: + if self.get_monotonic_time() - stamp > ping_timeout: raise MMQTTException("PINGRESP not returned from broker.") return rcs @@ -759,7 +786,7 @@ def publish( if qos == 0 and self.on_publish is not None: self.on_publish(self, self._user_data, topic, self._pid) if qos == 1: - stamp = time.monotonic() + stamp = self.get_monotonic_time() while True: op = self._wait_for_msg() if op == 0x40: @@ -773,7 +800,7 @@ def publish( return if op is None: - if time.monotonic() - stamp > self._recv_timeout: + if self.get_monotonic_time() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -825,11 +852,11 @@ def subscribe(self, topic: str, qos: int = 0) -> None: for t, q in topics: self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) self._sock.send(packet) - stamp = time.monotonic() + stamp = self.get_monotonic_time() while True: op = self._wait_for_msg() if op is None: - if time.monotonic() - stamp > self._recv_timeout: + if self.get_monotonic_time() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -892,10 +919,10 @@ def unsubscribe(self, topic: str) -> None: self._sock.send(packet) self.logger.debug("Waiting for UNSUBACK...") while True: - stamp = time.monotonic() + stamp = self.get_monotonic_time() op = self._wait_for_msg() if op is None: - if time.monotonic() - stamp > self._recv_timeout: + if self.get_monotonic_time() - stamp > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -989,8 +1016,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: self.logger.debug(f"waiting for messages for {timeout} seconds") if self._timestamp == 0: - self._timestamp = time.monotonic() - current_time = time.monotonic() + self._timestamp = self.get_monotonic_time() + current_time = self.get_monotonic_time() if current_time - self._timestamp >= self.keep_alive: self._timestamp = 0 # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server @@ -1000,14 +1027,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs = self.ping() return rcs - stamp = time.monotonic() + stamp = self.get_monotonic_time() rcs = [] while True: rc = self._wait_for_msg() if rc is not None: rcs.append(rc) - if time.monotonic() - stamp > timeout: + if self.get_monotonic_time() - stamp > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break @@ -1106,7 +1133,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray: :param int bufsize: number of bytes to receive :return: byte array """ - stamp = time.monotonic() + stamp = self.get_monotonic_time() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) @@ -1121,7 +1148,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray: recv_len = self._sock.recv_into(mv, to_read) to_read -= recv_len mv = mv[recv_len:] - if time.monotonic() - stamp > read_timeout: + if self.get_monotonic_time() - stamp > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) @@ -1141,7 +1168,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray: recv = self._sock.recv(to_read) to_read -= len(recv) rc += recv - if time.monotonic() - stamp > read_timeout: + if self.get_monotonic_time() - stamp > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) From 687c1e5acc5fa825d16c09b527d1f581548b3a96 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 6 Nov 2023 22:30:09 +0100 Subject: [PATCH 159/289] _wait_for_msg() should return message type --- adafruit_minimqtt/adafruit_minimqtt.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cbeb8f6c..eb62154e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1037,16 +1037,17 @@ def _wait_for_msg(self) -> Optional[int]: if res in [None, b"", b"\x00"]: # If we get here, it means that there is nothing to be received return None - if res[0] & MQTT_PKT_TYPE_MASK == MQTT_PINGRESP: + pkt_type = res[0] & MQTT_PKT_TYPE_MASK + self.logger.debug(f"Got message type: {hex(pkt_type)} pkt: {hex(res[0])}") + if pkt_type == MQTT_PINGRESP: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: raise MMQTTException(f"Unexpected PINGRESP returned from broker: {sz}.") - return MQTT_PINGRESP + return pkt_type - if res[0] & MQTT_PKT_TYPE_MASK != MQTT_PUBLISH: - self.logger.debug(f"Got message type: {hex(res[0])}") - return res[0] + if pkt_type != MQTT_PUBLISH: + return pkt_type # Handle only the PUBLISH packet type from now on. sz = self._recv_len() @@ -1080,7 +1081,7 @@ def _wait_for_msg(self) -> Optional[int]: elif res[0] & 6 == 4: assert 0 - return res[0] + return pkt_type def _recv_len(self) -> int: """Unpack MQTT message length.""" From db9998f5abca6e0a1c0a44ee84138d21b201e12e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 6 Nov 2023 22:37:46 +0100 Subject: [PATCH 160/289] adjust docs --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index eb62154e..37b3cfb5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -666,7 +666,7 @@ def disconnect(self) -> None: def ping(self) -> list[int]: """Pings the MQTT Broker to confirm if the broker is alive or if there is an active network connection. - Returns response codes of any messages received while waiting for PINGRESP. + Returns packet types of any messages received while waiting for PINGRESP. """ self._connected() self.logger.debug("Sending PINGREQ") @@ -981,7 +981,7 @@ def reconnect(self, resub_topics: bool = True) -> int: def loop(self, timeout: float = 0) -> Optional[list[int]]: # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to check for incoming messages. - Returns list of response codes of any messages received or None. + Returns list of packet types of any messages received or None. :param float timeout: return after this timeout, in seconds. From 4c58026fdcd055e648b98ca173ab65ff310eba3a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 18 Nov 2023 11:21:23 +0100 Subject: [PATCH 161/289] do not share CONNECT variable header fixes #185 --- adafruit_minimqtt/adafruit_minimqtt.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cbeb8f6c..d51b2bc4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -66,9 +66,6 @@ MQTT_PKT_TYPE_MASK = const(0xF0) -# Variable CONNECT header [MQTT 3.1.2] -MQTT_HDR_CONNECT = bytearray(b"\x04MQTT\x04\x02\0\0") - CONNACK_ERRORS = { const(0x01): "Connection Refused - Incorrect Protocol Version", @@ -567,10 +564,9 @@ def _connect( # Fixed Header fixed_header = bytearray([0x10]) - # NOTE: Variable header is - # MQTT_HDR_CONNECT = bytearray(b"\x04MQTT\x04\x02\0\0") - # because final 4 bytes are 4, 2, 0, 0 - var_header = MQTT_HDR_CONNECT + # Variable CONNECT header [MQTT 3.1.2] + # The byte array is used as a template. + var_header = bytearray(b"\x04MQTT\x04\x02\0\0") var_header[6] = clean_session << 1 # Set up variable header and remaining_length From 469c152db6240e257e34434c598d23fdff2f3a61 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 24 Nov 2023 19:20:10 +0100 Subject: [PATCH 162/289] make user_data "public" fixes #178 --- adafruit_minimqtt/adafruit_minimqtt.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cbeb8f6c..6faa6845 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -169,6 +169,8 @@ class MQTT: :param int connect_retries: How many times to try to connect to the broker before giving up on connect or reconnect. Exponential backoff will be used for the retries. :param class user_data: arbitrary data to pass as a second argument to the callbacks. + This works with all callbacks but "on_message"; there, it is necessary to extract + the user_data from the MQTT object (passed as 1st argument) using the 'user_data' member. """ @@ -205,7 +207,7 @@ def __init__( self._recv_timeout = recv_timeout self.keep_alive = keep_alive - self._user_data = user_data + self.user_data = user_data self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 @@ -638,7 +640,7 @@ def _connect( self._is_connected = True result = rc[0] & 1 if self.on_connect is not None: - self.on_connect(self, self._user_data, result, rc[2]) + self.on_connect(self, self.user_data, result, rc[2]) return result @@ -661,7 +663,7 @@ def disconnect(self) -> None: self._is_connected = False self._subscribed_topics = [] if self.on_disconnect is not None: - self.on_disconnect(self, self._user_data, 0) + self.on_disconnect(self, self.user_data, 0) def ping(self) -> list[int]: """Pings the MQTT Broker to confirm if the broker is alive or if @@ -757,7 +759,7 @@ def publish( self._sock.send(pub_hdr_var) self._sock.send(msg) if qos == 0 and self.on_publish is not None: - self.on_publish(self, self._user_data, topic, self._pid) + self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: stamp = time.monotonic() while True: @@ -769,7 +771,7 @@ def publish( rcv_pid = rcv_pid_buf[0] << 0x08 | rcv_pid_buf[1] if self._pid == rcv_pid: if self.on_publish is not None: - self.on_publish(self, self._user_data, topic, rcv_pid) + self.on_publish(self, self.user_data, topic, rcv_pid) return if op is None: @@ -849,7 +851,7 @@ def subscribe(self, topic: str, qos: int = 0) -> None: for t, q in topics: if self.on_subscribe is not None: - self.on_subscribe(self, self._user_data, t, q) + self.on_subscribe(self, self.user_data, t, q) self._subscribed_topics.append(t) return @@ -907,7 +909,7 @@ def unsubscribe(self, topic: str) -> None: assert rc[1] == packet_id_bytes[0] and rc[2] == packet_id_bytes[1] for t in topics: if self.on_unsubscribe is not None: - self.on_unsubscribe(self, self._user_data, t, self._pid) + self.on_unsubscribe(self, self.user_data, t, self._pid) self._subscribed_topics.remove(t) return From 8ead04c7af4486e0ed0b3cec4647270c02d77459 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 24 Nov 2023 19:34:25 +0100 Subject: [PATCH 163/289] improve documentation --- adafruit_minimqtt/adafruit_minimqtt.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6faa6845..76ef083f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -168,9 +168,10 @@ class MQTT: in seconds. :param int connect_retries: How many times to try to connect to the broker before giving up on connect or reconnect. Exponential backoff will be used for the retries. - :param class user_data: arbitrary data to pass as a second argument to the callbacks. - This works with all callbacks but "on_message"; there, it is necessary to extract - the user_data from the MQTT object (passed as 1st argument) using the 'user_data' member. + :param class user_data: arbitrary data to pass as a second argument to most of the callbacks. + This works with all callbacks but the "on_message" and those added via add_topic_callback(); + for those, to get access to the user_data use the 'user_data' member of the MQTT object + passed as 1st argument. """ @@ -415,6 +416,9 @@ def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: :param str mqtt_topic: MQTT topic identifier. :param function callback_method: The callback method. + + Expected method signature is ``on_message(client, topic, message)`` + To get access to the user_data, use the client argument. """ if mqtt_topic is None or callback_method is None: raise ValueError("MQTT topic and callback method must both be defined.") @@ -439,6 +443,7 @@ def on_message(self): """Called when a new message has been received on a subscribed topic. Expected method signature is ``on_message(client, topic, message)`` + To get access to the user_data, use the client argument. """ return self._on_message From 40fbdae5b62f6ded69e8cad7e9f3b8d9cf058f0a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 24 Nov 2023 22:00:03 +0100 Subject: [PATCH 164/289] add example code --- examples/cpython/user_data.py | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 examples/cpython/user_data.py diff --git a/examples/cpython/user_data.py b/examples/cpython/user_data.py new file mode 100644 index 00000000..1523f0e9 --- /dev/null +++ b/examples/cpython/user_data.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +# pylint: disable=logging-fstring-interpolation + +""" +Demonstrate on how to use user_data for various callbacks. +""" + +import logging +import socket +import ssl +import sys + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +# pylint: disable=unused-argument +def on_connect(mqtt_client, user_data, flags, ret_code): + """ + connect callback + """ + logger = logging.getLogger(__name__) + logger.debug("Connected to MQTT Broker!") + logger.debug(f"Flags: {flags}\n RC: {ret_code}") + + +# pylint: disable=unused-argument +def on_subscribe(mqtt_client, user_data, topic, granted_qos): + """ + subscribe callback + """ + logger = logging.getLogger(__name__) + logger.debug(f"Subscribed to {topic} with QOS level {granted_qos}") + + +def on_message(client, topic, message): + """ + received message callback + """ + logger = logging.getLogger(__name__) + logger.debug(f"New message on topic {topic}: {message}") + + messages = client.user_data + if not messages.get(topic): + messages[topic] = [] + messages[topic].append(message) + + +# pylint: disable=too-many-statements,too-many-locals +def main(): + """ + Main loop. + """ + + logging.basicConfig() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + # dictionary/map of topic to list of messages + messages = {} + + # connect to MQTT broker + mqtt = MQTT.MQTT( + broker="172.40.0.3", + port=1883, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + user_data=messages, + ) + + mqtt.on_connect = on_connect + mqtt.on_subscribe = on_subscribe + mqtt.on_message = on_message + + logger.info("Connecting to MQTT broker") + mqtt.connect() + logger.info("Subscribing") + mqtt.subscribe("foo/#", qos=0) + mqtt.add_topic_callback("foo/bar", on_message) + + i = 0 + while True: + i += 1 + logger.debug(f"Loop {i}") + # Make sure to stay connected to the broker e.g. in case of keep alive. + mqtt.loop(1) + + for topic, msg_list in messages.items(): + logger.info(f"Got {len(msg_list)} messages from topic {topic}") + for msg_cnt, msg in enumerate(msg_list): + logger.debug(f"#{msg_cnt}: {msg}") + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + sys.exit(0) From 9a1ecba332dff67d51ace79736dfdae58f7443b5 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 24 Nov 2023 22:00:10 +0100 Subject: [PATCH 165/289] improve doc --- adafruit_minimqtt/adafruit_minimqtt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 76ef083f..0d092e4f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -419,6 +419,8 @@ def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: Expected method signature is ``on_message(client, topic, message)`` To get access to the user_data, use the client argument. + + If a callback is called for the topic, then any "on_message" callback will not be called. """ if mqtt_topic is None or callback_method is None: raise ValueError("MQTT topic and callback method must both be defined.") From 66309c1269c750bfdee7562e50e0a825ed3692c7 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 24 Nov 2023 22:02:49 +0100 Subject: [PATCH 166/289] add header --- examples/cpython/user_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/cpython/user_data.py b/examples/cpython/user_data.py index 1523f0e9..dd19c275 100644 --- a/examples/cpython/user_data.py +++ b/examples/cpython/user_data.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# SPDX-License-Identifier: Unlicense # pylint: disable=logging-fstring-interpolation From f56577cbdf59aec11045fc98bc28b50dd1ed4c24 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 20 Nov 2023 23:30:13 +0100 Subject: [PATCH 167/289] encode remaining length properly for SUBSCRIBE fixes #160 --- adafruit_minimqtt/adafruit_minimqtt.py | 51 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 41e01605..d886944a 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -60,7 +60,7 @@ MQTT_PINGREQ = b"\xc0\0" MQTT_PINGRESP = const(0xD0) MQTT_PUBLISH = const(0x30) -MQTT_SUB = b"\x82" +MQTT_SUB = const(0x82) MQTT_UNSUB = b"\xA2" MQTT_DISCONNECT = b"\xe0\0" @@ -626,18 +626,7 @@ def _connect( var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[6] |= self._lw_retain << 5 - # Remaining length calculation - large_rel_length = False - if remaining_length > 0x7F: - large_rel_length = True - # Calculate Remaining Length [2.2.3] - while remaining_length > 0: - encoded_byte = remaining_length % 0x80 - remaining_length = remaining_length // 0x80 - # if there is more data to encode, set the top bit of the byte - if remaining_length > 0: - encoded_byte |= 0x80 - fixed_header.append(encoded_byte) + large_rel_length = self.encode_remaining_length(fixed_header, remaining_length) if large_rel_length: fixed_header.append(0x00) else: @@ -680,6 +669,25 @@ def _connect( f"No data received from broker for {self._recv_timeout} seconds." ) + # pylint: disable=no-self-use + def encode_remaining_length(self, fixed_header, remaining_length): + """ + Encode Remaining Length [2.2.3] + """ + # Remaining length calculation + large_rel_length = False + if remaining_length > 0x7F: + large_rel_length = True + while remaining_length > 0: + encoded_byte = remaining_length % 0x80 + remaining_length = remaining_length // 0x80 + # if there is more data to encode, set the top bit of the byte + if remaining_length > 0: + encoded_byte |= 0x80 + fixed_header.append(encoded_byte) + + return large_rel_length + def disconnect(self) -> None: """Disconnects the MiniMQTT client from the MQTT broker.""" self._connected() @@ -812,7 +820,7 @@ def publish( def subscribe(self, topic: str, qos: int = 0) -> None: """Subscribes to a topic on the MQTT Broker. - This method can subscribe to one topics or multiple topics. + This method can subscribe to one topic or multiple topics. :param str|tuple|list topic: Unique MQTT topic identifier string. If this is a `tuple`, then the tuple should @@ -842,20 +850,27 @@ def subscribe(self, topic: str, qos: int = 0) -> None: self._valid_topic(t) topics.append((t, q)) # Assemble packet + self.logger.debug("Sending SUBSCRIBE to broker...") + fixed_header = bytearray([MQTT_SUB]) packet_length = 2 + (2 * len(topics)) + (1 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) - packet_length_byte = packet_length.to_bytes(1, "big") + self.encode_remaining_length(fixed_header, remaining_length=packet_length) + self.logger.debug(f"Fixed Header: {fixed_header}") + self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") - # Packet with variable and fixed headers - packet = MQTT_SUB + packet_length_byte + packet_id_bytes + var_header = packet_id_bytes + self.logger.debug(f"Variable Header: {var_header}") + self._sock.send(var_header) # attaching topic and QOS level to the packet + packet = bytes() for t, q in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") packet += topic_size + t.encode() + qos_byte for t, q in topics: self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) + self.logger.debug(f"packet: {packet}") self._sock.send(packet) stamp = self.get_monotonic_time() while True: @@ -869,7 +884,7 @@ def subscribe(self, topic: str, qos: int = 0) -> None: if op == 0x90: rc = self._sock_exact_recv(3) # Check packet identifier. - assert rc[1] == packet[2] and rc[2] == packet[3] + assert rc[1] == var_header[0] and rc[2] == var_header[1] remaining_len = rc[0] - 2 assert remaining_len > 0 rc = self._sock_exact_recv(remaining_len) From 3ce387e05a26f4941fc07ac5877652854a776c2e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Nov 2023 22:07:17 +0100 Subject: [PATCH 168/289] use f-string for logging it seems the previous code does not properly work with Adafruit logging. This should fix it. --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d886944a..4355363e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -869,7 +869,7 @@ def subscribe(self, topic: str, qos: int = 0) -> None: qos_byte = q.to_bytes(1, "big") packet += topic_size + t.encode() + qos_byte for t, q in topics: - self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q) + self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"packet: {packet}") self._sock.send(packet) stamp = self.get_monotonic_time() From 1dc406b503ad0a80f7d5d636fd687b146fbaeed0 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Nov 2023 22:31:04 +0100 Subject: [PATCH 169/289] fix short remaining length encoding --- adafruit_minimqtt/adafruit_minimqtt.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 4355363e..00db3093 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -626,13 +626,8 @@ def _connect( var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[6] |= self._lw_retain << 5 - large_rel_length = self.encode_remaining_length(fixed_header, remaining_length) - if large_rel_length: - fixed_header.append(0x00) - else: - fixed_header.append(remaining_length) - fixed_header.append(0x00) - + self.encode_remaining_length(fixed_header, remaining_length) + fixed_header.append(0x00) self.logger.debug("Sending CONNECT to broker...") self.logger.debug(f"Fixed Header: {fixed_header}") self.logger.debug(f"Variable Header: {var_header}") @@ -670,14 +665,12 @@ def _connect( ) # pylint: disable=no-self-use - def encode_remaining_length(self, fixed_header, remaining_length): + def encode_remaining_length(self, fixed_header: bytearray, remaining_length: int): """ Encode Remaining Length [2.2.3] """ # Remaining length calculation - large_rel_length = False if remaining_length > 0x7F: - large_rel_length = True while remaining_length > 0: encoded_byte = remaining_length % 0x80 remaining_length = remaining_length // 0x80 @@ -685,8 +678,8 @@ def encode_remaining_length(self, fixed_header, remaining_length): if remaining_length > 0: encoded_byte |= 0x80 fixed_header.append(encoded_byte) - - return large_rel_length + else: + fixed_header.append(remaining_length) def disconnect(self) -> None: """Disconnects the MiniMQTT client from the MQTT broker.""" From 008ad191fc2bb2f813a00adebd001b46ad77c5de Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Nov 2023 22:31:22 +0100 Subject: [PATCH 170/289] rename the variable to match the purpose --- adafruit_minimqtt/adafruit_minimqtt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 00db3093..a5f6e6af 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -856,15 +856,15 @@ def subscribe(self, topic: str, qos: int = 0) -> None: self.logger.debug(f"Variable Header: {var_header}") self._sock.send(var_header) # attaching topic and QOS level to the packet - packet = bytes() + payload = bytes() for t, q in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") - packet += topic_size + t.encode() + qos_byte + payload += topic_size + t.encode() + qos_byte for t, q in topics: self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") - self.logger.debug(f"packet: {packet}") - self._sock.send(packet) + self.logger.debug(f"payload: {payload}") + self._sock.send(payload) stamp = self.get_monotonic_time() while True: op = self._wait_for_msg() From 973df686b3a052ef463d752177e10e0286cc9acb Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Nov 2023 22:39:06 +0100 Subject: [PATCH 171/289] the zero byte belongs to the variable header --- adafruit_minimqtt/adafruit_minimqtt.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index a5f6e6af..4f1e5f47 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -597,13 +597,12 @@ def _connect( self.broker, self.port, timeout=self._socket_timeout ) - # Fixed Header fixed_header = bytearray([0x10]) # Variable CONNECT header [MQTT 3.1.2] # The byte array is used as a template. - var_header = bytearray(b"\x04MQTT\x04\x02\0\0") - var_header[6] = clean_session << 1 + var_header = bytearray(b"\x00\x04MQTT\x04\x02\0\0") + var_header[7] = clean_session << 1 # Set up variable header and remaining_length remaining_length = 12 + len(self.client_id.encode("utf-8")) @@ -614,20 +613,19 @@ def _connect( + 2 + len(self._password.encode("utf-8")) ) - var_header[6] |= 0xC0 + var_header[7] |= 0xC0 if self.keep_alive: assert self.keep_alive < MQTT_TOPIC_LENGTH_LIMIT - var_header[7] |= self.keep_alive >> 8 - var_header[8] |= self.keep_alive & 0x00FF + var_header[8] |= self.keep_alive >> 8 + var_header[9] |= self.keep_alive & 0x00FF if self._lw_topic: remaining_length += ( 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) ) - var_header[6] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 - var_header[6] |= self._lw_retain << 5 + var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 + var_header[7] |= self._lw_retain << 5 self.encode_remaining_length(fixed_header, remaining_length) - fixed_header.append(0x00) self.logger.debug("Sending CONNECT to broker...") self.logger.debug(f"Fixed Header: {fixed_header}") self.logger.debug(f"Variable Header: {var_header}") From 7aacbfe048f595e091e9fee9eca9a674bf13c9d9 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Nov 2023 22:43:58 +0100 Subject: [PATCH 172/289] deduplicate remaining length encoding for PUBLISH packet --- adafruit_minimqtt/adafruit_minimqtt.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 4f1e5f47..24d93f9d 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -765,16 +765,7 @@ def publish( pub_hdr_var.append(self._pid >> 8) pub_hdr_var.append(self._pid & 0xFF) - # Calculate remaining length [2.2.3] - if remaining_length > 0x7F: - while remaining_length > 0: - encoded_byte = remaining_length % 0x80 - remaining_length = remaining_length // 0x80 - if remaining_length > 0: - encoded_byte |= 0x80 - pub_hdr_fixed.append(encoded_byte) - else: - pub_hdr_fixed.append(remaining_length) + self.encode_remaining_length(pub_hdr_fixed, remaining_length) self.logger.debug( "Sending PUBLISH\nTopic: %s\nMsg: %s\ From 4a520823eae910f6f48fc90c2629d3eb972a8524 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 16 Oct 2023 14:30:31 -0500 Subject: [PATCH 173/289] unpin sphinx and add sphinx-rtd-theme to docs reqs Signed-off-by: foamyguy --- docs/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 797aa045..979f5681 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,6 @@ # # SPDX-License-Identifier: Unlicense -sphinx>=4.0.0 +sphinx sphinxcontrib-jquery +sphinx-rtd-theme From 163c7305c76145debede17ec9960547480d46f35 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 30 Nov 2023 23:06:36 +0100 Subject: [PATCH 174/289] PUBLISH can arrive before SUBACK fixes #192 --- adafruit_minimqtt/adafruit_minimqtt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 24d93f9d..05fc1995 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -880,11 +880,15 @@ def subscribe(self, topic: str, qos: int = 0) -> None: if self.on_subscribe is not None: self.on_subscribe(self, self.user_data, t, q) self._subscribed_topics.append(t) + return - raise MMQTTException( - f"invalid message received as response to SUBSCRIBE: {hex(op)}" - ) + if op != MQTT_PUBLISH: + # [3.8.4] The Server is permitted to start sending PUBLISH packets + # matching the Subscription before the Server sends the SUBACK Packet. + raise MMQTTException( + f"invalid message received as response to SUBSCRIBE: {hex(op)}" + ) def unsubscribe(self, topic: str) -> None: """Unsubscribes from a MQTT topic. From 62f66f2c58a01c451da3708705595ddedc41fa11 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 30 Nov 2023 23:40:30 +0100 Subject: [PATCH 175/289] UNSUBSCRIBE needs to encode remaining length correctly too --- adafruit_minimqtt/adafruit_minimqtt.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 05fc1995..ebe43e6e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -61,7 +61,7 @@ MQTT_PINGRESP = const(0xD0) MQTT_PUBLISH = const(0x30) MQTT_SUB = const(0x82) -MQTT_UNSUB = b"\xA2" +MQTT_UNSUB = const(0xA2) MQTT_DISCONNECT = b"\xe0\0" MQTT_PKT_TYPE_MASK = const(0xF0) @@ -911,18 +911,25 @@ def unsubscribe(self, topic: str) -> None: "Topic must be subscribed to before attempting unsubscribe." ) # Assemble packet + self.logger.debug("Sending UNSUBSCRIBE to broker...") + fixed_header = bytearray([MQTT_UNSUB]) packet_length = 2 + (2 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic in topics) - packet_length_byte = packet_length.to_bytes(1, "big") + self.encode_remaining_length(fixed_header, remaining_length=packet_length) + self.logger.debug(f"Fixed Header: {fixed_header}") + self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") - packet = MQTT_UNSUB + packet_length_byte + packet_id_bytes + var_header = packet_id_bytes + self.logger.debug(f"Variable Header: {var_header}") + self._sock.send(var_header) + payload = bytes() for t in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") - packet += topic_size + t.encode() + payload += topic_size + t.encode() for t in topics: - self.logger.debug("UNSUBSCRIBING from topic %s", t) - self._sock.send(packet) + self.logger.debug(f"UNSUBSCRIBING from topic {t}") + self._sock.send(payload) self.logger.debug("Waiting for UNSUBACK...") while True: stamp = self.get_monotonic_time() From d65b797d7f20a00f5e9cc14d295064c61dd3faf3 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 13 Dec 2023 23:19:24 +0100 Subject: [PATCH 176/289] improve type hints for subscribe() --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ebe43e6e..6bc8030c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -800,7 +800,7 @@ def publish( f"No data received from broker for {self._recv_timeout} seconds." ) - def subscribe(self, topic: str, qos: int = 0) -> None: + def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> None: """Subscribes to a topic on the MQTT Broker. This method can subscribe to one topic or multiple topics. From 2cf2f28afec654319303ff15b5032680502f7898 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 13 Dec 2023 23:19:43 +0100 Subject: [PATCH 177/289] add protocol level test for SUBSCRIBE --- tests/test_subscribe.py | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/test_subscribe.py diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py new file mode 100644 index 00000000..4455aad7 --- /dev/null +++ b/tests/test_subscribe.py @@ -0,0 +1,148 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""subscribe tests""" + +import logging +import ssl +from unittest import mock + +import pytest + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +class Mocket: + """ + Mock Socket tailored for MiniMQTT testing. Records sent data, + hands out pre-recorded reply. + + Inspired by the Mocket class from Adafruit_CircuitPython_Requests + """ + + def __init__(self, to_send): + self._to_send = to_send + + self.sent = bytearray() + + self.timeout = mock.Mock() + self.connect = mock.Mock() + self.close = mock.Mock() + + def send(self, bytes_to_send): + """merely record the bytes. return the length of this bytearray.""" + self.sent.extend(bytes_to_send) + return len(bytes_to_send) + + # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. + def recv_into(self, retbuf, bufsize): + """return data from internal buffer""" + size = min(bufsize, len(self._to_send)) + if size == 0: + return size + chop = self._to_send[0:size] + retbuf[0:] = chop + self._to_send = self._to_send[size:] + return size + + +# pylint: disable=unused-argument +def handle_subscribe(client, user_data, topic, qos): + """ + Record topics into user data. + """ + assert topic + assert qos == 0 + + user_data.append(topic) + + +# The MQTT packet contents below were captured using Mosquitto client+server. +testdata = [ + # short topic with remaining length encoded as single byte + ( + "foo/bar", + bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), + bytearray( + [ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ] + ), + ), + # remaining length is encoded as 2 bytes due to long topic name. + ( + "f" + "o" * 257, + bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), + bytearray( + [ + 0x82, # fixed header + 0x87, # remaining length + 0x02, + 0x00, # message ID + 0x01, + 0x01, # topic length + 0x02, + 0x66, # topic + ] + + [0x6F] * 257 + + [0x00] # QoS + ), + ), +] + + +@pytest.mark.parametrize( + "topic,to_send,exp_recv", testdata, ids=["short_topic", "long_topic"] +) +def test_subscribe(topic, to_send, exp_recv) -> None: + """ + Protocol level testing of SUBSCRIBE and SUBACK packet handling. + + Nothing will travel over the wire, it is all fake. + """ + logging.basicConfig() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + host = "localhost" + port = 1883 + + subscribed_topics = [] + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + ssl_context=ssl.create_default_context(), + connect_retries=1, + user_data=subscribed_topics, + ) + + mqtt_client.on_subscribe = handle_subscribe + + # patch is_connected() to avoid CONNECT/CONNACK handling. + mqtt_client.is_connected = lambda: True + mocket = Mocket(to_send) + # pylint: disable=protected-access + mqtt_client._sock = mocket + + mqtt_client.logger = logger + + # pylint: disable=logging-fstring-interpolation + logger.info(f"subscribing to {topic}") + mqtt_client.subscribe(topic) + + assert topic in subscribed_topics + assert mocket.sent == exp_recv From f016d342470023e861db58336ca60ed07473ea3e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 14 Dec 2023 20:45:59 +0100 Subject: [PATCH 178/289] augment type hints for unsubscribe() --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6bc8030c..f971126d 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -890,7 +890,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N f"invalid message received as response to SUBSCRIBE: {hex(op)}" ) - def unsubscribe(self, topic: str) -> None: + def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: """Unsubscribes from a MQTT topic. :param str|list topic: Unique MQTT topic identifier string or list. From 1bb729b452aaafdb8367acc83dab2b2c792bbde0 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 14 Dec 2023 20:49:09 +0100 Subject: [PATCH 179/289] refactor Mocket into separate module --- tests/mocket.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/test_subscribe.py | 36 +----------------------------------- 2 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 tests/mocket.py diff --git a/tests/mocket.py b/tests/mocket.py new file mode 100644 index 00000000..31b41015 --- /dev/null +++ b/tests/mocket.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""fake socket class for protocol level testing""" + +from unittest import mock + + +class Mocket: + """ + Mock Socket tailored for MiniMQTT testing. Records sent data, + hands out pre-recorded reply. + + Inspired by the Mocket class from Adafruit_CircuitPython_Requests + """ + + def __init__(self, to_send): + self._to_send = to_send + + self.sent = bytearray() + + self.timeout = mock.Mock() + self.connect = mock.Mock() + self.close = mock.Mock() + + def send(self, bytes_to_send): + """merely record the bytes. return the length of this bytearray.""" + self.sent.extend(bytes_to_send) + return len(bytes_to_send) + + # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. + def recv_into(self, retbuf, bufsize): + """return data from internal buffer""" + size = min(bufsize, len(self._to_send)) + if size == 0: + return size + chop = self._to_send[0:size] + retbuf[0:] = chop + self._to_send = self._to_send[size:] + return size diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py index 4455aad7..413f10aa 100644 --- a/tests/test_subscribe.py +++ b/tests/test_subscribe.py @@ -6,47 +6,13 @@ import logging import ssl -from unittest import mock import pytest +from mocket import Mocket import adafruit_minimqtt.adafruit_minimqtt as MQTT -class Mocket: - """ - Mock Socket tailored for MiniMQTT testing. Records sent data, - hands out pre-recorded reply. - - Inspired by the Mocket class from Adafruit_CircuitPython_Requests - """ - - def __init__(self, to_send): - self._to_send = to_send - - self.sent = bytearray() - - self.timeout = mock.Mock() - self.connect = mock.Mock() - self.close = mock.Mock() - - def send(self, bytes_to_send): - """merely record the bytes. return the length of this bytearray.""" - self.sent.extend(bytes_to_send) - return len(bytes_to_send) - - # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. - def recv_into(self, retbuf, bufsize): - """return data from internal buffer""" - size = min(bufsize, len(self._to_send)) - if size == 0: - return size - chop = self._to_send[0:size] - retbuf[0:] = chop - self._to_send = self._to_send[size:] - return size - - # pylint: disable=unused-argument def handle_subscribe(client, user_data, topic, qos): """ From 8d50e96a4bb9f846b5e283f72572c420c127aaee Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 14 Dec 2023 21:18:00 +0100 Subject: [PATCH 180/289] add protocol tests for UNSUBSCRIBE packet --- tests/test_unsubscribe.py | 117 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/test_unsubscribe.py diff --git a/tests/test_unsubscribe.py b/tests/test_unsubscribe.py new file mode 100644 index 00000000..04c456b3 --- /dev/null +++ b/tests/test_unsubscribe.py @@ -0,0 +1,117 @@ +# SPDX-FileCopyrightText: 2023 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""unsubscribe tests""" + +import logging +import ssl + +import pytest +from mocket import Mocket + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +# pylint: disable=unused-argument +def handle_unsubscribe(client, user_data, topic, pid): + """ + Record topics into user data. + """ + assert topic + + user_data.append(topic) + + +# The MQTT packet contents below were captured using Mosquitto client+server. +# These are verbatim, except message ID that was changed from 2 to 1 since in the real world +# capture the UNSUBSCRIBE packet followed the SUBSCRIBE packet. +testdata = [ + # short topic with remaining length encoded as single byte + ( + "foo/bar", + bytearray([0xB0, 0x02, 0x00, 0x01]), + bytearray( + [ + 0xA2, # fixed header + 0x0B, # remaining length + 0x00, # message ID + 0x01, + 0x00, # topic length + 0x07, + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + ] + ), + ), + # remaining length is encoded as 2 bytes due to long topic name. + ( + "f" + "o" * 257, + bytearray([0xB0, 0x02, 0x00, 0x01]), + bytearray( + [ + 0xA2, # fixed header + 0x86, # remaining length + 0x02, + 0x00, # message ID + 0x01, + 0x01, # topic length + 0x02, + 0x66, # topic + ] + + [0x6F] * 257 + ), + ), +] + + +@pytest.mark.parametrize( + "topic,to_send,exp_recv", testdata, ids=["short_topic", "long_topic"] +) +def test_unsubscribe(topic, to_send, exp_recv) -> None: + """ + Protocol level testing of UNSUBSCRIBE and UNSUBACK packet handling. + + Nothing will travel over the wire, it is all fake. + Also, the topics are not subscribed into. + """ + logging.basicConfig() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + host = "localhost" + port = 1883 + + unsubscribed_topics = [] + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + ssl_context=ssl.create_default_context(), + connect_retries=1, + user_data=unsubscribed_topics, + ) + + mqtt_client.on_unsubscribe = handle_unsubscribe + + # patch is_connected() to avoid CONNECT/CONNACK handling. + mqtt_client.is_connected = lambda: True + mocket = Mocket(to_send) + # pylint: disable=protected-access + mqtt_client._sock = mocket + + mqtt_client.logger = logger + + # pylint: disable=protected-access + mqtt_client._subscribed_topics = [topic] + + # pylint: disable=logging-fstring-interpolation + logger.info(f"unsubscribing from {topic}") + mqtt_client.unsubscribe(topic) + + assert topic in unsubscribed_topics + assert mocket.sent == exp_recv From 4c64236162a668aff77184c0269fe384f55cb173 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 14 Dec 2023 21:53:33 +0100 Subject: [PATCH 181/289] add test for PUBLISH received first --- tests/test_subscribe.py | 53 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py index 413f10aa..9b004192 100644 --- a/tests/test_subscribe.py +++ b/tests/test_subscribe.py @@ -29,7 +29,7 @@ def handle_subscribe(client, user_data, topic, qos): # short topic with remaining length encoded as single byte ( "foo/bar", - bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), + bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), # SUBACK bytearray( [ 0x82, # fixed header @@ -52,7 +52,7 @@ def handle_subscribe(client, user_data, topic, qos): # remaining length is encoded as 2 bytes due to long topic name. ( "f" + "o" * 257, - bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), + bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), # SUBACK bytearray( [ 0x82, # fixed header @@ -68,11 +68,58 @@ def handle_subscribe(client, user_data, topic, qos): + [0x00] # QoS ), ), + # SUBSCRIBE responded to by PUBLISH followed by SUBACK + ( + "foo/bar", + bytearray( + [ + 0x30, # PUBLISH + 0x0C, + 0x00, + 0x07, + 0x66, + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x66, + 0x6F, + 0x6F, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + ] + ), + bytearray( + [ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ] + ), + ), ] @pytest.mark.parametrize( - "topic,to_send,exp_recv", testdata, ids=["short_topic", "long_topic"] + "topic,to_send,exp_recv", + testdata, + ids=["short_topic", "long_topic", "publish_first"], ) def test_subscribe(topic, to_send, exp_recv) -> None: """ From dceca0c567f57babf3b05fa95d6296d88e56aad4 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 15 Dec 2023 22:57:42 +0100 Subject: [PATCH 182/289] add test case with long list of topics --- tests/test_unsubscribe.py | 46 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/tests/test_unsubscribe.py b/tests/test_unsubscribe.py index 04c456b3..d5b67b65 100644 --- a/tests/test_unsubscribe.py +++ b/tests/test_unsubscribe.py @@ -24,8 +24,10 @@ def handle_unsubscribe(client, user_data, topic, pid): # The MQTT packet contents below were captured using Mosquitto client+server. -# These are verbatim, except message ID that was changed from 2 to 1 since in the real world -# capture the UNSUBSCRIBE packet followed the SUBSCRIBE packet. +# These are all verbatim, except: +# - message ID that was changed from 2 to 1 since in the real world +# the UNSUBSCRIBE packet followed the SUBSCRIBE packet. +# - the long list topics is sent as individual UNSUBSCRIBE packets by Mosquitto testdata = [ # short topic with remaining length encoded as single byte ( @@ -67,11 +69,37 @@ def handle_unsubscribe(client, user_data, topic, pid): + [0x6F] * 257 ), ), + # use list of topics for more coverage. If the range was (1, 10000), that would be + # long enough to use 3 bytes for remaining length, however that would make the test + # run for many minutes even on modern systems, so 1000 is used instead. + # This results in 2 bytes for the remaining length. + ( + [f"foo/bar{x:04}" for x in range(1, 1000)], + bytearray([0xB0, 0x02, 0x00, 0x01]), + bytearray( + [ + 0xA2, # fixed header + 0xBD, # remaining length + 0x65, + 0x00, # message ID + 0x01, + ] + + sum( + [ + [0x00, 0x0B] + list(f"foo/bar{x:04}".encode("ascii")) + for x in range(1, 1000) + ], + [], + ) + ), + ), ] @pytest.mark.parametrize( - "topic,to_send,exp_recv", testdata, ids=["short_topic", "long_topic"] + "topic,to_send,exp_recv", + testdata, + ids=["short_topic", "long_topic", "topic_list_long"], ) def test_unsubscribe(topic, to_send, exp_recv) -> None: """ @@ -107,11 +135,19 @@ def test_unsubscribe(topic, to_send, exp_recv) -> None: mqtt_client.logger = logger # pylint: disable=protected-access - mqtt_client._subscribed_topics = [topic] + if isinstance(topic, str): + mqtt_client._subscribed_topics = [topic] + elif isinstance(topic, list): + mqtt_client._subscribed_topics = topic # pylint: disable=logging-fstring-interpolation logger.info(f"unsubscribing from {topic}") mqtt_client.unsubscribe(topic) - assert topic in unsubscribed_topics + if isinstance(topic, str): + assert topic in unsubscribed_topics + elif isinstance(topic, list): + for topic_name in topic: + assert topic_name in unsubscribed_topics assert mocket.sent == exp_recv + assert len(mocket._to_send) == 0 From 279387e60d3e08572c6c2093f177bef7518c604e Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 15 Dec 2023 22:58:58 +0100 Subject: [PATCH 183/289] add test case for long list of topics this uncovered a bug in SUBACK processing --- adafruit_minimqtt/adafruit_minimqtt.py | 39 ++++++++------ tests/test_subscribe.py | 71 +++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index f971126d..7ffb7239 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -625,7 +625,7 @@ def _connect( var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[7] |= self._lw_retain << 5 - self.encode_remaining_length(fixed_header, remaining_length) + self._encode_remaining_length(fixed_header, remaining_length) self.logger.debug("Sending CONNECT to broker...") self.logger.debug(f"Fixed Header: {fixed_header}") self.logger.debug(f"Variable Header: {var_header}") @@ -663,10 +663,13 @@ def _connect( ) # pylint: disable=no-self-use - def encode_remaining_length(self, fixed_header: bytearray, remaining_length: int): - """ - Encode Remaining Length [2.2.3] - """ + def _encode_remaining_length( + self, fixed_header: bytearray, remaining_length: int + ) -> None: + """Encode Remaining Length [2.2.3]""" + if remaining_length > 268_435_455: + raise MMQTTException("invalid remaining length") + # Remaining length calculation if remaining_length > 0x7F: while remaining_length > 0: @@ -765,7 +768,7 @@ def publish( pub_hdr_var.append(self._pid >> 8) pub_hdr_var.append(self._pid & 0xFF) - self.encode_remaining_length(pub_hdr_fixed, remaining_length) + self._encode_remaining_length(pub_hdr_fixed, remaining_length) self.logger.debug( "Sending PUBLISH\nTopic: %s\nMsg: %s\ @@ -836,7 +839,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N fixed_header = bytearray([MQTT_SUB]) packet_length = 2 + (2 * len(topics)) + (1 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) - self.encode_remaining_length(fixed_header, remaining_length=packet_length) + self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -864,13 +867,13 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N ) else: if op == 0x90: - rc = self._sock_exact_recv(3) - # Check packet identifier. - assert rc[1] == var_header[0] and rc[2] == var_header[1] - remaining_len = rc[0] - 2 + remaining_len = self._decode_remaining_length() assert remaining_len > 0 - rc = self._sock_exact_recv(remaining_len) - for i in range(0, remaining_len): + rc = self._sock_exact_recv(2) + # Check packet identifier. + assert rc[0] == var_header[0] and rc[1] == var_header[1] + rc = self._sock_exact_recv(remaining_len - 2) + for i in range(0, remaining_len - 2): if rc[i] not in [0, 1, 2]: raise MMQTTException( f"SUBACK Failure for topic {topics[i][0]}: {hex(rc[i])}" @@ -915,7 +918,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: fixed_header = bytearray([MQTT_UNSUB]) packet_length = 2 + (2 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic in topics) - self.encode_remaining_length(fixed_header, remaining_length=packet_length) + self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -1090,7 +1093,7 @@ def _wait_for_msg(self) -> Optional[int]: return pkt_type # Handle only the PUBLISH packet type from now on. - sz = self._recv_len() + sz = self._decode_remaining_length() # topic length MSB & LSB topic_len_buf = self._sock_exact_recv(2) topic_len = int((topic_len_buf[0] << 8) | topic_len_buf[1]) @@ -1123,11 +1126,13 @@ def _wait_for_msg(self) -> Optional[int]: return pkt_type - def _recv_len(self) -> int: - """Unpack MQTT message length.""" + def _decode_remaining_length(self) -> int: + """Decode Remaining Length [2.2.3]""" n = 0 sh = 0 while True: + if sh > 28: + raise MMQTTException("invalid remaining length encoding") b = self._sock_exact_recv(1)[0] n |= (b & 0x7F) << sh if not b & 0x80: diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py index 9b004192..a66e7a87 100644 --- a/tests/test_subscribe.py +++ b/tests/test_subscribe.py @@ -49,6 +49,29 @@ def handle_subscribe(client, user_data, topic, qos): ] ), ), + # same as before but with tuple + ( + ("foo/bar", 0), + bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), # SUBACK + bytearray( + [ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ] + ), + ), # remaining length is encoded as 2 bytes due to long topic name. ( "f" + "o" * 257, @@ -113,13 +136,52 @@ def handle_subscribe(client, user_data, topic, qos): ] ), ), + # use list of topics for more coverage. If the range was (1, 10000), that would be + # long enough to use 3 bytes for remaining length, however that would make the test + # run for many minutes even on modern systems, so 1001 is used instead. + # This results in 2 bytes for the remaining length. + ( + [(f"foo/bar{x:04}", 0) for x in range(1, 1001)], + bytearray( + [ + 0x90, + 0xEA, # remaining length + 0x07, + 0x00, # message ID + 0x01, + ] + + [0x00] * 1000 # success for all topics + ), + bytearray( + [ + 0x82, # fixed header + 0xB2, # remaining length + 0x6D, + 0x00, # message ID + 0x01, + ] + + sum( + [ + [0x00, 0x0B] + list(f"foo/bar{x:04}".encode("ascii")) + [0x00] + for x in range(1, 1001) + ], + [], + ) + ), + ), ] @pytest.mark.parametrize( "topic,to_send,exp_recv", testdata, - ids=["short_topic", "long_topic", "publish_first"], + ids=[ + "short_topic", + "short_topic_tuple", + "long_topic", + "publish_first", + "topic_list_long", + ], ) def test_subscribe(topic, to_send, exp_recv) -> None: """ @@ -157,5 +219,10 @@ def test_subscribe(topic, to_send, exp_recv) -> None: logger.info(f"subscribing to {topic}") mqtt_client.subscribe(topic) - assert topic in subscribed_topics + if isinstance(topic, str): + assert topic in subscribed_topics + elif isinstance(topic, list): + for topic_name, _ in topic: + assert topic_name in subscribed_topics assert mocket.sent == exp_recv + assert len(mocket._to_send) == 0 From 56cec235af465a8bcdbd32598a5f6bd75a153164 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 22 Dec 2023 07:50:11 -0800 Subject: [PATCH 184/289] Switch to using ConnectionManager --- .gitignore | 6 +- .pre-commit-config.yaml | 2 +- adafruit_minimqtt/adafruit_minimqtt.py | 147 ++++--------------------- conftest.py | 17 +++ requirements.txt | 1 + tox.ini | 37 ++++++- 6 files changed, 79 insertions(+), 131 deletions(-) create mode 100644 conftest.py diff --git a/.gitignore b/.gitignore index 342b4999..a06dc67a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,9 @@ _build .vscode *~ -# tox local cache +# tox-specific files .tox +build + +# coverage-specific files +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70ade69d..e2c88316 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,4 +39,4 @@ repos: types: [python] files: "^tests/" args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 41e01605..4eeb6864 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -26,12 +26,21 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases +* Adafruit's Connection Manager library: + https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager + """ import errno import struct import time from random import randint +from adafruit_connectionmanager import ( + get_connection_manager, + SocketGetOSError, + SocketConnectMemoryError, +) + try: from typing import List, Optional, Tuple, Type, Union except ImportError: @@ -78,60 +87,12 @@ _default_sock = None # pylint: disable=invalid-name _fake_context = None # pylint: disable=invalid-name +TemporaryError = (SocketGetOSError, SocketConnectMemoryError) + class MMQTTException(Exception): """MiniMQTT Exception class.""" - # pylint: disable=unnecessary-pass - # pass - - -class TemporaryError(Exception): - """Temporary error class used for handling reconnects.""" - - -# Legacy ESP32SPI Socket API -def set_socket(sock, iface=None) -> None: - """Legacy API for setting the socket and network interface. - - :param sock: socket object. - :param iface: internet interface object - - """ - global _default_sock # pylint: disable=invalid-name, global-statement - global _fake_context # pylint: disable=invalid-name, global-statement - _default_sock = sock - if iface: - _default_sock.set_interface(iface) - _fake_context = _FakeSSLContext(iface) - - -class _FakeSSLSocket: - def __init__(self, socket, tls_mode) -> None: - self._socket = socket - self._mode = tls_mode - self.settimeout = socket.settimeout - self.send = socket.send - self.recv = socket.recv - self.close = socket.close - - def connect(self, address): - """connect wrapper to add non-standard mode parameter""" - try: - return self._socket.connect(address, self._mode) - except RuntimeError as error: - raise OSError(errno.ENOMEM) from error - - -class _FakeSSLContext: - def __init__(self, iface) -> None: - self._iface = iface - - def wrap_socket(self, socket, server_hostname=None) -> _FakeSSLSocket: - """Return the same socket""" - # pylint: disable=unused-argument - return _FakeSSLSocket(socket, self._iface.TLS_MODE) - class NullLogger: """Fake logger class that does not do anything""" @@ -139,7 +100,6 @@ class NullLogger: # pylint: disable=unused-argument def nothing(self, msg: str, *args) -> None: """no action""" - pass def __init__(self) -> None: for log_level in ["debug", "info", "warning", "error", "critical"]: @@ -194,6 +154,7 @@ def __init__( user_data=None, use_imprecise_time: Optional[bool] = None, ) -> None: + self._connection_manager = get_connection_manager(socket_pool) self._socket_pool = socket_pool self._ssl_context = ssl_context self._sock = None @@ -300,77 +261,6 @@ def get_monotonic_time(self) -> float: return time.monotonic() - # pylint: disable=too-many-branches - def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): - """Obtains a new socket and connects to a broker. - - :param str host: Desired broker hostname - :param int port: Desired broker port - :param int timeout: Desired socket timeout, in seconds - """ - # For reconnections - check if we're using a socket already and close it - if self._sock: - self._sock.close() - self._sock = None - - # Legacy API - use the interface's socket instead of a passed socket pool - if self._socket_pool is None: - self._socket_pool = _default_sock - - # Legacy API - fake the ssl context - if self._ssl_context is None: - self._ssl_context = _fake_context - - if not isinstance(port, int): - raise RuntimeError("Port must be an integer") - - if self._is_ssl and not self._ssl_context: - raise RuntimeError( - "ssl_context must be set before using adafruit_mqtt for secure MQTT." - ) - - if self._is_ssl: - self.logger.info(f"Establishing a SECURE SSL connection to {host}:{port}") - else: - self.logger.info(f"Establishing an INSECURE connection to {host}:{port}") - - addr_info = self._socket_pool.getaddrinfo( - host, port, 0, self._socket_pool.SOCK_STREAM - )[0] - - try: - sock = self._socket_pool.socket(addr_info[0], addr_info[1]) - except OSError as exc: - # Do not consider this for back-off. - self.logger.warning( - f"Failed to create socket for host {addr_info[0]} and port {addr_info[1]}" - ) - raise TemporaryError from exc - - connect_host = addr_info[-1][0] - if self._is_ssl: - sock = self._ssl_context.wrap_socket(sock, server_hostname=host) - connect_host = host - sock.settimeout(timeout) - - last_exception = None - try: - sock.connect((connect_host, port)) - except MemoryError as exc: - sock.close() - self.logger.warning(f"Failed to allocate memory for connect: {exc}") - # Do not consider this for back-off. - raise TemporaryError from exc - except OSError as exc: - sock.close() - last_exception = exc - - if last_exception: - raise last_exception - - self._backwards_compatible_sock = not hasattr(sock, "recv_into") - return sock - def __enter__(self): return self @@ -593,8 +483,15 @@ def _connect( time.sleep(self._reconnect_timeout) # Get a new socket - self._sock = self._get_connect_socket( - self.broker, self.port, timeout=self._socket_timeout + self._sock = self._connection_manager.get_socket( + self.broker, + self.port, + "mqtt:", + timeout=self._socket_timeout, + is_ssl=self._is_ssl, + ssl_context=self._ssl_context, + max_retries=1, # setting to 1 since we want to handle backoff internally + exception_passthrough=True, ) # Fixed Header @@ -689,7 +586,7 @@ def disconnect(self) -> None: except RuntimeError as e: self.logger.warning(f"Unable to send DISCONNECT packet: {e}") self.logger.debug("Closing socket") - self._sock.close() + self._connection_manager.free_socket(self._sock) self._is_connected = False self._subscribed_topics = [] if self.on_disconnect is not None: diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..a7aef478 --- /dev/null +++ b/conftest.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2023 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" PyTest Setup """ + +import pytest +import adafruit_connectionmanager + + +@pytest.fixture(autouse=True) +def reset_connection_manager(monkeypatch): + """Reset the ConnectionManager, since it's a singlton and will hold data""" + monkeypatch.setattr( + "adafruit_minimqtt.adafruit_minimqtt.get_connection_manager", + adafruit_connectionmanager.ConnectionManager, + ) diff --git a/requirements.txt b/requirements.txt index 7a984a47..77565050 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager diff --git a/tox.ini b/tox.ini index 6a9584b3..d707d5b7 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,38 @@ # SPDX-License-Identifier: MIT [tox] -envlist = py39 +envlist = py311 [testenv] -changedir = {toxinidir}/tests -deps = pytest==6.2.5 -commands = pytest -v +description = run tests +deps = + pytest==7.4.3 + pytest-subtests==0.11.0 +commands = pytest + +[testenv:coverage] +description = run coverage +deps = + pytest==7.4.3 + pytest-cov==4.1.0 + pytest-subtests==0.11.0 +package = editable +commands = + coverage run --source=. --omit=tests/* --branch {posargs} -m pytest + coverage report + coverage html + +[testenv:lint] +description = run linters +deps = + pre-commit==3.6.0 +skip_install = true +commands = pre-commit run {posargs} + +[testenv:docs] +description = build docs +deps = + -r requirements.txt + -r docs/requirements.txt +skip_install = true +commands = sphinx-build -W -b html docs/ _build/ From 024e83d05b0b68cc2c7acf1ff8d43389ccc657a4 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 22 Dec 2023 10:43:41 -0800 Subject: [PATCH 185/289] Switch from adafruit_connectionmanager.py -> adafruit_connection_manager.py --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- conftest.py | 4 ++-- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 4eeb6864..5e0986d2 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -35,7 +35,7 @@ import time from random import randint -from adafruit_connectionmanager import ( +from adafruit_connection_manager import ( get_connection_manager, SocketGetOSError, SocketConnectMemoryError, diff --git a/conftest.py b/conftest.py index a7aef478..a4b8d631 100644 --- a/conftest.py +++ b/conftest.py @@ -5,7 +5,7 @@ """ PyTest Setup """ import pytest -import adafruit_connectionmanager +import adafruit_connection_manager @pytest.fixture(autouse=True) @@ -13,5 +13,5 @@ def reset_connection_manager(monkeypatch): """Reset the ConnectionManager, since it's a singlton and will hold data""" monkeypatch.setattr( "adafruit_minimqtt.adafruit_minimqtt.get_connection_manager", - adafruit_connectionmanager.ConnectionManager, + adafruit_connection_manager.ConnectionManager, ) diff --git a/requirements.txt b/requirements.txt index 77565050..d83a6788 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager +Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager@connection-manager From fe125108c31687d0517cb8389c1673ae9b0f69a6 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 27 Dec 2023 07:57:27 -0800 Subject: [PATCH 186/289] Update tox docs command --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d707d5b7..6dc4619e 100644 --- a/tox.ini +++ b/tox.ini @@ -37,4 +37,4 @@ deps = -r requirements.txt -r docs/requirements.txt skip_install = true -commands = sphinx-build -W -b html docs/ _build/ +commands = sphinx-build -E -W -b html docs/. _build/html From cdee5577d5610a969c33675f8a0f80d947922347 Mon Sep 17 00:00:00 2001 From: Rob Ervin Jauquet Date: Wed, 8 Nov 2023 15:13:45 -0500 Subject: [PATCH 187/289] pass timeout through to _sock_exact_recv when _socket_pool does not have timeout attribute --- adafruit_minimqtt/adafruit_minimqtt.py | 15 ++++++++++----- tests/test_loop.py | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ffb7239..b32f2c26 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1047,7 +1047,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs = [] while True: - rc = self._wait_for_msg() + rc = self._wait_for_msg(timeout=timeout) if rc is not None: rcs.append(rc) if self.get_monotonic_time() - stamp > timeout: @@ -1056,11 +1056,13 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: return rcs if rcs else None - def _wait_for_msg(self) -> Optional[int]: + def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # pylint: disable = too-many-return-statements """Reads and processes network events. Return the packet type or None if there is nothing to be received. + + :param float timeout: return after this timeout, in seconds. """ # CPython socket module contains a timeout attribute if hasattr(self._socket_pool, "timeout"): @@ -1070,7 +1072,7 @@ def _wait_for_msg(self) -> Optional[int]: return None else: # socketpool, esp32spi try: - res = self._sock_exact_recv(1) + res = self._sock_exact_recv(1, timeout=timeout) except OSError as error: if error.errno in (errno.ETIMEDOUT, errno.EAGAIN): # raised by a socket timeout if 0 bytes were present @@ -1139,7 +1141,9 @@ def _decode_remaining_length(self) -> int: return n sh += 7 - def _sock_exact_recv(self, bufsize: int) -> bytearray: + def _sock_exact_recv( + self, bufsize: int, timeout: Optional[float] = None + ) -> bytearray: """Reads _exact_ number of bytes from the connected socket. Will only return bytearray with the exact number of bytes requested. @@ -1150,6 +1154,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray: bytes is returned or trigger a timeout exception. :param int bufsize: number of bytes to receive + :param float timeout: timeout, in seconds. Defaults to keep_alive :return: byte array """ stamp = self.get_monotonic_time() @@ -1161,7 +1166,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray: to_read = bufsize - recv_len if to_read < 0: raise MMQTTException(f"negative number of bytes to read: {to_read}") - read_timeout = self.keep_alive + read_timeout = timeout if timeout is not None else self.keep_alive mv = mv[recv_len:] while to_read > 0: recv_len = self._sock.recv_into(mv, to_read) diff --git a/tests/test_loop.py b/tests/test_loop.py index 6a762fde..a9c4f87b 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -21,9 +21,9 @@ class Loop(TestCase): INITIAL_RCS_VAL = 42 rcs_val = INITIAL_RCS_VAL - def fake_wait_for_msg(self): + def fake_wait_for_msg(self, timeout=1): """_wait_for_msg() replacement. Sleeps for 1 second and returns an integer.""" - time.sleep(1) + time.sleep(timeout) retval = self.rcs_val self.rcs_val += 1 return retval From 81857189a51c88865386b496deb5ecfcc5076d6b Mon Sep 17 00:00:00 2001 From: Rob Ervin Jauquet Date: Wed, 10 Jan 2024 13:21:41 -0500 Subject: [PATCH 188/289] adjust test constraint now that timeout is passed through to _wait_for_msg --- tests/test_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index a9c4f87b..ccca9247 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -62,7 +62,7 @@ def test_loop_basic(self) -> None: # Check the return value. assert rcs is not None - assert len(rcs) > 1 + assert len(rcs) >= 1 expected_rc = self.INITIAL_RCS_VAL for ret_code in rcs: assert ret_code == expected_rc From 0c48574e9ee13e4cd9e3ee9c6d34d29928ab3afe Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 24 Jan 2024 22:33:47 +0100 Subject: [PATCH 189/289] improve ping handling - do not send PINGREQ unnecessarily - send PINGREQ even on long loop timeouts fixes #198 --- adafruit_minimqtt/adafruit_minimqtt.py | 35 +++--- tests/test_loop.py | 154 +++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 13 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ffb7239..7d8cfcfc 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -226,7 +226,7 @@ def __init__( self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 - self._timestamp: float = 0 + self._last_msg_sent_timestamp: float = 0 self.logger = NullLogger() """An optional logging attribute that can be set with with a Logger to enable debug logging.""" @@ -640,6 +640,7 @@ def _connect( if self._username is not None: self._send_str(self._username) self._send_str(self._password) + self._last_msg_sent_timestamp = self.get_monotonic_time() self.logger.debug("Receiving CONNACK packet from broker") stamp = self.get_monotonic_time() while True: @@ -694,6 +695,7 @@ def disconnect(self) -> None: self._sock.close() self._is_connected = False self._subscribed_topics = [] + self._last_msg_sent_timestamp = 0 if self.on_disconnect is not None: self.on_disconnect(self, self.user_data, 0) @@ -707,6 +709,7 @@ def ping(self) -> list[int]: self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive stamp = self.get_monotonic_time() + self._last_msg_sent_timestamp = stamp rc, rcs = None, [] while rc != MQTT_PINGRESP: rc = self._wait_for_msg() @@ -781,6 +784,7 @@ def publish( self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) + self._last_msg_sent_timestamp = self.get_monotonic_time() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: @@ -858,6 +862,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"payload: {payload}") self._sock.send(payload) stamp = self.get_monotonic_time() + self._last_msg_sent_timestamp = stamp while True: op = self._wait_for_msg() if op is None: @@ -933,6 +938,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") self._sock.send(payload) + self._last_msg_sent_timestamp = self.get_monotonic_time() self.logger.debug("Waiting for UNSUBACK...") while True: stamp = self.get_monotonic_time() @@ -1022,7 +1028,6 @@ def reconnect(self, resub_topics: bool = True) -> int: return ret def loop(self, timeout: float = 0) -> Optional[list[int]]: - # pylint: disable = too-many-return-statements """Non-blocking message loop. Use this method to check for incoming messages. Returns list of packet types of any messages received or None. @@ -1031,22 +1036,26 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: """ self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") - if self._timestamp == 0: - self._timestamp = self.get_monotonic_time() - current_time = self.get_monotonic_time() - if current_time - self._timestamp >= self.keep_alive: - self._timestamp = 0 - # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server - self.logger.debug( - "KeepAlive period elapsed - requesting a PINGRESP from the server..." - ) - rcs = self.ping() - return rcs stamp = self.get_monotonic_time() rcs = [] while True: + if ( + self.get_monotonic_time() - self._last_msg_sent_timestamp + >= self.keep_alive + ): + # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server + self.logger.debug( + "KeepAlive period elapsed - requesting a PINGRESP from the server..." + ) + rcs.extend(self.ping()) + # ping() itself contains a _wait_for_msg() loop which might have taken a while, + # so check here as well. + if self.get_monotonic_time() - stamp > timeout: + self.logger.debug(f"Loop timed out after {timeout} seconds") + break + rc = self._wait_for_msg() if rc is not None: rcs.append(rc) diff --git a/tests/test_loop.py b/tests/test_loop.py index 6a762fde..3cb1845d 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -8,12 +8,99 @@ import socket import ssl import time +import errno + from unittest import TestCase, main from unittest.mock import patch +from unittest import mock import adafruit_minimqtt.adafruit_minimqtt as MQTT +class Nulltet: + """ + Mock Socket that does nothing. + + Inspired by the Mocket class from Adafruit_CircuitPython_Requests + """ + + def __init__(self): + self.sent = bytearray() + + self.timeout = mock.Mock() + self.connect = mock.Mock() + self.close = mock.Mock() + + def send(self, bytes_to_send): + """ + Record the bytes. return the length of this bytearray. + """ + self.sent.extend(bytes_to_send) + return len(bytes_to_send) + + # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. + # pylint: disable=unused-argument,no-self-use + def recv_into(self, retbuf, bufsize): + """Always raise timeout exception.""" + exc = OSError() + exc.errno = errno.ETIMEDOUT + raise exc + + +class Pingtet: + """ + Mock Socket tailored for PINGREQ testing. + Records sent data, hands out PINGRESP for each PINGREQ received. + + Inspired by the Mocket class from Adafruit_CircuitPython_Requests + """ + + PINGRESP = bytearray([0xD0, 0x00]) + + def __init__(self): + self._to_send = self.PINGRESP + + self.sent = bytearray() + + self.timeout = mock.Mock() + self.connect = mock.Mock() + self.close = mock.Mock() + + self._got_pingreq = False + + def send(self, bytes_to_send): + """ + Recognize PINGREQ and record the indication that it was received. + Assumes it was sent in one chunk (of 2 bytes). + Also record the bytes. return the length of this bytearray. + """ + self.sent.extend(bytes_to_send) + if bytes_to_send == b"\xc0\0": + self._got_pingreq = True + return len(bytes_to_send) + + # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. + def recv_into(self, retbuf, bufsize): + """ + If the PINGREQ indication is on, return PINGRESP, otherwise raise timeout exception. + """ + if self._got_pingreq: + size = min(bufsize, len(self._to_send)) + if size == 0: + return size + chop = self._to_send[0:size] + retbuf[0:] = chop + self._to_send = self._to_send[size:] + if len(self._to_send) == 0: + self._got_pingreq = False + self._to_send = self.PINGRESP + return size + + exc = OSError() + exc.errno = errno.ETIMEDOUT + raise exc + + class Loop(TestCase): """basic loop() test""" @@ -54,6 +141,8 @@ def test_loop_basic(self) -> None: time_before = time.monotonic() timeout = random.randint(3, 8) + # pylint: disable=protected-access + mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() @@ -64,6 +153,7 @@ def test_loop_basic(self) -> None: assert rcs is not None assert len(rcs) > 1 expected_rc = self.INITIAL_RCS_VAL + # pylint: disable=not-an-iterable for ret_code in rcs: assert ret_code == expected_rc expected_rc += 1 @@ -84,6 +174,70 @@ def test_loop_is_connected(self): assert "not connected" in str(context.exception) + # pylint: disable=no-self-use + def test_loop_ping_timeout(self): + """Verify that ping will be sent even with loop timeout bigger than keep alive timeout + and no outgoing messages are sent.""" + + recv_timeout = 2 + keep_alive_timeout = recv_timeout * 2 + mqtt_client = MQTT.MQTT( + broker="localhost", + port=1883, + ssl_context=ssl.create_default_context(), + connect_retries=1, + socket_timeout=1, + recv_timeout=recv_timeout, + keep_alive=keep_alive_timeout, + ) + + # patch is_connected() to avoid CONNECT/CONNACK handling. + mqtt_client.is_connected = lambda: True + mocket = Pingtet() + # pylint: disable=protected-access + mqtt_client._sock = mocket + + start = time.monotonic() + res = mqtt_client.loop(timeout=2 * keep_alive_timeout) + assert time.monotonic() - start >= 2 * keep_alive_timeout + assert len(mocket.sent) > 0 + assert len(res) == 2 + assert set(res) == {int(0xD0)} + + # pylint: disable=no-self-use + def test_loop_ping_vs_msgs_sent(self): + """Verify that ping will not be sent unnecessarily.""" + + recv_timeout = 2 + keep_alive_timeout = recv_timeout * 2 + mqtt_client = MQTT.MQTT( + broker="localhost", + port=1883, + ssl_context=ssl.create_default_context(), + connect_retries=1, + socket_timeout=1, + recv_timeout=recv_timeout, + keep_alive=keep_alive_timeout, + ) + + # patch is_connected() to avoid CONNECT/CONNACK handling. + mqtt_client.is_connected = lambda: True + + mocket = Nulltet() + # pylint: disable=protected-access + mqtt_client._sock = mocket + + i = 0 + topic = "foo" + message = "bar" + for _ in range(3 * keep_alive_timeout): + mqtt_client.publish(topic, message) + mqtt_client.loop(1) + i += 1 + + # This means no other messages than the PUBLISH messages generated by the code above. + assert len(mocket.sent) == i * (2 + 2 + len(topic) + len(message)) + if __name__ == "__main__": main() From d82465dc8a5c3f8c6b243fe17afd676b62afe2e3 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 24 Jan 2024 22:41:35 +0100 Subject: [PATCH 190/289] on QoS --- tests/test_loop.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 3cb1845d..482d6245 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -223,6 +223,7 @@ def test_loop_ping_vs_msgs_sent(self): # patch is_connected() to avoid CONNECT/CONNACK handling. mqtt_client.is_connected = lambda: True + # With QoS=0 no PUBACK message is sent, so Nulltet can be used. mocket = Nulltet() # pylint: disable=protected-access mqtt_client._sock = mocket @@ -231,7 +232,7 @@ def test_loop_ping_vs_msgs_sent(self): topic = "foo" message = "bar" for _ in range(3 * keep_alive_timeout): - mqtt_client.publish(topic, message) + mqtt_client.publish(topic, message, qos=0) mqtt_client.loop(1) i += 1 From d5fffe993e7f3c4479d342cbf828898fb32a35e0 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 26 Jan 2024 21:12:28 +0100 Subject: [PATCH 191/289] enforce loop timeout < socket timeout fixes #195 --- adafruit_minimqtt/adafruit_minimqtt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ffb7239..355b55eb 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1029,6 +1029,11 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: :param float timeout: return after this timeout, in seconds. """ + if timeout < self._socket_timeout: + raise MMQTTException( + f"loop timeout ({timeout}) must be bigger than socket timeout ({self._socket_timeout})" + ) + self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") if self._timestamp == 0: From d8d4e393055a3a9578d0987573abf18e0bb5d095 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 26 Jan 2024 21:20:53 +0100 Subject: [PATCH 192/289] add unit test --- tests/test_loop.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_loop.py b/tests/test_loop.py index 6a762fde..8c38e76a 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -68,6 +68,24 @@ def test_loop_basic(self) -> None: assert ret_code == expected_rc expected_rc += 1 + def test_loop_timeout_vs_socket_timeout(self): + """ + loop() should throw MMQTTException if the timeout argument is bigger than the socket timeout. + """ + mqtt_client = MQTT.MQTT( + broker="127.0.0.1", + port=1883, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + socket_timeout=1 + ) + + mqtt_client.is_connected = lambda: True + with self.assertRaises(MQTT.MMQTTException) as context: + mqtt_client.loop(timeout=0.5) + + assert "loop timeout" in str(context.exception) + def test_loop_is_connected(self): """ loop() should throw MMQTTException if not connected From f942dd4a088551816ae33c64d9111664d5cd7a9b Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 26 Jan 2024 21:28:04 +0100 Subject: [PATCH 193/289] fix pylint --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 355b55eb..2ec01003 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1031,7 +1031,9 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: """ if timeout < self._socket_timeout: raise MMQTTException( - f"loop timeout ({timeout}) must be bigger than socket timeout ({self._socket_timeout})" + # pylint: disable=consider-using-f-string + "loop timeout ({}) must be bigger ".format(timeout) + + "than socket timeout ({}))".format(self._socket_timeout) ) self._connected() From c93efd90173f29f03689ef8a0d8b4922fe529a61 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Fri, 26 Jan 2024 21:29:40 +0100 Subject: [PATCH 194/289] fix pylint --- tests/test_loop.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 8c38e76a..0115a5d9 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -68,16 +68,18 @@ def test_loop_basic(self) -> None: assert ret_code == expected_rc expected_rc += 1 + # pylint: disable=invalid-name def test_loop_timeout_vs_socket_timeout(self): """ - loop() should throw MMQTTException if the timeout argument is bigger than the socket timeout. + loop() should throw MMQTTException if the timeout argument + is bigger than the socket timeout. """ mqtt_client = MQTT.MQTT( broker="127.0.0.1", port=1883, socket_pool=socket, ssl_context=ssl.create_default_context(), - socket_timeout=1 + socket_timeout=1, ) mqtt_client.is_connected = lambda: True From 497ea40c08c6141c3f4acd922c631a56da216f39 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 3 Feb 2024 14:16:20 -0800 Subject: [PATCH 195/289] Specify proto on get_socket --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6562635c..aeef9920 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -486,7 +486,7 @@ def _connect( self._sock = self._connection_manager.get_socket( self.broker, self.port, - "mqtt:", + proto="mqtt:", timeout=self._socket_timeout, is_ssl=self._is_ssl, ssl_context=self._ssl_context, From 3e5d4abbd16ec63a9a93aa0b0eeea5773407ac83 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 10 Feb 2024 11:38:02 -0800 Subject: [PATCH 196/289] Simplify socket exceptions --- adafruit_minimqtt/adafruit_minimqtt.py | 12 +++--------- tests/test_port_ssl.py | 5 +++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index e74d3910..27413f65 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -353,7 +353,6 @@ def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): connect_host = host sock.settimeout(timeout) - last_exception = None try: sock.connect((connect_host, port)) except MemoryError as exc: @@ -363,10 +362,9 @@ def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1): raise TemporaryError from exc except OSError as exc: sock.close() - last_exception = exc - - if last_exception: - raise last_exception + self.logger.warning(f"Failed to connect: {exc}") + # Do not consider this for back-off. + raise TemporaryError from exc self._backwards_compatible_sock = not hasattr(sock, "recv_into") return sock @@ -543,10 +541,6 @@ def connect( except TemporaryError as e: self.logger.warning(f"temporary error when connecting: {e}") backoff = False - except OSError as e: - last_exception = e - self.logger.info(f"failed to connect: {e}") - backoff = True except MMQTTException as e: last_exception = e self.logger.info(f"MMQT error: {e}") diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 8474b56d..6263dbfc 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -20,7 +20,7 @@ class PortSslSetup(TestCase): def test_default_port(self) -> None: """verify default port value and that TLS is not used""" host = "127.0.0.1" - port = 1883 + expected_port = 1883 with patch.object(socket.socket, "connect") as connect_mock: ssl_context = ssl.create_default_context() @@ -31,14 +31,15 @@ def test_default_port(self) -> None: connect_retries=1, ) + connect_mock.side_effect = OSError ssl_mock = Mock() ssl_context.wrap_socket = ssl_mock with self.assertRaises(MQTT.MMQTTException): - expected_port = port mqtt_client.connect() ssl_mock.assert_not_called() + connect_mock.assert_called() # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((host, expected_port))]) From d905b5d30af9a8ba37022cf7942822ceb0d069a6 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 14 Feb 2024 09:06:02 -0800 Subject: [PATCH 197/289] Simplify socket connect retry logic --- adafruit_minimqtt/adafruit_minimqtt.py | 14 +++----------- tests/test_port_ssl.py | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2a355cfe..9483b092 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -35,11 +35,7 @@ import time from random import randint -from adafruit_connection_manager import ( - get_connection_manager, - SocketGetOSError, - SocketConnectMemoryError, -) +from adafruit_connection_manager import get_connection_manager try: from typing import List, Optional, Tuple, Type, Union @@ -87,8 +83,6 @@ _default_sock = None # pylint: disable=invalid-name _fake_context = None # pylint: disable=invalid-name -TemporaryError = (SocketGetOSError, SocketConnectMemoryError) - class MMQTTException(Exception): """MiniMQTT Exception class.""" @@ -430,8 +424,8 @@ def connect( ) self._reset_reconnect_backoff() return ret - except TemporaryError as e: - self.logger.warning(f"temporary error when connecting: {e}") + except RuntimeError as e: + self.logger.warning(f"Socket error when connecting: {e}") backoff = False except MMQTTException as e: last_exception = e @@ -486,8 +480,6 @@ def _connect( timeout=self._socket_timeout, is_ssl=self._is_ssl, ssl_context=self._ssl_context, - max_retries=1, # setting to 1 since we want to handle backoff internally - exception_passthrough=True, ) fixed_header = bytearray([0x10]) diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 6263dbfc..1c6d5e3f 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -116,7 +116,7 @@ def test_tls_without_ssl_context(self) -> None: connect_retries=1, ) - with self.assertRaises(RuntimeError) as context: + with self.assertRaises(AttributeError) as context: mqtt_client.connect() self.assertTrue("ssl_context must be set" in str(context)) From c493c7b945a388a20e322e537dd0522ab9566ce8 Mon Sep 17 00:00:00 2001 From: Leland Sindt Date: Sun, 18 Feb 2024 12:51:07 -0600 Subject: [PATCH 198/289] Add `timeout` value. Add `timeout` value to avoid `MMQTTException: loop timeout (0) must be bigger than socket timeout (1))` error. --- .../native_networking/minimqtt_adafruitio_native_networking.py | 2 +- .../minimqtt_pub_sub_blocking_native_networking.py | 2 +- ...nimqtt_pub_sub_blocking_topic_callbacks_native_networking.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 512b83e6..3fcd8179 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -90,7 +90,7 @@ def message(client, topic, message): photocell_val = 0 while True: # Poll the message queue - mqtt_client.loop() + mqtt_client.loop(timeout=1) # Send a new message print(f"Sending photocell value: {photocell_val}...") diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 0ba76dfb..57b71f9e 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -92,7 +92,7 @@ def message(client, topic, message): # NOTE: Network reconnection is handled within this loop while True: try: - mqtt_client.loop() + mqtt_client.loop(timeout=1) except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) wifi.reset() diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 82b9ce18..55a77693 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -104,7 +104,7 @@ def on_message(client, topic, message): # NOTE: NO code below this loop will execute while True: try: - client.loop() + client.loop(timeout=1) except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) wifi.reset() From 769e723485f44b825cb71660d8c69c567e04e16c Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 13:27:31 -0800 Subject: [PATCH 199/289] Change to pypi and other tweaks --- .pre-commit-config.yaml | 6 +++--- adafruit_minimqtt/adafruit_minimqtt.py | 1 + requirements.txt | 2 +- tox.ini | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2c88316..09cc1f1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 23.3.0 + rev: 24.2.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool @@ -32,11 +32,11 @@ repos: types: [python] files: "^examples/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name - id: pylint name: pylint (test code) description: Run pylint rules on "tests/*.py" files types: [python] files: "^tests/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 9483b092..64e56581 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -481,6 +481,7 @@ def _connect( is_ssl=self._is_ssl, ssl_context=self._ssl_context, ) + self._backwards_compatible_sock = not hasattr(self._sock, "recv_into") fixed_header = bytearray([0x10]) diff --git a/requirements.txt b/requirements.txt index d83a6788..25052887 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager@connection-manager +Adafruit-Circuitpython-ConnectionManager diff --git a/tox.ini b/tox.ini index 6dc4619e..693cfb88 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2023 Vladimír Kotal +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -9,7 +10,7 @@ envlist = py311 description = run tests deps = pytest==7.4.3 - pytest-subtests==0.11.0 +pytest-subtests==0.11.0 commands = pytest [testenv:coverage] @@ -17,7 +18,7 @@ description = run coverage deps = pytest==7.4.3 pytest-cov==4.1.0 - pytest-subtests==0.11.0 +pytest-subtests==0.11.0 package = editable commands = coverage run --source=. --omit=tests/* --branch {posargs} -m pytest From 7659f215bf7bfdea70a6e4cb44e976025f3c448a Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 27 Feb 2024 03:20:29 -0800 Subject: [PATCH 200/289] Standardize tests --- tests/{backoff_test.py => test_backoff.py} | 12 ++-- tests/test_loop.py | 16 ++--- tests/test_port_ssl.py | 80 ++++++++++------------ tox.ini | 2 - 4 files changed, 48 insertions(+), 62 deletions(-) rename tests/{backoff_test.py => test_backoff.py} (86%) diff --git a/tests/backoff_test.py b/tests/test_backoff.py similarity index 86% rename from tests/backoff_test.py rename to tests/test_backoff.py index ed66dd5f..573b2050 100644 --- a/tests/backoff_test.py +++ b/tests/test_backoff.py @@ -4,16 +4,16 @@ """exponential back-off tests""" +import pytest import socket import ssl import time -from unittest import TestCase, main from unittest.mock import call, patch import adafruit_minimqtt.adafruit_minimqtt as MQTT -class ExpBackOff(TestCase): +class TestExpBackOff: """basic exponential back-off test""" connect_times = [] @@ -42,9 +42,9 @@ def test_failing_connect(self) -> None: connect_retries=connect_retries, ) print("connecting") - with self.assertRaises(MQTT.MMQTTException) as context: + with pytest.raises(MQTT.MMQTTException) as context: mqtt_client.connect() - self.assertTrue("Repeated connect failures" in str(context.exception)) + assert "Repeated connect failures" in str(context) mock_method.assert_called() calls = [call((host, port)) for _ in range(0, connect_retries)] @@ -53,7 +53,3 @@ def test_failing_connect(self) -> None: print(f"connect() call times: {self.connect_times}") for i in range(1, connect_retries): assert self.connect_times[i] >= 2**i - - -if __name__ == "__main__": - main() diff --git a/tests/test_loop.py b/tests/test_loop.py index 173fe526..9253b560 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -4,13 +4,13 @@ """loop() tests""" +import pytest import random import socket import ssl import time import errno -from unittest import TestCase, main from unittest.mock import patch from unittest import mock @@ -101,7 +101,7 @@ def recv_into(self, retbuf, bufsize): raise exc -class Loop(TestCase): +class TestLoop: """basic loop() test""" connect_times = [] @@ -173,10 +173,10 @@ def test_loop_timeout_vs_socket_timeout(self): ) mqtt_client.is_connected = lambda: True - with self.assertRaises(MQTT.MMQTTException) as context: + with pytest.raises(MQTT.MMQTTException) as context: mqtt_client.loop(timeout=0.5) - assert "loop timeout" in str(context.exception) + assert "loop timeout" in str(context) def test_loop_is_connected(self): """ @@ -189,10 +189,10 @@ def test_loop_is_connected(self): ssl_context=ssl.create_default_context(), ) - with self.assertRaises(MQTT.MMQTTException) as context: + with pytest.raises(MQTT.MMQTTException) as context: mqtt_client.loop(timeout=1) - assert "not connected" in str(context.exception) + assert "not connected" in str(context) # pylint: disable=no-self-use def test_loop_ping_timeout(self): @@ -258,7 +258,3 @@ def test_loop_ping_vs_msgs_sent(self): # This means no other messages than the PUBLISH messages generated by the code above. assert len(mocket.sent) == i * (2 + 2 + len(topic) + len(message)) - - -if __name__ == "__main__": - main() diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 1c6d5e3f..4204bb60 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -4,15 +4,15 @@ """tests that verify the connect behavior w.r.t. port number and TLS""" +import pytest import socket import ssl -from unittest import TestCase, main from unittest.mock import Mock, call, patch import adafruit_minimqtt.adafruit_minimqtt as MQTT -class PortSslSetup(TestCase): +class TestPortSslSetup: """This class contains tests that verify how host/port and TLS is set for connect(). These tests assume that there is no MQTT broker running on the hosts/ports they connect to. """ @@ -35,7 +35,7 @@ def test_default_port(self) -> None: ssl_mock = Mock() ssl_context.wrap_socket = ssl_mock - with self.assertRaises(MQTT.MMQTTException): + with pytest.raises(MQTT.MMQTTException): mqtt_client.connect() ssl_mock.assert_not_called() @@ -58,51 +58,51 @@ def test_connect_override(self): connect_retries=1, ) - with self.assertRaises(MQTT.MMQTTException): + with pytest.raises(MQTT.MMQTTException): expected_host = "127.0.0.2" expected_port = 1884 - self.assertNotEqual(expected_port, port, "port override should differ") - self.assertNotEqual(expected_host, host, "host override should differ") + assert expected_port != port # port override should differ + assert expected_host != host # host override should differ mqtt_client.connect(host=expected_host, port=expected_port) connect_mock.assert_called() # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((expected_host, expected_port))]) - def test_tls_port(self) -> None: + @pytest.mark.parametrize("port", (None, 8883)) + def test_tls_port(self, port) -> None: """verify that when is_ssl=True is set, the default port is 8883 and the socket is TLS wrapped. Also test that the TLS port can be overridden.""" host = "127.0.0.1" - for port in [None, 8884]: - if port is None: - expected_port = 8883 - else: - expected_port = port - with self.subTest(): - ssl_mock = Mock() - mqtt_client = MQTT.MQTT( - broker=host, - port=port, - socket_pool=socket, - is_ssl=True, - ssl_context=ssl_mock, - connect_retries=1, - ) - - socket_mock = Mock() - connect_mock = Mock(side_effect=OSError) - socket_mock.connect = connect_mock - ssl_mock.wrap_socket = Mock(return_value=socket_mock) - - with self.assertRaises(MQTT.MMQTTException): - mqtt_client.connect() - - ssl_mock.wrap_socket.assert_called() - - connect_mock.assert_called() - # Assuming the repeated calls will have the same arguments. - connect_mock.assert_has_calls([call((host, expected_port))]) + if port is None: + expected_port = 8883 + else: + expected_port = port + + ssl_mock = Mock() + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + is_ssl=True, + ssl_context=ssl_mock, + connect_retries=1, + ) + + socket_mock = Mock() + connect_mock = Mock(side_effect=OSError) + socket_mock.connect = connect_mock + ssl_mock.wrap_socket = Mock(return_value=socket_mock) + + with pytest.raises(MQTT.MMQTTException): + mqtt_client.connect() + + ssl_mock.wrap_socket.assert_called() + + connect_mock.assert_called() + # Assuming the repeated calls will have the same arguments. + connect_mock.assert_has_calls([call((host, expected_port))]) def test_tls_without_ssl_context(self) -> None: """verify that when is_ssl=True is set, the code will check that ssl_context is not None""" @@ -116,10 +116,6 @@ def test_tls_without_ssl_context(self) -> None: connect_retries=1, ) - with self.assertRaises(AttributeError) as context: + with pytest.raises(AttributeError) as context: mqtt_client.connect() - self.assertTrue("ssl_context must be set" in str(context)) - - -if __name__ == "__main__": - main() + assert "ssl_context must be set" in str(context) diff --git a/tox.ini b/tox.ini index 693cfb88..d0cddcf3 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ envlist = py311 description = run tests deps = pytest==7.4.3 -pytest-subtests==0.11.0 commands = pytest [testenv:coverage] @@ -18,7 +17,6 @@ description = run coverage deps = pytest==7.4.3 pytest-cov==4.1.0 -pytest-subtests==0.11.0 package = editable commands = coverage run --source=. --omit=tests/* --branch {posargs} -m pytest From f1222a2c2d716f4da5ddccfedb65706c3e578852 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 27 Feb 2024 04:03:40 -0800 Subject: [PATCH 201/289] Remove set_socket --- .../cellular/minimqtt_adafruitio_cellular.py | 31 +++++++------ .../cellular/minimqtt_simpletest_cellular.py | 29 +++++++------ .../cpython/minimqtt_adafruitio_cpython.py | 19 ++++---- .../cpython/minimqtt_simpletest_cpython.py | 23 +++++----- .../esp32spi/minimqtt_adafruitio_esp32spi.py | 8 ++-- .../esp32spi/minimqtt_certificate_esp32spi.py | 12 ++++-- .../minimqtt_pub_sub_blocking_esp32spi.py | 12 ++++-- ...b_sub_blocking_topic_callbacks_esp32spi.py | 13 ++++-- .../minimqtt_pub_sub_nonblocking_esp32spi.py | 12 ++++-- .../minimqtt_pub_sub_pyportal_esp32spi.py | 29 +++++++------ .../esp32spi/minimqtt_simpletest_esp32spi.py | 43 ++++++++----------- examples/ethernet/minimqtt_adafruitio_eth.py | 29 +++++++------ examples/ethernet/minimqtt_simpletest_eth.py | 26 +++++------ examples/minimqtt_simpletest.py | 8 ++-- .../minimqtt_adafruitio_native_networking.py | 5 +-- ...mqtt_pub_sub_blocking_native_networking.py | 5 +-- ...cking_topic_callbacks_native_networking.py | 5 +-- 17 files changed, 166 insertions(+), 143 deletions(-) diff --git a/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index 5f1c4d11..88fa317b 100755 --- a/examples/cellular/minimqtt_adafruitio_cellular.py +++ b/examples/cellular/minimqtt_adafruitio_cellular.py @@ -1,22 +1,24 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio import digitalio +import adafruit_connection_manager from adafruit_fona.adafruit_fona import FONA import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as socket +import adafruit_fona.adafruit_fona_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Get Adafruit IO details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("GPRS secrets are kept in secrets.py, please add them there!") - raise +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your GPRS credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") ### Cellular ### @@ -29,10 +31,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = secrets["aio_username"] + "/feeds/photocell" +photocell_feed = aio_username + "/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = secrets["aio_username"] + "/feeds/onoff" +onoff_feed = aio_username + "/feeds/onoff" ### Code ### @@ -60,7 +62,7 @@ def message(client, topic, message): # Initialize cellular data network network = network.CELLULAR( - fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"]) + fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password")) ) while not network.is_attached: @@ -74,16 +76,17 @@ def message(client, topic, message): time.sleep(0.5) print("Network Connected!") -# Initialize MQTT interface with the cellular interface -MQTT.set_socket(socket, fona) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, fona) # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - username=secrets["aio_username"], - password=secrets["aio_key"], + username=aio_username, + password=aio_key, is_ssl=False, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/cellular/minimqtt_simpletest_cellular.py b/examples/cellular/minimqtt_simpletest_cellular.py index 57b11676..09e7350d 100644 --- a/examples/cellular/minimqtt_simpletest_cellular.py +++ b/examples/cellular/minimqtt_simpletest_cellular.py @@ -1,24 +1,24 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio import digitalio +import adafruit_connection_manager from adafruit_fona.adafruit_fona import FONA import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as socket +import adafruit_fona.adafruit_fona_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT -### Cellular ### +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your GPRS credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Get cellular details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("Cellular secrets are kept in secrets.py, please add them there!") - raise +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) @@ -71,7 +71,7 @@ def publish(client, userdata, topic, pid): # Initialize cellular data network network = network.CELLULAR( - fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"]) + fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password")) ) while not network.is_attached: @@ -85,15 +85,16 @@ def publish(client, userdata, topic, pid): time.sleep(0.5) print("Network Connected!") -# Initialize MQTT interface with the cellular interface -MQTT.set_socket(socket, fona) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, fona) # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=secrets["broker"], - username=secrets["user"], - password=secrets["pass"], + broker=os.getenv("broker"), + username=os.getenv("username"), + password=os.getenv("password"), is_ssl=False, + socket_pool=pool, + ssl_context=ssl_context, ) # Connect callback handlers to client diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 7ebcf0fe..1440ebad 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import socket import ssl import time @@ -9,19 +10,19 @@ ### Secrets File Setup ### -try: - from secrets import secrets -except ImportError: - print("Connection secrets are kept in secrets.py, please add them there!") - raise +# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = secrets["aio_username"] + "/feeds/photocell" +photocell_feed = aio_username + "/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = secrets["aio_username"] + "/feeds/onoff" +onoff_feed = aio_username + "/feeds/onoff" ### Code ### @@ -50,8 +51,8 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - username=secrets["aio_username"], - password=secrets["aio_key"], + username=aio_username, + password=aio_key, socket_pool=socket, is_ssl=True, ssl_context=ssl.create_default_context(), diff --git a/examples/cpython/minimqtt_simpletest_cpython.py b/examples/cpython/minimqtt_simpletest_cpython.py index 551fa0b7..2a6e3a97 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -1,19 +1,16 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import ssl import socket import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Add settings.toml to your filesystems. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") ### Topic Setup ### @@ -23,7 +20,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = secrets["aio_username"] + "/feeds/temperature" +# mqtt_topic = aio_username + "/feeds/temperature" ### Code ### @@ -64,9 +61,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - username=secrets["aio_username"], - password=secrets["aio_key"], + broker=os.getenv("broker"), + username=aio_username, + password=aio_key, socket_pool=socket, ssl_context=ssl.create_default_context(), ) diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 68bf3dd6..88a8c10c 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -7,8 +7,9 @@ import busio from digitalio import DigitalInOut import neopixel +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -82,14 +83,15 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -# Initialize MQTT interface with the esp interface -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", username=aio_username, password=aio_key, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index f3e2d791..6039667f 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -5,9 +5,10 @@ import busio from digitalio import DigitalInOut import neopixel +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -110,12 +111,15 @@ def publish(client, userdata, topic, pid): wifi.connect() print("Connected!") -# Initialize MQTT interface with the esp interface -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=secrets["broker"], username=secrets["user"], password=secrets["pass"] + broker=secrets["broker"], + username=secrets["user"], + password=secrets["pass"], + socket_pool=pool, + ssl_context=ssl_context, ) # Connect callback handlers to client diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index a4e508c9..414dbf02 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -7,8 +7,9 @@ import busio from digitalio import DigitalInOut import neopixel +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -82,12 +83,15 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -# Initialize MQTT interface with the esp interface -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", username=aio_username, password=aio_key + broker="io.adafruit.com", + username=aio_username, + password=aio_key, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index 5094e4b4..0b9313e3 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -1,14 +1,16 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio from digitalio import DigitalInOut import neopixel +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -91,10 +93,15 @@ def on_message(client, topic, message): wifi.connect() print("Connected!") -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client -client = MQTT.MQTT(broker=secrets["broker"], port=secrets["broker_port"]) +client = MQTT.MQTT( + broker=os.getenv("broker"), + port=os.getenv("broker_port"), + socket_pool=pool, + ssl_context=ssl_context, +) # Setup the callback methods above client.on_connect = connected diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index fee8f285..cc937512 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -7,8 +7,9 @@ import busio from digitalio import DigitalInOut import neopixel +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -81,12 +82,15 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -# Initialize MQTT interface with the esp interface -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", username=aio_username, password=aio_key + broker="io.adafruit.com", + username=aio_username, + password=aio_key, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index b98d92b5..9c53c41f 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -1,22 +1,22 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_connection_manager +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_pyportal import adafruit_minimqtt.adafruit_minimqtt as MQTT pyportal = adafruit_pyportal.PyPortal() -### WiFi ### +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # ------------- MQTT Topic Setup ------------- # mqtt_topic = "test/topic" @@ -51,16 +51,19 @@ def message(client, topic, message): pyportal.network.connect() print("Connected!") -# Initialize MQTT interface with the esp interface # pylint: disable=protected-access -MQTT.set_socket(socket, pyportal.network._wifi.esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context( + pool, pyportal.network._wifi.esp +) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - username=secrets["user"], - password=secrets["pass"], + broker=os.getenv("broker"), + username=os.getenv("username"), + password=os.getenv("password"), is_ssl=False, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index 96a71474..a4f27c5a 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -1,27 +1,21 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT + +import os import board import busio from digitalio import DigitalInOut +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# Set your Adafruit IO Username and Key in secrets.py -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) -aio_username = secrets["aio_username"] -aio_key = secrets["aio_key"] +# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys +# with your WiFi credentials. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -39,7 +33,7 @@ print("Connecting to AP...") while not esp.is_connected: try: - esp.connect_AP(secrets["ssid"], secrets["password"]) + esp.connect_AP(os.getenv("ssid"), os.getenv("password")) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue @@ -53,7 +47,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = secrets["aio_username"] + '/feeds/temperature' +# mqtt_topic = aio_username + '/feeds/temperature' ### Code ### @@ -92,15 +86,16 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -socket.set_interface(esp) -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=secrets["broker"], - port=secrets["port"], - username=secrets["username"], - password=secrets["password"], + broker=os.getenv("broker"), + port=os.getenv("port"), + username=os.getenv("username"), + password=os.getenv("password"), + socket_pool=pool, + ssl_context=ssl_context, ) # Connect callback handlers to mqtt_client diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 5c114cee..5b19faa0 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -1,22 +1,22 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import time import board import busio from digitalio import DigitalInOut - +import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Get Adafruit IO details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("Adafruit IO secrets are kept in secrets.py, please add them there!") - raise +# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -27,10 +27,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = secrets["aio_username"] + "/feeds/photocell" +photocell_feed = aio_username + "/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = secrets["aio_username"] + "/feeds/onoff" +onoff_feed = aio_username + "/feeds/onoff" ### Code ### @@ -56,16 +56,17 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -# Initialize MQTT interface with the ethernet interface -MQTT.set_socket(socket, eth) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, eth) # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - username=secrets["aio_username"], - password=secrets["aio_key"], + username=aio_username, + password=aio_key, is_ssl=False, + socket_pool=pool, + ssl_context=ssl_context, ) # Setup the callback methods above diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index 16846d5c..850cf5eb 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -1,20 +1,21 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import os import board import busio from digitalio import DigitalInOut +import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Get MQTT details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("MQTT secrets are kept in secrets.py, please add them there!") - raise +# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. +# DO NOT share that file or commit it into Git or other source control. + +aio_username = os.getenv("aio_username") +aio_key = os.getenv("aio_key") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -64,16 +65,17 @@ def publish(client, userdata, topic, pid): print("Published to {0} with PID {1}".format(topic, pid)) -# Initialize MQTT interface with the ethernet interface -MQTT.set_socket(socket, eth) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, eth) # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. client = MQTT.MQTT( - broker=secrets["broker"], - username=secrets["user"], - password=secrets["pass"], + broker=os.getenv("broker"), + username=os.getenv("username"), + password=os.getenv("password"), is_ssl=False, + socket_pool=pool, + ssl_context=ssl_context, ) # Connect callback handlers to client diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index f9ab2771..a5757866 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -5,8 +5,9 @@ import board import busio from digitalio import DigitalInOut +import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -88,14 +89,15 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -socket.set_interface(esp) -MQTT.set_socket(socket, esp) +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", username=aio_username, password=aio_key, + socket_pool=pool, + ssl_context=ssl_context, ) # Connect callback handlers to mqtt_client diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 512b83e6..a828d25f 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -60,12 +60,11 @@ def message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the lines below and adding the following keys to the "secrets" -# dictionary in your secrets.py file: +# ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] +# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 0ba76dfb..36084458 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -60,12 +60,11 @@ def message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the lines below and adding the following keys to the "secrets" -# dictionary in your secrets.py file: +# ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] +# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 82b9ce18..9e50bb08 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -67,12 +67,11 @@ def on_message(client, topic, message): ssl_context = ssl.create_default_context() # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the -# ssl context by uncommenting the lines below and adding the following keys to the "secrets" -# dictionary in your secrets.py file: +# ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=secrets["device_cert_path"], keyfile=secrets["device_key_path"] +# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") # ) # Set up a MiniMQTT Client From 1f0c0c71b83c3513f9d956c6f0ea10cfc5a95bda Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 27 Feb 2024 04:38:16 -0800 Subject: [PATCH 202/289] Fix linting --- tests/test_backoff.py | 3 ++- tests/test_loop.py | 4 +++- tests/test_port_ssl.py | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_backoff.py b/tests/test_backoff.py index 573b2050..e26d07a4 100644 --- a/tests/test_backoff.py +++ b/tests/test_backoff.py @@ -4,12 +4,13 @@ """exponential back-off tests""" -import pytest + import socket import ssl import time from unittest.mock import call, patch +import pytest import adafruit_minimqtt.adafruit_minimqtt as MQTT diff --git a/tests/test_loop.py b/tests/test_loop.py index 9253b560..4d79b65b 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -4,7 +4,6 @@ """loop() tests""" -import pytest import random import socket import ssl @@ -14,6 +13,7 @@ from unittest.mock import patch from unittest import mock +import pytest import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -158,6 +158,7 @@ def test_loop_basic(self) -> None: assert ret_code == expected_rc expected_rc += 1 + # pylint: disable=no-self-use # pylint: disable=invalid-name def test_loop_timeout_vs_socket_timeout(self): """ @@ -178,6 +179,7 @@ def test_loop_timeout_vs_socket_timeout(self): assert "loop timeout" in str(context) + # pylint: disable=no-self-use def test_loop_is_connected(self): """ loop() should throw MMQTTException if not connected diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 4204bb60..9ac154da 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -4,11 +4,11 @@ """tests that verify the connect behavior w.r.t. port number and TLS""" -import pytest import socket import ssl from unittest.mock import Mock, call, patch +import pytest import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -17,6 +17,7 @@ class TestPortSslSetup: These tests assume that there is no MQTT broker running on the hosts/ports they connect to. """ + # pylint: disable=no-self-use def test_default_port(self) -> None: """verify default port value and that TLS is not used""" host = "127.0.0.1" @@ -44,6 +45,7 @@ def test_default_port(self) -> None: # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((host, expected_port))]) + # pylint: disable=no-self-use def test_connect_override(self): """Test that connect() can override host and port.""" host = "127.0.0.1" @@ -69,6 +71,7 @@ def test_connect_override(self): # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((expected_host, expected_port))]) + # pylint: disable=no-self-use @pytest.mark.parametrize("port", (None, 8883)) def test_tls_port(self, port) -> None: """verify that when is_ssl=True is set, the default port is 8883 @@ -104,6 +107,7 @@ def test_tls_port(self, port) -> None: # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((host, expected_port))]) + # pylint: disable=no-self-use def test_tls_without_ssl_context(self) -> None: """verify that when is_ssl=True is set, the code will check that ssl_context is not None""" host = "127.0.0.1" From 508c281746f49717d3b7198e24755833703f02e8 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 29 Feb 2024 06:36:22 -0800 Subject: [PATCH 203/289] Fix README requirements --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 2ca1660e..f5fcd464 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,7 @@ Dependencies This driver depends on: * `Adafruit CircuitPython `_ +* `Adafruit CircuitPython ConnectionManager `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading From 8a1b617e2d44ccab83203eea0ae019381cf9f8c5 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 29 Feb 2024 11:21:21 -0800 Subject: [PATCH 204/289] Move conftest --- conftest.py => tests/conftest.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename conftest.py => tests/conftest.py (100%) diff --git a/conftest.py b/tests/conftest.py similarity index 100% rename from conftest.py rename to tests/conftest.py From 112b25bec3dbc07ffb67265201f4ca34274f1051 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 1 Mar 2024 09:11:55 -0800 Subject: [PATCH 205/289] Fix get_radio_ssl_context --- examples/esp32spi/minimqtt_adafruitio_esp32spi.py | 4 ++-- examples/esp32spi/minimqtt_certificate_esp32spi.py | 4 ++-- examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py | 4 ++-- .../minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py | 4 ++-- examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py | 4 ++-- examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py | 6 +++--- examples/esp32spi/minimqtt_simpletest_esp32spi.py | 4 ++-- examples/ethernet/minimqtt_adafruitio_eth.py | 4 ++-- examples/ethernet/minimqtt_simpletest_eth.py | 4 ++-- examples/minimqtt_simpletest.py | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 88a8c10c..d944157e 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -9,7 +9,6 @@ import neopixel import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -83,7 +82,8 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index 6039667f..4467f8df 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -8,7 +8,6 @@ import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -111,7 +110,8 @@ def publish(client, userdata, topic, pid): wifi.connect() print("Connected!") -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index 414dbf02..e7eed067 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -9,7 +9,6 @@ import neopixel import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -83,7 +82,8 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index 0b9313e3..cb89ed17 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -10,7 +10,6 @@ import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -93,7 +92,8 @@ def on_message(client, topic, message): wifi.connect() print("Connected!") -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index cc937512..20191915 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -9,7 +9,6 @@ import neopixel import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -82,7 +81,8 @@ def message(client, topic, message): esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected!") -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index 9c53c41f..3db02803 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -4,7 +4,6 @@ import os import time import adafruit_connection_manager -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_pyportal import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -52,8 +51,9 @@ def message(client, topic, message): print("Connected!") # pylint: disable=protected-access -ssl_context = adafruit_connection_manager.create_fake_ssl_context( - pool, pyportal.network._wifi.esp +pool = adafruit_connection_manager.get_radio_socketpool(pyportal.network._wifi.esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context( + pyportal.network._wifi.esp ) # Set up a MiniMQTT Client diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index a4f27c5a..a1cc2712 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -7,7 +7,6 @@ from digitalio import DigitalInOut import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -86,7 +85,8 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 5b19faa0..5ad677dd 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -8,7 +8,6 @@ from digitalio import DigitalInOut import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -56,7 +55,8 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, eth) +pool = adafruit_connection_manager.get_radio_socketpool(eth) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(eth) # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index 850cf5eb..a5ba5892 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -7,7 +7,6 @@ from digitalio import DigitalInOut import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -65,7 +64,8 @@ def publish(client, userdata, topic, pid): print("Published to {0} with PID {1}".format(topic, pid)) -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, eth) +pool = adafruit_connection_manager.get_radio_socketpool(eth) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(eth) # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index a5757866..45aa077b 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -7,7 +7,6 @@ from digitalio import DigitalInOut import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -89,7 +88,8 @@ def message(client, topic, message): print("New message on topic {0}: {1}".format(topic, message)) -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, esp) +pool = adafruit_connection_manager.get_radio_socketpool(esp) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( From fe57038d026100f63a2c7109c77513006cc27819 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 4 Mar 2024 10:17:47 -0800 Subject: [PATCH 206/289] Change free_socket to close_socket --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 64e56581..7ede9559 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -578,7 +578,7 @@ def disconnect(self) -> None: except RuntimeError as e: self.logger.warning(f"Unable to send DISCONNECT packet: {e}") self.logger.debug("Closing socket") - self._connection_manager.free_socket(self._sock) + self._connection_manager.close_socket(self._sock) self._is_connected = False self._subscribed_topics = [] self._last_msg_sent_timestamp = 0 From 70f963b9c7881afd8b88f921b73c5259d095e1ef Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Fri, 22 Mar 2024 18:59:35 -0400 Subject: [PATCH 207/289] maintain time.monotonic precision by using ns integer timestamps --- adafruit_minimqtt/adafruit_minimqtt.py | 95 ++++++++++++++++---------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ede9559..d2ff2ab9 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -51,7 +51,7 @@ from .matcher import MQTTMatcher -__version__ = "0.0.0+auto.0" +__version__ = "7.6.3" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git" # Client-specific variables @@ -181,7 +181,7 @@ def __init__( self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 - self._last_msg_sent_timestamp: float = 0 + self._last_msg_sent_timestamp: int = 0 self.logger = NullLogger() """An optional logging attribute that can be set with with a Logger to enable debug logging.""" @@ -197,7 +197,8 @@ def __init__( self._username = username self._password = password if ( - self._password and len(password.encode("utf-8")) > MQTT_TOPIC_LENGTH_LIMIT + self._password and len(password.encode( + "utf-8")) > MQTT_TOPIC_LENGTH_LIMIT ): # [MQTT-3.1.3.5] raise MMQTTException("Password length is too large.") @@ -220,11 +221,12 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - time_int = int(self.get_monotonic_time() * 100) % 1000 + time_int = (self.get_monotonic_time() % 10000000000) // 10000000 self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: - raise ValueError("MQTT Client ID must be between 1 and 23 bytes") + raise ValueError( + "MQTT Client ID must be between 1 and 23 bytes") # LWT self._lw_topic = None @@ -246,14 +248,22 @@ def __init__( def get_monotonic_time(self) -> float: """ - Provide monotonic time in seconds. Based on underlying implementation + Provide monotonic time in nanoseconds. Based on underlying implementation this might result in imprecise time, that will result in the library not being able to operate if running contiguously for more than 24 days or so. + Keeping timestamps in nanosecond ints from monotonic_ns should preserve precision. """ if self.use_monotonic_ns: - return time.monotonic_ns() / 1000000000 + return time.monotonic_ns() - return time.monotonic() + return int(time.monotonic() * 1000000000) + + def diff_ns(self, stamp): + """ + Taking timestamp differences using nanosecond ints before dividing + should maintain precision. + """ + return (self.get_monotonic_time() - stamp)/1000000000 def __enter__(self): return self @@ -307,7 +317,8 @@ def will_set( self.logger.debug("Setting last will properties") self._valid_qos(qos) if self._is_connected: - raise MMQTTException("Last Will should only be called before connect().") + raise MMQTTException( + "Last Will should only be called before connect().") if payload is None: payload = "" if isinstance(payload, (int, float, str)): @@ -331,7 +342,8 @@ def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: If a callback is called for the topic, then any "on_message" callback will not be called. """ if mqtt_topic is None or callback_method is None: - raise ValueError("MQTT topic and callback method must both be defined.") + raise ValueError( + "MQTT topic and callback method must both be defined.") self._on_message_filtered[mqtt_topic] = callback_method def remove_topic_callback(self, mqtt_topic: str) -> None: @@ -379,7 +391,8 @@ def username_pw_set(self, username: str, password: Optional[str] = None) -> None """ if self._is_connected: - raise MMQTTException("This method must be called before connect().") + raise MMQTTException( + "This method must be called before connect().") self._username = username if password is not None: self._password = password @@ -508,7 +521,8 @@ def _connect( remaining_length += ( 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) ) - var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 + var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | ( + self._lw_qos & 0x2) << 3 var_header[7] |= self._lw_retain << 5 self._encode_remaining_length(fixed_header, remaining_length) @@ -544,7 +558,7 @@ def _connect( return result if op is None: - if self.get_monotonic_time() - stamp > self._recv_timeout: + if self.diff_ns(stamp) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -601,7 +615,7 @@ def ping(self) -> list[int]: rc = self._wait_for_msg() if rc: rcs.append(rc) - if self.get_monotonic_time() - stamp > ping_timeout: + if self.diff_ns(stamp) > ping_timeout: raise MMQTTException("PINGRESP not returned from broker.") return rcs @@ -637,7 +651,8 @@ def publish( else: raise MMQTTException("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: - raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") + raise MMQTTException( + f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") assert ( 0 <= qos <= 1 ), "Quality of Service Level 2 is unsupported by this library." @@ -684,11 +699,12 @@ def publish( rcv_pid = rcv_pid_buf[0] << 0x08 | rcv_pid_buf[1] if self._pid == rcv_pid: if self.on_publish is not None: - self.on_publish(self, self.user_data, topic, rcv_pid) + self.on_publish( + self, self.user_data, topic, rcv_pid) return if op is None: - if self.get_monotonic_time() - stamp > self._recv_timeout: + if self.diff_ns(stamp) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -728,8 +744,10 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug("Sending SUBSCRIBE to broker...") fixed_header = bytearray([MQTT_SUB]) packet_length = 2 + (2 * len(topics)) + (1 * len(topics)) - packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) - self._encode_remaining_length(fixed_header, remaining_length=packet_length) + packet_length += sum(len(topic.encode("utf-8")) + for topic, qos in topics) + self._encode_remaining_length( + fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -752,7 +770,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N while True: op = self._wait_for_msg() if op is None: - if self.get_monotonic_time() - stamp > self._recv_timeout: + if self.diff_ns(stamp) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -809,7 +827,8 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: fixed_header = bytearray([MQTT_UNSUB]) packet_length = 2 + (2 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic in topics) - self._encode_remaining_length(fixed_header, remaining_length=packet_length) + self._encode_remaining_length( + fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -830,7 +849,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: stamp = self.get_monotonic_time() op = self._wait_for_msg() if op is None: - if self.get_monotonic_time() - stamp > self._recv_timeout: + if self.diff_ns(stamp) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -842,7 +861,8 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: assert rc[1] == packet_id_bytes[0] and rc[2] == packet_id_bytes[1] for t in topics: if self.on_unsubscribe is not None: - self.on_unsubscribe(self, self.user_data, t, self._pid) + self.on_unsubscribe( + self, self.user_data, t, self._pid) self._subscribed_topics.remove(t) return @@ -860,7 +880,8 @@ def _recompute_reconnect_backoff(self) -> None: self._reconnect_timeout = 2**self._reconnect_attempt # pylint: disable=consider-using-f-string self.logger.debug( - "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) + "Reconnect timeout computed to {:.2f}".format( + self._reconnect_timeout) ) if self._reconnect_timeout > self._reconnect_maximum_backoff: @@ -935,7 +956,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: while True: if ( - self.get_monotonic_time() - self._last_msg_sent_timestamp + self.diff_ns(self._last_msg_sent_timestamp) >= self.keep_alive ): # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server @@ -945,14 +966,15 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs.extend(self.ping()) # ping() itself contains a _wait_for_msg() loop which might have taken a while, # so check here as well. - if self.get_monotonic_time() - stamp > timeout: - self.logger.debug(f"Loop timed out after {timeout} seconds") + if self.diff_ns(stamp) > timeout: + self.logger.debug( + f"Loop timed out after {timeout} seconds") break rc = self._wait_for_msg() if rc is not None: rcs.append(rc) - if self.get_monotonic_time() - stamp > timeout: + if self.diff_ns(stamp) > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break @@ -960,7 +982,6 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # pylint: disable = too-many-return-statements - """Reads and processes network events. Return the packet type or None if there is nothing to be received. @@ -985,12 +1006,14 @@ def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # If we get here, it means that there is nothing to be received return None pkt_type = res[0] & MQTT_PKT_TYPE_MASK - self.logger.debug(f"Got message type: {hex(pkt_type)} pkt: {hex(res[0])}") + self.logger.debug( + f"Got message type: {hex(pkt_type)} pkt: {hex(res[0])}") if pkt_type == MQTT_PINGRESP: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: - raise MMQTTException(f"Unexpected PINGRESP returned from broker: {sz}.") + raise MMQTTException( + f"Unexpected PINGRESP returned from broker: {sz}.") return pkt_type if pkt_type != MQTT_PUBLISH: @@ -1019,7 +1042,8 @@ def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") - self.logger.debug("Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) + self.logger.debug( + "Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) self._handle_on_message(topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") @@ -1067,14 +1091,15 @@ def _sock_exact_recv( recv_len = self._sock.recv_into(rc, bufsize) to_read = bufsize - recv_len if to_read < 0: - raise MMQTTException(f"negative number of bytes to read: {to_read}") + raise MMQTTException( + f"negative number of bytes to read: {to_read}") read_timeout = timeout if timeout is not None else self.keep_alive mv = mv[recv_len:] while to_read > 0: recv_len = self._sock.recv_into(mv, to_read) to_read -= recv_len mv = mv[recv_len:] - if self.get_monotonic_time() - stamp > read_timeout: + if self.diff_ns(stamp) > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) @@ -1094,7 +1119,7 @@ def _sock_exact_recv( recv = self._sock.recv(to_read) to_read -= len(recv) rc += recv - if self.get_monotonic_time() - stamp > read_timeout: + if self.diff_ns(stamp) > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) From 89bf045b6069ffcfb1347fdef5f3d0dd00a00135 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Fri, 22 Mar 2024 19:08:35 -0400 Subject: [PATCH 208/289] formatted with Black --- adafruit_minimqtt/adafruit_minimqtt.py | 61 +++++++++----------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d2ff2ab9..d9028054 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -197,8 +197,7 @@ def __init__( self._username = username self._password = password if ( - self._password and len(password.encode( - "utf-8")) > MQTT_TOPIC_LENGTH_LIMIT + self._password and len(password.encode("utf-8")) > MQTT_TOPIC_LENGTH_LIMIT ): # [MQTT-3.1.3.5] raise MMQTTException("Password length is too large.") @@ -225,8 +224,7 @@ def __init__( self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: - raise ValueError( - "MQTT Client ID must be between 1 and 23 bytes") + raise ValueError("MQTT Client ID must be between 1 and 23 bytes") # LWT self._lw_topic = None @@ -263,7 +261,7 @@ def diff_ns(self, stamp): Taking timestamp differences using nanosecond ints before dividing should maintain precision. """ - return (self.get_monotonic_time() - stamp)/1000000000 + return (self.get_monotonic_time() - stamp) / 1000000000 def __enter__(self): return self @@ -317,8 +315,7 @@ def will_set( self.logger.debug("Setting last will properties") self._valid_qos(qos) if self._is_connected: - raise MMQTTException( - "Last Will should only be called before connect().") + raise MMQTTException("Last Will should only be called before connect().") if payload is None: payload = "" if isinstance(payload, (int, float, str)): @@ -342,8 +339,7 @@ def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: If a callback is called for the topic, then any "on_message" callback will not be called. """ if mqtt_topic is None or callback_method is None: - raise ValueError( - "MQTT topic and callback method must both be defined.") + raise ValueError("MQTT topic and callback method must both be defined.") self._on_message_filtered[mqtt_topic] = callback_method def remove_topic_callback(self, mqtt_topic: str) -> None: @@ -391,8 +387,7 @@ def username_pw_set(self, username: str, password: Optional[str] = None) -> None """ if self._is_connected: - raise MMQTTException( - "This method must be called before connect().") + raise MMQTTException("This method must be called before connect().") self._username = username if password is not None: self._password = password @@ -521,8 +516,7 @@ def _connect( remaining_length += ( 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) ) - var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | ( - self._lw_qos & 0x2) << 3 + var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[7] |= self._lw_retain << 5 self._encode_remaining_length(fixed_header, remaining_length) @@ -651,8 +645,7 @@ def publish( else: raise MMQTTException("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: - raise MMQTTException( - f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") + raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") assert ( 0 <= qos <= 1 ), "Quality of Service Level 2 is unsupported by this library." @@ -699,8 +692,7 @@ def publish( rcv_pid = rcv_pid_buf[0] << 0x08 | rcv_pid_buf[1] if self._pid == rcv_pid: if self.on_publish is not None: - self.on_publish( - self, self.user_data, topic, rcv_pid) + self.on_publish(self, self.user_data, topic, rcv_pid) return if op is None: @@ -744,10 +736,8 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug("Sending SUBSCRIBE to broker...") fixed_header = bytearray([MQTT_SUB]) packet_length = 2 + (2 * len(topics)) + (1 * len(topics)) - packet_length += sum(len(topic.encode("utf-8")) - for topic, qos in topics) - self._encode_remaining_length( - fixed_header, remaining_length=packet_length) + packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) + self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -827,8 +817,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: fixed_header = bytearray([MQTT_UNSUB]) packet_length = 2 + (2 * len(topics)) packet_length += sum(len(topic.encode("utf-8")) for topic in topics) - self._encode_remaining_length( - fixed_header, remaining_length=packet_length) + self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") self._sock.send(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 @@ -861,8 +850,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: assert rc[1] == packet_id_bytes[0] and rc[2] == packet_id_bytes[1] for t in topics: if self.on_unsubscribe is not None: - self.on_unsubscribe( - self, self.user_data, t, self._pid) + self.on_unsubscribe(self, self.user_data, t, self._pid) self._subscribed_topics.remove(t) return @@ -880,8 +868,7 @@ def _recompute_reconnect_backoff(self) -> None: self._reconnect_timeout = 2**self._reconnect_attempt # pylint: disable=consider-using-f-string self.logger.debug( - "Reconnect timeout computed to {:.2f}".format( - self._reconnect_timeout) + "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) ) if self._reconnect_timeout > self._reconnect_maximum_backoff: @@ -955,10 +942,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs = [] while True: - if ( - self.diff_ns(self._last_msg_sent_timestamp) - >= self.keep_alive - ): + if self.diff_ns(self._last_msg_sent_timestamp) >= self.keep_alive: # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server self.logger.debug( "KeepAlive period elapsed - requesting a PINGRESP from the server..." @@ -967,8 +951,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: # ping() itself contains a _wait_for_msg() loop which might have taken a while, # so check here as well. if self.diff_ns(stamp) > timeout: - self.logger.debug( - f"Loop timed out after {timeout} seconds") + self.logger.debug(f"Loop timed out after {timeout} seconds") break rc = self._wait_for_msg() @@ -1006,14 +989,12 @@ def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # If we get here, it means that there is nothing to be received return None pkt_type = res[0] & MQTT_PKT_TYPE_MASK - self.logger.debug( - f"Got message type: {hex(pkt_type)} pkt: {hex(res[0])}") + self.logger.debug(f"Got message type: {hex(pkt_type)} pkt: {hex(res[0])}") if pkt_type == MQTT_PINGRESP: self.logger.debug("Got PINGRESP") sz = self._sock_exact_recv(1)[0] if sz != 0x00: - raise MMQTTException( - f"Unexpected PINGRESP returned from broker: {sz}.") + raise MMQTTException(f"Unexpected PINGRESP returned from broker: {sz}.") return pkt_type if pkt_type != MQTT_PUBLISH: @@ -1042,8 +1023,7 @@ def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: # read message contents raw_msg = self._sock_exact_recv(sz) msg = raw_msg if self._use_binary_mode else str(raw_msg, "utf-8") - self.logger.debug( - "Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) + self.logger.debug("Receiving PUBLISH \nTopic: %s\nMsg: %s\n", topic, raw_msg) self._handle_on_message(topic, msg) if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") @@ -1091,8 +1071,7 @@ def _sock_exact_recv( recv_len = self._sock.recv_into(rc, bufsize) to_read = bufsize - recv_len if to_read < 0: - raise MMQTTException( - f"negative number of bytes to read: {to_read}") + raise MMQTTException(f"negative number of bytes to read: {to_read}") read_timeout = timeout if timeout is not None else self.keep_alive mv = mv[recv_len:] while to_read > 0: From 639160a1b0d930f6550cc7e9c7b1515cda74ba6b Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Fri, 22 Mar 2024 19:20:02 -0400 Subject: [PATCH 209/289] added _ns suffix to vars and func --- adafruit_minimqtt/adafruit_minimqtt.py | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index d9028054..cc2b8355 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -181,7 +181,7 @@ def __init__( self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 - self._last_msg_sent_timestamp: int = 0 + self._last_msg_sent_timestamp_ns: int = 0 self.logger = NullLogger() """An optional logging attribute that can be set with with a Logger to enable debug logging.""" @@ -220,7 +220,7 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - time_int = (self.get_monotonic_time() % 10000000000) // 10000000 + time_int = (self.get_monotonic_ns_time() % 10000000000) // 10000000 self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: @@ -244,7 +244,7 @@ def __init__( self.on_subscribe = None self.on_unsubscribe = None - def get_monotonic_time(self) -> float: + def get_monotonic_ns_time(self) -> float: """ Provide monotonic time in nanoseconds. Based on underlying implementation this might result in imprecise time, that will result in the library @@ -256,12 +256,12 @@ def get_monotonic_time(self) -> float: return int(time.monotonic() * 1000000000) - def diff_ns(self, stamp): + def diff_ns(self, stamp_ns): """ Taking timestamp differences using nanosecond ints before dividing should maintain precision. """ - return (self.get_monotonic_time() - stamp) / 1000000000 + return (self.get_monotonic_ns_time() - stamp_ns) / 1000000000 def __enter__(self): return self @@ -534,9 +534,9 @@ def _connect( if self._username is not None: self._send_str(self._username) self._send_str(self._password) - self._last_msg_sent_timestamp = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() self.logger.debug("Receiving CONNACK packet from broker") - stamp = self.get_monotonic_time() + stamp_ns = self.get_monotonic_ns_time() while True: op = self._wait_for_msg() if op == 32: @@ -552,7 +552,7 @@ def _connect( return result if op is None: - if self.diff_ns(stamp) > self._recv_timeout: + if self.diff_ns(stamp_ns) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -589,7 +589,7 @@ def disconnect(self) -> None: self._connection_manager.close_socket(self._sock) self._is_connected = False self._subscribed_topics = [] - self._last_msg_sent_timestamp = 0 + self._last_msg_sent_timestamp_ns = 0 if self.on_disconnect is not None: self.on_disconnect(self, self.user_data, 0) @@ -602,14 +602,14 @@ def ping(self) -> list[int]: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive - stamp = self.get_monotonic_time() - self._last_msg_sent_timestamp = stamp + stamp_ns = self.get_monotonic_ns_time() + self._last_msg_sent_timestamp_ns = stamp_ns rc, rcs = None, [] while rc != MQTT_PINGRESP: rc = self._wait_for_msg() if rc: rcs.append(rc) - if self.diff_ns(stamp) > ping_timeout: + if self.diff_ns(stamp_ns) > ping_timeout: raise MMQTTException("PINGRESP not returned from broker.") return rcs @@ -678,11 +678,11 @@ def publish( self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) - self._last_msg_sent_timestamp = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: - stamp = self.get_monotonic_time() + stamp_ns = self.get_monotonic_ns_time() while True: op = self._wait_for_msg() if op == 0x40: @@ -696,7 +696,7 @@ def publish( return if op is None: - if self.diff_ns(stamp) > self._recv_timeout: + if self.diff_ns(stamp_ns) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -755,12 +755,12 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"payload: {payload}") self._sock.send(payload) - stamp = self.get_monotonic_time() - self._last_msg_sent_timestamp = stamp + stamp_ns = self.get_monotonic_ns_time() + self._last_msg_sent_timestamp_ns = stamp_ns while True: op = self._wait_for_msg() if op is None: - if self.diff_ns(stamp) > self._recv_timeout: + if self.diff_ns(stamp_ns) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -832,13 +832,13 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") self._sock.send(payload) - self._last_msg_sent_timestamp = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() self.logger.debug("Waiting for UNSUBACK...") while True: - stamp = self.get_monotonic_time() + stamp_ns = self.get_monotonic_ns_time() op = self._wait_for_msg() if op is None: - if self.diff_ns(stamp) > self._recv_timeout: + if self.diff_ns(stamp_ns) > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -938,11 +938,11 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") - stamp = self.get_monotonic_time() + stamp_ns = self.get_monotonic_ns_time() rcs = [] while True: - if self.diff_ns(self._last_msg_sent_timestamp) >= self.keep_alive: + if self.diff_ns(self._last_msg_sent_timestamp_ns) >= self.keep_alive: # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server self.logger.debug( "KeepAlive period elapsed - requesting a PINGRESP from the server..." @@ -950,14 +950,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs.extend(self.ping()) # ping() itself contains a _wait_for_msg() loop which might have taken a while, # so check here as well. - if self.diff_ns(stamp) > timeout: + if self.diff_ns(stamp_ns) > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break rc = self._wait_for_msg() if rc is not None: rcs.append(rc) - if self.diff_ns(stamp) > timeout: + if self.diff_ns(stamp_ns) > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break @@ -1063,7 +1063,7 @@ def _sock_exact_recv( :param float timeout: timeout, in seconds. Defaults to keep_alive :return: byte array """ - stamp = self.get_monotonic_time() + stamp_ns = self.get_monotonic_ns_time() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) @@ -1078,7 +1078,7 @@ def _sock_exact_recv( recv_len = self._sock.recv_into(mv, to_read) to_read -= recv_len mv = mv[recv_len:] - if self.diff_ns(stamp) > read_timeout: + if self.diff_ns(stamp_ns) > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) @@ -1098,7 +1098,7 @@ def _sock_exact_recv( recv = self._sock.recv(to_read) to_read -= len(recv) rc += recv - if self.diff_ns(stamp) > read_timeout: + if self.diff_ns(stamp_ns) > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) From 9af0e3f7882782033fd5f9b7e0a89e7fd8cab193 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Fri, 22 Mar 2024 19:24:18 -0400 Subject: [PATCH 210/289] reverted func name back to get_monotonic_time to pass build check --- adafruit_minimqtt/adafruit_minimqtt.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cc2b8355..5720361c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -220,7 +220,7 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - time_int = (self.get_monotonic_ns_time() % 10000000000) // 10000000 + time_int = (self.get_monotonic_time() % 10000000000) // 10000000 self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: @@ -244,7 +244,7 @@ def __init__( self.on_subscribe = None self.on_unsubscribe = None - def get_monotonic_ns_time(self) -> float: + def get_monotonic_time(self) -> float: """ Provide monotonic time in nanoseconds. Based on underlying implementation this might result in imprecise time, that will result in the library @@ -261,7 +261,7 @@ def diff_ns(self, stamp_ns): Taking timestamp differences using nanosecond ints before dividing should maintain precision. """ - return (self.get_monotonic_ns_time() - stamp_ns) / 1000000000 + return (self.get_monotonic_time() - stamp_ns) / 1000000000 def __enter__(self): return self @@ -534,9 +534,9 @@ def _connect( if self._username is not None: self._send_str(self._username) self._send_str(self._password) - self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time() self.logger.debug("Receiving CONNACK packet from broker") - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() while True: op = self._wait_for_msg() if op == 32: @@ -602,7 +602,7 @@ def ping(self) -> list[int]: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() self._last_msg_sent_timestamp_ns = stamp_ns rc, rcs = None, [] while rc != MQTT_PINGRESP: @@ -678,11 +678,11 @@ def publish( self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) - self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() while True: op = self._wait_for_msg() if op == 0x40: @@ -755,7 +755,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"payload: {payload}") self._sock.send(payload) - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() self._last_msg_sent_timestamp_ns = stamp_ns while True: op = self._wait_for_msg() @@ -832,10 +832,10 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") self._sock.send(payload) - self._last_msg_sent_timestamp_ns = self.get_monotonic_ns_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time() self.logger.debug("Waiting for UNSUBACK...") while True: - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() op = self._wait_for_msg() if op is None: if self.diff_ns(stamp_ns) > self._recv_timeout: @@ -938,7 +938,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() rcs = [] while True: @@ -1063,7 +1063,7 @@ def _sock_exact_recv( :param float timeout: timeout, in seconds. Defaults to keep_alive :return: byte array """ - stamp_ns = self.get_monotonic_ns_time() + stamp_ns = self.get_monotonic_time() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) From 120d8a908e472c005dd48dd1d183d0bf45e7cd0f Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Mon, 25 Mar 2024 14:35:55 -0400 Subject: [PATCH 211/289] retain imprecise get_monotonic_time, add precision get_monotonic_time_ns function --- adafruit_minimqtt/adafruit_minimqtt.py | 41 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5720361c..3ee93ea4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -51,7 +51,7 @@ from .matcher import MQTTMatcher -__version__ = "7.6.3" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git" # Client-specific variables @@ -220,7 +220,7 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - time_int = (self.get_monotonic_time() % 10000000000) // 10000000 + time_int = (self.get_monotonic_time_ns() % 10000000000) // 10000000 self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: @@ -245,6 +245,17 @@ def __init__( self.on_unsubscribe = None def get_monotonic_time(self) -> float: + """ + Provide monotonic time in seconds. Based on underlying implementation + this might result in imprecise time, that will result in the library + not being able to operate if running contiguously for more than 24 days or so. + """ + if self.use_monotonic_ns: + return time.monotonic_ns() / 1000000000 + + return time.monotonic() + + def get_monotonic_time_ns(self) -> int: """ Provide monotonic time in nanoseconds. Based on underlying implementation this might result in imprecise time, that will result in the library @@ -256,12 +267,12 @@ def get_monotonic_time(self) -> float: return int(time.monotonic() * 1000000000) - def diff_ns(self, stamp_ns): + def diff_ns(self, stamp_ns) -> float: """ Taking timestamp differences using nanosecond ints before dividing - should maintain precision. + should maintain precision. Returns time difference in seconds. """ - return (self.get_monotonic_time() - stamp_ns) / 1000000000 + return (self.get_monotonic_time_ns() - stamp_ns) / 1000000000 def __enter__(self): return self @@ -534,9 +545,9 @@ def _connect( if self._username is not None: self._send_str(self._username) self._send_str(self._password) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() self.logger.debug("Receiving CONNACK packet from broker") - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() while True: op = self._wait_for_msg() if op == 32: @@ -602,7 +613,7 @@ def ping(self) -> list[int]: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() self._last_msg_sent_timestamp_ns = stamp_ns rc, rcs = None, [] while rc != MQTT_PINGRESP: @@ -678,11 +689,11 @@ def publish( self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() while True: op = self._wait_for_msg() if op == 0x40: @@ -755,7 +766,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"payload: {payload}") self._sock.send(payload) - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() self._last_msg_sent_timestamp_ns = stamp_ns while True: op = self._wait_for_msg() @@ -832,10 +843,10 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") self._sock.send(payload) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time() + self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() self.logger.debug("Waiting for UNSUBACK...") while True: - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() op = self._wait_for_msg() if op is None: if self.diff_ns(stamp_ns) > self._recv_timeout: @@ -938,7 +949,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() rcs = [] while True: @@ -1063,7 +1074,7 @@ def _sock_exact_recv( :param float timeout: timeout, in seconds. Defaults to keep_alive :return: byte array """ - stamp_ns = self.get_monotonic_time() + stamp_ns = self.get_monotonic_time_ns() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) From 5378f8f69811130f93129dc4a6a28c33b24c6b94 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Mon, 25 Mar 2024 16:35:20 -0400 Subject: [PATCH 212/289] reverted timestamps back to using ticks_ms from adafruit_ticks library --- adafruit_minimqtt/adafruit_minimqtt.py | 75 +++++++++++--------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 3ee93ea4..21e154a4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -36,6 +36,7 @@ from random import randint from adafruit_connection_manager import get_connection_manager +from adafruit_ticks import ticks_ms, ticks_diff try: from typing import List, Optional, Tuple, Type, Union @@ -51,7 +52,7 @@ from .matcher import MQTTMatcher -__version__ = "0.0.0+auto.0" +__version__ = "7.6.3" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git" # Client-specific variables @@ -181,7 +182,7 @@ def __init__( self._is_connected = False self._msg_size_lim = MQTT_MSG_SZ_LIM self._pid = 0 - self._last_msg_sent_timestamp_ns: int = 0 + self._last_msg_sent_timestamp: int = 0 self.logger = NullLogger() """An optional logging attribute that can be set with with a Logger to enable debug logging.""" @@ -220,7 +221,7 @@ def __init__( self.client_id = client_id else: # assign a unique client_id - time_int = (self.get_monotonic_time_ns() % 10000000000) // 10000000 + time_int = int(ticks_ms() / 10) % 1000 self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}" # generated client_id's enforce spec.'s length rules if len(self.client_id.encode("utf-8")) > 23 or not self.client_id: @@ -255,25 +256,6 @@ def get_monotonic_time(self) -> float: return time.monotonic() - def get_monotonic_time_ns(self) -> int: - """ - Provide monotonic time in nanoseconds. Based on underlying implementation - this might result in imprecise time, that will result in the library - not being able to operate if running contiguously for more than 24 days or so. - Keeping timestamps in nanosecond ints from monotonic_ns should preserve precision. - """ - if self.use_monotonic_ns: - return time.monotonic_ns() - - return int(time.monotonic() * 1000000000) - - def diff_ns(self, stamp_ns) -> float: - """ - Taking timestamp differences using nanosecond ints before dividing - should maintain precision. Returns time difference in seconds. - """ - return (self.get_monotonic_time_ns() - stamp_ns) / 1000000000 - def __enter__(self): return self @@ -545,9 +527,9 @@ def _connect( if self._username is not None: self._send_str(self._username) self._send_str(self._password) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() + self._last_msg_sent_timestamp = ticks_ms() self.logger.debug("Receiving CONNACK packet from broker") - stamp_ns = self.get_monotonic_time_ns() + stamp = ticks_ms() while True: op = self._wait_for_msg() if op == 32: @@ -563,7 +545,7 @@ def _connect( return result if op is None: - if self.diff_ns(stamp_ns) > self._recv_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -600,7 +582,7 @@ def disconnect(self) -> None: self._connection_manager.close_socket(self._sock) self._is_connected = False self._subscribed_topics = [] - self._last_msg_sent_timestamp_ns = 0 + self._last_msg_sent_timestamp = 0 if self.on_disconnect is not None: self.on_disconnect(self, self.user_data, 0) @@ -613,14 +595,14 @@ def ping(self) -> list[int]: self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) ping_timeout = self.keep_alive - stamp_ns = self.get_monotonic_time_ns() - self._last_msg_sent_timestamp_ns = stamp_ns + stamp = ticks_ms() + self._last_msg_sent_timestamp = stamp rc, rcs = None, [] while rc != MQTT_PINGRESP: rc = self._wait_for_msg() if rc: rcs.append(rc) - if self.diff_ns(stamp_ns) > ping_timeout: + if ticks_diff(ticks_ms(), stamp): raise MMQTTException("PINGRESP not returned from broker.") return rcs @@ -689,11 +671,11 @@ def publish( self._sock.send(pub_hdr_fixed) self._sock.send(pub_hdr_var) self._sock.send(msg) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() + self._last_msg_sent_timestamp = ticks_ms() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) if qos == 1: - stamp_ns = self.get_monotonic_time_ns() + stamp = ticks_ms() while True: op = self._wait_for_msg() if op == 0x40: @@ -707,7 +689,7 @@ def publish( return if op is None: - if self.diff_ns(stamp_ns) > self._recv_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -766,12 +748,12 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"payload: {payload}") self._sock.send(payload) - stamp_ns = self.get_monotonic_time_ns() - self._last_msg_sent_timestamp_ns = stamp_ns + stamp = ticks_ms() + self._last_msg_sent_timestamp = stamp while True: op = self._wait_for_msg() if op is None: - if self.diff_ns(stamp_ns) > self._recv_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -843,13 +825,13 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") self._sock.send(payload) - self._last_msg_sent_timestamp_ns = self.get_monotonic_time_ns() + self._last_msg_sent_timestamp = ticks_ms() self.logger.debug("Waiting for UNSUBACK...") while True: - stamp_ns = self.get_monotonic_time_ns() + stamp = ticks_ms() op = self._wait_for_msg() if op is None: - if self.diff_ns(stamp_ns) > self._recv_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > self._recv_timeout: raise MMQTTException( f"No data received from broker for {self._recv_timeout} seconds." ) @@ -949,11 +931,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: self._connected() self.logger.debug(f"waiting for messages for {timeout} seconds") - stamp_ns = self.get_monotonic_time_ns() + stamp = ticks_ms() rcs = [] while True: - if self.diff_ns(self._last_msg_sent_timestamp_ns) >= self.keep_alive: + if ( + ticks_diff(ticks_ms(), self._last_msg_sent_timestamp) / 1000 + >= self.keep_alive + ): # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server self.logger.debug( "KeepAlive period elapsed - requesting a PINGRESP from the server..." @@ -961,14 +946,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs.extend(self.ping()) # ping() itself contains a _wait_for_msg() loop which might have taken a while, # so check here as well. - if self.diff_ns(stamp_ns) > timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break rc = self._wait_for_msg() if rc is not None: rcs.append(rc) - if self.diff_ns(stamp_ns) > timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > timeout: self.logger.debug(f"Loop timed out after {timeout} seconds") break @@ -1074,7 +1059,7 @@ def _sock_exact_recv( :param float timeout: timeout, in seconds. Defaults to keep_alive :return: byte array """ - stamp_ns = self.get_monotonic_time_ns() + stamp = ticks_ms() if not self._backwards_compatible_sock: # CPython/Socketpool Impl. rc = bytearray(bufsize) @@ -1089,7 +1074,7 @@ def _sock_exact_recv( recv_len = self._sock.recv_into(mv, to_read) to_read -= recv_len mv = mv[recv_len:] - if self.diff_ns(stamp_ns) > read_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) @@ -1109,7 +1094,7 @@ def _sock_exact_recv( recv = self._sock.recv(to_read) to_read -= len(recv) rc += recv - if self.diff_ns(stamp_ns) > read_timeout: + if ticks_diff(ticks_ms(), stamp) / 1000 > read_timeout: raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) From 51b055075030b103f11175d438a244abbdfa1eb4 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Mon, 25 Mar 2024 16:39:12 -0400 Subject: [PATCH 213/289] fix ping_timeout --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 21e154a4..04c189fa 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -602,7 +602,7 @@ def ping(self) -> list[int]: rc = self._wait_for_msg() if rc: rcs.append(rc) - if ticks_diff(ticks_ms(), stamp): + if ticks_diff(ticks_ms(), stamp) > ping_timeout * 1000: raise MMQTTException("PINGRESP not returned from broker.") return rcs From 5b0c8ccd03d26482818e00b3bd160891c486194c Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:31:36 -0400 Subject: [PATCH 214/289] add adafruit_ticks to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 25052887..afd7a2d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager +Adafruit-Ticks From 5ad6abbeb357b87e86056a26bd848510079e5938 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:32:44 -0400 Subject: [PATCH 215/289] revert --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afd7a2d4..25052887 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager -Adafruit-Ticks From bce70b29030a82cb573e7271b46beb17bcc0916a Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:34:15 -0400 Subject: [PATCH 216/289] add adafruit_ticks to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 25052887..11e0e031 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager +Adafruit-Ticks \ No newline at end of file From 92035c0720dbd7bedec8f4def918c173eb1ab6af Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:38:47 -0400 Subject: [PATCH 217/289] try lower case in requirements.txt --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 04c189fa..5599cada 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -52,7 +52,7 @@ from .matcher import MQTTMatcher -__version__ = "7.6.3" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git" # Client-specific variables diff --git a/requirements.txt b/requirements.txt index 11e0e031..94b85801 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager -Adafruit-Ticks \ No newline at end of file +adafruit-ticks \ No newline at end of file From 1dd46527d0694c070867bec365fc310b06445330 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:41:47 -0400 Subject: [PATCH 218/289] ok, adafruit-circuitpython-ticks --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 94b85801..1059d657 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager -adafruit-ticks \ No newline at end of file +adafruit-circuitpython-ticks \ No newline at end of file From d5626ec6adcb20e1bfd35ea8625004fac6ff2ecc Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 26 Mar 2024 19:44:16 -0400 Subject: [PATCH 219/289] fix end of file error? --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1059d657..8075f629 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager -adafruit-circuitpython-ticks \ No newline at end of file +adafruit-circuitpython-ticks From 07239ac30d2823b77896df45bb026efde197cbea Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 26 Nov 2023 23:16:28 +0100 Subject: [PATCH 220/289] use recv_timeout instead of keep_alive fixes #189 --- adafruit_minimqtt/adafruit_minimqtt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ede9559..66ca7522 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1068,7 +1068,7 @@ def _sock_exact_recv( to_read = bufsize - recv_len if to_read < 0: raise MMQTTException(f"negative number of bytes to read: {to_read}") - read_timeout = timeout if timeout is not None else self.keep_alive + read_timeout = timeout if timeout is not None else self._recv_timeout mv = mv[recv_len:] while to_read > 0: recv_len = self._sock.recv_into(mv, to_read) @@ -1079,7 +1079,7 @@ def _sock_exact_recv( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) else: # ESP32SPI Impl. - # This will timeout with socket timeout (not keepalive timeout) + # This will time out with socket timeout (not receive timeout). rc = self._sock.recv(bufsize) if not rc: self.logger.debug("_sock_exact_recv timeout") @@ -1089,7 +1089,7 @@ def _sock_exact_recv( # or raise exception if wait longer than read_timeout to_read = bufsize - len(rc) assert to_read >= 0 - read_timeout = self.keep_alive + read_timeout = self._recv_timeout while to_read > 0: recv = self._sock.recv(to_read) to_read -= len(recv) From 71f9f9d7c9e88278fcc726f742b84840003b79ea Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 20 Jan 2024 20:28:31 +0100 Subject: [PATCH 221/289] add unit test for recv timeout --- tests/test_recv_timeout.py | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/test_recv_timeout.py diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py new file mode 100644 index 00000000..0d9d0ee3 --- /dev/null +++ b/tests/test_recv_timeout.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2024 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""receive timeout tests""" + +import socket +import time +from unittest import TestCase, main +from unittest.mock import Mock + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +class RecvTimeout(TestCase): + """This class contains tests for receive timeout handling.""" + + def test_recv_timeout_vs_keepalive(self) -> None: + """verify that receive timeout as used via ping() is different to keep alive timeout""" + host = "127.0.0.1" + + recv_timeout = 4 + keep_alive = recv_timeout * 2 + mqtt_client = MQTT.MQTT( + broker=host, + socket_pool=socket, + connect_retries=1, + socket_timeout=recv_timeout // 2, + recv_timeout=recv_timeout, + keep_alive=keep_alive, + ) + + # Create a mock socket that will accept anything and return nothing. + socket_mock = Mock() + socket_mock.recv_into = Mock(side_effect=lambda ret_buf, buf_size: 0) + # pylint: disable=protected-access + mqtt_client._sock = socket_mock + + mqtt_client._connected = lambda: True + start = time.monotonic() + with self.assertRaises(MQTT.MMQTTException): + mqtt_client.ping() + + # Verify the mock interactions. + socket_mock.send.assert_called_once() + socket_mock.recv_into.assert_called() + + now = time.monotonic() + assert recv_timeout <= (now - start) < keep_alive + + +if __name__ == "__main__": + main() From 913d2759fb2ddb45792fa355975876ba8a13d2bf Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 20 Jan 2024 21:31:58 +0100 Subject: [PATCH 222/289] use receive timeout for ping handling as well --- adafruit_minimqtt/adafruit_minimqtt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 66ca7522..2ca47ae8 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -593,7 +593,7 @@ def ping(self) -> list[int]: self._connected() self.logger.debug("Sending PINGREQ") self._sock.send(MQTT_PINGREQ) - ping_timeout = self.keep_alive + ping_timeout = self._recv_timeout stamp = self.get_monotonic_time() self._last_msg_sent_timestamp = stamp rc, rcs = None, [] @@ -602,7 +602,9 @@ def ping(self) -> list[int]: if rc: rcs.append(rc) if self.get_monotonic_time() - stamp > ping_timeout: - raise MMQTTException("PINGRESP not returned from broker.") + raise MMQTTException( + f"PINGRESP not returned from broker within {ping_timeout} seconds." + ) return rcs # pylint: disable=too-many-branches, too-many-statements From 5814fd0bd9792e63f7bc50b2f75801336b138f70 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 20 Jan 2024 21:53:57 +0100 Subject: [PATCH 223/289] add subtest for timeout exception --- tests/test_recv_timeout.py | 63 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index 0d9d0ee3..f65dc403 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -17,36 +17,39 @@ class RecvTimeout(TestCase): def test_recv_timeout_vs_keepalive(self) -> None: """verify that receive timeout as used via ping() is different to keep alive timeout""" - host = "127.0.0.1" - - recv_timeout = 4 - keep_alive = recv_timeout * 2 - mqtt_client = MQTT.MQTT( - broker=host, - socket_pool=socket, - connect_retries=1, - socket_timeout=recv_timeout // 2, - recv_timeout=recv_timeout, - keep_alive=keep_alive, - ) - - # Create a mock socket that will accept anything and return nothing. - socket_mock = Mock() - socket_mock.recv_into = Mock(side_effect=lambda ret_buf, buf_size: 0) - # pylint: disable=protected-access - mqtt_client._sock = socket_mock - - mqtt_client._connected = lambda: True - start = time.monotonic() - with self.assertRaises(MQTT.MMQTTException): - mqtt_client.ping() - - # Verify the mock interactions. - socket_mock.send.assert_called_once() - socket_mock.recv_into.assert_called() - - now = time.monotonic() - assert recv_timeout <= (now - start) < keep_alive + + for side_effect in [lambda ret_buf, buf_size: 0, socket.timeout]: + with self.subTest(): + host = "127.0.0.1" + + recv_timeout = 4 + keep_alive = recv_timeout * 2 + mqtt_client = MQTT.MQTT( + broker=host, + socket_pool=socket, + connect_retries=1, + socket_timeout=recv_timeout // 2, + recv_timeout=recv_timeout, + keep_alive=keep_alive, + ) + + # Create a mock socket that will accept anything and return nothing. + socket_mock = Mock() + socket_mock.recv_into = Mock(side_effect=side_effect) + # pylint: disable=protected-access + mqtt_client._sock = socket_mock + + mqtt_client._connected = lambda: True + start = time.monotonic() + with self.assertRaises(MQTT.MMQTTException): + mqtt_client.ping() + + # Verify the mock interactions. + socket_mock.send.assert_called_once() + socket_mock.recv_into.assert_called() + + now = time.monotonic() + assert recv_timeout <= (now - start) < keep_alive if __name__ == "__main__": From 128e3f039524126b9f378fa100a4d978b2d77781 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 20:16:51 -0400 Subject: [PATCH 224/289] purge last remnants of time.monotonic(), get rid of imprecise_time argument parameter --- adafruit_minimqtt/adafruit_minimqtt.py | 28 -------------------------- 1 file changed, 28 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5599cada..3d530f00 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -124,8 +124,6 @@ class MQTT: This works with all callbacks but the "on_message" and those added via add_topic_callback(); for those, to get access to the user_data use the 'user_data' member of the MQTT object passed as 1st argument. - :param bool use_imprecise_time: on boards without time.monotonic_ns() one has to set - this to True in order to operate correctly over more than 24 days or so """ @@ -147,7 +145,6 @@ def __init__( socket_timeout: int = 1, connect_retries: int = 5, user_data=None, - use_imprecise_time: Optional[bool] = None, ) -> None: self._connection_manager = get_connection_manager(socket_pool) self._socket_pool = socket_pool @@ -156,20 +153,6 @@ def __init__( self._backwards_compatible_sock = False self._use_binary_mode = use_binary_mode - self.use_monotonic_ns = False - try: - time.monotonic_ns() - self.use_monotonic_ns = True - except AttributeError: - if use_imprecise_time: - self.use_monotonic_ns = False - else: - raise MMQTTException( # pylint: disable=raise-missing-from - "time.monotonic_ns() is not available. " - "Will use imprecise time however only if the" - "use_imprecise_time argument is set to True." - ) - if recv_timeout <= socket_timeout: raise MMQTTException( "recv_timeout must be strictly greater than socket_timeout" @@ -245,17 +228,6 @@ def __init__( self.on_subscribe = None self.on_unsubscribe = None - def get_monotonic_time(self) -> float: - """ - Provide monotonic time in seconds. Based on underlying implementation - this might result in imprecise time, that will result in the library - not being able to operate if running contiguously for more than 24 days or so. - """ - if self.use_monotonic_ns: - return time.monotonic_ns() / 1000000000 - - return time.monotonic() - def __enter__(self): return self From 830af789ce68018a2aebbbfc03ac2565d0d8d4f1 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 20:24:52 -0400 Subject: [PATCH 225/289] remove test_loop dependence on mqtt.get_monotonic_time function --- tests/test_loop.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 4d79b65b..a40b66e9 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -142,7 +142,8 @@ def test_loop_basic(self) -> None: time_before = time.monotonic() timeout = random.randint(3, 8) # pylint: disable=protected-access - mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() + # mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() + mqtt_client._last_msg_sent_timestamp = time.monotonic() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() From 82d6f9d650595b7f163d808d61df62e58a8aed09 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 20:41:35 -0400 Subject: [PATCH 226/289] timestamp uses ticks_ms --- tests/test_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index a40b66e9..c8328b40 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -143,7 +143,7 @@ def test_loop_basic(self) -> None: timeout = random.randint(3, 8) # pylint: disable=protected-access # mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() - mqtt_client._last_msg_sent_timestamp = time.monotonic() + mqtt_client._last_msg_sent_timestamp = mqtt_client.ticks_ms() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() From a1eec26d1cc0c06c1a77f1426e89311b7f1c85be Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 20:44:46 -0400 Subject: [PATCH 227/289] fix ticks_ms ref --- tests/test_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index c8328b40..4e4f526e 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -143,7 +143,7 @@ def test_loop_basic(self) -> None: timeout = random.randint(3, 8) # pylint: disable=protected-access # mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() - mqtt_client._last_msg_sent_timestamp = mqtt_client.ticks_ms() + mqtt_client._last_msg_sent_timestamp = MQTT.ticks_ms() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() From e873f43fa970ee86198b8a6680bce6bda4016d12 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 20:57:51 -0400 Subject: [PATCH 228/289] modify ping_timeout test for 3 res --- tests/test_loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 4e4f526e..756e9572 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -142,7 +142,6 @@ def test_loop_basic(self) -> None: time_before = time.monotonic() timeout = random.randint(3, 8) # pylint: disable=protected-access - # mqtt_client._last_msg_sent_timestamp = mqtt_client.get_monotonic_time() mqtt_client._last_msg_sent_timestamp = MQTT.ticks_ms() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() @@ -224,7 +223,8 @@ def test_loop_ping_timeout(self): res = mqtt_client.loop(timeout=2 * keep_alive_timeout) assert time.monotonic() - start >= 2 * keep_alive_timeout assert len(mocket.sent) > 0 - assert len(res) == 2 + # assert len(res) == 2 + assert len(res) == 3 # not sure if 3 is ok assert set(res) == {int(0xD0)} # pylint: disable=no-self-use From 804d7f70bfc5059768ffbc41aebaa7c3e5f6e71e Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 5 May 2024 21:05:13 -0400 Subject: [PATCH 229/289] black formatting --- tests/test_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 756e9572..1ca6591f 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -224,7 +224,7 @@ def test_loop_ping_timeout(self): assert time.monotonic() - start >= 2 * keep_alive_timeout assert len(mocket.sent) > 0 # assert len(res) == 2 - assert len(res) == 3 # not sure if 3 is ok + assert len(res) == 3 # not sure if 3 is ok assert set(res) == {int(0xD0)} # pylint: disable=no-self-use From b569ce7881f5fc3b4e2d5c5e2e3753e2c8b23c58 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Tue, 7 May 2024 18:39:12 -0400 Subject: [PATCH 230/289] Remove micropython from docs/conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index eb5696d4..3b989959 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["micropython", "microcontroller", "random"] +autodoc_mock_imports = ["microcontroller", "random"] intersphinx_mapping = { From 89bd1e131ce07660c4bd356028c53000c3caf277 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Wed, 8 May 2024 20:13:05 -0400 Subject: [PATCH 231/289] make loop ping timeout test more robust --- tests/test_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index 1ca6591f..ea3df5f0 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -220,7 +220,7 @@ def test_loop_ping_timeout(self): mqtt_client._sock = mocket start = time.monotonic() - res = mqtt_client.loop(timeout=2 * keep_alive_timeout) + res = mqtt_client.loop(timeout=2 * keep_alive_timeout + recv_timeout) assert time.monotonic() - start >= 2 * keep_alive_timeout assert len(mocket.sent) > 0 # assert len(res) == 2 From ee32e5e32b65a53b2abcc442f3bdfcb0d38453c1 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Thu, 9 May 2024 18:06:06 -0400 Subject: [PATCH 232/289] cleaned up test_loop --- tests/test_loop.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_loop.py b/tests/test_loop.py index ea3df5f0..6666d86f 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -223,8 +223,7 @@ def test_loop_ping_timeout(self): res = mqtt_client.loop(timeout=2 * keep_alive_timeout + recv_timeout) assert time.monotonic() - start >= 2 * keep_alive_timeout assert len(mocket.sent) > 0 - # assert len(res) == 2 - assert len(res) == 3 # not sure if 3 is ok + assert len(res) == 3 assert set(res) == {int(0xD0)} # pylint: disable=no-self-use From 1037c14b97118693fba35d569ec5816852626b1e Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 11 May 2024 09:35:46 -0700 Subject: [PATCH 233/289] Update test exceptions --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- tests/test_port_ssl.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7ede9559..622c6e68 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -424,7 +424,7 @@ def connect( ) self._reset_reconnect_backoff() return ret - except RuntimeError as e: + except (MemoryError, OSError, RuntimeError) as e: self.logger.warning(f"Socket error when connecting: {e}") backoff = False except MMQTTException as e: diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 9ac154da..2aa877f5 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -120,6 +120,6 @@ def test_tls_without_ssl_context(self) -> None: connect_retries=1, ) - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: mqtt_client.connect() - assert "ssl_context must be set" in str(context) + assert "ssl_context must be provided if using ssl" in str(context) From 93b7b3e2b1bdeec1a761c474cd110d099420dd93 Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 12 May 2024 16:40:56 -0400 Subject: [PATCH 234/289] fixed up ping_timeout --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 3d530f00..3bc018d0 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -574,8 +574,8 @@ def ping(self) -> list[int]: rc = self._wait_for_msg() if rc: rcs.append(rc) - if ticks_diff(ticks_ms(), stamp) > ping_timeout * 1000: - raise MMQTTException("PINGRESP not returned from broker.") + if ticks_diff(ticks_ms(), stamp) / 1000 > ping_timeout: + raise MMQTTException(f"PINGRESP not returned from broker within {ping_timeout} seconds.") return rcs # pylint: disable=too-many-branches, too-many-statements From 427225233daca44cd4da49c06f176b7573c897ce Mon Sep 17 00:00:00 2001 From: kevin-tritz Date: Sun, 12 May 2024 16:58:48 -0400 Subject: [PATCH 235/289] ok, reverted all of the black reformats and just formatted minimqtt.py --- adafruit_minimqtt/adafruit_minimqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 3bc018d0..702b403f 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -575,7 +575,9 @@ def ping(self) -> list[int]: if rc: rcs.append(rc) if ticks_diff(ticks_ms(), stamp) / 1000 > ping_timeout: - raise MMQTTException(f"PINGRESP not returned from broker within {ping_timeout} seconds.") + raise MMQTTException( + f"PINGRESP not returned from broker within {ping_timeout} seconds." + ) return rcs # pylint: disable=too-many-branches, too-many-statements From 906e6764d871897bb175f5e878b7e59370d3b884 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 13 May 2024 14:32:32 -0700 Subject: [PATCH 236/289] Don't retry when MQTT response is unauthorized --- adafruit_minimqtt/adafruit_minimqtt.py | 30 +++++++++++++++----- tests/test_backoff.py | 38 +++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 622c6e68..82b43abb 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -72,12 +72,18 @@ MQTT_PKT_TYPE_MASK = const(0xF0) +CONNACK_ERROR_INCORRECT_PROTOCOL = const(0x01) +CONNACK_ERROR_ID_REJECTED = const(0x02) +CONNACK_ERROR_SERVER_UNAVAILABLE = const(0x03) +CONNACK_ERROR_INCORECT_USERNAME_PASSWORD = const(0x04) +CONNACK_ERROR_UNAUTHORIZED = const(0x05) + CONNACK_ERRORS = { - const(0x01): "Connection Refused - Incorrect Protocol Version", - const(0x02): "Connection Refused - ID Rejected", - const(0x03): "Connection Refused - Server unavailable", - const(0x04): "Connection Refused - Incorrect username/password", - const(0x05): "Connection Refused - Unauthorized", + CONNACK_ERROR_INCORRECT_PROTOCOL: "Connection Refused - Incorrect Protocol Version", + CONNACK_ERROR_ID_REJECTED: "Connection Refused - ID Rejected", + CONNACK_ERROR_SERVER_UNAVAILABLE: "Connection Refused - Server unavailable", + CONNACK_ERROR_INCORECT_USERNAME_PASSWORD: "Connection Refused - Incorrect username/password", + CONNACK_ERROR_UNAUTHORIZED: "Connection Refused - Unauthorized", } _default_sock = None # pylint: disable=invalid-name @@ -87,6 +93,10 @@ class MMQTTException(Exception): """MiniMQTT Exception class.""" + def __init__(self, error, code=None): + super().__init__(error, code) + self.code = code + class NullLogger: """Fake logger class that does not do anything""" @@ -428,8 +438,14 @@ def connect( self.logger.warning(f"Socket error when connecting: {e}") backoff = False except MMQTTException as e: - last_exception = e self.logger.info(f"MMQT error: {e}") + if e.code in [ + CONNACK_ERROR_INCORECT_USERNAME_PASSWORD, + CONNACK_ERROR_UNAUTHORIZED, + ]: + # No sense trying these again, re-raise + raise + last_exception = e backoff = True if self._reconnect_attempts_max > 1: @@ -535,7 +551,7 @@ def _connect( rc = self._sock_exact_recv(3) assert rc[0] == 0x02 if rc[2] != 0x00: - raise MMQTTException(CONNACK_ERRORS[rc[2]]) + raise MMQTTException(CONNACK_ERRORS[rc[2]], code=rc[2]) self._is_connected = True result = rc[0] & 1 if self.on_connect is not None: diff --git a/tests/test_backoff.py b/tests/test_backoff.py index e26d07a4..ce6097fa 100644 --- a/tests/test_backoff.py +++ b/tests/test_backoff.py @@ -18,18 +18,24 @@ class TestExpBackOff: """basic exponential back-off test""" connect_times = [] + raise_exception = None # pylint: disable=unused-argument def fake_connect(self, arg): """connect() replacement that records the call times and always raises OSError""" self.connect_times.append(time.monotonic()) - raise OSError("this connect failed") + raise self.raise_exception def test_failing_connect(self) -> None: """test that exponential back-off is used when connect() always raises OSError""" # use RFC 1918 address to avoid dealing with IPv6 in the call list below host = "172.40.0.3" port = 1883 + self.connect_times = [] + error_code = MQTT.CONNACK_ERROR_SERVER_UNAVAILABLE + self.raise_exception = MQTT.MMQTTException( + MQTT.CONNACK_ERRORS[error_code], code=error_code + ) with patch.object(socket.socket, "connect") as mock_method: mock_method.side_effect = self.fake_connect @@ -54,3 +60,33 @@ def test_failing_connect(self) -> None: print(f"connect() call times: {self.connect_times}") for i in range(1, connect_retries): assert self.connect_times[i] >= 2**i + + def test_unauthorized(self) -> None: + """test that exponential back-off is used when connect() always raises OSError""" + # use RFC 1918 address to avoid dealing with IPv6 in the call list below + host = "172.40.0.3" + port = 1883 + self.connect_times = [] + error_code = MQTT.CONNACK_ERROR_UNAUTHORIZED + self.raise_exception = MQTT.MMQTTException( + MQTT.CONNACK_ERRORS[error_code], code=error_code + ) + + with patch.object(socket.socket, "connect") as mock_method: + mock_method.side_effect = self.fake_connect + + connect_retries = 3 + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + connect_retries=connect_retries, + ) + print("connecting") + with pytest.raises(MQTT.MMQTTException) as context: + mqtt_client.connect() + assert "Connection Refused - Unauthorized" in str(context) + + mock_method.assert_called() + assert len(self.connect_times) == 1 From 16b6c6db1e9a54e664c5eb9ef4f2848731c482a5 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sun, 19 May 2024 21:14:10 -0700 Subject: [PATCH 237/289] Make sure socket is closed on exception --- adafruit_minimqtt/adafruit_minimqtt.py | 12 +++++++++--- tests/test_backoff.py | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 82b43abb..22b1f80a 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -438,6 +438,7 @@ def connect( self.logger.warning(f"Socket error when connecting: {e}") backoff = False except MMQTTException as e: + self._close_socket() self.logger.info(f"MMQT error: {e}") if e.code in [ CONNACK_ERROR_INCORECT_USERNAME_PASSWORD, @@ -452,9 +453,9 @@ def connect( exc_msg = "Repeated connect failures" else: exc_msg = "Connect failure" + if last_exception: raise MMQTTException(exc_msg) from last_exception - raise MMQTTException(exc_msg) # pylint: disable=too-many-branches, too-many-statements, too-many-locals @@ -565,6 +566,12 @@ def _connect( f"No data received from broker for {self._recv_timeout} seconds." ) + def _close_socket(self): + if self._sock: + self.logger.debug("Closing socket") + self._connection_manager.close_socket(self._sock) + self._sock = None + # pylint: disable=no-self-use def _encode_remaining_length( self, fixed_header: bytearray, remaining_length: int @@ -593,8 +600,7 @@ def disconnect(self) -> None: self._sock.send(MQTT_DISCONNECT) except RuntimeError as e: self.logger.warning(f"Unable to send DISCONNECT packet: {e}") - self.logger.debug("Closing socket") - self._connection_manager.close_socket(self._sock) + self._close_socket() self._is_connected = False self._subscribed_topics = [] self._last_msg_sent_timestamp = 0 diff --git a/tests/test_backoff.py b/tests/test_backoff.py index ce6097fa..44d2da9d 100644 --- a/tests/test_backoff.py +++ b/tests/test_backoff.py @@ -51,6 +51,7 @@ def test_failing_connect(self) -> None: print("connecting") with pytest.raises(MQTT.MMQTTException) as context: mqtt_client.connect() + assert mqtt_client._sock is None assert "Repeated connect failures" in str(context) mock_method.assert_called() @@ -86,6 +87,7 @@ def test_unauthorized(self) -> None: print("connecting") with pytest.raises(MQTT.MMQTTException) as context: mqtt_client.connect() + assert mqtt_client._sock is None assert "Connection Refused - Unauthorized" in str(context) mock_method.assert_called() From 124fe91d51d721faed9e776aa5ba82a9cfbf355f Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 21 May 2024 08:51:49 -0700 Subject: [PATCH 238/289] Update comments specific to networking modules --- adafruit_minimqtt/adafruit_minimqtt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index ad919e67..e28a7677 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -996,7 +996,7 @@ def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: res = self._sock_exact_recv(1) except self._socket_pool.timeout: return None - else: # socketpool, esp32spi + else: # socketpool, esp32spi, wiznet5k try: res = self._sock_exact_recv(1, timeout=timeout) except OSError as error: @@ -1085,7 +1085,7 @@ def _sock_exact_recv( """ stamp = self.get_monotonic_time() if not self._backwards_compatible_sock: - # CPython/Socketpool Impl. + # CPython, socketpool, esp32spi, wiznet5k rc = bytearray(bufsize) mv = memoryview(rc) recv_len = self._sock.recv_into(rc, bufsize) @@ -1102,7 +1102,7 @@ def _sock_exact_recv( raise MMQTTException( f"Unable to receive {to_read} bytes within {read_timeout} seconds." ) - else: # ESP32SPI Impl. + else: # Legacy: fona, esp_atcontrol # This will time out with socket timeout (not receive timeout). rc = self._sock.recv(bufsize) if not rc: From bbf6850a1de0a38d5b26ae3404bf30fa30f1ebd8 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 21 May 2024 17:43:42 -0700 Subject: [PATCH 239/289] pystack and other errors --- adafruit_minimqtt/adafruit_minimqtt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index e28a7677..73182d17 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -435,7 +435,10 @@ def connect( self._reset_reconnect_backoff() return ret except (MemoryError, OSError, RuntimeError) as e: + if isinstance(e, RuntimeError) and e.args == ("pystack exhausted",): + raise self.logger.warning(f"Socket error when connecting: {e}") + last_exception = e backoff = False except MMQTTException as e: self._close_socket() From 1ffbe5e508a7ddd8e7c793d66f96ad42263c31e8 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 17 Jun 2024 09:54:29 -0500 Subject: [PATCH 240/289] fix for timeout test --- tests/test_recv_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index f65dc403..646aa7c4 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -49,7 +49,7 @@ def test_recv_timeout_vs_keepalive(self) -> None: socket_mock.recv_into.assert_called() now = time.monotonic() - assert recv_timeout <= (now - start) < keep_alive + assert recv_timeout <= round(now - start, 2) <= keep_alive if __name__ == "__main__": From 2d562af9713d394d674eb57cb48c2edf715b9f38 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 5 Jul 2024 12:05:34 -0500 Subject: [PATCH 241/289] tolerance value instead of rounding Co-authored-by: Dan Halbert --- tests/test_recv_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index 646aa7c4..b291dd83 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -49,7 +49,7 @@ def test_recv_timeout_vs_keepalive(self) -> None: socket_mock.recv_into.assert_called() now = time.monotonic() - assert recv_timeout <= round(now - start, 2) <= keep_alive + assert recv_timeout <= (now - start) <= (keep_alive + 0.1) if __name__ == "__main__": From 2197ad7294b2eff14094d396cf1eee8b2cfa20fc Mon Sep 17 00:00:00 2001 From: Ed Hagerty Date: Wed, 24 Jul 2024 19:02:36 +0100 Subject: [PATCH 242/289] refactor will_set function to match the publish function to allow the msg/payload to be encoded bytes --- adafruit_minimqtt/adafruit_minimqtt.py | 62 ++++++++++++++++++++------ 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 53e98011..514d429e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -269,38 +269,74 @@ def mqtt_msg(self, msg_size: int) -> None: def will_set( self, - topic: Optional[str] = None, - payload: Optional[Union[int, float, str]] = None, - qos: int = 0, + topic: str, + msg: Union[str, int, float, bytes], retain: bool = False, + qos: int = 0, ) -> None: """Sets the last will and testament properties. MUST be called before `connect()`. :param str topic: MQTT Broker topic. - :param int|float|str payload: Last will disconnection payload. - payloads of type int & float are converted to a string. + :param str|int|float|bytes msg: Last will disconnection msg. + msgs of type int & float are converted to a string. + msgs of type byetes are left unchanged, as it is in the publish function. :param int qos: Quality of Service level, defaults to zero. Conventional options are ``0`` (send at most once), ``1`` (send at least once), or ``2`` (send exactly once). - .. note:: Only options ``1`` or ``0`` are QoS levels supported by this library. - :param bool retain: Specifies if the payload is to be retained when + :param bool retain: Specifies if the msg is to be retained when it is published. """ self.logger.debug("Setting last will properties") - self._valid_qos(qos) if self._is_connected: raise MMQTTException("Last Will should only be called before connect().") - if payload is None: - payload = "" - if isinstance(payload, (int, float, str)): - payload = str(payload).encode() + + # check topic/msg/qos kwargs + self._valid_topic(topic) + if "+" in topic or "#" in topic: + raise MMQTTException("Publish topic can not contain wildcards.") + + if msg is None: + raise MMQTTException("Message can not be None.") + if isinstance(msg, (int, float)): + msg = str(msg).encode("ascii") + elif isinstance(msg, str): + msg = str(msg).encode("utf-8") + elif isinstance(msg, bytes): + pass else: raise MMQTTException("Invalid message data type.") + if len(msg) > MQTT_MSG_MAX_SZ: + raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") + + self._valid_qos(qos) + assert ( + 0 <= qos <= 1 + ), "Quality of Service Level 2 is unsupported by this library." + + # fixed header. [3.3.1.2], [3.3.1.3] + pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) + + # variable header = 2-byte Topic length (big endian) + pub_hdr_var = bytearray(struct.pack(">H", len(topic.encode("utf-8")))) + pub_hdr_var.extend(topic.encode("utf-8")) # Topic name + + remaining_length = 2 + len(msg) + len(topic.encode("utf-8")) + if qos > 0: + # packet identifier where QoS level is 1 or 2. [3.3.2.2] + remaining_length += 2 + self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 + pub_hdr_var.append(self._pid >> 8) + pub_hdr_var.append(self._pid & 0xFF) + + self._encode_remaining_length(pub_hdr_fixed, remaining_length) + self._lw_qos = qos self._lw_topic = topic - self._lw_msg = payload + self._lw_msg = msg self._lw_retain = retain + self.logger.debug("Last will properties successfully set") + def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: """Registers a callback_method for a specific MQTT topic. From 88787e69f55070c3f4395faa30773a5408bb7943 Mon Sep 17 00:00:00 2001 From: Ed Hagerty Date: Thu, 25 Jul 2024 13:30:00 +0100 Subject: [PATCH 243/289] trying to correctly set up pre-commit --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09cc1f1d..fdeed905 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,21 +4,21 @@ repos: - repo: https://github.com/python/black - rev: 24.2.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 + rev: v4.0.3 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + rev: v3.2.6 hooks: - id: pylint name: pylint (library code) From 65ea984e61da52a5ae43553f96988b5df7008ed6 Mon Sep 17 00:00:00 2001 From: Ed Hagerty Date: Thu, 25 Jul 2024 13:31:45 +0100 Subject: [PATCH 244/289] trying to correctly set up pre-commit --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 514d429e..0cbc2667 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -267,6 +267,7 @@ def mqtt_msg(self, msg_size: int) -> None: if msg_size < MQTT_MSG_MAX_SZ: self._msg_size_lim = msg_size + # pylint: disable=too-many-branches, too-many-statements def will_set( self, topic: str, @@ -337,7 +338,6 @@ def will_set( self._lw_retain = retain self.logger.debug("Last will properties successfully set") - def add_topic_callback(self, mqtt_topic: str, callback_method) -> None: """Registers a callback_method for a specific MQTT topic. From 2d3708d1c61d07b841c7ebca55b486d5e628e2cf Mon Sep 17 00:00:00 2001 From: Ed Hagerty Date: Thu, 25 Jul 2024 13:42:39 +0100 Subject: [PATCH 245/289] having to fix adafruit code to migrate to yield-from --- adafruit_minimqtt/matcher.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index c14a3514..1162d3c4 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -88,11 +88,9 @@ def rec(node: MQTTMatcher.Node, i: int = 0): else: part = lst[i] if part in node.children: - for content in rec(node.children[part], i + 1): - yield content + yield from rec(node.children[part], i + 1) if "+" in node.children and (normal or i > 0): - for content in rec(node.children["+"], i + 1): - yield content + yield from rec(node.children["+"], i + 1) if "#" in node.children and (normal or i > 0): content = node.children["#"].content if content is not None: From 9c0ecfc30b7da49565d6ba89469ce226124bb397 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 25 Aug 2024 10:38:36 -0500 Subject: [PATCH 246/289] revert pre-commit versions --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fdeed905..09cc1f1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,21 +4,21 @@ repos: - repo: https://github.com/python/black - rev: 24.4.2 + rev: 24.2.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: v4.0.3 + rev: v1.1.2 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v3.2.6 + rev: v2.17.4 hooks: - id: pylint name: pylint (library code) From b689b75e50bd92628973a93b2cadefc0b79954cb Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 26 Aug 2024 09:38:03 -0500 Subject: [PATCH 247/289] use ruff instead of pylint and black --- .gitattributes | 5 + .pre-commit-config.yaml | 42 +- .pylintrc | 399 ------------------ README.rst | 6 +- adafruit_minimqtt/adafruit_minimqtt.py | 104 ++--- adafruit_minimqtt/matcher.py | 1 - docs/conf.py | 8 +- .../cellular/minimqtt_adafruitio_cellular.py | 10 +- .../cellular/minimqtt_simpletest_cellular.py | 16 +- .../cpython/minimqtt_adafruitio_cpython.py | 3 +- .../cpython/minimqtt_simpletest_cpython.py | 14 +- examples/cpython/user_data.py | 4 - .../esp32spi/minimqtt_adafruitio_esp32spi.py | 12 +- .../esp32spi/minimqtt_certificate_esp32spi.py | 24 +- .../minimqtt_pub_sub_blocking_esp32spi.py | 16 +- ...b_sub_blocking_topic_callbacks_esp32spi.py | 25 +- .../minimqtt_pub_sub_nonblocking_esp32spi.py | 10 +- .../minimqtt_pub_sub_pyportal_esp32spi.py | 9 +- .../esp32spi/minimqtt_simpletest_esp32spi.py | 17 +- examples/ethernet/minimqtt_adafruitio_eth.py | 8 +- examples/ethernet/minimqtt_simpletest_eth.py | 14 +- examples/minimqtt_simpletest.py | 21 +- .../minimqtt_adafruitio_native_networking.py | 9 +- ...mqtt_pub_sub_blocking_native_networking.py | 9 +- ...cking_topic_callbacks_native_networking.py | 17 +- ruff.toml | 99 +++++ tests/conftest.py | 4 +- tests/test_backoff.py | 11 +- tests/test_loop.py | 22 +- tests/test_port_ssl.py | 5 +- tests/test_recv_timeout.py | 1 - tests/test_subscribe.py | 3 - tests/test_unsubscribe.py | 9 +- 33 files changed, 270 insertions(+), 687 deletions(-) create mode 100644 .gitattributes delete mode 100644 .pylintrc create mode 100644 ruff.toml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d54c593c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +* text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09cc1f1d..f27b7860 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,42 +1,22 @@ # SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 24.2.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 + hooks: + - id: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e920..00000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=board - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/README.rst b/README.rst index f5fcd464..a17dcfd7 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT/actions/ :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff MQTT Client library for CircuitPython. diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 0cbc2667..7e7b598b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -6,8 +6,6 @@ # Modified Work Copyright (c) 2019 Bradley Beach, esp32spi_mqtt # Modified Work Copyright (c) 2012-2019 Roger Light and others, Paho MQTT Python -# pylint: disable=too-many-lines - """ `adafruit_minimqtt` ================================================================================ @@ -30,13 +28,14 @@ https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager """ + import errno import struct import time from random import randint from adafruit_connection_manager import get_connection_manager -from adafruit_ticks import ticks_ms, ticks_diff +from adafruit_ticks import ticks_diff, ticks_ms try: from typing import List, Optional, Tuple, Type, Union @@ -87,8 +86,8 @@ CONNACK_ERROR_UNAUTHORIZED: "Connection Refused - Unauthorized", } -_default_sock = None # pylint: disable=invalid-name -_fake_context = None # pylint: disable=invalid-name +_default_sock = None +_fake_context = None class MMQTTException(Exception): @@ -102,7 +101,6 @@ def __init__(self, error, code=None): class NullLogger: """Fake logger class that does not do anything""" - # pylint: disable=unused-argument def nothing(self, msg: str, *args) -> None: """no action""" @@ -137,8 +135,7 @@ class MQTT: """ - # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements,not-callable,invalid-name,no-member,too-many-locals - def __init__( + def __init__( # noqa: PLR0915, PLR0913, Too many statements, Too many arguments self, *, broker: str, @@ -164,9 +161,7 @@ def __init__( self._use_binary_mode = use_binary_mode if recv_timeout <= socket_timeout: - raise MMQTTException( - "recv_timeout must be strictly greater than socket_timeout" - ) + raise MMQTTException("recv_timeout must be strictly greater than socket_timeout") self._socket_timeout = socket_timeout self._recv_timeout = recv_timeout @@ -267,7 +262,6 @@ def mqtt_msg(self, msg_size: int) -> None: if msg_size < MQTT_MSG_MAX_SZ: self._msg_size_lim = msg_size - # pylint: disable=too-many-branches, too-many-statements def will_set( self, topic: str, @@ -311,9 +305,7 @@ def will_set( raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") self._valid_qos(qos) - assert ( - 0 <= qos <= 1 - ), "Quality of Service Level 2 is unsupported by this library." + assert 0 <= qos <= 1, "Quality of Service Level 2 is unsupported by this library." # fixed header. [3.3.1.2], [3.3.1.3] pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) @@ -363,9 +355,7 @@ def remove_topic_callback(self, mqtt_topic: str) -> None: try: del self._on_message_filtered[mqtt_topic] except KeyError: - raise KeyError( - "MQTT topic callback not added with add_topic_callback." - ) from None + raise KeyError("MQTT topic callback not added with add_topic_callback.") from None @property def on_message(self): @@ -470,8 +460,7 @@ def connect( raise MMQTTException(exc_msg) from last_exception raise MMQTTException(exc_msg) - # pylint: disable=too-many-branches, too-many-statements, too-many-locals - def _connect( + def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements self, clean_session: bool = True, host: Optional[str] = None, @@ -523,10 +512,7 @@ def _connect( remaining_length = 12 + len(self.client_id.encode("utf-8")) if self._username is not None: remaining_length += ( - 2 - + len(self._username.encode("utf-8")) - + 2 - + len(self._password.encode("utf-8")) + 2 + len(self._username.encode("utf-8")) + 2 + len(self._password.encode("utf-8")) ) var_header[7] |= 0xC0 if self.keep_alive: @@ -534,9 +520,7 @@ def _connect( var_header[8] |= self.keep_alive >> 8 var_header[9] |= self.keep_alive & 0x00FF if self._lw_topic: - remaining_length += ( - 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) - ) + remaining_length += 2 + len(self._lw_topic.encode("utf-8")) + 2 + len(self._lw_msg) var_header[7] |= 0x4 | (self._lw_qos & 0x1) << 3 | (self._lw_qos & 0x2) << 3 var_header[7] |= self._lw_retain << 5 @@ -584,10 +568,7 @@ def _close_socket(self): self._connection_manager.close_socket(self._sock) self._sock = None - # pylint: disable=no-self-use - def _encode_remaining_length( - self, fixed_header: bytearray, remaining_length: int - ) -> None: + def _encode_remaining_length(self, fixed_header: bytearray, remaining_length: int) -> None: """Encode Remaining Length [2.2.3]""" if remaining_length > 268_435_455: raise MMQTTException("invalid remaining length") @@ -642,8 +623,7 @@ def ping(self) -> list[int]: ) return rcs - # pylint: disable=too-many-branches, too-many-statements - def publish( + def publish( # noqa: PLR0912, Too many branches self, topic: str, msg: Union[str, int, float, bytes], @@ -675,9 +655,7 @@ def publish( raise MMQTTException("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") - assert ( - 0 <= qos <= 1 - ), "Quality of Service Level 2 is unsupported by this library." + assert 0 <= qos <= 1, "Quality of Service Level 2 is unsupported by this library." # fixed header. [3.3.1.2], [3.3.1.3] pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) @@ -730,7 +708,9 @@ def publish( f"No data received from broker for {self._recv_timeout} seconds." ) - def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> None: + def subscribe( # noqa: PLR0912, PLR0915, Too many branches, Too many statements + self, topic: Optional[Union[tuple, str, list]], qos: int = 0 + ) -> None: """Subscribes to a topic on the MQTT Broker. This method can subscribe to one topic or multiple topics. @@ -775,7 +755,7 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N self.logger.debug(f"Variable Header: {var_header}") self._sock.send(var_header) # attaching topic and QOS level to the packet - payload = bytes() + payload = b"" for t, q in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") qos_byte = q.to_bytes(1, "big") @@ -821,7 +801,9 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N f"invalid message received as response to SUBSCRIBE: {hex(op)}" ) - def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: + def unsubscribe( # noqa: PLR0912, Too many branches + self, topic: Optional[Union[str, list]] + ) -> None: """Unsubscribes from a MQTT topic. :param str|list topic: Unique MQTT topic identifier string or list. @@ -835,12 +817,10 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: topics = [] for t in topic: self._valid_topic(t) - topics.append((t)) + topics.append(t) for t in topics: if t not in self._subscribed_topics: - raise MMQTTException( - "Topic must be subscribed to before attempting unsubscribe." - ) + raise MMQTTException("Topic must be subscribed to before attempting unsubscribe.") # Assemble packet self.logger.debug("Sending UNSUBSCRIBE to broker...") fixed_header = bytearray([MQTT_UNSUB]) @@ -854,7 +834,7 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None: var_header = packet_id_bytes self.logger.debug(f"Variable Header: {var_header}") self._sock.send(var_header) - payload = bytes() + payload = b"" for t in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") payload += topic_size + t.encode() @@ -895,10 +875,7 @@ def _recompute_reconnect_backoff(self) -> None: """ self._reconnect_attempt = self._reconnect_attempt + 1 self._reconnect_timeout = 2**self._reconnect_attempt - # pylint: disable=consider-using-f-string - self.logger.debug( - "Reconnect timeout computed to {:.2f}".format(self._reconnect_timeout) - ) + self.logger.debug(f"Reconnect timeout computed to {self._reconnect_timeout:.2f}") if self._reconnect_timeout > self._reconnect_maximum_backoff: self.logger.debug( @@ -909,12 +886,7 @@ def _recompute_reconnect_backoff(self) -> None: # Add a sub-second jitter. # Even truncated timeout should have jitter added to it. This is why it is added here. jitter = randint(0, 1000) / 1000 - # pylint: disable=consider-using-f-string - self.logger.debug( - "adding jitter {:.2f} to {:.2f} seconds".format( - jitter, self._reconnect_timeout - ) - ) + self.logger.debug(f"adding jitter {jitter:.2f} to {self._reconnect_timeout:.2f} seconds") self._reconnect_timeout += jitter def _reset_reconnect_backoff(self) -> None: @@ -939,9 +911,7 @@ def reconnect(self, resub_topics: bool = True) -> int: ret = self.connect() self.logger.debug("Reconnected with broker") if resub_topics: - self.logger.debug( - "Attempting to resubscribe to previously subscribed topics." - ) + self.logger.debug("Attempting to resubscribe to previously subscribed topics.") subscribed_topics = self._subscribed_topics.copy() self._subscribed_topics = [] while subscribed_topics: @@ -959,9 +929,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: """ if timeout < self._socket_timeout: raise MMQTTException( - # pylint: disable=consider-using-f-string - "loop timeout ({}) must be bigger ".format(timeout) - + "than socket timeout ({}))".format(self._socket_timeout) + f"loop timeout ({timeout}) must be bigger " + + f"than socket timeout ({self._socket_timeout}))" ) self._connected() @@ -971,10 +940,7 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: rcs = [] while True: - if ( - ticks_diff(ticks_ms(), self._last_msg_sent_timestamp) / 1000 - >= self.keep_alive - ): + if ticks_diff(ticks_ms(), self._last_msg_sent_timestamp) / 1000 >= self.keep_alive: # Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server self.logger.debug( "KeepAlive period elapsed - requesting a PINGRESP from the server..." @@ -995,8 +961,9 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]: return rcs if rcs else None - def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]: - # pylint: disable = too-many-return-statements + def _wait_for_msg( # noqa: PLR0912, Too many branches + self, timeout: Optional[float] = None + ) -> Optional[int]: """Reads and processes network events. Return the packet type or None if there is nothing to be received. @@ -1079,9 +1046,7 @@ def _decode_remaining_length(self) -> int: return n sh += 7 - def _sock_exact_recv( - self, bufsize: int, timeout: Optional[float] = None - ) -> bytearray: + def _sock_exact_recv(self, bufsize: int, timeout: Optional[float] = None) -> bytearray: """Reads _exact_ number of bytes from the connected socket. Will only return bytearray with the exact number of bytes requested. @@ -1203,7 +1168,6 @@ def enable_logger(self, log_pkg, log_level: int = 20, logger_name: str = "log"): :return logger object """ - # pylint: disable=attribute-defined-outside-init self.logger = log_pkg.getLogger(logger_name) self.logger.setLevel(log_level) diff --git a/adafruit_minimqtt/matcher.py b/adafruit_minimqtt/matcher.py index 1162d3c4..6531f482 100644 --- a/adafruit_minimqtt/matcher.py +++ b/adafruit_minimqtt/matcher.py @@ -26,7 +26,6 @@ class MQTTMatcher: some topic name. """ - # pylint: disable=too-few-public-methods class Node: """Individual node on the MQTT prefix tree.""" diff --git a/docs/conf.py b/docs/conf.py index 3b989959..95930913 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) @@ -47,9 +45,7 @@ creation_year = "2019" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " Brent Rubell" author = "Brent Rubell" diff --git a/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index 88fa317b..686062c1 100755 --- a/examples/cellular/minimqtt_adafruitio_cellular.py +++ b/examples/cellular/minimqtt_adafruitio_cellular.py @@ -3,13 +3,14 @@ import os import time + +import adafruit_connection_manager +import adafruit_fona.adafruit_fona_network as network +import adafruit_fona.adafruit_fona_socket as pool import board import busio import digitalio -import adafruit_connection_manager from adafruit_fona.adafruit_fona import FONA -import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -40,7 +41,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -57,7 +57,7 @@ def disconnected(client, userdata, rc): def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Initialize cellular data network diff --git a/examples/cellular/minimqtt_simpletest_cellular.py b/examples/cellular/minimqtt_simpletest_cellular.py index 09e7350d..c24ef907 100644 --- a/examples/cellular/minimqtt_simpletest_cellular.py +++ b/examples/cellular/minimqtt_simpletest_cellular.py @@ -3,13 +3,14 @@ import os import time + +import adafruit_connection_manager +import adafruit_fona.adafruit_fona_network as network +import adafruit_fona.adafruit_fona_socket as pool import board import busio import digitalio -import adafruit_connection_manager from adafruit_fona.adafruit_fona import FONA -import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as pool import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -40,12 +41,11 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(client, userdata, rc): @@ -56,17 +56,17 @@ def disconnect(client, userdata, rc): def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(client, userdata, topic, pid): # This method is called when the client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") # Initialize cellular data network diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 1440ebad..1f350c46 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -28,7 +28,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -45,7 +44,7 @@ def disconnected(client, userdata, rc): def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Set up a MiniMQTT Client diff --git a/examples/cpython/minimqtt_simpletest_cpython.py b/examples/cpython/minimqtt_simpletest_cpython.py index 2a6e3a97..a435b6a3 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -2,8 +2,9 @@ # SPDX-License-Identifier: MIT import os -import ssl import socket +import ssl + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystems. Add your Adafruit IO username and key as well. @@ -25,12 +26,11 @@ ### Code ### # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): # This function will be called when the mqtt_client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(mqtt_client, userdata, rc): @@ -41,22 +41,22 @@ def disconnect(mqtt_client, userdata, rc): def subscribe(mqtt_client, userdata, topic, granted_qos): # This method is called when the mqtt_client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") def message(client, topic, message): # Method callled when a client's subscribed feed has a new value. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Set up a MiniMQTT Client diff --git a/examples/cpython/user_data.py b/examples/cpython/user_data.py index dd19c275..14f13709 100644 --- a/examples/cpython/user_data.py +++ b/examples/cpython/user_data.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2023 Vladimír Kotal # SPDX-License-Identifier: Unlicense -# pylint: disable=logging-fstring-interpolation """ Demonstrate on how to use user_data for various callbacks. @@ -15,7 +14,6 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# pylint: disable=unused-argument def on_connect(mqtt_client, user_data, flags, ret_code): """ connect callback @@ -25,7 +23,6 @@ def on_connect(mqtt_client, user_data, flags, ret_code): logger.debug(f"Flags: {flags}\n RC: {ret_code}") -# pylint: disable=unused-argument def on_subscribe(mqtt_client, user_data, topic, granted_qos): """ subscribe callback @@ -47,7 +44,6 @@ def on_message(client, topic, message): messages[topic].append(message) -# pylint: disable=too-many-statements,too-many-locals def main(): """ Main loop. diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index d944157e..7266beb8 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -3,12 +3,13 @@ import os import time + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut import neopixel -import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -32,9 +33,7 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel( - board.NEOPIXEL, 1, brightness=0.2 -) # Uncomment for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -57,7 +56,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -74,7 +72,7 @@ def disconnected(client, userdata, rc): def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Connect to WiFi diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index 4467f8df..66c397c9 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -1,13 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut import neopixel -import adafruit_connection_manager -from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager +from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -33,9 +32,7 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel( - board.NEOPIXEL, 1, brightness=0.2 -) # Uncomment for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -61,12 +58,11 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(client, userdata, rc): @@ -77,26 +73,24 @@ def disconnect(client, userdata, rc): def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(client, userdata, topic, pid): # This method is called when the client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") # Get certificate and private key from a certificates.py file try: from certificates import DEVICE_CERT, DEVICE_KEY except ImportError: - print( - "Certificate and private key data is kept in certificates.py, please add them there!" - ) + print("Certificate and private key data is kept in certificates.py, please add them there!") raise # Set Device Certificate diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index e7eed067..cb8bbb46 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -3,12 +3,13 @@ import os import time + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut import neopixel -import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -32,9 +33,7 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel( - board.NEOPIXEL, 1, brightness=0.2 -) # Uncomment for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -54,7 +53,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -74,7 +72,7 @@ def message(client, topic, message): :param str topic: The topic of the feed with a new value. :param str message: The new value """ - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Connect to WiFi @@ -113,9 +111,7 @@ def message(client, topic, message): print("Failed to get data, retrying\n", e) esp.reset() time.sleep(1) - esp.connect_AP( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") - ) + esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) mqtt_client.reconnect() continue time.sleep(1) diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index cb89ed17..1b8d8f0d 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -3,13 +3,13 @@ import os import time + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut import neopixel -import adafruit_connection_manager -from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager +from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -35,9 +35,7 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel( - board.NEOPIXEL, 1, brightness=0.2 -) # Uncomment for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -53,7 +51,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -67,24 +64,24 @@ def disconnected(client, userdata, rc): def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value - print("Battery level: {}v".format(message)) + print(f"Battery level: {message}v") # client.remove_topic_callback(secrets["aio_username"] + "/feeds/device.batterylevel") def on_message(client, topic, message): # Method callled when a client's subscribed feed has a new value. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Connect to WiFi @@ -109,9 +106,7 @@ def on_message(client, topic, message): client.on_subscribe = subscribe client.on_unsubscribe = unsubscribe client.on_message = on_message -client.add_topic_callback( - secrets["aio_username"] + "/feeds/device.batterylevel", on_battery_msg -) +client.add_topic_callback(secrets["aio_username"] + "/feeds/device.batterylevel", on_battery_msg) # Connect the client to the MQTT broker. print("Connecting to MQTT broker...") diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 20191915..642d824c 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -3,12 +3,13 @@ import os import time + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut import neopixel -import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -32,9 +33,7 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel( - board.NEOPIXEL, 1, brightness=0.2 -) # Uncomment for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -53,7 +52,6 @@ ### Code ### # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index 3db02803..5f726d19 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -3,6 +3,7 @@ import os import time + import adafruit_connection_manager import adafruit_pyportal @@ -23,7 +24,6 @@ ### Code ### # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -42,7 +42,7 @@ def message(client, topic, message): :param str topic: The topic of the feed with a new value. :param str message: The new value """ - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Connect to WiFi @@ -50,11 +50,8 @@ def message(client, topic, message): pyportal.network.connect() print("Connected!") -# pylint: disable=protected-access pool = adafruit_connection_manager.get_radio_socketpool(pyportal.network._wifi.esp) -ssl_context = adafruit_connection_manager.get_radio_ssl_context( - pyportal.network._wifi.esp -) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(pyportal.network._wifi.esp) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index a1cc2712..37dae061 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -2,11 +2,13 @@ # SPDX-License-Identifier: MIT import os + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut -import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -52,12 +54,11 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): # This function will be called when the mqtt_client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(mqtt_client, userdata, rc): @@ -68,21 +69,21 @@ def disconnect(mqtt_client, userdata, rc): def subscribe(mqtt_client, userdata, topic, granted_qos): # This method is called when the mqtt_client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") def message(client, topic, message): - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") pool = adafruit_connection_manager.get_radio_socketpool(esp) diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 5ad677dd..5cac4183 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -3,11 +3,12 @@ import os import time + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut -import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -35,7 +36,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -52,7 +52,7 @@ def disconnected(client, userdata, rc): def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") pool = adafruit_connection_manager.get_radio_socketpool(eth) diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index a5ba5892..35535800 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -2,11 +2,12 @@ # SPDX-License-Identifier: MIT import os + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut -import adafruit_connection_manager from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K +from digitalio import DigitalInOut import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -35,12 +36,11 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(client, userdata, rc): @@ -51,17 +51,17 @@ def disconnect(client, userdata, rc): def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(client, userdata, topic, pid): # This method is called when the client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") pool = adafruit_connection_manager.get_radio_socketpool(eth) diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index 45aa077b..c1027d9a 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -2,11 +2,13 @@ # SPDX-License-Identifier: MIT import os + +import adafruit_connection_manager import board import busio -from digitalio import DigitalInOut -import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -32,9 +34,7 @@ print("Connecting to AP...") while not esp.is_connected: try: - esp.connect_AP( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") - ) + esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue @@ -55,12 +55,11 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connect(mqtt_client, userdata, flags, rc): # This function will be called when the mqtt_client is connected # successfully to the broker. print("Connected to MQTT Broker!") - print("Flags: {0}\n RC: {1}".format(flags, rc)) + print(f"Flags: {flags}\n RC: {rc}") def disconnect(mqtt_client, userdata, rc): @@ -71,21 +70,21 @@ def disconnect(mqtt_client, userdata, rc): def subscribe(mqtt_client, userdata, topic, granted_qos): # This method is called when the mqtt_client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def publish(mqtt_client, userdata, topic, pid): # This method is called when the mqtt_client publishes data to a feed. - print("Published to {0} with PID {1}".format(topic, pid)) + print(f"Published to {topic} with PID {pid}") def message(client, topic, message): - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") pool = adafruit_connection_manager.get_radio_socketpool(esp) diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 65632043..e1d2e8eb 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -2,10 +2,12 @@ # SPDX-License-Identifier: MIT import os -import time import ssl +import time + import socketpool import wifi + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -19,9 +21,7 @@ aio_key = os.getenv("aio_key") print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") -wifi.radio.connect( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") -) +wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") ### Feeds ### @@ -35,7 +35,6 @@ # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index 5aad73c7..b59dff80 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -2,10 +2,12 @@ # SPDX-License-Identifier: MIT import os -import time import ssl +import time + import socketpool import wifi + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -19,9 +21,7 @@ aio_key = os.getenv("aio_key") print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) -wifi.radio.connect( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") -) +wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Adafruit IO Setup ### @@ -32,7 +32,6 @@ ### Code ### # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 86f4e9cd..07f0f9d2 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -2,10 +2,12 @@ # SPDX-License-Identifier: MIT import os -import time import ssl +import time + import socketpool import wifi + import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys @@ -19,16 +21,13 @@ aio_key = os.getenv("aio_key") print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) -wifi.radio.connect( - os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") -) +wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) ### Code ### # Define callback methods which are called when events occur -# pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. @@ -42,24 +41,24 @@ def disconnected(client, userdata, rc): def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. - print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos)) + print(f"Subscribed to {topic} with QOS level {granted_qos}") def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a feed. - print("Unsubscribed from {0} with PID {1}".format(topic, pid)) + print(f"Unsubscribed from {topic} with PID {pid}") def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value - print("Battery level: {}v".format(message)) + print(f"Battery level: {message}v") # client.remove_topic_callback(aio_username + "/feeds/device.batterylevel") def on_message(client, topic, message): # Method callled when a client's subscribed feed has a new value. - print("New message on topic {0}: {1}".format(topic, message)) + print(f"New message on topic {topic}: {message}") # Create a socket pool diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..db37c83e --- /dev/null +++ b/ruff.toml @@ -0,0 +1,99 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + +] + +[format] +line-ending = "lf" diff --git a/tests/conftest.py b/tests/conftest.py index a4b8d631..93eefcbd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,10 +2,10 @@ # # SPDX-License-Identifier: Unlicense -""" PyTest Setup """ +"""PyTest Setup""" -import pytest import adafruit_connection_manager +import pytest @pytest.fixture(autouse=True) diff --git a/tests/test_backoff.py b/tests/test_backoff.py index 44d2da9d..7adff324 100644 --- a/tests/test_backoff.py +++ b/tests/test_backoff.py @@ -4,13 +4,13 @@ """exponential back-off tests""" - import socket import ssl import time from unittest.mock import call, patch import pytest + import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -20,7 +20,6 @@ class TestExpBackOff: connect_times = [] raise_exception = None - # pylint: disable=unused-argument def fake_connect(self, arg): """connect() replacement that records the call times and always raises OSError""" self.connect_times.append(time.monotonic()) @@ -33,9 +32,7 @@ def test_failing_connect(self) -> None: port = 1883 self.connect_times = [] error_code = MQTT.CONNACK_ERROR_SERVER_UNAVAILABLE - self.raise_exception = MQTT.MMQTTException( - MQTT.CONNACK_ERRORS[error_code], code=error_code - ) + self.raise_exception = MQTT.MMQTTException(MQTT.CONNACK_ERRORS[error_code], code=error_code) with patch.object(socket.socket, "connect") as mock_method: mock_method.side_effect = self.fake_connect @@ -69,9 +66,7 @@ def test_unauthorized(self) -> None: port = 1883 self.connect_times = [] error_code = MQTT.CONNACK_ERROR_UNAUTHORIZED - self.raise_exception = MQTT.MMQTTException( - MQTT.CONNACK_ERRORS[error_code], code=error_code - ) + self.raise_exception = MQTT.MMQTTException(MQTT.CONNACK_ERRORS[error_code], code=error_code) with patch.object(socket.socket, "connect") as mock_method: mock_method.side_effect = self.fake_connect diff --git a/tests/test_loop.py b/tests/test_loop.py index 6666d86f..834a0d4f 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -4,16 +4,16 @@ """loop() tests""" +import errno import random import socket import ssl import time -import errno - -from unittest.mock import patch from unittest import mock +from unittest.mock import patch import pytest + import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -39,7 +39,6 @@ def send(self, bytes_to_send): return len(bytes_to_send) # MiniMQTT checks for the presence of "recv_into" and switches behavior based on that. - # pylint: disable=unused-argument,no-self-use def recv_into(self, retbuf, bufsize): """Always raise timeout exception.""" exc = OSError() @@ -131,9 +130,7 @@ def test_loop_basic(self) -> None: ssl_context=ssl.create_default_context(), ) - with patch.object( - mqtt_client, "_wait_for_msg" - ) as wait_for_msg_mock, patch.object( + with patch.object(mqtt_client, "_wait_for_msg") as wait_for_msg_mock, patch.object( mqtt_client, "is_connected" ) as is_connected_mock: wait_for_msg_mock.side_effect = self.fake_wait_for_msg @@ -141,7 +138,6 @@ def test_loop_basic(self) -> None: time_before = time.monotonic() timeout = random.randint(3, 8) - # pylint: disable=protected-access mqtt_client._last_msg_sent_timestamp = MQTT.ticks_ms() rcs = mqtt_client.loop(timeout=timeout) time_after = time.monotonic() @@ -153,13 +149,10 @@ def test_loop_basic(self) -> None: assert rcs is not None assert len(rcs) >= 1 expected_rc = self.INITIAL_RCS_VAL - # pylint: disable=not-an-iterable for ret_code in rcs: assert ret_code == expected_rc expected_rc += 1 - # pylint: disable=no-self-use - # pylint: disable=invalid-name def test_loop_timeout_vs_socket_timeout(self): """ loop() should throw MMQTTException if the timeout argument @@ -179,7 +172,6 @@ def test_loop_timeout_vs_socket_timeout(self): assert "loop timeout" in str(context) - # pylint: disable=no-self-use def test_loop_is_connected(self): """ loop() should throw MMQTTException if not connected @@ -196,7 +188,6 @@ def test_loop_is_connected(self): assert "not connected" in str(context) - # pylint: disable=no-self-use def test_loop_ping_timeout(self): """Verify that ping will be sent even with loop timeout bigger than keep alive timeout and no outgoing messages are sent.""" @@ -216,7 +207,6 @@ def test_loop_ping_timeout(self): # patch is_connected() to avoid CONNECT/CONNACK handling. mqtt_client.is_connected = lambda: True mocket = Pingtet() - # pylint: disable=protected-access mqtt_client._sock = mocket start = time.monotonic() @@ -224,9 +214,8 @@ def test_loop_ping_timeout(self): assert time.monotonic() - start >= 2 * keep_alive_timeout assert len(mocket.sent) > 0 assert len(res) == 3 - assert set(res) == {int(0xD0)} + assert set(res) == {0xD0} - # pylint: disable=no-self-use def test_loop_ping_vs_msgs_sent(self): """Verify that ping will not be sent unnecessarily.""" @@ -247,7 +236,6 @@ def test_loop_ping_vs_msgs_sent(self): # With QoS=0 no PUBACK message is sent, so Nulltet can be used. mocket = Nulltet() - # pylint: disable=protected-access mqtt_client._sock = mocket i = 0 diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 2aa877f5..196f8c73 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -9,6 +9,7 @@ from unittest.mock import Mock, call, patch import pytest + import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -17,7 +18,6 @@ class TestPortSslSetup: These tests assume that there is no MQTT broker running on the hosts/ports they connect to. """ - # pylint: disable=no-self-use def test_default_port(self) -> None: """verify default port value and that TLS is not used""" host = "127.0.0.1" @@ -45,7 +45,6 @@ def test_default_port(self) -> None: # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((host, expected_port))]) - # pylint: disable=no-self-use def test_connect_override(self): """Test that connect() can override host and port.""" host = "127.0.0.1" @@ -71,7 +70,6 @@ def test_connect_override(self): # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((expected_host, expected_port))]) - # pylint: disable=no-self-use @pytest.mark.parametrize("port", (None, 8883)) def test_tls_port(self, port) -> None: """verify that when is_ssl=True is set, the default port is 8883 @@ -107,7 +105,6 @@ def test_tls_port(self, port) -> None: # Assuming the repeated calls will have the same arguments. connect_mock.assert_has_calls([call((host, expected_port))]) - # pylint: disable=no-self-use def test_tls_without_ssl_context(self) -> None: """verify that when is_ssl=True is set, the code will check that ssl_context is not None""" host = "127.0.0.1" diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index b291dd83..1855525a 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -36,7 +36,6 @@ def test_recv_timeout_vs_keepalive(self) -> None: # Create a mock socket that will accept anything and return nothing. socket_mock = Mock() socket_mock.recv_into = Mock(side_effect=side_effect) - # pylint: disable=protected-access mqtt_client._sock = socket_mock mqtt_client._connected = lambda: True diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py index a66e7a87..f7b037b9 100644 --- a/tests/test_subscribe.py +++ b/tests/test_subscribe.py @@ -13,7 +13,6 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# pylint: disable=unused-argument def handle_subscribe(client, user_data, topic, qos): """ Record topics into user data. @@ -210,12 +209,10 @@ def test_subscribe(topic, to_send, exp_recv) -> None: # patch is_connected() to avoid CONNECT/CONNACK handling. mqtt_client.is_connected = lambda: True mocket = Mocket(to_send) - # pylint: disable=protected-access mqtt_client._sock = mocket mqtt_client.logger = logger - # pylint: disable=logging-fstring-interpolation logger.info(f"subscribing to {topic}") mqtt_client.subscribe(topic) diff --git a/tests/test_unsubscribe.py b/tests/test_unsubscribe.py index d5b67b65..f7bbb21a 100644 --- a/tests/test_unsubscribe.py +++ b/tests/test_unsubscribe.py @@ -13,7 +13,6 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# pylint: disable=unused-argument def handle_unsubscribe(client, user_data, topic, pid): """ Record topics into user data. @@ -85,10 +84,7 @@ def handle_unsubscribe(client, user_data, topic, pid): 0x01, ] + sum( - [ - [0x00, 0x0B] + list(f"foo/bar{x:04}".encode("ascii")) - for x in range(1, 1000) - ], + [[0x00, 0x0B] + list(f"foo/bar{x:04}".encode("ascii")) for x in range(1, 1000)], [], ) ), @@ -129,18 +125,15 @@ def test_unsubscribe(topic, to_send, exp_recv) -> None: # patch is_connected() to avoid CONNECT/CONNACK handling. mqtt_client.is_connected = lambda: True mocket = Mocket(to_send) - # pylint: disable=protected-access mqtt_client._sock = mocket mqtt_client.logger = logger - # pylint: disable=protected-access if isinstance(topic, str): mqtt_client._subscribed_topics = [topic] elif isinstance(topic, list): mqtt_client._subscribed_topics = topic - # pylint: disable=logging-fstring-interpolation logger.info(f"unsubscribing from {topic}") mqtt_client.unsubscribe(topic) From e84432109f0868f8e57a0e0a78d9a208f1c7d6f1 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 27 Aug 2024 15:37:13 -0500 Subject: [PATCH 248/289] limit gitattributes to specific extensions --- .gitattributes | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index d54c593c..21c125c8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,10 @@ # # SPDX-License-Identifier: Unlicense -* text eol=lf +.py text eol=lf +.rst text eol=lf +.txt text eol=lf +.yaml text eol=lf +.toml text eol=lf +.license text eol=lf +.md text eol=lf From 25a92182ca741be4efbdcfac3f9cb9b5504fc296 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 7 Oct 2024 09:24:05 -0500 Subject: [PATCH 249/289] remove deprecated get_html_theme_path() call Signed-off-by: foamyguy --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 95930913..064f3486 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -99,7 +99,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 8595e361d42182c22c3ec1261309cb982bdd9317 Mon Sep 17 00:00:00 2001 From: Rafael Bedia Date: Tue, 8 Oct 2024 15:52:08 -0400 Subject: [PATCH 250/289] Close the socket when there is an error using the socket. This fixes two related problems which made reconnecting not work after the connection to the server gets dropped unexpectedly. The MQTT client continues sending messages and on ESP32 eventually an OSError is raised. The client must then reconnect. But reconnect was failing because the Adafruit Connection Manager was returning the same broken socket for the server since it hadn't yet been closed by MiniMQTT. Explicitly calling disconnect() would also not close the socket because it tries to send the disconnect packet on the broken socket which raised an OSError preventing the socket from being closed. --- adafruit_minimqtt/adafruit_minimqtt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7e7b598b..feedf19c 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -436,6 +436,7 @@ def connect( except (MemoryError, OSError, RuntimeError) as e: if isinstance(e, RuntimeError) and e.args == ("pystack exhausted",): raise + self._close_socket() self.logger.warning(f"Socket error when connecting: {e}") last_exception = e backoff = False @@ -591,7 +592,7 @@ def disconnect(self) -> None: self.logger.debug("Sending DISCONNECT packet to broker") try: self._sock.send(MQTT_DISCONNECT) - except RuntimeError as e: + except (MemoryError, OSError, RuntimeError) as e: self.logger.warning(f"Unable to send DISCONNECT packet: {e}") self._close_socket() self._is_connected = False From 920e64f1b2bd91499e3be27fd419c568e76939f9 Mon Sep 17 00:00:00 2001 From: ch4nsuk3 Date: Wed, 9 Oct 2024 14:04:39 -0500 Subject: [PATCH 251/289] Resolve race condition for UNSUBACK Corrects the behavior of erroring out while waiting for an UNSUBACK when a publish message from the server arrives before the UNSUBACK does. Also changed op comparisons from using magic numbers to named constants for clarity. --- adafruit_minimqtt/adafruit_minimqtt.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 7e7b598b..72b43c90 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -66,7 +66,9 @@ MQTT_PINGRESP = const(0xD0) MQTT_PUBLISH = const(0x30) MQTT_SUB = const(0x82) +MQTT_SUBACK = const(0x90) MQTT_UNSUB = const(0xA2) +MQTT_UNSUBACK = const(0xB0) MQTT_DISCONNECT = b"\xe0\0" MQTT_PKT_TYPE_MASK = const(0xF0) @@ -774,7 +776,7 @@ def subscribe( # noqa: PLR0912, PLR0915, Too many branches, Too many statements f"No data received from broker for {self._recv_timeout} seconds." ) else: - if op == 0x90: + if op == MQTT_SUBACK: remaining_len = self._decode_remaining_length() assert remaining_len > 0 rc = self._sock_exact_recv(2) @@ -852,7 +854,7 @@ def unsubscribe( # noqa: PLR0912, Too many branches f"No data received from broker for {self._recv_timeout} seconds." ) else: - if op == 176: + if op == MQTT_UNSUBACK: rc = self._sock_exact_recv(3) assert rc[0] == 0x02 # [MQTT-3.32] @@ -862,10 +864,10 @@ def unsubscribe( # noqa: PLR0912, Too many branches self.on_unsubscribe(self, self.user_data, t, self._pid) self._subscribed_topics.remove(t) return - - raise MMQTTException( - f"invalid message received as response to UNSUBSCRIBE: {hex(op)}" - ) + if op != MQTT_PUBLISH: + raise MMQTTException( + f"invalid message received as response to UNSUBSCRIBE: {hex(op)}" + ) def _recompute_reconnect_backoff(self) -> None: """ From 0356eec98d139d13e47a15ad2e1aac73e1ecca4f Mon Sep 17 00:00:00 2001 From: ch4nsuk3 Date: Fri, 8 Nov 2024 17:36:19 -0600 Subject: [PATCH 252/289] Added Comment Explaining Behavior Added a comment referencing the MQTT specification for why the server may not immediately respond to an UNSUBACK --- adafruit_minimqtt/adafruit_minimqtt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 72b43c90..2724eca4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -865,6 +865,8 @@ def unsubscribe( # noqa: PLR0912, Too many branches self._subscribed_topics.remove(t) return if op != MQTT_PUBLISH: + # [3.10.4] The Server may continue to deliver existing messages buffered for delivery + # to the client prior to sending the UNSUBACK Packet. raise MMQTTException( f"invalid message received as response to UNSUBSCRIBE: {hex(op)}" ) From 64638cfded572b9865445648059b7e56a5ad3123 Mon Sep 17 00:00:00 2001 From: ch4nsuk3 Date: Fri, 8 Nov 2024 17:39:51 -0600 Subject: [PATCH 253/289] Correct Ruff Line Too Long Error Adjusted formatting to resolve the ruff E501 Error. --- adafruit_minimqtt/adafruit_minimqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 2724eca4..c3d63c21 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -865,8 +865,8 @@ def unsubscribe( # noqa: PLR0912, Too many branches self._subscribed_topics.remove(t) return if op != MQTT_PUBLISH: - # [3.10.4] The Server may continue to deliver existing messages buffered for delivery - # to the client prior to sending the UNSUBACK Packet. + # [3.10.4] The Server may continue to deliver existing messages buffered + # for delivery to the client prior to sending the UNSUBACK Packet. raise MMQTTException( f"invalid message received as response to UNSUBSCRIBE: {hex(op)}" ) From 3ec6d2c53cfa763004b621fc83cbb2c0b4524546 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 22 Nov 2024 13:16:16 -0600 Subject: [PATCH 254/289] timout 1 instead of 0 --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index feedf19c..6b1e5de3 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -921,7 +921,7 @@ def reconnect(self, resub_topics: bool = True) -> int: return ret - def loop(self, timeout: float = 0) -> Optional[list[int]]: + def loop(self, timeout: float = 1.0) -> Optional[list[int]]: """Non-blocking message loop. Use this method to check for incoming messages. Returns list of packet types of any messages received or None. From c09644d78c64ffb0cf23e4e77b36c0c95fe36271 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 25 Nov 2024 08:56:34 -0600 Subject: [PATCH 255/289] update timeout message --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6b1e5de3..cdd0aca5 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -930,7 +930,7 @@ def loop(self, timeout: float = 1.0) -> Optional[list[int]]: """ if timeout < self._socket_timeout: raise MMQTTException( - f"loop timeout ({timeout}) must be bigger " + f"loop timeout ({timeout}) must be >= " + f"than socket timeout ({self._socket_timeout}))" ) From 1d5a1e30a3e69358ffb1d9ca29ff5e66a67cede7 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Mon, 25 Nov 2024 10:08:04 -0500 Subject: [PATCH 256/289] fix grammar in error msg --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index cdd0aca5..09f7662b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -931,7 +931,7 @@ def loop(self, timeout: float = 1.0) -> Optional[list[int]]: if timeout < self._socket_timeout: raise MMQTTException( f"loop timeout ({timeout}) must be >= " - + f"than socket timeout ({self._socket_timeout}))" + + f"socket timeout ({self._socket_timeout}))" ) self._connected() From 54d9af3cd5235e41403aa3db3c64d4d14657bead Mon Sep 17 00:00:00 2001 From: Mike Stemle Date: Sun, 29 Dec 2024 13:50:06 -0500 Subject: [PATCH 257/289] Update minimqtt_adafruitio_native_networking.py The newer versions of the miniMQTT library seem to break when you put the whole key for the feed in there. `foo/feed/group.feed` no longer works ( [this breaks it](https://github.com/adafruit/Adafruit_CircuitPython_AdafruitIO/blob/ece3e396ccb5504558f6b11423a96d06a6dfb5c9/adafruit_io/adafruit_io.py#L43-L54) ), but instead it needs to be just `group.feed`. --- .../minimqtt_adafruitio_native_networking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index e1d2e8eb..facee071 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -26,10 +26,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = aio_username + "/feeds/photocell" +photocell_feed = "photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = aio_username + "/feeds/onoff" +onoff_feed = "onoff" ### Code ### From 3acb2234c7faaa424bf016bc6e7778b7a544cd69 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 1 Jan 2025 17:11:51 -0500 Subject: [PATCH 258/289] handle partial socket send()'s --- adafruit_minimqtt/adafruit_minimqtt.py | 51 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 09f7662b..5028dcd9 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -461,6 +461,21 @@ def connect( raise MMQTTException(exc_msg) from last_exception raise MMQTTException(exc_msg) + def _send_bytes( + self, + buffer: Union[bytes, bytearray, memoryview], + ): + bytes_sent: int = 0 + bytes_to_send = len(buffer) + view = memoryview(buffer) + while bytes_sent < bytes_to_send: + try: + bytes_sent += self._sock.send(view[bytes_sent:]) + except OSError as exc: + if exc.errno == EAGAIN: + continue + raise + def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements self, clean_session: bool = True, @@ -529,8 +544,8 @@ def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements self.logger.debug("Sending CONNECT to broker...") self.logger.debug(f"Fixed Header: {fixed_header}") self.logger.debug(f"Variable Header: {var_header}") - self._sock.send(fixed_header) - self._sock.send(var_header) + self._send_bytes(fixed_header) + self._send_bytes(var_header) # [MQTT-3.1.3-4] self._send_str(self.client_id) if self._lw_topic: @@ -591,7 +606,7 @@ def disconnect(self) -> None: self._connected() self.logger.debug("Sending DISCONNECT packet to broker") try: - self._sock.send(MQTT_DISCONNECT) + self._send_bytes(MQTT_DISCONNECT) except (MemoryError, OSError, RuntimeError) as e: self.logger.warning(f"Unable to send DISCONNECT packet: {e}") self._close_socket() @@ -608,7 +623,7 @@ def ping(self) -> list[int]: """ self._connected() self.logger.debug("Sending PINGREQ") - self._sock.send(MQTT_PINGREQ) + self._send_bytes(MQTT_PINGREQ) ping_timeout = self.keep_alive stamp = ticks_ms() @@ -683,9 +698,9 @@ def publish( # noqa: PLR0912, Too many branches qos, retain, ) - self._sock.send(pub_hdr_fixed) - self._sock.send(pub_hdr_var) - self._sock.send(msg) + self._send_bytes(pub_hdr_fixed) + self._send_bytes(pub_hdr_var) + self._send_bytes(msg) self._last_msg_sent_timestamp = ticks_ms() if qos == 0 and self.on_publish is not None: self.on_publish(self, self.user_data, topic, self._pid) @@ -749,12 +764,12 @@ def subscribe( # noqa: PLR0912, PLR0915, Too many branches, Too many statements packet_length += sum(len(topic.encode("utf-8")) for topic, qos in topics) self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") - self._sock.send(fixed_header) + self._send_bytes(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") var_header = packet_id_bytes self.logger.debug(f"Variable Header: {var_header}") - self._sock.send(var_header) + self._send_bytes(var_header) # attaching topic and QOS level to the packet payload = b"" for t, q in topics: @@ -764,7 +779,7 @@ def subscribe( # noqa: PLR0912, PLR0915, Too many branches, Too many statements for t, q in topics: self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}") self.logger.debug(f"payload: {payload}") - self._sock.send(payload) + self._send_bytes(payload) stamp = ticks_ms() self._last_msg_sent_timestamp = stamp while True: @@ -829,19 +844,19 @@ def unsubscribe( # noqa: PLR0912, Too many branches packet_length += sum(len(topic.encode("utf-8")) for topic in topics) self._encode_remaining_length(fixed_header, remaining_length=packet_length) self.logger.debug(f"Fixed Header: {fixed_header}") - self._sock.send(fixed_header) + self._send_bytes(fixed_header) self._pid = self._pid + 1 if self._pid < 0xFFFF else 1 packet_id_bytes = self._pid.to_bytes(2, "big") var_header = packet_id_bytes self.logger.debug(f"Variable Header: {var_header}") - self._sock.send(var_header) + self._send_bytes(var_header) payload = b"" for t in topics: topic_size = len(t.encode("utf-8")).to_bytes(2, "big") payload += topic_size + t.encode() for t in topics: self.logger.debug(f"UNSUBSCRIBING from topic {t}") - self._sock.send(payload) + self._send_bytes(payload) self._last_msg_sent_timestamp = ticks_ms() self.logger.debug("Waiting for UNSUBACK...") while True: @@ -1028,7 +1043,7 @@ def _wait_for_msg( # noqa: PLR0912, Too many branches if res[0] & 0x06 == 0x02: pkt = bytearray(b"\x40\x02\0\0") struct.pack_into("!H", pkt, 2, pid) - self._sock.send(pkt) + self._send_bytes(pkt) elif res[0] & 6 == 4: assert 0 @@ -1109,11 +1124,11 @@ def _send_str(self, string: str) -> None: """ if isinstance(string, str): - self._sock.send(struct.pack("!H", len(string.encode("utf-8")))) - self._sock.send(str.encode(string, "utf-8")) + self._send_bytes(struct.pack("!H", len(string.encode("utf-8")))) + self._send_bytes(str.encode(string, "utf-8")) else: - self._sock.send(struct.pack("!H", len(string))) - self._sock.send(string) + self._send_bytes(struct.pack("!H", len(string))) + self._send_bytes(string) @staticmethod def _valid_topic(topic: str) -> None: From 6558d8b1e5ee4bcc00c84c439277139b4f7f5d7d Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 1 Jan 2025 17:39:24 -0500 Subject: [PATCH 259/289] test_recv_timeout.py: use Mocket so _send_bytes() will work --- tests/test_recv_timeout.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index 1855525a..ab042280 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -9,6 +9,8 @@ from unittest import TestCase, main from unittest.mock import Mock +from mocket import Mocket + import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -34,7 +36,7 @@ def test_recv_timeout_vs_keepalive(self) -> None: ) # Create a mock socket that will accept anything and return nothing. - socket_mock = Mock() + socket_mock = Mocket(b"") socket_mock.recv_into = Mock(side_effect=side_effect) mqtt_client._sock = socket_mock @@ -43,10 +45,6 @@ def test_recv_timeout_vs_keepalive(self) -> None: with self.assertRaises(MQTT.MMQTTException): mqtt_client.ping() - # Verify the mock interactions. - socket_mock.send.assert_called_once() - socket_mock.recv_into.assert_called() - now = time.monotonic() assert recv_timeout <= (now - start) <= (keep_alive + 0.1) From 9be1a4cdafac0570d84b0866d2277b6dbe5bb004 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 1 Jan 2025 17:45:07 -0500 Subject: [PATCH 260/289] test_recv_timeout>py: allow a little more slack in timeout interval --- tests/test_recv_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index ab042280..099a5049 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -46,7 +46,7 @@ def test_recv_timeout_vs_keepalive(self) -> None: mqtt_client.ping() now = time.monotonic() - assert recv_timeout <= (now - start) <= (keep_alive + 0.1) + assert recv_timeout <= (now - start) <= (keep_alive + 0.2) if __name__ == "__main__": From e0c783d3a7a0c259a512ca2cef3116f548062231 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 4 Nov 2024 23:11:46 +0100 Subject: [PATCH 261/289] allow to specify session_id for connect() --- adafruit_minimqtt/adafruit_minimqtt.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5028dcd9..e09c6942 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -399,6 +399,7 @@ def connect( host: Optional[str] = None, port: Optional[int] = None, keep_alive: Optional[int] = None, + session_id: Optional[str] = None, ) -> int: """Initiates connection with the MQTT Broker. Will perform exponential back-off on connect failures. @@ -408,7 +409,8 @@ def connect( :param int port: Network port of the remote broker. :param int keep_alive: Maximum period allowed for communication within single connection attempt, in seconds. - + :param str session_id: unique session ID, + used for multiple simultaneous connections to the same host """ last_exception = None @@ -430,6 +432,7 @@ def connect( host=host, port=port, keep_alive=keep_alive, + session_id=session_id ) self._reset_reconnect_backoff() return ret @@ -482,6 +485,7 @@ def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements host: Optional[str] = None, port: Optional[int] = None, keep_alive: Optional[int] = None, + session_id: Optional[str] = None, ) -> int: """Initiates connection with the MQTT Broker. @@ -489,6 +493,8 @@ def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements :param str host: Hostname or IP address of the remote broker. :param int port: Network port of the remote broker. :param int keep_alive: Maximum period allowed for communication, in seconds. + :param str session_id: unique session ID, + used for multiple simultaneous connections to the same host """ if host: @@ -511,6 +517,7 @@ def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements self.broker, self.port, proto="mqtt:", + session_id=session_id, timeout=self._socket_timeout, is_ssl=self._is_ssl, ssl_context=self._ssl_context, From 58ff51c254b76205cc4928ac33bf5cae33150f59 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 4 Nov 2024 23:15:18 +0100 Subject: [PATCH 262/289] add comma --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index e09c6942..510888bb 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -432,7 +432,7 @@ def connect( host=host, port=port, keep_alive=keep_alive, - session_id=session_id + session_id=session_id, ) self._reset_reconnect_backoff() return ret From 269d10bdcdb9a945cf26d59a9b8f8637a064305f Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 4 Nov 2024 23:20:48 +0100 Subject: [PATCH 263/289] suppress warning about too many arguments --- adafruit_minimqtt/adafruit_minimqtt.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 510888bb..6b400f5b 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -393,7 +393,7 @@ def username_pw_set(self, username: str, password: Optional[str] = None) -> None if password is not None: self._password = password - def connect( + def connect( # noqa: PLR0913, too many arguments in function definition self, clean_session: bool = True, host: Optional[str] = None, @@ -464,22 +464,7 @@ def connect( raise MMQTTException(exc_msg) from last_exception raise MMQTTException(exc_msg) - def _send_bytes( - self, - buffer: Union[bytes, bytearray, memoryview], - ): - bytes_sent: int = 0 - bytes_to_send = len(buffer) - view = memoryview(buffer) - while bytes_sent < bytes_to_send: - try: - bytes_sent += self._sock.send(view[bytes_sent:]) - except OSError as exc: - if exc.errno == EAGAIN: - continue - raise - - def _connect( # noqa: PLR0912, PLR0915, Too many branches, Too many statements + def _connect( # noqa: PLR0912, PLR0913, PLR0915, Too many branches, Too many arguments, Too many statements self, clean_session: bool = True, host: Optional[str] = None, From dfaf68edc7e93f8dbde06debf6be4a47f4776122 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 2 Jan 2025 17:48:58 +0100 Subject: [PATCH 264/289] restore _send_bytes() --- adafruit_minimqtt/adafruit_minimqtt.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6b400f5b..5a21dee6 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -464,6 +464,21 @@ def connect( # noqa: PLR0913, too many arguments in function definition raise MMQTTException(exc_msg) from last_exception raise MMQTTException(exc_msg) + def _send_bytes( + self, + buffer: Union[bytes, bytearray, memoryview], + ): + bytes_sent: int = 0 + bytes_to_send = len(buffer) + view = memoryview(buffer) + while bytes_sent < bytes_to_send: + try: + bytes_sent += self._sock.send(view[bytes_sent:]) + except OSError as exc: + if exc.errno == EAGAIN: + continue + raise + def _connect( # noqa: PLR0912, PLR0913, PLR0915, Too many branches, Too many arguments, Too many statements self, clean_session: bool = True, From 72f8e81456b3918ce327f7d277cfe335d928895b Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 2 Jan 2025 21:26:15 +0100 Subject: [PATCH 265/289] fix EAGAIN reference --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 5a21dee6..63cb6c82 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -475,7 +475,7 @@ def _send_bytes( try: bytes_sent += self._sock.send(view[bytes_sent:]) except OSError as exc: - if exc.errno == EAGAIN: + if exc.errno == errno.EAGAIN: continue raise From b47ed70fd49c651e1d3cdf1957471344e590bbf8 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 5 Jan 2025 15:14:26 -0500 Subject: [PATCH 266/289] Handle ESP32SPI Socket.send(), which does not return a byte count --- adafruit_minimqtt/adafruit_minimqtt.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 63cb6c82..954d894e 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -473,7 +473,11 @@ def _send_bytes( view = memoryview(buffer) while bytes_sent < bytes_to_send: try: - bytes_sent += self._sock.send(view[bytes_sent:]) + sent_now = self._sock.send(view[bytes_sent:]) + # Some versions of `Socket.send()` do not return the number of bytes sent. + if not isinstance(sent_now, int): + return + bytes_sent += sent_now except OSError as exc: if exc.errno == errno.EAGAIN: continue From c9ac0f8e356eb91e1e9cfddc7b2f620a723041b4 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 14 Jan 2025 11:32:34 -0600 Subject: [PATCH 267/289] add sphinx configuration to rtd.yaml Signed-off-by: foamyguy --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33c2a610..88bca9fa 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,6 +8,9 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: os: ubuntu-20.04 tools: From 0cd7893e56304dd4b63893969993071dd3818518 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 9 Jan 2025 11:55:37 +0100 Subject: [PATCH 268/289] add test for the PUBLISH+UNSUBACK case --- tests/test_unsubscribe.py | 45 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/test_unsubscribe.py b/tests/test_unsubscribe.py index f7bbb21a..1dfbb856 100644 --- a/tests/test_unsubscribe.py +++ b/tests/test_unsubscribe.py @@ -68,6 +68,49 @@ def handle_unsubscribe(client, user_data, topic, pid): + [0x6F] * 257 ), ), + # UNSUBSCRIBE responded to by PUBLISH followed by UNSUBACK + ( + "foo/bar", + bytearray( + [ + 0x30, # PUBLISH + 0x0C, + 0x00, + 0x07, + 0x66, + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x66, + 0x6F, + 0x6F, + 0xB0, # UNSUBACK + 0x02, + 0x00, + 0x01, + ] + ), + bytearray( + [ + 0xA2, # fixed header + 0x0B, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + ] + ), + ), # use list of topics for more coverage. If the range was (1, 10000), that would be # long enough to use 3 bytes for remaining length, however that would make the test # run for many minutes even on modern systems, so 1000 is used instead. @@ -95,7 +138,7 @@ def handle_unsubscribe(client, user_data, topic, pid): @pytest.mark.parametrize( "topic,to_send,exp_recv", testdata, - ids=["short_topic", "long_topic", "topic_list_long"], + ids=["short_topic", "long_topic", "publish_first", "topic_list_long"], ) def test_unsubscribe(topic, to_send, exp_recv) -> None: """ From b8fa023c7e0d94be9f362fa8bdd71c7f11b50484 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 19 Jan 2025 10:58:27 +0100 Subject: [PATCH 269/289] no need to check zero byte returned from recv_into() fixes #157 --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index b216b16e..75148ed4 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1015,7 +1015,7 @@ def _wait_for_msg( # noqa: PLR0912, Too many branches return None raise MMQTTException from error - if res in [None, b"", b"\x00"]: + if res in [None, b""]: # If we get here, it means that there is nothing to be received return None pkt_type = res[0] & MQTT_PKT_TYPE_MASK From 0a504ff8bb731eb471a9f413d4cd56a879ffee02 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 19 Jan 2025 13:03:57 -0500 Subject: [PATCH 270/289] increase test_recv_timeout delay --- tests/test_recv_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_recv_timeout.py b/tests/test_recv_timeout.py index 099a5049..73b1b19c 100644 --- a/tests/test_recv_timeout.py +++ b/tests/test_recv_timeout.py @@ -46,7 +46,7 @@ def test_recv_timeout_vs_keepalive(self) -> None: mqtt_client.ping() now = time.monotonic() - assert recv_timeout <= (now - start) <= (keep_alive + 0.2) + assert recv_timeout <= (now - start) <= (keep_alive + 0.5) if __name__ == "__main__": From fdd436e4fbb253a2f07fca529d04b5d0b8bad4d5 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 28 Jan 2025 19:07:35 +0100 Subject: [PATCH 271/289] reduce the use of MMQTTException use it for protocol/network/system level errors only fixes #201 --- adafruit_minimqtt/adafruit_minimqtt.py | 63 ++++++++++++++++---------- tests/test_loop.py | 8 ++-- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 75148ed4..264c8e3d 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -93,13 +93,26 @@ class MMQTTException(Exception): - """MiniMQTT Exception class.""" + """ + MiniMQTT Exception class. + + Raised for various mostly protocol or network/system level errors. + In general, the robust way to recover is to call reconnect(). + """ def __init__(self, error, code=None): super().__init__(error, code) self.code = code +class MMQTTStateError(MMQTTException): + """ + MiniMQTT invalid state error. + + Raised e.g. if a function is called in unexpected state. + """ + + class NullLogger: """Fake logger class that does not do anything""" @@ -163,7 +176,7 @@ def __init__( # noqa: PLR0915, PLR0913, Too many statements, Too many arguments self._use_binary_mode = use_binary_mode if recv_timeout <= socket_timeout: - raise MMQTTException("recv_timeout must be strictly greater than socket_timeout") + raise ValueError("recv_timeout must be strictly greater than socket_timeout") self._socket_timeout = socket_timeout self._recv_timeout = recv_timeout @@ -181,7 +194,7 @@ def __init__( # noqa: PLR0915, PLR0913, Too many statements, Too many arguments self._reconnect_timeout = float(0) self._reconnect_maximum_backoff = 32 if connect_retries <= 0: - raise MMQTTException("connect_retries must be positive") + raise ValueError("connect_retries must be positive") self._reconnect_attempts_max = connect_retries self.broker = broker @@ -190,7 +203,7 @@ def __init__( # noqa: PLR0915, PLR0913, Too many statements, Too many arguments if ( self._password and len(password.encode("utf-8")) > MQTT_TOPIC_LENGTH_LIMIT ): # [MQTT-3.1.3.5] - raise MMQTTException("Password length is too large.") + raise ValueError("Password length is too large.") # The connection will be insecure unless is_ssl is set to True. # If the port is not specified, the security will be set based on the is_ssl parameter. @@ -286,15 +299,15 @@ def will_set( """ self.logger.debug("Setting last will properties") if self._is_connected: - raise MMQTTException("Last Will should only be called before connect().") + raise MMQTTStateError("Last Will should only be called before connect().") # check topic/msg/qos kwargs self._valid_topic(topic) if "+" in topic or "#" in topic: - raise MMQTTException("Publish topic can not contain wildcards.") + raise ValueError("Publish topic can not contain wildcards.") if msg is None: - raise MMQTTException("Message can not be None.") + raise ValueError("Message can not be None.") if isinstance(msg, (int, float)): msg = str(msg).encode("ascii") elif isinstance(msg, str): @@ -302,12 +315,11 @@ def will_set( elif isinstance(msg, bytes): pass else: - raise MMQTTException("Invalid message data type.") + raise ValueError("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: - raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") + raise ValueError(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") self._valid_qos(qos) - assert 0 <= qos <= 1, "Quality of Service Level 2 is unsupported by this library." # fixed header. [3.3.1.2], [3.3.1.3] pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) @@ -390,7 +402,7 @@ def username_pw_set(self, username: str, password: Optional[str] = None) -> None """ if self._is_connected: - raise MMQTTException("This method must be called before connect().") + raise MMQTTStateError("This method must be called before connect().") self._username = username if password is not None: self._password = password @@ -670,10 +682,10 @@ def publish( # noqa: PLR0912, Too many branches self._connected() self._valid_topic(topic) if "+" in topic or "#" in topic: - raise MMQTTException("Publish topic can not contain wildcards.") + raise ValueError("Publish topic can not contain wildcards.") # check msg/qos kwargs if msg is None: - raise MMQTTException("Message can not be None.") + raise ValueError("Message can not be None.") if isinstance(msg, (int, float)): msg = str(msg).encode("ascii") elif isinstance(msg, str): @@ -681,10 +693,11 @@ def publish( # noqa: PLR0912, Too many branches elif isinstance(msg, bytes): pass else: - raise MMQTTException("Invalid message data type.") + raise ValueError("Invalid message data type.") if len(msg) > MQTT_MSG_MAX_SZ: - raise MMQTTException(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") - assert 0 <= qos <= 1, "Quality of Service Level 2 is unsupported by this library." + raise ValueError(f"Message size larger than {MQTT_MSG_MAX_SZ} bytes.") + + self._valid_qos(qos) # fixed header. [3.3.1.2], [3.3.1.3] pub_hdr_fixed = bytearray([MQTT_PUBLISH | retain | qos << 1]) @@ -849,7 +862,7 @@ def unsubscribe( # noqa: PLR0912, Too many branches topics.append(t) for t in topics: if t not in self._subscribed_topics: - raise MMQTTException("Topic must be subscribed to before attempting unsubscribe.") + raise MMQTTStateError("Topic must be subscribed to before attempting unsubscribe.") # Assemble packet self.logger.debug("Sending UNSUBSCRIBE to broker...") fixed_header = bytearray([MQTT_UNSUB]) @@ -959,7 +972,7 @@ def loop(self, timeout: float = 1.0) -> Optional[list[int]]: """ if timeout < self._socket_timeout: - raise MMQTTException( + raise ValueError( f"loop timeout ({timeout}) must be >= " + f"socket timeout ({self._socket_timeout}))" ) @@ -1153,13 +1166,13 @@ def _valid_topic(topic: str) -> None: """ if topic is None: - raise MMQTTException("Topic may not be NoneType") + raise ValueError("Topic may not be NoneType") # [MQTT-4.7.3-1] if not topic: - raise MMQTTException("Topic may not be empty.") + raise ValueError("Topic may not be empty.") # [MQTT-4.7.3-3] if len(topic.encode("utf-8")) > MQTT_TOPIC_LENGTH_LIMIT: - raise MMQTTException("Topic length is too large.") + raise ValueError(f"Encoded topic length is larger than {MQTT_TOPIC_LENGTH_LIMIT}") @staticmethod def _valid_qos(qos_level: int) -> None: @@ -1170,16 +1183,16 @@ def _valid_qos(qos_level: int) -> None: """ if isinstance(qos_level, int): if qos_level < 0 or qos_level > 2: - raise MMQTTException("QoS must be between 1 and 2.") + raise NotImplementedError("QoS must be between 1 and 2.") else: - raise MMQTTException("QoS must be an integer.") + raise ValueError("QoS must be an integer.") def _connected(self) -> None: """Returns MQTT client session status as True if connected, raises - a `MMQTTException` if `False`. + a `MMQTTStateError exception` if `False`. """ if not self.is_connected(): - raise MMQTTException("MiniMQTT is not connected") + raise MMQTTStateError("MiniMQTT is not connected") def is_connected(self) -> bool: """Returns MQTT client session status as True if connected, False diff --git a/tests/test_loop.py b/tests/test_loop.py index 834a0d4f..f64dd18c 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -155,7 +155,7 @@ def test_loop_basic(self) -> None: def test_loop_timeout_vs_socket_timeout(self): """ - loop() should throw MMQTTException if the timeout argument + loop() should throw ValueError if the timeout argument is bigger than the socket timeout. """ mqtt_client = MQTT.MQTT( @@ -167,14 +167,14 @@ def test_loop_timeout_vs_socket_timeout(self): ) mqtt_client.is_connected = lambda: True - with pytest.raises(MQTT.MMQTTException) as context: + with pytest.raises(ValueError) as context: mqtt_client.loop(timeout=0.5) assert "loop timeout" in str(context) def test_loop_is_connected(self): """ - loop() should throw MMQTTException if not connected + loop() should throw MMQTTStateError if not connected """ mqtt_client = MQTT.MQTT( broker="127.0.0.1", @@ -183,7 +183,7 @@ def test_loop_is_connected(self): ssl_context=ssl.create_default_context(), ) - with pytest.raises(MQTT.MMQTTException) as context: + with pytest.raises(MQTT.MMQTTStateError) as context: mqtt_client.loop(timeout=1) assert "not connected" in str(context) From 1778c7cfd6773c6523ef6390bd973929e21bfc0a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 9 Feb 2025 23:34:03 +0100 Subject: [PATCH 272/289] fix typo --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 93eefcbd..2b8b03ec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ @pytest.fixture(autouse=True) def reset_connection_manager(monkeypatch): - """Reset the ConnectionManager, since it's a singlton and will hold data""" + """Reset the ConnectionManager, since it's a singleton and will hold data""" monkeypatch.setattr( "adafruit_minimqtt.adafruit_minimqtt.get_connection_manager", adafruit_connection_manager.ConnectionManager, From dc361342e4604b9725ed3af6a84c6d4bafeb8302 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sun, 9 Feb 2025 23:34:37 +0100 Subject: [PATCH 273/289] disconnect on reconnect() if connected fixes #243 --- adafruit_minimqtt/adafruit_minimqtt.py | 11 +- tests/test_reconnect.py | 205 +++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 tests/test_reconnect.py diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 75148ed4..1c63d5ca 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -939,11 +939,18 @@ def reconnect(self, resub_topics: bool = True) -> int: """ self.logger.debug("Attempting to reconnect with MQTT broker") + subscribed_topics = [] + if self.is_connected(): + # disconnect() will reset subscribed topics so stash them now. + if resub_topics: + subscribed_topics = self._subscribed_topics.copy() + self.disconnect() + ret = self.connect() self.logger.debug("Reconnected with broker") - if resub_topics: + + if resub_topics and subscribed_topics: self.logger.debug("Attempting to resubscribe to previously subscribed topics.") - subscribed_topics = self._subscribed_topics.copy() self._subscribed_topics = [] while subscribed_topics: feed = subscribed_topics.pop() diff --git a/tests/test_reconnect.py b/tests/test_reconnect.py new file mode 100644 index 00000000..6b049246 --- /dev/null +++ b/tests/test_reconnect.py @@ -0,0 +1,205 @@ +# SPDX-FileCopyrightText: 2025 Vladimír Kotal +# +# SPDX-License-Identifier: Unlicense + +"""reconnect tests""" + +import logging +import ssl +import sys + +import pytest +from mocket import Mocket + +import adafruit_minimqtt.adafruit_minimqtt as MQTT + +if not sys.implementation.name == "circuitpython": + from typing import Optional + + from circuitpython_typing.socket import ( + SocketType, + SSLContextType, + ) + + +class FakeConnectionManager: + """ + Fake ConnectionManager class + """ + + def __init__(self, socket): + self._socket = socket + + def get_socket( # noqa: PLR0913, Too many arguments + self, + host: str, + port: int, + proto: str, + session_id: Optional[str] = None, + *, + timeout: float = 1.0, + is_ssl: bool = False, + ssl_context: Optional[SSLContextType] = None, + ) -> SocketType: + """ + Return the specified socket. + """ + return self._socket + + def close_socket(self, socket) -> None: + pass + + +def handle_subscribe(client, user_data, topic, qos): + """ + Record topics into user data. + """ + assert topic + assert user_data["topics"] is not None + assert qos == 0 + + user_data["topics"].append(topic) + + +def handle_disconnect(client, user_data, zero): + """ + Record disconnect. + """ + + user_data["disconnect"] = True + + +# The MQTT packet contents below were captured using Mosquitto client+server. +testdata = [ + ( + [], + bytearray( + [ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + ] + ), + ), + ( + [("foo/bar", 0)], + bytearray( + [ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + ] + ), + ), + ( + [("foo/bar", 0), ("bah", 0)], + bytearray( + [ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x03, + 0x00, + ] + ), + ), +] + + +@pytest.mark.parametrize( + "topics,to_send", + testdata, + ids=[ + "no_topic", + "single_topic", + "multi_topic", + ], +) +def test_reconnect(topics, to_send) -> None: + """ + Test reconnect() handling, mainly that it performs disconnect on already connected socket. + + Nothing will travel over the wire, it is all fake. + """ + logging.basicConfig() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + host = "localhost" + port = 1883 + + user_data = {"topics": [], "disconnect": False} + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + ssl_context=ssl.create_default_context(), + connect_retries=1, + user_data=user_data, + ) + + mocket = Mocket(to_send) + mqtt_client._connection_manager = FakeConnectionManager(mocket) + mqtt_client.connect() + + mqtt_client.logger = logger + + if topics: + logger.info(f"subscribing to {topics}") + mqtt_client.subscribe(topics) + + logger.info("reconnecting") + mqtt_client.on_subscribe = handle_subscribe + mqtt_client.on_disconnect = handle_disconnect + mqtt_client.reconnect() + + assert user_data.get("disconnect") == True + assert set(user_data.get("topics")) == set([t[0] for t in topics]) From 6ebbb2601bf934ab0d376c7deaca9f3ce524bf04 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 10 Feb 2025 08:02:21 +0100 Subject: [PATCH 274/289] check close count --- tests/test_reconnect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_reconnect.py b/tests/test_reconnect.py index 6b049246..5e0a5331 100644 --- a/tests/test_reconnect.py +++ b/tests/test_reconnect.py @@ -29,6 +29,7 @@ class FakeConnectionManager: def __init__(self, socket): self._socket = socket + self.close_cnt = 0 def get_socket( # noqa: PLR0913, Too many arguments self, @@ -47,7 +48,7 @@ def get_socket( # noqa: PLR0913, Too many arguments return self._socket def close_socket(self, socket) -> None: - pass + self.close_cnt += 1 def handle_subscribe(client, user_data, topic, qos): @@ -202,4 +203,5 @@ def test_reconnect(topics, to_send) -> None: mqtt_client.reconnect() assert user_data.get("disconnect") == True + assert mqtt_client._connection_manager.close_cnt == 1 assert set(user_data.get("topics")) == set([t[0] for t in topics]) From 8f4c421c69aa412f64176c3d2034e56db0b88fd2 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 10 Feb 2025 08:10:57 +0100 Subject: [PATCH 275/289] add test for the not connected case --- tests/test_reconnect.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_reconnect.py b/tests/test_reconnect.py index 5e0a5331..bd9d3926 100644 --- a/tests/test_reconnect.py +++ b/tests/test_reconnect.py @@ -205,3 +205,43 @@ def test_reconnect(topics, to_send) -> None: assert user_data.get("disconnect") == True assert mqtt_client._connection_manager.close_cnt == 1 assert set(user_data.get("topics")) == set([t[0] for t in topics]) + + +def test_reconnect_not_connected() -> None: + """ + Test reconnect() handling not connected. + """ + logging.basicConfig() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + host = "localhost" + port = 1883 + + user_data = {"topics": [], "disconnect": False} + mqtt_client = MQTT.MQTT( + broker=host, + port=port, + ssl_context=ssl.create_default_context(), + connect_retries=1, + user_data=user_data, + ) + + mocket = Mocket( + bytearray( + [ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + ] + ) + ) + mqtt_client._connection_manager = FakeConnectionManager(mocket) + + mqtt_client.logger = logger + mqtt_client.on_disconnect = handle_disconnect + mqtt_client.reconnect() + + assert user_data.get("disconnect") == False + assert mqtt_client._connection_manager.close_cnt == 0 From 933b1cb66daa7b2d0e1995d329eeebea12052bd5 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Mon, 10 Feb 2025 18:02:41 +0100 Subject: [PATCH 276/289] preserve sesion ID on reconnect --- adafruit_minimqtt/adafruit_minimqtt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 1c63d5ca..a8d53428 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -204,6 +204,8 @@ def __init__( # noqa: PLR0915, PLR0913, Too many statements, Too many arguments if port: self.port = port + self.session_id = None + # define client identifier if client_id: # user-defined client_id MAY allow client_id's > 23 bytes or @@ -528,6 +530,7 @@ def _connect( # noqa: PLR0912, PLR0913, PLR0915, Too many branches, Too many ar is_ssl=self._is_ssl, ssl_context=self._ssl_context, ) + self.session_id = session_id self._backwards_compatible_sock = not hasattr(self._sock, "recv_into") fixed_header = bytearray([0x10]) @@ -946,7 +949,7 @@ def reconnect(self, resub_topics: bool = True) -> int: subscribed_topics = self._subscribed_topics.copy() self.disconnect() - ret = self.connect() + ret = self.connect(session_id=self.session_id) self.logger.debug("Reconnected with broker") if resub_topics and subscribed_topics: From 67f971fe3f247ac222c72593a97960145a14a4af Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 24 Feb 2025 13:55:32 -0800 Subject: [PATCH 277/289] Remove secrets usage --- .../cellular/minimqtt_adafruitio_cellular.py | 23 ++++++----- .../cellular/minimqtt_simpletest_cellular.py | 35 +++++++++-------- .../cpython/minimqtt_adafruitio_cpython.py | 18 +++++---- .../cpython/minimqtt_simpletest_cpython.py | 24 ++++++------ .../esp32spi/minimqtt_adafruitio_esp32spi.py | 26 ++++++------- .../esp32spi/minimqtt_certificate_esp32spi.py | 38 +++++++++---------- .../minimqtt_pub_sub_blocking_esp32spi.py | 26 ++++++------- ...b_sub_blocking_topic_callbacks_esp32spi.py | 33 ++++++++-------- .../minimqtt_pub_sub_nonblocking_esp32spi.py | 22 +++++------ .../minimqtt_pub_sub_pyportal_esp32spi.py | 22 +++++------ .../esp32spi/minimqtt_simpletest_esp32spi.py | 33 ++++++++-------- examples/ethernet/minimqtt_adafruitio_eth.py | 15 ++++---- examples/ethernet/minimqtt_simpletest_eth.py | 27 +++++++------ examples/minimqtt_simpletest.py | 26 ++++++------- .../minimqtt_adafruitio_native_networking.py | 34 ++++++++--------- ...mqtt_pub_sub_blocking_native_networking.py | 30 +++++++-------- ...cking_topic_callbacks_native_networking.py | 34 ++++++++--------- 17 files changed, 227 insertions(+), 239 deletions(-) diff --git a/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index 686062c1..6c63de9f 100755 --- a/examples/cellular/minimqtt_adafruitio_cellular.py +++ b/examples/cellular/minimqtt_adafruitio_cellular.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import adafruit_fona.adafruit_fona_network as network @@ -14,12 +14,13 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your GPRS credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get FONA details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +apn = getenv("apn") +apn_username = getenv("apn_username") +apn_password = getenv("apn_password") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") ### Cellular ### @@ -44,7 +45,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed) + print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(onoff_feed) @@ -61,9 +62,7 @@ def message(client, topic, message): # Initialize cellular data network -network = network.CELLULAR( - fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password")) -) +network = network.CELLULAR(fona, (apn, apn_username, apn_password)) while not network.is_attached: print("Attaching to network...") @@ -105,7 +104,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d..." % photocell_val) + print(f"Sending photocell value: {photocell_val}...") mqtt_client.publish(photocell_feed, photocell_val) print("Sent!") photocell_val += 1 diff --git a/examples/cellular/minimqtt_simpletest_cellular.py b/examples/cellular/minimqtt_simpletest_cellular.py index c24ef907..35fc90cb 100644 --- a/examples/cellular/minimqtt_simpletest_cellular.py +++ b/examples/cellular/minimqtt_simpletest_cellular.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import adafruit_fona.adafruit_fona_network as network @@ -14,12 +14,13 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your GPRS credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get FONA details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +apn = getenv("apn") +apn_username = getenv("apn_username") +apn_password = getenv("apn_password") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) @@ -70,9 +71,7 @@ def publish(client, userdata, topic, pid): # Initialize cellular data network -network = network.CELLULAR( - fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password")) -) +network = network.CELLULAR(fona, (apn, apn_username, apn_password)) while not network.is_attached: print("Attaching to network...") @@ -89,9 +88,9 @@ def publish(client, userdata, topic, pid): # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=os.getenv("broker"), - username=os.getenv("username"), - password=os.getenv("password"), + broker="io.adafruit.com", + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -104,17 +103,17 @@ def publish(client, userdata, topic, pid): client.on_unsubscribe = unsubscribe client.on_publish = publish -print("Attempting to connect to %s" % client.broker) +print(f"Attempting to connect to {client.broker}") client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % client.broker) +print(f"Disconnecting from {client.broker}") client.disconnect() diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 1f350c46..60d94c36 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -1,20 +1,22 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import socket import ssl import time +from os import getenv import adafruit_minimqtt.adafruit_minimqtt as MQTT -### Secrets File Setup ### +### Key Setup ### -# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. +# Add your Adafruit IO username and key to your env. +# example: +# export ADAFRUIT_AIO_USERNAME=your-aio-username +# export ADAFRUIT_AIO_KEY=your-aio-key -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") ### Feeds ### @@ -31,7 +33,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed) + print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(onoff_feed) @@ -72,7 +74,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d..." % photocell_val) + print(f"Sending photocell value: {photocell_val}...") mqtt_client.publish(photocell_feed, photocell_val) print("Sent!") photocell_val += 1 diff --git a/examples/cpython/minimqtt_simpletest_cpython.py b/examples/cpython/minimqtt_simpletest_cpython.py index a435b6a3..ef875534 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -1,17 +1,19 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import socket import ssl +from os import getenv import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystems. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. +# Add your Adafruit IO username and key to your env. +# example: +# export ADAFRUIT_AIO_USERNAME=your-aio-username +# export ADAFRUIT_AIO_KEY=your-aio-key -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") ### Topic Setup ### @@ -61,7 +63,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), + broker="io.adafruit.com", username=aio_username, password=aio_key, socket_pool=socket, @@ -76,17 +78,17 @@ def message(client, topic, message): mqtt_client.on_publish = publish mqtt_client.on_message = message -print("Attempting to connect to %s" % mqtt_client.broker) +print(f"Attempting to connect to {mqtt_client.broker}") mqtt_client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") mqtt_client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") mqtt_client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") mqtt_client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % mqtt_client.broker) +print(f"Disconnecting from {mqtt_client.broker}") mqtt_client.disconnect() diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 7266beb8..5c9e6856 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import board @@ -13,12 +13,12 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -33,16 +33,16 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED # import adafruit_rgbled # from adafruit_esp32spi import PWMOut # RED_LED = PWMOut.PWMOut(esp, 26) # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +# status_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) ### Feeds ### @@ -59,7 +59,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed) + print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(onoff_feed) @@ -77,7 +77,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) +esp.connect_AP(ssid, password) print("Connected!") pool = adafruit_connection_manager.get_radio_socketpool(esp) @@ -107,7 +107,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d..." % photocell_val) + print(f"Sending photocell value: {photocell_val}...") mqtt_client.publish(photocell_feed, photocell_val) print("Sent!") photocell_val += 1 diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index 66c397c9..eade1211 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -10,14 +10,14 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# Get WiFi details and MQTT keys, ensure these are setup in settings.toml +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +broker = getenv("broker") +username = getenv("username") +paswword = getenv("paswword") -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +### WiFi ### # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -32,17 +32,17 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED # import adafruit_rgbled # from adafruit_esp32spi import PWMOut # RED_LED = PWMOut.PWMOut(esp, 26) # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) +# status_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel) ### Topic Setup ### @@ -109,9 +109,9 @@ def publish(client, userdata, topic, pid): # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=secrets["broker"], - username=secrets["user"], - password=secrets["pass"], + broker=broker, + username=username, + password=password, socket_pool=pool, ssl_context=ssl_context, ) @@ -123,17 +123,17 @@ def publish(client, userdata, topic, pid): client.on_unsubscribe = unsubscribe client.on_publish = publish -print("Attempting to connect to %s" % client.broker) +print(f"Attempting to connect to {client.broker}") client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % client.broker) +print(f"Disconnecting from {client.broker}") client.disconnect() diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index cb8bbb46..139fc844 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import board @@ -13,12 +13,12 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -33,16 +33,16 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED # import adafruit_rgbled # from adafruit_esp32spi import PWMOut # RED_LED = PWMOut.PWMOut(esp, 26) # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +# status_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) ### Adafruit IO Setup ### @@ -56,7 +56,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to MQTT broker! Listening for topic changes on %s" % default_topic) + print(f"Connected to MQTT broker! Listening for topic changes on {default_topic}") # Subscribe to all changes on the default_topic feed. client.subscribe(default_topic) @@ -77,7 +77,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) +esp.connect_AP(ssid, password) print("Connected!") pool = adafruit_connection_manager.get_radio_socketpool(esp) @@ -111,7 +111,7 @@ def message(client, topic, message): print("Failed to get data, retrying\n", e) esp.reset() time.sleep(1) - esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) + esp.connect_AP(ssid, password) mqtt_client.reconnect() continue time.sleep(1) diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index 1b8d8f0d..f0b175d1 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import board @@ -13,14 +13,13 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# Get WiFi details and broker keys, ensure these are setup in settings.toml +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +broker = getenv("broker") +broker_port = getenv("broker_port") -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +### WiFi ### # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -35,17 +34,17 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED # import adafruit_rgbled # from adafruit_esp32spi import PWMOut # RED_LED = PWMOut.PWMOut(esp, 26) # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) +# status_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel) ### Code ### @@ -76,7 +75,7 @@ def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value print(f"Battery level: {message}v") - # client.remove_topic_callback(secrets["aio_username"] + "/feeds/device.batterylevel") + # client.remove_topic_callback(aio_username + "/feeds/device.batterylevel") def on_message(client, topic, message): @@ -94,8 +93,8 @@ def on_message(client, topic, message): # Set up a MiniMQTT Client client = MQTT.MQTT( - broker=os.getenv("broker"), - port=os.getenv("broker_port"), + broker=broker, + port=broker_port, socket_pool=pool, ssl_context=ssl_context, ) @@ -106,14 +105,14 @@ def on_message(client, topic, message): client.on_subscribe = subscribe client.on_unsubscribe = unsubscribe client.on_message = on_message -client.add_topic_callback(secrets["aio_username"] + "/feeds/device.batterylevel", on_battery_msg) +client.add_topic_callback(aio_username + "/feeds/device.batterylevel", on_battery_msg) # Connect the client to the MQTT broker. print("Connecting to MQTT broker...") client.connect() # Subscribe to all notifications on the device group -client.subscribe(secrets["aio_username"] + "/groups/device", 1) +client.subscribe(aio_username + "/groups/device", 1) # Start a blocking message loop... # NOTE: NO code below this loop will execute diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index 642d824c..fcd8cb67 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import board @@ -13,12 +13,12 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -33,16 +33,16 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED # import adafruit_rgbled # from adafruit_esp32spi import PWMOut # RED_LED = PWMOut.PWMOut(esp, 26) # GREEN_LED = PWMOut.PWMOut(esp, 27) # BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +# status_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) ### Adafruit IO Setup ### @@ -76,7 +76,7 @@ def message(client, topic, message): # Connect to WiFi print("Connecting to WiFi...") -esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) +esp.connect_AP(ssid, password) print("Connected!") pool = adafruit_connection_manager.get_radio_socketpool(esp) diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index 5f726d19..33933a5d 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import adafruit_pyportal @@ -11,12 +11,10 @@ pyportal = adafruit_pyportal.PyPortal() -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # ------------- MQTT Topic Setup ------------- # mqtt_topic = "test/topic" @@ -27,7 +25,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Subscribing to %s" % (mqtt_topic)) + print(f"Subscribing to {mqtt_topic}") client.subscribe(mqtt_topic) @@ -55,9 +53,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), - username=os.getenv("username"), - password=os.getenv("password"), + broker="io.adafruit.com", + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -77,7 +75,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d" % photocell_val) + print(f"Sending photocell value: {photocell_val}") mqtt_client.publish(mqtt_topic, photocell_val) photocell_val += 1 time.sleep(1) diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index 37dae061..8de8f446 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os +from os import getenv import adafruit_connection_manager import board @@ -11,12 +11,12 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -34,7 +34,7 @@ print("Connecting to AP...") while not esp.is_connected: try: - esp.connect_AP(os.getenv("ssid"), os.getenv("password")) + esp.connect_AP(ssid, password) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue @@ -91,10 +91,9 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), - port=os.getenv("port"), - username=os.getenv("username"), - password=os.getenv("password"), + broker="io.adafruit.com", + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) @@ -107,17 +106,17 @@ def message(client, topic, message): mqtt_client.on_publish = publish mqtt_client.on_message = message -print("Attempting to connect to %s" % mqtt_client.broker) +print(f"Attempting to connect to {mqtt_client.broker}") mqtt_client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") mqtt_client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") mqtt_client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") mqtt_client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % mqtt_client.broker) +print(f"Disconnecting from {mqtt_client.broker}") mqtt_client.disconnect() diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 5cac4183..b18d286d 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os import time +from os import getenv import adafruit_connection_manager import board @@ -12,11 +12,10 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -39,7 +38,7 @@ def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. - print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed) + print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(onoff_feed) @@ -84,7 +83,7 @@ def message(client, topic, message): mqtt_client.loop() # Send a new message - print("Sending photocell value: %d..." % photocell_val) + print(f"Sending photocell value: {photocell_val}...") mqtt_client.publish(photocell_feed, photocell_val) print("Sent!") photocell_val += 1 diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index 35535800..39d86dd7 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os +from os import getenv import adafruit_connection_manager import board @@ -11,11 +11,10 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -70,9 +69,9 @@ def publish(client, userdata, topic, pid): # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. client = MQTT.MQTT( - broker=os.getenv("broker"), - username=os.getenv("username"), - password=os.getenv("password"), + broker="io.adafruit.com", + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -85,17 +84,17 @@ def publish(client, userdata, topic, pid): client.on_unsubscribe = unsubscribe client.on_publish = publish -print("Attempting to connect to %s" % client.broker) +print(f"Attempting to connect to {client.broker}") client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % client.broker) +print(f"Disconnecting from {client.broker}") client.disconnect() diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index c1027d9a..b5086ba0 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os +from os import getenv import adafruit_connection_manager import board @@ -11,12 +11,12 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. Add your Adafruit IO username and key as well. -# DO NOT share that file or commit it into Git or other source control. - -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -34,7 +34,7 @@ print("Connecting to AP...") while not esp.is_connected: try: - esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) + esp.connect_AP(ssid, password) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue @@ -107,17 +107,17 @@ def message(client, topic, message): mqtt_client.on_publish = publish mqtt_client.on_message = message -print("Attempting to connect to %s" % mqtt_client.broker) +print(f"Attempting to connect to {mqtt_client.broker}") mqtt_client.connect() -print("Subscribing to %s" % mqtt_topic) +print(f"Subscribing to {mqtt_topic}") mqtt_client.subscribe(mqtt_topic) -print("Publishing to %s" % mqtt_topic) +print(f"Publishing to {mqtt_topic}") mqtt_client.publish(mqtt_topic, "Hello Broker!") -print("Unsubscribing from %s" % mqtt_topic) +print(f"Unsubscribing from {mqtt_topic}") mqtt_client.unsubscribe(mqtt_topic) -print("Disconnecting from %s" % mqtt_client.broker) +print(f"Disconnecting from {mqtt_client.broker}") mqtt_client.disconnect() diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index facee071..873b2d7d 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -1,28 +1,24 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os -import ssl import time +from os import getenv -import socketpool +import adafruit_connection_manager import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") -# Set your Adafruit IO Username, Key and Port in settings.toml -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") - -print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") -wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) -print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") +print(f"Connecting to {ssid}") +wifi.radio.connect(ssid, password) +print(f"Connected to {ssid}!") ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed @@ -54,16 +50,16 @@ def message(client, topic, message): print(f"New message on topic {topic}: {message}") -# Create a socket pool -pool = socketpool.SocketPool(wifi.radio) -ssl_context = ssl.create_default_context() +# Create a socket pool and ssl_context +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the # ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") +# certfile=getenv("device_cert_path"), keyfile=getenv("device_key_path") # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index b59dff80..fe02c38f 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -1,11 +1,10 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os -import ssl import time +from os import getenv -import socketpool +import adafruit_connection_manager import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT @@ -14,15 +13,16 @@ # with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. -# Set your Adafruit IO Username, Key and Port in settings.toml -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") -print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) -wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) -print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) +print(f"Connecting to {ssid}") +wifi.radio.connect(ssid, password) +print(f"Connected to {ssid}!") ### Adafruit IO Setup ### @@ -54,16 +54,16 @@ def message(client, topic, message): print(f"New message on topic {topic}: {message}") -# Create a socket pool -pool = socketpool.SocketPool(wifi.radio) -ssl_context = ssl.create_default_context() +# Create a socket pool and ssl_context +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the # ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") +# certfile=getenv("device_cert_path"), keyfile=getenv("device_key_path") # ) # Set up a MiniMQTT Client diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 07f0f9d2..7dceba7a 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -1,28 +1,24 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import os -import ssl import time +from os import getenv -import socketpool +import adafruit_connection_manager import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT -# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys -# with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. +# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") -# Set your Adafruit IO Username, Key and Port in settings.toml -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) -aio_username = os.getenv("aio_username") -aio_key = os.getenv("aio_key") - -print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) -wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) -print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) +print(f"Connecting to {ssid}") +wifi.radio.connect(ssid, password) +print(f"Connected to {ssid}!") ### Code ### @@ -61,16 +57,16 @@ def on_message(client, topic, message): print(f"New message on topic {topic}: {message}") -# Create a socket pool -pool = socketpool.SocketPool(wifi.radio) -ssl_context = ssl.create_default_context() +# Create a socket pool and ssl_context +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) # If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the # ssl context by uncommenting the lines below and adding the following keys to your settings.toml: # "device_cert_path" - Path to the Device Certificate # "device_key_path" - Path to the RSA Private Key # ssl_context.load_cert_chain( -# certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path") +# certfile=getenv("device_cert_path"), keyfile=getenv("device_key_path") # ) # Set up a MiniMQTT Client From ce1a94d4e90819c017ddc58adef5d1fc2228f6dc Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 1 Mar 2025 08:01:55 -0800 Subject: [PATCH 278/289] PR comments --- examples/cellular/minimqtt_adafruitio_cellular.py | 4 ++-- examples/cellular/minimqtt_simpletest_cellular.py | 3 ++- examples/cpython/minimqtt_adafruitio_cpython.py | 4 ++-- examples/cpython/minimqtt_simpletest_cpython.py | 7 +++++-- examples/esp32spi/minimqtt_adafruitio_esp32spi.py | 4 ++-- .../minimqtt_pub_sub_blocking_esp32spi.py | 2 +- ...t_pub_sub_blocking_topic_callbacks_esp32spi.py | 15 ++++++++++----- .../minimqtt_pub_sub_nonblocking_esp32spi.py | 2 +- .../minimqtt_pub_sub_pyportal_esp32spi.py | 3 ++- examples/esp32spi/minimqtt_simpletest_esp32spi.py | 7 ++++--- examples/ethernet/minimqtt_adafruitio_eth.py | 4 ++-- examples/ethernet/minimqtt_simpletest_eth.py | 3 ++- examples/minimqtt_simpletest.py | 4 ++-- .../minimqtt_adafruitio_native_networking.py | 4 ++-- ...minimqtt_pub_sub_blocking_native_networking.py | 3 +-- ..._blocking_topic_callbacks_native_networking.py | 7 +++---- 16 files changed, 43 insertions(+), 33 deletions(-) diff --git a/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index 6c63de9f..f2f3061b 100755 --- a/examples/cellular/minimqtt_adafruitio_cellular.py +++ b/examples/cellular/minimqtt_adafruitio_cellular.py @@ -33,10 +33,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = aio_username + "/feeds/photocell" +photocell_feed = f"{aio_username}/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = aio_username + "/feeds/onoff" +onoff_feed = f"{aio_username}/feeds/onoff" ### Code ### diff --git a/examples/cellular/minimqtt_simpletest_cellular.py b/examples/cellular/minimqtt_simpletest_cellular.py index 35fc90cb..fb83e292 100644 --- a/examples/cellular/minimqtt_simpletest_cellular.py +++ b/examples/cellular/minimqtt_simpletest_cellular.py @@ -21,6 +21,7 @@ apn_password = getenv("apn_password") aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) @@ -88,7 +89,7 @@ def publish(client, userdata, topic, pid): # Set up a MiniMQTT Client client = MQTT.MQTT( - broker="io.adafruit.com", + broker=broker, username=aio_username, password=aio_key, is_ssl=False, diff --git a/examples/cpython/minimqtt_adafruitio_cpython.py b/examples/cpython/minimqtt_adafruitio_cpython.py index 60d94c36..5fa03556 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -21,10 +21,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = aio_username + "/feeds/photocell" +photocell_feed = f"{aio_username}/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = aio_username + "/feeds/onoff" +onoff_feed = f"{aio_username}/feeds/onoff" ### Code ### diff --git a/examples/cpython/minimqtt_simpletest_cpython.py b/examples/cpython/minimqtt_simpletest_cpython.py index ef875534..3254e2d2 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -8,12 +8,15 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT # Add your Adafruit IO username and key to your env. +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) # example: # export ADAFRUIT_AIO_USERNAME=your-aio-username # export ADAFRUIT_AIO_KEY=your-aio-key +# export broker=io.adafruit.com aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") ### Topic Setup ### @@ -23,7 +26,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = aio_username + "/feeds/temperature" +# mqtt_topic = f"{aio_username}/feeds/temperature" ### Code ### @@ -63,7 +66,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", + broker=broker, username=aio_username, password=aio_key, socket_pool=socket, diff --git a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py index 5c9e6856..7a52f5ee 100644 --- a/examples/esp32spi/minimqtt_adafruitio_esp32spi.py +++ b/examples/esp32spi/minimqtt_adafruitio_esp32spi.py @@ -47,10 +47,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = aio_username + "/feeds/photocell" +photocell_feed = f"{aio_username}/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = aio_username + "/feeds/onoff" +onoff_feed = f"{aio_username}/feeds/onoff" ### Code ### diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py index 139fc844..98e9cffa 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_esp32spi.py @@ -47,7 +47,7 @@ ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### diff --git a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py index f0b175d1..3301891d 100644 --- a/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_blocking_topic_callbacks_esp32spi.py @@ -14,10 +14,13 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT # Get WiFi details and broker keys, ensure these are setup in settings.toml +# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) ssid = getenv("CIRCUITPY_WIFI_SSID") password = getenv("CIRCUITPY_WIFI_PASSWORD") -broker = getenv("broker") -broker_port = getenv("broker_port") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") +broker_port = int(getenv("broker_port", "8883")) # Port 1883 insecure, 8883 secure ### WiFi ### @@ -75,7 +78,7 @@ def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value print(f"Battery level: {message}v") - # client.remove_topic_callback(aio_username + "/feeds/device.batterylevel") + # client.remove_topic_callback(f"{aio_username}/feeds/device.batterylevel") def on_message(client, topic, message): @@ -95,6 +98,8 @@ def on_message(client, topic, message): client = MQTT.MQTT( broker=broker, port=broker_port, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) @@ -105,14 +110,14 @@ def on_message(client, topic, message): client.on_subscribe = subscribe client.on_unsubscribe = unsubscribe client.on_message = on_message -client.add_topic_callback(aio_username + "/feeds/device.batterylevel", on_battery_msg) +client.add_topic_callback(f"{aio_username}/feeds/device.batterylevel", on_battery_msg) # Connect the client to the MQTT broker. print("Connecting to MQTT broker...") client.connect() # Subscribe to all notifications on the device group -client.subscribe(aio_username + "/groups/device", 1) +client.subscribe(f"{aio_username}/groups/device", 1) # Start a blocking message loop... # NOTE: NO code below this loop will execute diff --git a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py index fcd8cb67..e396c53e 100644 --- a/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_nonblocking_esp32spi.py @@ -47,7 +47,7 @@ ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### diff --git a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py index 33933a5d..cacb37ab 100644 --- a/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py +++ b/examples/esp32spi/minimqtt_pub_sub_pyportal_esp32spi.py @@ -15,6 +15,7 @@ # (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") # ------------- MQTT Topic Setup ------------- # mqtt_topic = "test/topic" @@ -53,7 +54,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", + broker=broker, username=aio_username, password=aio_key, is_ssl=False, diff --git a/examples/esp32spi/minimqtt_simpletest_esp32spi.py b/examples/esp32spi/minimqtt_simpletest_esp32spi.py index 8de8f446..f17f44dd 100644 --- a/examples/esp32spi/minimqtt_simpletest_esp32spi.py +++ b/examples/esp32spi/minimqtt_simpletest_esp32spi.py @@ -17,6 +17,7 @@ password = getenv("CIRCUITPY_WIFI_PASSWORD") aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -38,7 +39,7 @@ except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) +print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi) ### Topic Setup ### @@ -48,7 +49,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -# mqtt_topic = aio_username + '/feeds/temperature' +# mqtt_topic = f"{aio_username}/feeds/temperature" ### Code ### @@ -91,7 +92,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", + broker=broker, username=aio_username, password=aio_key, socket_pool=pool, diff --git a/examples/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index b18d286d..6474be24 100755 --- a/examples/ethernet/minimqtt_adafruitio_eth.py +++ b/examples/ethernet/minimqtt_adafruitio_eth.py @@ -26,10 +26,10 @@ ### Feeds ### # Setup a feed named 'photocell' for publishing to a feed -photocell_feed = aio_username + "/feeds/photocell" +photocell_feed = f"{aio_username}/feeds/photocell" # Setup a feed named 'onoff' for subscribing to changes -onoff_feed = aio_username + "/feeds/onoff" +onoff_feed = f"{aio_username}/feeds/onoff" ### Code ### diff --git a/examples/ethernet/minimqtt_simpletest_eth.py b/examples/ethernet/minimqtt_simpletest_eth.py index 39d86dd7..5130f496 100644 --- a/examples/ethernet/minimqtt_simpletest_eth.py +++ b/examples/ethernet/minimqtt_simpletest_eth.py @@ -15,6 +15,7 @@ # (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.) aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -69,7 +70,7 @@ def publish(client, userdata, topic, pid): # Set up a MiniMQTT Client # NOTE: We'll need to connect insecurely for ethernet configurations. client = MQTT.MQTT( - broker="io.adafruit.com", + broker=broker, username=aio_username, password=aio_key, is_ssl=False, diff --git a/examples/minimqtt_simpletest.py b/examples/minimqtt_simpletest.py index b5086ba0..9746eab8 100644 --- a/examples/minimqtt_simpletest.py +++ b/examples/minimqtt_simpletest.py @@ -38,7 +38,7 @@ except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) +print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi) ### Topic Setup ### @@ -48,7 +48,7 @@ # Adafruit IO-style Topic # Use this topic if you'd like to connect to io.adafruit.com -mqtt_topic = aio_username + "/feeds/temperature" +mqtt_topic = f"{aio_username}/feeds/temperature" ### Code ### diff --git a/examples/native_networking/minimqtt_adafruitio_native_networking.py b/examples/native_networking/minimqtt_adafruitio_native_networking.py index 873b2d7d..85dc0b32 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -15,6 +15,7 @@ password = getenv("CIRCUITPY_WIFI_PASSWORD") aio_username = getenv("ADAFRUIT_AIO_USERNAME") aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") print(f"Connecting to {ssid}") wifi.radio.connect(ssid, password) @@ -64,8 +65,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker="io.adafruit.com", - port=1883, + broker=broker, username=aio_username, password=aio_key, socket_pool=pool, diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py index fe02c38f..9c345154 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_native_networking.py @@ -27,7 +27,7 @@ ### Adafruit IO Setup ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### @@ -69,7 +69,6 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="io.adafruit.com", - port=1883, username=aio_username, password=aio_key, socket_pool=pool, diff --git a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py index 7dceba7a..8867925e 100644 --- a/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py +++ b/examples/native_networking/minimqtt_pub_sub_blocking_topic_callbacks_native_networking.py @@ -49,7 +49,7 @@ def on_battery_msg(client, topic, message): # Method called when device/batteryLife has a new value print(f"Battery level: {message}v") - # client.remove_topic_callback(aio_username + "/feeds/device.batterylevel") + # client.remove_topic_callback(f"{aio_username}/feeds/device.batterylevel") def on_message(client, topic, message): @@ -72,7 +72,6 @@ def on_message(client, topic, message): # Set up a MiniMQTT Client client = MQTT.MQTT( broker="io.adafruit.com", - port=1883, username=aio_username, password=aio_key, socket_pool=pool, @@ -85,14 +84,14 @@ def on_message(client, topic, message): client.on_subscribe = subscribe client.on_unsubscribe = unsubscribe client.on_message = on_message -client.add_topic_callback(aio_username + "/feeds/device.batterylevel", on_battery_msg) +client.add_topic_callback(f"{aio_username}/feeds/device.batterylevel", on_battery_msg) # Connect the client to the MQTT broker. print("Connecting to MQTT broker...") client.connect() # Subscribe to all notifications on the device group -client.subscribe(aio_username + "/groups/device", 1) +client.subscribe(f"{aio_username}/groups/device", 1) # Start a blocking message loop... # NOTE: NO code below this loop will execute From 8a6253d41989f343baf4033b3d819612a6022361 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 5 Mar 2025 21:12:08 -0800 Subject: [PATCH 279/289] Missing import --- examples/esp32spi/minimqtt_certificate_esp32spi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index eade1211..5dcd7cd7 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +from os import getenv + import adafruit_connection_manager import board import busio From 538c2eb010e4fdb31be184def7428c3761fef0ed Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:21:55 +0000 Subject: [PATCH 280/289] Update ruff-pre-commit, reorder format after fix, and enable error again https://github.com/astral-sh/ruff-pre-commit/blob/2c8dce6094fa2b4b668e74f694ca63ceffd38614/README.md?plain=1#L63-L65 --- .pre-commit-config.yaml | 2 +- examples/esp32spi/minimqtt_certificate_esp32spi.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f27b7860..c099b9e7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.9.9 hooks: - id: ruff-format - id: ruff diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index 5dcd7cd7..eade1211 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -1,8 +1,6 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -from os import getenv - import adafruit_connection_manager import board import busio From ddebed1b5d2c89e7c97385aa4833addfcbc48a51 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:24:04 +0000 Subject: [PATCH 281/289] Drop rule E999 (now included for free) --- ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index db37c83e..a245da32 100644 --- a/ruff.toml +++ b/ruff.toml @@ -16,7 +16,7 @@ extend-select = [ "PLC2401", # non-ascii-name "PLC2801", # unnecessary-dunder-call "PLC3002", # unnecessary-direct-lambda-call - "E999", # syntax-error + # "E999", # syntax-error "PLE0101", # return-in-init "F706", # return-outside-function "F704", # yield-outside-function From 054a4d08bb39db8770ff79a3770b2d73e3349e77 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:29:16 +0000 Subject: [PATCH 282/289] Reorder and add rule F821 Undefined Name --- .pre-commit-config.yaml | 2 +- ruff.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c099b9e7..029d1e95 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,9 +13,9 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.9 hooks: - - id: ruff-format - id: ruff args: ["--fix"] + - id: ruff-format - repo: https://github.com/fsfe/reuse-tool rev: v3.0.1 hooks: diff --git a/ruff.toml b/ruff.toml index a245da32..3fed4e9c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -27,6 +27,7 @@ extend-select = [ "PLE0604", # invalid-all-object "PLE0605", # invalid-all-format "PLE0643", # potential-index-error + "F821", # undefined name "PLE0704", # misplaced-bare-raise "PLE1141", # dict-iter-missing-items "PLE1142", # await-outside-async From 88754672d61c3b6178309f9d18c9455c78c40400 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:31:54 +0000 Subject: [PATCH 283/289] Enable preview features in ruff --- ruff.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ruff.toml b/ruff.toml index 3fed4e9c..0cbd6c66 100644 --- a/ruff.toml +++ b/ruff.toml @@ -5,6 +5,9 @@ target-version = "py38" line-length = 100 +# Enable preview features. +preview = true + [lint] select = ["I", "PL", "UP"] From fb2b101c740fa8d380e831b5793809e31fdc8834 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:41:21 +0000 Subject: [PATCH 284/289] Ignore class length but keep rule active + remove error --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- examples/esp32spi/minimqtt_certificate_esp32spi.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 75148ed4..55eb0f72 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -111,7 +111,7 @@ def __init__(self) -> None: setattr(NullLogger, log_level, self.nothing) -class MQTT: +class MQTT: # noqa: PLR0904 # too-many-public-methods """MQTT Client for CircuitPython. :param str broker: MQTT Broker URL or IP Address. diff --git a/examples/esp32spi/minimqtt_certificate_esp32spi.py b/examples/esp32spi/minimqtt_certificate_esp32spi.py index eade1211..5dcd7cd7 100644 --- a/examples/esp32spi/minimqtt_certificate_esp32spi.py +++ b/examples/esp32spi/minimqtt_certificate_esp32spi.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +from os import getenv + import adafruit_connection_manager import board import busio From a9ce58da3985c5bb71864e69aa770e8ebdc5f627 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 6 Mar 2025 20:46:07 +0000 Subject: [PATCH 285/289] format noqa line --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 55eb0f72..6cfcaf88 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -111,7 +111,7 @@ def __init__(self) -> None: setattr(NullLogger, log_level, self.nothing) -class MQTT: # noqa: PLR0904 # too-many-public-methods +class MQTT: # noqa: PLR0904 # too-many-public-methods """MQTT Client for CircuitPython. :param str broker: MQTT Broker URL or IP Address. From 29e12f6f6e03651a1418c1e5ae5693dd5ef45e55 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 6 Mar 2025 13:05:59 -0800 Subject: [PATCH 286/289] ruff ignores --- adafruit_minimqtt/adafruit_minimqtt.py | 2 + tests/test_loop.py | 2 + tests/test_port_ssl.py | 2 + tests/test_subscribe.py | 146 ++++++++++++------------- tests/test_unsubscribe.py | 106 +++++++++--------- 5 files changed, 125 insertions(+), 133 deletions(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 6cfcaf88..f088fdef 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -29,6 +29,8 @@ """ +# ruff: noqa: PLR6104,PLR6201,PLR6301 non-augmented-assignment,literal-membership,no-self-use + import errno import struct import time diff --git a/tests/test_loop.py b/tests/test_loop.py index 834a0d4f..7d1677ae 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Unlicense +# ruff: noqa: PLR6301 no-self-use + """loop() tests""" import errno diff --git a/tests/test_port_ssl.py b/tests/test_port_ssl.py index 196f8c73..6156a6ca 100644 --- a/tests/test_port_ssl.py +++ b/tests/test_port_ssl.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Unlicense +# ruff: noqa: PLR6301 no-self-use + """tests that verify the connect behavior w.r.t. port number and TLS""" import socket diff --git a/tests/test_subscribe.py b/tests/test_subscribe.py index f7b037b9..90e5b21f 100644 --- a/tests/test_subscribe.py +++ b/tests/test_subscribe.py @@ -29,47 +29,43 @@ def handle_subscribe(client, user_data, topic, qos): ( "foo/bar", bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), # SUBACK - bytearray( - [ - 0x82, # fixed header - 0x0C, # remaining length - 0x00, - 0x01, # message ID - 0x00, - 0x07, # topic length - 0x66, # topic - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - 0x00, # QoS - ] - ), + bytearray([ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ]), ), # same as before but with tuple ( ("foo/bar", 0), bytearray([0x90, 0x03, 0x00, 0x01, 0x00]), # SUBACK - bytearray( - [ - 0x82, # fixed header - 0x0C, # remaining length - 0x00, - 0x01, # message ID - 0x00, - 0x07, # topic length - 0x66, # topic - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - 0x00, # QoS - ] - ), + bytearray([ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ]), ), # remaining length is encoded as 2 bytes due to long topic name. ( @@ -93,47 +89,43 @@ def handle_subscribe(client, user_data, topic, qos): # SUBSCRIBE responded to by PUBLISH followed by SUBACK ( "foo/bar", - bytearray( - [ - 0x30, # PUBLISH - 0x0C, - 0x00, - 0x07, - 0x66, - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - 0x66, - 0x6F, - 0x6F, - 0x90, # SUBACK - 0x03, - 0x00, - 0x01, - 0x00, - ] - ), - bytearray( - [ - 0x82, # fixed header - 0x0C, # remaining length - 0x00, - 0x01, # message ID - 0x00, - 0x07, # topic length - 0x66, # topic - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - 0x00, # QoS - ] - ), + bytearray([ + 0x30, # PUBLISH + 0x0C, + 0x00, + 0x07, + 0x66, + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x66, + 0x6F, + 0x6F, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + ]), + bytearray([ + 0x82, # fixed header + 0x0C, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x00, # QoS + ]), ), # use list of topics for more coverage. If the range was (1, 10000), that would be # long enough to use 3 bytes for remaining length, however that would make the test diff --git a/tests/test_unsubscribe.py b/tests/test_unsubscribe.py index 1dfbb856..0f9ed2ff 100644 --- a/tests/test_unsubscribe.py +++ b/tests/test_unsubscribe.py @@ -32,23 +32,21 @@ def handle_unsubscribe(client, user_data, topic, pid): ( "foo/bar", bytearray([0xB0, 0x02, 0x00, 0x01]), - bytearray( - [ - 0xA2, # fixed header - 0x0B, # remaining length - 0x00, # message ID - 0x01, - 0x00, # topic length - 0x07, - 0x66, # topic - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - ] - ), + bytearray([ + 0xA2, # fixed header + 0x0B, # remaining length + 0x00, # message ID + 0x01, + 0x00, # topic length + 0x07, + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + ]), ), # remaining length is encoded as 2 bytes due to long topic name. ( @@ -71,45 +69,41 @@ def handle_unsubscribe(client, user_data, topic, pid): # UNSUBSCRIBE responded to by PUBLISH followed by UNSUBACK ( "foo/bar", - bytearray( - [ - 0x30, # PUBLISH - 0x0C, - 0x00, - 0x07, - 0x66, - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - 0x66, - 0x6F, - 0x6F, - 0xB0, # UNSUBACK - 0x02, - 0x00, - 0x01, - ] - ), - bytearray( - [ - 0xA2, # fixed header - 0x0B, # remaining length - 0x00, - 0x01, # message ID - 0x00, - 0x07, # topic length - 0x66, # topic - 0x6F, - 0x6F, - 0x2F, - 0x62, - 0x61, - 0x72, - ] - ), + bytearray([ + 0x30, # PUBLISH + 0x0C, + 0x00, + 0x07, + 0x66, + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + 0x66, + 0x6F, + 0x6F, + 0xB0, # UNSUBACK + 0x02, + 0x00, + 0x01, + ]), + bytearray([ + 0xA2, # fixed header + 0x0B, # remaining length + 0x00, + 0x01, # message ID + 0x00, + 0x07, # topic length + 0x66, # topic + 0x6F, + 0x6F, + 0x2F, + 0x62, + 0x61, + 0x72, + ]), ), # use list of topics for more coverage. If the range was (1, 10000), that would be # long enough to use 3 bytes for remaining length, however that would make the test From 40a4fa91268f871aaae23354748177a57873ab56 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Wed, 12 Mar 2025 19:04:03 +0000 Subject: [PATCH 287/289] ruff format reconnect tests --- tests/test_reconnect.py | 152 +++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 80 deletions(-) diff --git a/tests/test_reconnect.py b/tests/test_reconnect.py index bd9d3926..52b8c76f 100644 --- a/tests/test_reconnect.py +++ b/tests/test_reconnect.py @@ -74,84 +74,78 @@ def handle_disconnect(client, user_data, zero): testdata = [ ( [], - bytearray( - [ - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x01, - 0x00, - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x02, - 0x00, - ] - ), + bytearray([ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + ]), ), ( [("foo/bar", 0)], - bytearray( - [ - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x01, - 0x00, - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x02, - 0x00, - ] - ), + bytearray([ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + ]), ), ( [("foo/bar", 0), ("bah", 0)], - bytearray( - [ - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x01, - 0x00, - 0x00, - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x02, - 0x00, - 0x90, # SUBACK - 0x03, - 0x00, - 0x03, - 0x00, - ] - ), + bytearray([ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x01, + 0x00, + 0x00, + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x02, + 0x00, + 0x90, # SUBACK + 0x03, + 0x00, + 0x03, + 0x00, + ]), ), ] @@ -228,14 +222,12 @@ def test_reconnect_not_connected() -> None: ) mocket = Mocket( - bytearray( - [ - 0x20, # CONNACK - 0x02, - 0x00, - 0x00, - ] - ) + bytearray([ + 0x20, # CONNACK + 0x02, + 0x00, + 0x00, + ]) ) mqtt_client._connection_manager = FakeConnectionManager(mocket) From a0f49a46a1e09de14df5e86769b5376aee6b3442 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Sun, 13 Apr 2025 21:45:52 +0200 Subject: [PATCH 288/289] fix missing parameter in MMQTTException --- adafruit_minimqtt/adafruit_minimqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 953718ba..2a1d6000 100644 --- a/adafruit_minimqtt/adafruit_minimqtt.py +++ b/adafruit_minimqtt/adafruit_minimqtt.py @@ -1038,7 +1038,7 @@ def _wait_for_msg( # noqa: PLR0912, Too many branches if error.errno in (errno.ETIMEDOUT, errno.EAGAIN): # raised by a socket timeout if 0 bytes were present return None - raise MMQTTException from error + raise MMQTTException("Unexpected error while waiting for messages") from error if res in [None, b""]: # If we get here, it means that there is nothing to be received From 12e549e6554ca0eebb507b03307a3afeae83e43b Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 4 Jun 2025 10:00:20 -0500 Subject: [PATCH 289/289] update rtd.yml file Signed-off-by: foamyguy --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 88bca9fa..255dafd2 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy