diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f27b7860..029d1e95 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,11 +11,11 @@ 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 args: ["--fix"] + - id: ruff-format - repo: https://github.com/fsfe/reuse-tool rev: v3.0.1 hooks: 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: diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py index 954d894e..2a1d6000 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 @@ -66,7 +68,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) @@ -91,13 +95,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""" @@ -109,7 +126,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. @@ -161,7 +178,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 @@ -179,7 +196,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 @@ -188,7 +205,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. @@ -202,6 +219,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 @@ -284,15 +303,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): @@ -300,12 +319,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]) @@ -388,7 +406,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 @@ -526,6 +544,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]) @@ -668,10 +687,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): @@ -679,10 +698,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]) @@ -801,7 +821,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) @@ -847,7 +867,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]) @@ -879,7 +899,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] @@ -889,10 +909,12 @@ 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: + # [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)}" + ) def _recompute_reconnect_backoff(self) -> None: """ @@ -935,11 +957,18 @@ def reconnect(self, resub_topics: bool = True) -> int: """ self.logger.debug("Attempting to reconnect with MQTT broker") - ret = self.connect() + 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(session_id=self.session_id) 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() @@ -955,7 +984,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}))" ) @@ -1009,9 +1038,9 @@ 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"", 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 @@ -1149,13 +1178,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: @@ -1166,16 +1195,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/examples/cellular/minimqtt_adafruitio_cellular.py b/examples/cellular/minimqtt_adafruitio_cellular.py index 686062c1..f2f3061b 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 ### @@ -32,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 ### @@ -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..fb83e292 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,14 @@ 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") +broker = getenv("broker", "io.adafruit.com") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) @@ -70,9 +72,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 +89,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=broker, + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -104,17 +104,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..5fa03556 100644 --- a/examples/cpython/minimqtt_adafruitio_cpython.py +++ b/examples/cpython/minimqtt_adafruitio_cpython.py @@ -1,28 +1,30 @@ # 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 ### # 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 ### @@ -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..3254e2d2 100644 --- a/examples/cpython/minimqtt_simpletest_cpython.py +++ b/examples/cpython/minimqtt_simpletest_cpython.py @@ -1,17 +1,22 @@ # 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. +# (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 = os.getenv("aio_username") -aio_key = os.getenv("aio_key") +aio_username = getenv("ADAFRUIT_AIO_USERNAME") +aio_key = getenv("ADAFRUIT_AIO_KEY") +broker = getenv("broker", "io.adafruit.com") ### Topic Setup ### @@ -21,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 ### @@ -61,7 +66,7 @@ def message(client, topic, message): # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( - broker=os.getenv("broker"), + broker=broker, username=aio_username, password=aio_key, socket_pool=socket, @@ -76,17 +81,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..7a52f5ee 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,24 +33,24 @@ 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 ### # 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 ### @@ -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..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 @@ -10,14 +12,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 +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) ### Topic Setup ### @@ -109,9 +111,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 +125,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..98e9cffa 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,21 +33,21 @@ 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 ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### @@ -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..3301891d 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,16 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT -### WiFi ### +# 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") +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 -# 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 +37,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 +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(secrets["aio_username"] + "/feeds/device.batterylevel") + # client.remove_topic_callback(f"{aio_username}/feeds/device.batterylevel") def on_message(client, topic, message): @@ -94,8 +96,10 @@ 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, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) @@ -106,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(secrets["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(secrets["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 642d824c..e396c53e 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,21 +33,21 @@ 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 ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### @@ -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..cacb37ab 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,11 @@ 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") +broker = getenv("broker", "io.adafruit.com") # ------------- MQTT Topic Setup ------------- # mqtt_topic = "test/topic" @@ -27,7 +26,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 +54,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=broker, + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -77,7 +76,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..f17f44dd 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,13 @@ 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") +broker = getenv("broker", "io.adafruit.com") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -34,11 +35,11 @@ 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 -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,10 +92,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=broker, + username=aio_username, + password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) @@ -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/ethernet/minimqtt_adafruitio_eth.py b/examples/ethernet/minimqtt_adafruitio_eth.py index 5cac4183..6474be24 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) @@ -27,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 ### @@ -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..5130f496 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,11 @@ 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") +broker = getenv("broker", "io.adafruit.com") cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -70,9 +70,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=broker, + username=aio_username, + password=aio_key, is_ssl=False, socket_pool=pool, ssl_context=ssl_context, @@ -85,17 +85,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..9746eab8 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,11 +34,11 @@ 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 -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 ### @@ -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..85dc0b32 100644 --- a/examples/native_networking/minimqtt_adafruitio_native_networking.py +++ b/examples/native_networking/minimqtt_adafruitio_native_networking.py @@ -1,28 +1,25 @@ # 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. - -# 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')}!") +# 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") +broker = getenv("broker", "io.adafruit.com") + +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,22 +51,21 @@ 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 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 b59dff80..9c345154 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,20 +13,21 @@ # 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 ### # Setup a feed named `testfeed` for publishing. -default_topic = aio_username + "/feeds/testfeed" +default_topic = f"{aio_username}/feeds/testfeed" ### Code ### @@ -54,22 +54,21 @@ 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 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 07f0f9d2..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 @@ -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 ### @@ -53,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): @@ -61,22 +57,21 @@ 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 client = MQTT.MQTT( broker="io.adafruit.com", - port=1883, username=aio_username, password=aio_key, socket_pool=pool, @@ -89,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 diff --git a/ruff.toml b/ruff.toml index db37c83e..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"] @@ -16,7 +19,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 @@ -27,6 +30,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 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, diff --git a/tests/test_loop.py b/tests/test_loop.py index 834a0d4f..898d8946 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 @@ -155,7 +157,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 +169,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 +185,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) 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_reconnect.py b/tests/test_reconnect.py new file mode 100644 index 00000000..52b8c76f --- /dev/null +++ b/tests/test_reconnect.py @@ -0,0 +1,239 @@ +# 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 + self.close_cnt = 0 + + 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: + self.close_cnt += 1 + + +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 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 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__": 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 f7bbb21a..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. ( @@ -68,6 +66,45 @@ 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 +132,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: """