adafruit / Adafruit_CircuitPython_MiniMQTT

MQTT Client Library for CircuitPython
Other
80 stars 49 forks source link

MMQTTException Error Handling #163

Open DJDevon3 opened 1 year ago

DJDevon3 commented 1 year ago

Circuit Python 8.0.5 stable release

import board
import gc
import time
import ssl
import socketpool
import wifi
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_dps310.basic import DPS310
from adafruit_io.adafruit_io import IO_MQTT

i2c = board.I2C()  # uses board.SCL and board.SDA
dps310 = DPS310(i2c)

try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

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"])

# Define callback functions which will be called when certain events happen.
# pylint: disable=unused-argument
def connected(client):
    # Connected function will be called when the client is connected to Adafruit IO.
    # This is a good place to subscribe to feed changes.  The client parameter
    # passed to this function is the Adafruit IO MQTT client so you can make
    # calls against it easily.
    print("Connected to Adafruit IO!  Listening for DemoFeed changes...")
    # Subscribe to changes on a feed named DemoFeed.
    client.subscribe("DemoFeed")

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))

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))

# pylint: disable=unused-argument
def disconnected(client):
    # Disconnected function will be called when the client disconnects.
    print("Disconnected from Adafruit IO!")

# pylint: disable=unused-argument
def message(client, feed_id, payload):
    # Message function will be called when a subscribed feed has a new value.
    # The feed_id parameter identifies the feed, and the payload parameter has
    # the new value.
    print("Feed {0} received new value: {1}".format(feed_id, payload))

# Create a socket pool
pool = socketpool.SocketPool(wifi.radio)

# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    port=1883,
    username=secrets["aio_username"],
    password=secrets["aio_key"],
    socket_pool=pool,
    ssl_context=ssl.create_default_context(),
)

# Initialize an Adafruit IO MQTT Client
io = IO_MQTT(mqtt_client)

# Connect the callback methods defined above to Adafruit IO
io.on_connect = connected
io.on_disconnect = disconnected
io.on_subscribe = subscribe
io.on_unsubscribe = unsubscribe
io.on_message = message

# Connect to Adafruit IO
print("Connecting to Adafruit IO...")
try:
    io.connect()
except (ValueError, RuntimeError) as e:
    print("Failed to connect, retrying\n", e)

# Below is an example of manually publishing a new value to Adafruit IO.
last = 0
polling_rate = 10

print("Publishing a new message every " + str(polling_rate) + " seconds...\n")
while True:
    temperature = dps310.temperature
    pressure = dps310.pressure

    #print("Temperature = %.2f *F" % (dps310.temperature * 1.8 + 32))
    #print("Pressure = %.2f hPa" % dps310.pressure)

    try:
        io.loop()
    except (MMQTTException) as e:
        print("MQTTException: \n", e)
        time.sleep(300)
        continue

    if (time.monotonic() - last) >= polling_rate:
        value = temperature * 1.8 + 32
        print("")
        print("Monotonic: ", time.monotonic())
        print("Monotonic Hours: ", time.monotonic()/60/60)
        print("Publishing {0} to DemoFeed.".format(value))
        io.publish("DemoFeed", value)
        last = time.monotonic()
Traceback (most recent call last):
  File "code.py", line 118, in <module>
  File "adafruit_io/adafruit_io.py", line 239, in loop
  File "adafruit_minimqtt/adafruit_minimqtt.py", line 1002, in loop
  File "adafruit_minimqtt/adafruit_minimqtt.py", line 683, in ping
MMQTTException: PINGRESP not returned from broker.

Line 118 is the io.loop() line

MMQTTException not behaving as expected. Does not fail gracefully and continue. The script stops with an MMQTTException error. Possible I'm doing it wrong.

Danh suggested rolling it into RuntimeError.
I think MMQTTException is just not a subclass of ValueError or RuntimeError. So you want to catch more classes of exceptions than what you have now, or we should make MMQTTException be a subclass of RuntimeError.

Expected Behavior: Fail like adafruit_requests library, ignore the error, continue the script after x amount of time, and continue polling the broker server (AdafruitIO)

How to replicate: Run script, pull wifi or connectivity, will force the broker no response error or some other type of error which will cause the entire script to crash/stop running.

With my adafruit_requests related projects this does not happen, the script will error but will reconnect and continue eventually, forever. This is the preferred expected behavior for an always online IoT device.

There is no retry or reconnect because of this one. Connectivity loss seems to be the trigger to get the error handling to fail reliably. Since I have constant wifi issues this one is pretty easy for me to replicate.

DJDevon3 commented 1 year ago

As Danh pointed out I'm missing an import for the error handler. It should be

from adafruit_minimqtt.adafruit_minimqtt import MMQTTException

import adafruit_minimqtt.adafruit_minimqtt as MQTT at the top of my script should also include the import ? Seems like a double import for some reason that shouldn't be necessary.

brentru commented 1 year ago

@DJDevon3 Agreed that it looks like a double import as adafruit_io.py already imports MMQTTException.

Does the inclusion of from adafruit_minimqtt.adafruit_minimqtt import MMQTTException in your code.py resolve this error?

DJDevon3 commented 1 year ago

@brentru this issue was part of #7907 I was beta testing on a specific board. I'm no longer testing but from what I can remember yes it helped but the error handling still stopped the script even though I was catching the correct one. I can't explain it other than that, it doesn't behave as I would normally expect with a continue. It doesn't actually continue, Without the double import it definitely does not behave as expected even more so.

extrasleepy commented 1 year ago

Hello,

I'm having a similar MMQTTException error to what's described above. I was referred to this open issue by the Adafruit Forum. I've been trying multiple "try..except" statments all of which result in a fatal error and require a power reset on the microcontroller.

I recently tried:

except (MMQTTException, MQTTException) as mqttissue: 

with no success

Here are my specifics:

Hardware setup: Adafruit ESP32-S2 Feather with a few I2C sensors

CircuitPython version: the latest version 8.0.5

Library version: circuitpython-bundle-8.x-mpy-20230328

The code: https://gist.github.com/extrasleepy/875dbc9ff177301e50a94c7d4aeec01f

The error message: Screen Shot 2023-04-30 at 2 00 50 PM Screen Shot 2023-04-26 at 3 36 28 PM

Time duration before the problem appears: It's unpredictable, but the system rarely runs for longer than 48 hours before getting the error. However, the error can happen in less than 24.