circuitpython: adafruit_minimqtt produces non-descript error inside _wait_for_msg on Pico W

CircuitPython version

Adafruit CircuitPython 0.8.0-beta1; Raspberry Pi Pico W

Code/REPL

"""
Based on https://gist.github.com/sammachin/b67cc4f395265bccd9b2da5972663e6d
http://www.steves-internet-guide.com/into-mqtt-python-client/
"""
import json
from secrets import secrets

import board
import digitalio
import busio
import socketpool
import wifi
import microcontroller
from binascii import hexlify
from adafruit_minimqtt.adafruit_minimqtt import MQTT

SSID, PASSWORD = secrets["ssid"], secrets["password"]

BROKER = "test.mosquitto.org"

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"

pool = socketpool.SocketPool(wifi.radio)

wifi.radio.connect(SSID, PASSWORD)

uid = microcontroller.Processor.uid
my_id = hexlify(b"{uid}").decode()

prefix = f"sdl-demo/picow/{my_id}/"
print(f"prefix: {prefix}")

onboard_led = digitalio.DigitalInOut(board.LED)  # only works for Pico W
onboard_led.direction = digitalio.Direction.OUTPUT

def on_connect(client, userdata, flags, rc):
    print("Connected with result code " + str(rc))
    client.subscribe(prefix + "GPIO/#", qos=0)


def callback(topic, msg):
    t = topic.decode("utf-8").lstrip(prefix)
    print(t)

    if t[:5] == "GPIO/":
        p = int(t[5:])
        print(msg)
        data = json.loads(msg)
        onboard_led.value = data["value"]
        payload = {"onboard_led_value": onboard_led.value}
        print(payload)
        client.publish(prefix + "onboard_led/", payload, qos=0)


# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic + " " + str(msg.payload))


# Set up a MiniMQTT Client
client = MQTT(
    client_id=prefix,
    broker=BROKER,
    username=None,
    password=None,
    is_ssl=False,
    socket_pool=pool,
)

client.connect()

client.add_topic_callback(prefix + "GPIO/#", callback)
client.on_connect = on_connect
client.on_message = on_message
client.subscribe(prefix + "GPIO/#")

while True:
    client.check_msg()

Behavior

prefix: sdl-demo/picow/<PICO ID>/
Traceback (most recent call last):
  File "<stdin>", line 87, in <module>
  File "adafruit_minimqtt/adafruit_minimqtt.py", line 741, in subscribe
  File "adafruit_minimqtt/adafruit_minimqtt.py", line 876, in _wait_for_msg
MMQTTException:

For reference: File "<stdin>", line 87, in <module> is:

client.connect()

EDIT:

client.subscribe(prefix + "GPIO/#")

https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT/blob/f2cb3bbe830f05cb75cd397fe83803ee1e59946b/adafruit_minimqtt/adafruit_minimqtt.py#L741

https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT/blob/f2cb3bbe830f05cb75cd397fe83803ee1e59946b/adafruit_minimqtt/adafruit_minimqtt.py#L861-L876

Description

  • adafruit_minimqtt fails for Pico W

Additional information

I’ve been porting my MicroPython implementation at https://github.com/sparks-baird/self-driving-lab-demo/blob/main/src/public_mqtt_sdl_demo/main.py to CircuitPython. Part of the appeal of using CircuitPython is to use ArduCam, which only has C and CircuitPython support – no MicroPython support as far as I can tell https://github.com/ArduCAM/PICO_SPI_CAM/issues/8 https://github.com/namato/micropython-ov2640/issues/6. I’d be OK with another basic color camera, but had trouble finding one that looked like it would work as easily as ArduCam.

adafruit_minimqtt documentation

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 23 (5 by maintainers)

Most upvoted comments

It seems to me like errno should be returned here instead of ret when something goes wrong: https://github.com/adafruit/circuitpython/blob/main/ports/raspberrypi/common-hal/socketpool/Socket.c#L1057-L1082

That way we would get MP_EAGAIN and MP_ETIMEDOUT instead of -1.


A workaround for now seems to be to add -1 to the ignored errors in _wait_for_msg() in adafruit_mqtt.py as such:

try:
    res = self._sock_exact_recv(1)
except OSError as error:
    if error.errno in (errno.ETIMEDOUT, errno.EAGAIN, -1):
        # raised by a socket timeout if 0 bytes were present
        return None
    raise MMQTTException from error

@georgboe Looks like it works now, thanks! Using 8.0.0-beta.3-15-g0bc986ea7 I reverted back to the unmodified version of adafruit_minimqtt.mpy and it still works without having to modify the timeouts. I’ll let it run for a couple of days to test the stability.

I’m getting the same problem on an QT PY ESP32-S2, after upgrading from CircuitPython 7.3.3 to 8.0.0-beta.3. My program subscribes to a topic on Adafruit IO. It’s been very reliable in CircuitPython 7 but is unusable in 8. I tried hard coding a 1 second timeout in the _wait_for_msg() method, and it fixed the MMQTTException during subscribing. However, the loop() method then fails with:

  File "/lib/adafruit_minimqtt/adafruit_minimqtt.py", line 864, in loop
  File "/lib/adafruit_minimqtt/adafruit_minimqtt.py", line 894, in _wait_for_msg
]0;🐍10.0.7.41 | 894@/lib/adafruit_minimqtt/adafruit_ MMQTTException | 8.0.0-beta.3\

I tried hard coding longer timeouts and zero timeout in the loop() method but I can’t get it to work.

Well, the paradigm is a little different, and the implementation is early. There is no equivalent to MicroPython’s wlan.status. At the moment, you would try wifi.radio.connect(SSID, PASSWORD) blindly, there is no harm trying to connect if already connected, it becomes a NOP internally.

In the CircuitPython espressif port, the presence of an IP address signifies connection to the AP, but I believe the uP/CYW43 driver has issues with that https://github.com/micropython/micropython/issues/9455#issuecomment-1264414826

addendum: (I’m really not sure how pedantically CircuitPython raspberrypi API is going to try to mirror the espressif API. It should be close, but there may be limitations due to the driver.)