Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create minimqtt_multipub_simpletest.py #191

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions examples/minimqtt_multipub_simpletest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# SPDX-FileCopyrightText: 2023 DJDevon3
# SPDX-License-Identifier: MIT
# MQTT Multi-Feed Publish Example
# Coded for Circuit Python 8.2.x

import traceback
import os
import time
import ssl
import wifi
import socketpool
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_minimqtt.adafruit_minimqtt import MMQTTException

# Initialize Web Sockets (This should always be near the top of a script!)
# There can be only one pool
pool = socketpool.SocketPool(wifi.radio)

# 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.
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
appw = os.getenv("CIRCUITPY_WIFI_PASSWORD")
aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

# MQTT Topic
# Use this format for a standard MQTT broker
feed_01 = aio_username + "/feeds/BME280-RealTemp"
feed_02 = aio_username + "/feeds/BME280-Pressure"
feed_03 = aio_username + "/feeds/BME280-Humidity"
feed_04 = aio_username + "/feeds/BME280-Altitude"

# Time in seconds between updates (polling)
# 600 = 10 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
sleep_time = 900


# Converts seconds to minutes/hours/days
# Attribution: Written by DJDevon3 & refined by Elpekenin
def time_calc(input_time):
if input_time < 60:
return f"{input_time:.0f} seconds"
if input_time < 3600:
return f"{input_time / 60:.0f} minutes"
if input_time < 86400:
return f"{input_time / 60 / 60:.0f} hours"
return f"{input_time / 60 / 60 / 24:.1f} days"


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connect(client, userdata, flags, rc):
# Method when mqtt_client connected to the broker.
print("| | ✅ Connected to MQTT Broker!")


def disconnect(client, userdata, rc):
# Method when the mqtt_client disconnects from broker.
print("| | ✂️ Disconnected from MQTT Broker")


def publish(client, userdata, topic, pid):
# Method when the mqtt_client publishes data to a feed.
print("| | | Published to {0} with PID {1}".format(topic, pid))


# Initialize a new MQTT Client object
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(),
is_ssl=True,
)

# Connect callback handlers to mqtt_client
mqtt_client.on_connect = connect
mqtt_client.on_disconnect = disconnect
mqtt_client.on_publish = publish

while True:
# These are fake values, replace with sensor variables.
BME280_temperature = 80
BME280_pressure = round(1014.89, 1)
BME280_humidity = round(49.57, 1)
BME280_altitude = round(100.543, 2)
print("===============================")

# Board Uptime
print("Board Uptime: ", time_calc(time.monotonic()))
print("| Connecting to WiFi...")

while not wifi.radio.ipv4_address:
try:
wifi.radio.connect(ssid, appw)
except ConnectionError as e:
print("Connection Error:", e)
print("Retrying in 10 seconds")
time.sleep(10)
print("| ✅ WiFi!")

while wifi.radio.ipv4_address:
try:
# Connect to MQTT Broker
mqtt_client.connect()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to connect (and later) disconnect from the broker repeatedly. This is most likely undesired - single TCP connection is better in terms of resource utilization. The right approach is to connect first and then enter a cycle with loop() inside.

Copy link
Author

@DJDevon3 DJDevon3 Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capture

I figured that was the right approach as AdafruitIO /overview seems to like that approach.

Also in this screenshot if the delay isn't provided then only the first publish is recorded. I'll assume AdafruitIO wants them somewhat spaced apart with delays. They typically all show up registered as within the same minute.

I know loop is preferred for checking messages but since I'm not checking messages and this is only as a publish example I figured loop was unnecessary?

Also in my example it only connects/disconnects every 15 minutes. There's more than can go wrong by staying connected permanently in that time frame with wifi going down and other errors that I figured connect/disconnect would be a better way to go.

Will look into adding loop. Hopefully that will get all data points to publish at the same identical time too.

Copy link
Contributor

@vladak vladak Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling loop() is necessary to avoid being disconnected from the server once the keep alive timer expires.

Copy link
Author

@DJDevon3 DJDevon3 Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing as that's not how the simpletest example works. Connect, publish, disconnect works fine even without increasing the keep alive timer manually in mqtt_client setup. loop is mainly for checking for messages which I'm not at all interested in if all I want to do is connect & publish. If that's not how it should be done then the simpletest example needs to be corrected to reflect that and the RTD for MQTT.loop() needs to be updated with that bit of information.

If I remove the explicit disconnect from the bottom of the script then the next time I publish it will automatically disconnect & reconnect anyway. For subscribing keeping that connection alive would be very important but since I'm only publishing I can close it immediately.

I believe it's best practice while only publishing to disconnect explicitly because I've run into an issue where keeping that connection open while using it with multiple other API's can end up running out of sockets. Disconnecting closes the socket to be reused by another API immediately afterwards in the script.

===============================
Board Uptime:  4.9 days
Temp:  81.8589
Actual Temp: 81.1
| Connecting to WiFi...
|WiFi!
| | Attempting to GET Weather!
| | Account within Request Limit message
| |Connected to OpenWeatherMap
| | | Sunrise: 06:55
| | | Sunset: 17:26
| | | Temp: 82.85
| | | Pressure: 1017
| | | Humidity: 74
| | | Weather Type: Clouds
| | | Wind Speed: 10.36
| | | Timestamp: 2023/12/02 13:45
| | ✂️ Disconnected from OpenWeatherMap
| |Connected to MQTT Broker!
| | | Published DJDevon3/feeds/BME280-Unbiased
| | | Published DJDevon3/feeds/BME280-RealTemp
| | | Published DJDevon3/feeds/BME280-Pressure
| | | Published DJDevon3/feeds/BME280-Humidity
| | | Published DJDevon3/feeds/BME280-Altitude
| | ✂️ Disconnected from MQTT Broker
| ✂️ Disconnected from Wifi
Next Update:  15 minutes

In my main script it functions exactly as intended. This example is just a small snippet of that. I wanted to share so others don't have to pull out their hair for a week trying to figure out how to do multiple publish using pure MQTT. This is for a multi-publish simpletest so I wanted to keep it simple.

mqtt_client.publish(feed_01, BME280_temperature)
# slight delay required between publishes!
# otherwise only the 1st publish will succeed
Comment on lines +111 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's curious. Do you have any idea why ?

Copy link
Author

@DJDevon3 DJDevon3 Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'm unsure why that happens. I only found the workaround by giving it a delay. Was quite puzzled about that myself.

I have 2 values that are being graphed together. By giving it a short delay it will publish the first two within a small enough time frame to be registered together by AdafruitIO dashboard. The other 3 data points I'm not concerned if they all publish together. This is so when I view it in the dashboard the first two points are grouped together.

Capture

time.sleep(0.001)
mqtt_client.publish(feed_02, BME280_pressure)
time.sleep(1)
mqtt_client.publish(feed_03, BME280_humidity)
time.sleep(1)
mqtt_client.publish(feed_04, BME280_altitude)
time.sleep(1)
except MMQTTException as e:
print("| | ❌ MMQTTException", e)
traceback.print_exception(e, e, e.__traceback__)
break

mqtt_client.disconnect()
print("| ✂️ Disconnected from Wifi")
print("Next Update: ", time_calc(sleep_time))

time.sleep(sleep_time)
break