Closed prcutler closed 6 months ago
Update after testing this afternoon with Tyeth and @justmobilize (who asked to be tagged).
CircuitPython version: absolute latest - adafruit-circuitpython-pyportal_titano-en_US-20240518-main-PR9247-e0f745c.uf2 Nina firmware version: 1.7.7
The bug according to Justin is that it's raising a string "timed out"
instead of errno.ETIMEDOUT
(though we're not sure if this is a CircuitPython bug or Nina firmware bug)
It looks like the root cause is running out of memory, removing a lot of the display code and setting CIRCUITPY_PYSTACK_SIZE=3072
in settings.toml
got it to run.
@dhalbert any ideas why the timeout error would be a string?
Here was the code that was run.
@prcutler could you uncomment all the display code and let me know if it fails or not?
# SPDX-License-Identifier: MIT
import sys
is_microcontroller = sys.implementation.name == "circuitpython"
#from adafruit_pyportal import PyPortal
import adafruit_connection_manager
import adafruit_requests
#import displayio
import json
#import terminalio
#from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import time
import adafruit_logging as logging
def getenv(value):
if is_microcontroller:
from os import getenv as cp_getenv
return cp_getenv(value)
else:
import tomllib
with open("settings.toml", "rb") as toml_file:
settings = tomllib.load(toml_file)
return settings[value]
secrets = {
"ssid": getenv("CIRCUITPY_WIFI_SSID"),
"password": getenv("CIRCUITPY_WIFI_PASSWORD"),
"broker": getenv("broker"),
"user": getenv("ADAFRUIT_AIO_USERNAME"),
"pass": getenv("ADAFRUIT_AIO_KEY"),
}
if secrets == {"ssid": None, "password": None}:
try:
# Fallback on secrets.py until depreciation is over and option is removed
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in settings.toml, please add them there!")
raise
IMAGE_URL = "https://silversaucer.com/static/img/album-art/image_300.bmp"
# pyportal = PyPortal()
# pyportal.network.connect()
# Set up WIFI w/ConnectionManager
if is_microcontroller:
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
else:
esp = adafruit_connection_manager.CPythonNetwork()
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if is_microcontroller:
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("sleeping for 10 seconds to make sure wifi is good...")
time.sleep(10)
else:
print("Using CPython sockets...")
# Get the Album title and artist name from JSON
data_source = "https://silversaucer.com/album/data"
try:
resp = requests.get(data_source)
data = resp.json()
print(data)
except RuntimeError:
resp = requests.get(data_source)
data = resp.json()
print(data)
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_info = artist + " - " + album
# album_info = "Garbage - Garbage"
# Load image on disk and display it on first boot
#display = board.DISPLAY
#display.rotation = 90
#winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
#tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader, x=0)
# Create a Group to hold the TileGrid
#group = displayio.Group()
# Add the TileGrid to the Group
#group.append(tile_grid_1)
#album_art = displayio.OnDiskBitmap("albumart.bmp")
#tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
#group.append(tile_grid_2)
#font = terminalio.FONT
#color = 0x00E200
#text_area = label.Label(font, text=album_info, color=color)
#text_area.x = 130
#text_area.y = 42
#group.append(text_area)
# Add the Group to the Display
#display.root_group = group
# ------------- MQTT Topic Setup ------------- #
mqtt_topic = "prcutler/feeds/albumart"
#mqtt_topic = "justmobilize/feeds/albumart"
### Code ###
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
print("Subscribing to %s" % (mqtt_topic))
client.subscribe(mqtt_topic)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from MQTT Broker!")
def message(client, topic, message):
"""Method callled when a client's subscribed feed has a new
value.
:param str topic: The topic of the feed with a new value.
:param str message: The new value
"""
if message == "New album picked!":
print("New message on topic {0}: {1}".format(topic, message))
response = None
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
response = requests.get(url)
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
response.close()
resp = requests.get(data_source)
data = resp.json()
print(data)
# There's a few different places we look for data in the photo of the day
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_str = artist + " - " + album
album_info = album_str[:30]
#winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
#tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader)
# Create a Group to hold the TileGrid
#group = displayio.Group()
# Add the TileGrid to the Group
#group.append(tile_grid_1)
#album_art = displayio.OnDiskBitmap("albumart.bmp")
#tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
#group.append(tile_grid_2)
#font = terminalio.FONT
#color = 0x00E200
#text_area = label.Label(font, text=album_info, color=color)
#text_area.x = 130
#text_area.y = 42
#group.append(text_area)
# Add the Group to the Display
#display.root_group = group
time.sleep(10)
else:
print("Bad get request")
# Initialize MQTT interface with the esp interface
# pylint: disable=protected-access
# MQTT.set_socket(socket, pyportal.network._wifi.esp)
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker=getenv("broker"),
username=getenv("ADAFRUIT_AIO_USERNAME"),
password=getenv("ADAFRUIT_AIO_KEY"),
port=1883,
socket_timeout=5,
recv_timeout=10,
socket_pool=pool,
ssl_context=ssl_context,
is_ssl=False,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
mqtt_client.logger = logger
mqtt_client.connect()
while True:
# Poll the message queue
try:
mqtt_client.loop(5)
except RuntimeError or ConnectionError:
time.sleep(10)
mqtt_client.connect()
mqtt_client.loop()
# Send a new message
time.sleep(5)
Thank you for the reminder to add my code. Here it is with the display stuff uncommented out.
It appears to be working, though it does crash from time to time, but nothing I can measure, it appears to be random. I apologize in advance for any ugliness in my code. :)
Thanks again @justmobilize for all the troubleshooting.
# SPDX-License-Identifier: MIT
import sys
is_microcontroller = sys.implementation.name == "circuitpython"
#from adafruit_pyportal import PyPortal
import adafruit_connection_manager
import adafruit_requests
import displayio
import json
import terminalio
from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import time
import adafruit_logging as logging
def getenv(value):
if is_microcontroller:
from os import getenv as cp_getenv
return cp_getenv(value)
else:
import tomllib
with open("settings.toml", "rb") as toml_file:
settings = tomllib.load(toml_file)
return settings[value]
secrets = {
"ssid": getenv("CIRCUITPY_WIFI_SSID"),
"password": getenv("CIRCUITPY_WIFI_PASSWORD"),
"broker": getenv("broker"),
"user": getenv("ADAFRUIT_AIO_USERNAME"),
"pass": getenv("ADAFRUIT_AIO_KEY"),
}
if secrets == {"ssid": None, "password": None}:
try:
# Fallback on secrets.py until depreciation is over and option is removed
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in settings.toml, please add them there!")
raise
IMAGE_URL = "https://silversaucer.com/static/img/album-art/image_300.bmp"
# pyportal = PyPortal()
# pyportal.network.connect()
# Set up WIFI w/ConnectionManager
if is_microcontroller:
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
print(esp.firmware_version)
else:
esp = adafruit_connection_manager.CPythonNetwork()
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if is_microcontroller:
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("sleeping for 10 seconds to make sure wifi is good...")
time.sleep(10)
else:
print("Using CPython sockets...")
# Get the Album title and artist name from JSON
data_source = "https://silversaucer.com/album/data"
try:
resp = requests.get(data_source)
data = resp.json()
print(data)
except RuntimeError:
resp = requests.get(data_source)
data = resp.json()
print(data)
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_info = artist + " - " + album
# album_info = "Garbage - Garbage"
# Load image on disk and display it on first boot
display = board.DISPLAY
display.rotation = 90
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader, x=0)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
# ------------- MQTT Topic Setup ------------- #
mqtt_topic = "prcutler/feeds/albumart"
#mqtt_topic = "justmobilize/feeds/albumart"
### Code ###
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
print("Subscribing to %s" % (mqtt_topic))
client.subscribe(mqtt_topic)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from MQTT Broker!")
def message(client, topic, message):
"""Method callled when a client's subscribed feed has a new
value.
:param str topic: The topic of the feed with a new value.
:param str message: The new value
"""
if message == "New album picked!":
print("New message on topic {0}: {1}".format(topic, message))
response = None
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
response = requests.get(url)
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
response.close()
resp = requests.get(data_source)
data = resp.json()
print(data)
# There's a few different places we look for data in the photo of the day
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_str = artist + " - " + album
album_info = album_str[:30]
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
time.sleep(10)
else:
print("Bad get request")
# Initialize MQTT interface with the esp interface
# pylint: disable=protected-access
# MQTT.set_socket(socket, pyportal.network._wifi.esp)
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker=getenv("broker"),
username=getenv("ADAFRUIT_AIO_USERNAME"),
password=getenv("ADAFRUIT_AIO_KEY"),
port=1883,
socket_timeout=5,
recv_timeout=10,
socket_pool=pool,
ssl_context=ssl_context,
is_ssl=False,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
mqtt_client.logger = logger
mqtt_client.connect()
while True:
# Poll the message queue
try:
mqtt_client.loop(5)
except RuntimeError or ConnectionError:
time.sleep(10)
mqtt_client.connect()
mqtt_client.loop()
# Send a new message
time.sleep(5)
@prcutler what crashes are you getting?
It was a generic MQTT error like yesterday, so it's probably memory problems. Of course it's running perfectly for the last 15 minutes while I wait for something to happen. :)
As soon as it happens, I'll paste it in. Thanks!
I have updated the library with a couple of pending PR's, and released as 7.8.0, if you want to re-test with that.
@dhalbert did you see the issue with getting a string error back?
Yes, but that's separate, is that right? I thought I would try to get some fixes in.
He did need to add that valve to an error check to get it to work. I have no idea where it's coming from
Aha, it is not a string, it is an exception in adafruit_espspi_socketpool.py
named timeout
:
class timeout(TimeoutError): # pylint: disable=invalid-name
"""TimeoutError class. An instance of this error will be raised by recv_into() if
the timeout has elapsed and we haven't received any data yet."""
It is from this deprecated CPython exception:
https://docs.python.org/3/library/socket.html#socket.timeout
I think this could be removed, but we'd need to check the examples and the Learn Guide repo for any use of this.
EDIT: more details:
In adafruit_minimqtt.py, there is this code:
# CPython socket module contains a timeout attribute
if hasattr(self._socket_pool, "timeout"):
try:
res = self._sock_exact_recv(1)
except self._socket_pool.timeout:
return None
else: # socketpool, esp32spi
try:
res = self._sock_exact_recv(1, timeout=timeout)
except OSError as error:
if error.errno in (errno.ETIMEDOUT, errno.EAGAIN):
# raised by a socket timeout if 0 bytes were present
return None
raise MMQTTException from error
And in adafruit_espspi_socketpool.py
, there is currently this code:
if self._timeout > 0 and time.monotonic() - last_read_time > self._timeout:
raise timeout("timed out")
In CPython, the exception class socket.timeout
was made a subclass of OSError
in CPython 3.3, and was made an alias of TimeoutError
(which itself is a subclass of OSError
) in CPython 3.10. It was not deprecated until 3.10. Since we would want to support CPython versions earlier than 3.10, we need to keep this historical baggage for now.
@dhalbert oh man....
Two options I see:
raise timeout(errno.ETIMEDOUT)
in adafruit_esp32spi_socketpool.py
adafruit_minimqtt.py
have the if error.errno in (errno.ETIMEDOUT, errno.EAGAIN):
also check or isinstance(error, TimeoutError)
)Thoughts?
I think this one will fix a bunch of esp32spi mqtt issues.
But is if hasattr(self._socket_pool, "timeout"):
going to be True
when using the ESP32SPI socketpool? Then it should be taking that branch already? This is despite the comment about CPython.
Oh, your right, the code comment # socketpool, esp32spi
threw me. But he was totally getting that error.
@prcutler could you grab the method _wait_for_msg
from minimqtt
from your device when you have a chance?
I replaced lines 978-982 in adafruit_minimqtt.py
per Justin with:
except OSError as error:
print(f"OSError on _wait_for_msg: {error.errno}")
if error.errno in (errno.ETIMEDOUT, errno.EAGAIN, "timed out"):
# raised by a socket timeout if 0 bytes were present
return None
raise MMQTTException from error
@dhalbert I tagged you in Discord, with what we saw on the error, and the pyportal not using the right timeout block...
@prcutler is this goof to close?
Resolved with the latest ESPI32 and MiniMQTT changes.
After updating to CircuitPython 9.1.0-beta.3 yesterday with the latest miniMQTT, I'm still getting errors, with the most common being an ESP32 not responding
. I would estimate it happens about 2 out of 3 times when I send a message to the broker at AdafruitIO. It seems to happen anywhere from 5-15 minutes after powering on the PyPortal. It seems to poll ok until it receives a message.
Here's the error and my latest code:
[09:15:30.342] Topic: prcutler/feeds/albumart
[09:15:30.342] Msg: bytearray(b'New album picked!')
[09:15:30.345] New message on topic prcutler/feeds/albumart: New album picked!
[09:15:43.375] Traceback (most recent call last):
[09:15:43.375] File "main.py", line 267, in <module>
[09:15:43.376] File "adafruit_minimqtt/adafruit_minimqtt.py", line 976, in loop
[09:15:43.376] File "adafruit_minimqtt/adafruit_minimqtt.py", line 1047, in _wait_for_msg
[09:15:43.376] File "adafruit_minimqtt/adafruit_minimqtt.py", line 382, in _handle_on_message
[09:15:43.379] File "main.py", line 181, in message
[09:15:43.379] File "adafruit_requests.py", line 683, in get
[09:15:43.380] File "adafruit_requests.py", line 615, in request
[09:15:43.380] File "adafruit_connection_manager.py", line 332, in get_socket
[09:15:43.380] File "adafruit_connection_manager.py", line 244, in _get_connected_socket
[09:15:43.380] File "adafruit_connection_manager.py", line 61, in connect
[09:15:43.380] File "adafruit_esp32spi/adafruit_esp32spi_socketpool.py", line 114, in connect
[09:15:43.380] File "adafruit_esp32spi/adafruit_esp32spi.py", line 836, in socket_connect
[09:15:43.381] File "adafruit_esp32spi/adafruit_esp32spi.py", line 732, in socket_open
[09:15:43.382] File "adafruit_esp32spi/adafruit_esp32spi.py", line 341, in _send_command_get_response
[09:15:43.382] File "adafruit_esp32spi/adafruit_esp32spi.py", line 297, in _wait_response_cmd
[09:15:43.383] File "adafruit_esp32spi/adafruit_esp32spi.py", line 206, in _wait_for_ready
[09:15:43.383] TimeoutError: ESP32 not responding
[09:15:43.384]
[09:15:43.384] Code done running
# SPDX-License-Identifier: MIT
import sys
is_microcontroller = sys.implementation.name == "circuitpython"
# from adafruit_pyportal import PyPortal
import adafruit_connection_manager
import adafruit_requests
import displayio
import json
import terminalio
from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import time
import adafruit_logging as logging
import sdcardio
import storage
def getenv(value):
if is_microcontroller:
from os import getenv as cp_getenv
return cp_getenv(value)
else:
import tomllib
with open("settings.toml", "rb") as toml_file:
settings = tomllib.load(toml_file)
return settings[value]
secrets = {
"ssid": getenv("CIRCUITPY_WIFI_SSID"),
"password": getenv("CIRCUITPY_WIFI_PASSWORD"),
"broker": getenv("broker"),
"user": getenv("ADAFRUIT_AIO_USERNAME"),
"pass": getenv("ADAFRUIT_AIO_KEY"),
}
if secrets == {"ssid": None, "password": None}:
try:
# Fallback on secrets.py until depreciation is over and option is removed
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in settings.toml, please add them there!")
raise
# Set up WIFI w/ConnectionManager
if is_microcontroller:
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
# import adafruit_esp32spi
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
print(esp.firmware_version)
else:
esp = adafruit_connection_manager.CPythonNetwork()
# Set up SD Card
sdcard = sdcardio.SDCard(spi, board.SD_CS)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
if is_microcontroller:
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("sleeping for 10 seconds to make sure wifi is good...")
time.sleep(10)
else:
print("Using CPython sockets...")
# Get the Album title and artist name from JSON
IMAGE_URL = "https://silversaucer.com/static/img/album-art/image_300.bmp"
data_source = "https://silversaucer.com/album/data"
try:
resp = requests.get(data_source)
data = resp.json()
print(data)
except RuntimeError:
resp = requests.get(data_source)
data = resp.json()
print(data)
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_info = artist + " - " + album
# album_info = "Garbage - Garbage"
# Load image on disk and display it on first boot
display = board.DISPLAY
display.rotation = 90
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader, x=0)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
# ------------- MQTT Topic Setup ------------- #
mqtt_topic = "prcutler/feeds/albumart"
# Code
# Define callback methods which are called when events occur
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))
client.subscribe(mqtt_topic)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from MQTT Broker!")
def message(client, topic, message):
"""Method callled when a client's subscribed feed has a new
value.
:param str topic: The topic of the feed with a new value.
:param str message: The new value
"""
if message == "New album picked!":
print("New message on topic {0}: {1}".format(topic, message))
response = None
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
response = requests.get(url)
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
response.close()
resp = requests.get(data_source)
data = resp.json()
print(data)
# There's a few different places we look for data in the photo of the day
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_str = artist + " - " + album
album_info = album_str[:30]
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
time.sleep(10)
else:
print("Bad get request")
# Initialize MQTT interface with the esp interface
# pylint: disable=protected-access
# MQTT.set_socket(socket, pyportal.network._wifi.esp)
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker=getenv("broker"),
username=getenv("ADAFRUIT_AIO_USERNAME"),
password=getenv("ADAFRUIT_AIO_KEY"),
port=1883,
socket_timeout=5,
recv_timeout=10,
socket_pool=pool,
ssl_context=ssl_context,
is_ssl=False,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
mqtt_client.logger = logger
mqtt_client.connect()
while True:
# Poll the message queue
try:
mqtt_client.loop(5)
except RuntimeError or ConnectionError:
time.sleep(10)
mqtt_client.connect()
mqtt_client.loop()
# Send a new message
time.sleep(5)
Can you please try updating:
try:
resp = requests.get(data_source)
data = resp.json()
print(data)
except RuntimeError:
resp = requests.get(data_source)
data = resp.json()
print(data)
To:
try:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
except RuntimeError:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
This will make sure that the first socket is closed
I would do the same here:
...
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
with requests.get(url) as response:
if response.status_code == 200:
...
I think I've updated the program to use with
- upon sending a message I get:
[11:24:55.228] New message on topic prcutler/feeds/albumart: New album picked!
[11:24:55.235] Traceback (most recent call last):
[11:24:55.235] File "main.py", line 266, in <module>
[11:24:55.236] File "adafruit_minimqtt/adafruit_minimqtt.py", line 976, in loop
[11:24:55.236] File "adafruit_minimqtt/adafruit_minimqtt.py", line 1047, in _wait_for_msg
[11:24:55.237] File "adafruit_minimqtt/adafruit_minimqtt.py", line 382, in _handle_on_message
[11:24:55.237] File "main.py", line 180, in message
[11:24:55.238] File "adafruit_requests.py", line 683, in get
[11:24:55.238] File "adafruit_requests.py", line 615, in request
[11:24:55.239] File "adafruit_connection_manager.py", line 332, in get_socket
[11:24:55.239] File "adafruit_connection_manager.py", line 244, in _get_connected_socket
[11:24:55.240] File "adafruit_connection_manager.py", line 61, in connect
[11:24:55.240] File "adafruit_esp32spi/adafruit_esp32spi_socketpool.py", line 114, in connect
[11:24:55.242] File "adafruit_esp32spi/adafruit_esp32spi.py", line 836, in socket_connect
[11:24:55.245] File "adafruit_esp32spi/adafruit_esp32spi.py", line 721, in socket_open
[11:24:55.245] OSError: 23
[11:24:55.245]
[11:24:55.245] Code done running.
Latest code:
# SPDX-License-Identifier: MIT
import sys
is_microcontroller = sys.implementation.name == "circuitpython"
# from adafruit_pyportal import PyPortal
import adafruit_connection_manager
import adafruit_requests
import displayio
import json
import terminalio
from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import time
import adafruit_logging as logging
import sdcardio
import storage
def getenv(value):
if is_microcontroller:
from os import getenv as cp_getenv
return cp_getenv(value)
else:
import tomllib
with open("settings.toml", "rb") as toml_file:
settings = tomllib.load(toml_file)
return settings[value]
secrets = {
"ssid": getenv("CIRCUITPY_WIFI_SSID"),
"password": getenv("CIRCUITPY_WIFI_PASSWORD"),
"broker": getenv("broker"),
"user": getenv("ADAFRUIT_AIO_USERNAME"),
"pass": getenv("ADAFRUIT_AIO_KEY"),
}
if secrets == {"ssid": None, "password": None}:
try:
# Fallback on secrets.py until depreciation is over and option is removed
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in settings.toml, please add them there!")
raise
# Set up WIFI w/ConnectionManager
if is_microcontroller:
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
# import adafruit_esp32spi
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
print(esp.firmware_version)
else:
esp = adafruit_connection_manager.CPythonNetwork()
# Set up SD Card
sdcard = sdcardio.SDCard(spi, board.SD_CS)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
if is_microcontroller:
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("sleeping for 10 seconds to make sure wifi is good...")
time.sleep(10)
else:
print("Using CPython sockets...")
# Get the Album title and artist name from JSON
IMAGE_URL = "https://silversaucer.com/static/img/album-art/image_300.bmp"
data_source = "https://silversaucer.com/album/data"
try:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
except RuntimeError:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_info = artist + " - " + album
# album_info = "Garbage - Garbage"
# Load image on disk and display it on first boot
display = board.DISPLAY
display.rotation = 90
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader, x=0)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
# ------------- MQTT Topic Setup ------------- #
mqtt_topic = "prcutler/feeds/albumart"
# Code
# Define callback methods which are called when events occur
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))
client.subscribe(mqtt_topic)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from MQTT Broker!")
def message(client, topic, message):
"""Method callled when a client's subscribed feed has a new
value.
:param str topic: The topic of the feed with a new value.
:param str message: The new value
"""
if message == "New album picked!":
print("New message on topic {0}: {1}".format(topic, message))
response = None
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
with requests.get(url) as response:
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
response.close()
else:
print("Bad get request")
# resp = requests.get(data_source)
with requests.get(data_source) as resp:
data = resp.json()
print(data)
# There's a few different places we look for data in the photo of the day
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_str = artist + " - " + album
album_info = album_str[:30]
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
time.sleep(10)
# Initialize MQTT interface with the esp interface
# pylint: disable=protected-access
# MQTT.set_socket(socket, pyportal.network._wifi.esp)
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker=getenv("broker"),
username=getenv("ADAFRUIT_AIO_USERNAME"),
password=getenv("ADAFRUIT_AIO_KEY"),
port=8883,
socket_timeout=5,
recv_timeout=10,
socket_pool=pool,
ssl_context=ssl_context,
is_ssl=True,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
mqtt_client.logger = logger
mqtt_client.connect()
while True:
# Poll the message queue
try:
mqtt_client.loop(5)
except RuntimeError or ConnectionError:
time.sleep(10)
mqtt_client.connect()
mqtt_client.loop()
# Send a new message
time.sleep(5)
Small tweak:
with requests.get(url) as response:
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
else:
print("Bad get request")
# resp = requests.get(data_source)
with requests.get(data_source) as resp:
data = resp.json()
print(data)
...
the second with
changed (took it out of the above with
, so the other response is fully released.
Although in the error it doesn't quite get there.
@dhalbert any memory specific changes between beta 2 and 3?
@dhalbert any memory specific changes between beta 2 and 3?
No. For this, the main difference is that the frozen libs are updated.
Same error:
[11:40:05.136] New message on topic prcutler/feeds/albumart: New album picked!
[11:40:05.145] Traceback (most recent call last):
[11:40:05.145] File "main.py", line 265, in <module>
[11:40:05.145] File "adafruit_minimqtt/adafruit_minimqtt.py", line 976, in loop
[11:40:05.146] File "adafruit_minimqtt/adafruit_minimqtt.py", line 1047, in _wait_for_msg
[11:40:05.146] File "adafruit_minimqtt/adafruit_minimqtt.py", line 382, in _handle_on_message
[11:40:05.147] File "main.py", line 180, in message
[11:40:05.147] File "adafruit_requests.py", line 683, in get
[11:40:05.147] File "adafruit_requests.py", line 615, in request
[11:40:05.148] File "adafruit_connection_manager.py", line 332, in get_socket
[11:40:05.150] File "adafruit_connection_manager.py", line 244, in _get_connected_socket
[11:40:05.150] File "adafruit_connection_manager.py", line 61, in connect
[11:40:05.150] File "adafruit_esp32spi/adafruit_esp32spi_socketpool.py", line 114, in connect
[11:40:05.151] File "adafruit_esp32spi/adafruit_esp32spi.py", line 836, in socket_connect
[11:40:05.151] File "adafruit_esp32spi/adafruit_esp32spi.py", line 721, in socket_open
[11:40:05.151] OSError: 23
[11:40:05.152]
[11:40:05.152] Code done running.
Just to confirm I did it right:
# SPDX-License-Identifier: MIT
import sys
is_microcontroller = sys.implementation.name == "circuitpython"
# from adafruit_pyportal import PyPortal
import adafruit_connection_manager
import adafruit_requests
import displayio
import json
import terminalio
from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import time
import adafruit_logging as logging
import sdcardio
import storage
def getenv(value):
if is_microcontroller:
from os import getenv as cp_getenv
return cp_getenv(value)
else:
import tomllib
with open("settings.toml", "rb") as toml_file:
settings = tomllib.load(toml_file)
return settings[value]
secrets = {
"ssid": getenv("CIRCUITPY_WIFI_SSID"),
"password": getenv("CIRCUITPY_WIFI_PASSWORD"),
"broker": getenv("broker"),
"user": getenv("ADAFRUIT_AIO_USERNAME"),
"pass": getenv("ADAFRUIT_AIO_KEY"),
}
if secrets == {"ssid": None, "password": None}:
try:
# Fallback on secrets.py until depreciation is over and option is removed
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in settings.toml, please add them there!")
raise
# Set up WIFI w/ConnectionManager
if is_microcontroller:
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
# import adafruit_esp32spi
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
print(esp.firmware_version)
else:
esp = adafruit_connection_manager.CPythonNetwork()
# Set up SD Card
sdcard = sdcardio.SDCard(spi, board.SD_CS)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
if is_microcontroller:
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("sleeping for 10 seconds to make sure wifi is good...")
time.sleep(10)
else:
print("Using CPython sockets...")
# Get the Album title and artist name from JSON
IMAGE_URL = "https://silversaucer.com/static/img/album-art/image_300.bmp"
data_source = "https://silversaucer.com/album/data"
try:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
except RuntimeError:
with requests.get(data_source) as resp:
data = resp.json()
print(data)
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_info = artist + " - " + album
# album_info = "Garbage - Garbage"
# Load image on disk and display it on first boot
display = board.DISPLAY
display.rotation = 90
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader, x=0)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
# ------------- MQTT Topic Setup ------------- #
mqtt_topic = "prcutler/feeds/albumart"
# Code
# Define callback methods which are called when events occur
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))
client.subscribe(mqtt_topic)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from MQTT Broker!")
def message(client, topic, message):
"""Method callled when a client's subscribed feed has a new
value.
:param str topic: The topic of the feed with a new value.
:param str message: The new value
"""
if message == "New album picked!":
print("New message on topic {0}: {1}".format(topic, message))
response = None
url = "https://silversaucer.com/static/img/album-art/image_300.bmp"
with requests.get(url) as response:
if response.status_code == 200:
print("Starting image download...")
with open("albumart.bmp", "wb") as f:
for chunk in response.iter_content(chunk_size=32):
f.write(chunk)
print("Album art saved")
else:
print("Bad get request")
# resp = requests.get(data_source)
with requests.get(data_source) as resp:
data = resp.json()
print(data)
# There's a few different places we look for data in the photo of the day
image_location = data["image_url"]
artist = data["artist"]
album = data["album"]
album_str = artist + " - " + album
album_info = album_str[:30]
winamp = displayio.OnDiskBitmap(open("winamp256.bmp", "rb"))
# Create a TileGrid to hold the bitmap
tile_grid_1 = displayio.TileGrid(winamp, pixel_shader=winamp.pixel_shader)
# Create a Group to hold the TileGrid
group = displayio.Group()
# Add the TileGrid to the Group
group.append(tile_grid_1)
album_art = displayio.OnDiskBitmap("albumart.bmp")
tile_grid_2 = displayio.TileGrid(album_art, pixel_shader=album_art.pixel_shader, y=120)
group.append(tile_grid_2)
font = terminalio.FONT
color = 0x00E200
text_area = label.Label(font, text=album_info, color=color)
text_area.x = 130
text_area.y = 42
group.append(text_area)
# Add the Group to the Display
display.root_group = group
time.sleep(10)
# Initialize MQTT interface with the esp interface
# pylint: disable=protected-access
# MQTT.set_socket(socket, pyportal.network._wifi.esp)
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker=getenv("broker"),
username=getenv("ADAFRUIT_AIO_USERNAME"),
password=getenv("ADAFRUIT_AIO_KEY"),
port=8883,
socket_timeout=5,
recv_timeout=10,
socket_pool=pool,
ssl_context=ssl_context,
is_ssl=True,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
mqtt_client.logger = logger
mqtt_client.connect()
while True:
# Poll the message queue
try:
mqtt_client.loop(5)
except RuntimeError or ConnectionError:
time.sleep(10)
mqtt_client.connect()
mqtt_client.loop()
# Send a new message
time.sleep(5)
@prcutler since we figured out SSL was the issue, good to close again?
Also if you want to follow: https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI/issues/134 for the fact only 1 SSL socket can be open
Clsoing - the error was due to trying to use 2 or more SSL connections.
Troubleshooting this with Tyeth and Justin in Discord. Using Justin's latest on his branch at https://github.com/justmobilize/Adafruit_CircuitPython_MiniMQTT/tree/no-retry-on-unauthorized.
I have a PyPortal Titano with latest Nina Firmware and running absolute latest CircuitPython:
Adafruit CircuitPython 9.1.0-beta.2-6-ge0f745c14c on 2024-05-18; Adafruit PyPortal Titano with samd51j20
I have a program that listens for messages and then downloads an image when a message is received, however it never is able to connect, it fails on the line
mqtt_client.loop(35)
and I have MQTT set up as:and the error is: