Description
ESP_SPIcontrol.connect_AP is not reporting connection failures correctly for the actual failure causes. Testing is pointing to a possible failure or limitation in the NINA fw. The problem could also be the way, and which, commands are being sent to the fw.
Observed:
When specifying an SSID that exists, but using the wrong password, connect_AP reports 'No such ssid'.
When specifying an SSID that does not exist, connect_AP reports 'Failed to connect to ssid'
Expected:
The reverse.
To demonstrate, I took a copy of the simple_test.py example, cut it down, modified it, and added to it. As part of that, I created a subclass of ESP_SPIcontrol to add a testing version of connect_AP. That version monitors and reports the status changes more closely, but raises the same exceptions. The result is below, along with the output I get. In that, I noticed that the status never goes back to idle.
# SPDX-FileCopyrightText: 2024 µMerlin
# SPDX-License-Identifier: MIT
"""testing connection failure statuses"""
import sys
import os
import time
# pylint:disable=import-error
import board
import busio
from digitalio import DigitalInOut
# pylint:enable=import-error
from adafruit_esp32spi import adafruit_esp32spi as wl_const
from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol
class ExtendedSPIControl(ESP_SPIcontrol):
"""Override individual method for testing"""
def testing_connect(self, ssid:str, password:str, timeout_s:int=10) -> int:
"""emulate connect_AP() to add debug tracing"""
print(f'{type(self).__name__ =} in testing version of connect_ap()')
stat_changes = []
time_pt0 = time.monotonic()
stat_changes.append((self.status, time_pt0))
if isinstance(ssid, str):
ssid = bytes(ssid, "utf-8")
if password:
if isinstance(password, str):
password = bytes(password, "utf-8")
self.wifi_set_passphrase(ssid, password)
time_pt1 = time.monotonic()
else:
self.wifi_set_network(ssid)
time_pt1 = time.monotonic()
stat_changes.append((self.status, time_pt1))
while (time.monotonic() - time_pt1) < timeout_s: # wait until connected or timeout
stat = self.status
if stat != stat_changes[-1][0]:
stat_changes.append((stat, time.monotonic()))
if stat == wl_const.WL_CONNECTED:
break
time.sleep(0.05)
stat_changes.append((stat, time.monotonic()))
print(f'set_passphrase elapsed: {time_pt1 - time_pt0}')
prev_tm = time_pt0
for idx, stamp in enumerate(stat_changes):
print(f'{idx:3}: {stamp[0]} at {stamp[1] - prev_tm}')
prev_tm = stamp[1]
if stat in (wl_const.WL_CONNECT_FAILED, wl_const.WL_CONNECTION_LOST,
wl_const.WL_DISCONNECTED):
raise ConnectionError("Failed to connect to ssid", ssid)
if stat == wl_const.WL_NO_SSID_AVAIL:
raise ConnectionError("No such ssid", ssid)
if stat != wl_const.WL_CONNECTED:
raise OSError(f"Unknown error {stat:02X}")
return stat
secrets = {
"ssid": os.getenv("CIRCUITPY_WIFI_SSID"),
"password": os.getenv("CIRCUITPY_WIFI_PASSWORD"),
}
if secrets == {"ssid": None, "password": None}:
raise OSError('no credentials found in settings.toml')
print("ESP32 SPI AP connection test")
print(f" Board ID: {getattr(board, 'board_id', 'Unknown')}")
print(f' Implementation: {sys.implementation}')
print(f' Platform: {sys.platform}')
# If you are using a board with pre-defined ESP32 Pins:
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 = ExtendedSPIControl(spi, esp32_cs, esp32_ready, esp32_reset)
if esp.status == wl_const.WL_IDLE_STATUS:
print("ESP32 found and in idle mode")
print(f" Firmware vers. {esp.firmware_version.decode('utf-8'):11}")
MAC = ':'.join(f'{byte:02X}' for byte in esp.MAC_address)
print(f" MAC addr: {MAC}")
print("Connecting to AP...")
esp.connect_AP(secrets["ssid"], secrets["password"])
print(f'{esp.status =}')
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
esp.disconnect()
time.sleep(1.0)
try:
esp.connect_AP(secrets["ssid"], "BAD PASSWORD")
except ConnectionError as exc:
print(f'Error when SSID exists, but using wrong password: {exc}')
print(f'{esp.status =}')
time.sleep(1.0)
try:
esp.connect_AP("NO SUCH SSID HERE", secrets["password"])
except ConnectionError as exc:
print(f'Error when SSID does not exist: {exc}')
print(f'{esp.status =}')
time.sleep(1.0)
esp.connect_AP(secrets["ssid"], secrets["password"])
print(f'{esp.status =}')
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
esp.disconnect()
time.sleep(1.0)
print("\nrepeat with test version of connect_AP\n")
print("Connecting to AP...")
esp.testing_connect(secrets["ssid"], secrets["password"])
print(f'{esp.status =}')
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
esp.disconnect()
time.sleep(1.0)
try:
esp.testing_connect(secrets["ssid"], "BAD PASSWORD")
except ConnectionError as exc:
print(f'Error when SSID exists, but using wrong password: {exc}')
print(f'{esp.status =}')
time.sleep(1.0)
try:
esp.testing_connect("NO SUCH SSID HERE", secrets["password"])
except ConnectionError as exc:
print(f'Error when SSID does not exist: {exc}')
print(f'{esp.status =}')
time.sleep(1.0)
esp.testing_connect(secrets["ssid"], secrets["password"])
print(f'{esp.status =}')
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print("Done!")
main.py output:
ESP32 SPI AP connection test
Board ID: pyportal
Implementation: (name='circuitpython', version=(8, 2, 10), mpy=517)
Platform: MicroChip SAMD51
ESP32 found and in idle mode
Firmware vers. 1.7.7
MAC addr: 14:48:57:12:CF:A4
Connecting to AP...
esp.status =3
Connected to Dungeon RSSI: -44
My IP address is 192.168.2.25
Error when SSID exists, but using wrong password: ('No such ssid', b'Dungeon')
esp.status =1
Error when SSID does not exist: ('Failed to connect to ssid', b'NO SUCH SSID HERE')
esp.status =4
esp.status =3
Connected to Dungeon RSSI: -45
My IP address is 192.168.2.25
repeat with test version of connect_AP
Connecting to AP...
type(self).__name__ =ExtendedSPIControl in testing version of connect_ap()
set_passphrase elapsed: 0.193848
0: 6 at 0.0
1: 1 at 0.193848
2: 3 at 1.98193
3: 3 at 0.0
esp.status =3
Connected to Dungeon RSSI: -45
My IP address is 192.168.2.25
type(self).__name__ =ExtendedSPIControl in testing version of connect_ap()
set_passphrase elapsed: 0.217285
0: 6 at 0.0
1: 1 at 0.217285
2: 1 at 10.0059
Error when SSID exists, but using wrong password: ('No such ssid', b'Dungeon')
esp.status =1
type(self).__name__ =ExtendedSPIControl in testing version of connect_ap()
set_passphrase elapsed: 0.20166
0: 1 at 0.0
1: 6 at 0.20166
2: 4 at 1.93311
3: 4 at 8.07324
Error when SSID does not exist: ('Failed to connect to ssid', b'NO SUCH SSID HERE')
esp.status =4
type(self).__name__ =ExtendedSPIControl in testing version of connect_ap()
set_passphrase elapsed: 0.222168
0: 4 at 0.0
1: 1 at 0.222168
2: 3 at 2.13477
3: 3 at 0.0
esp.status =3
Connected to Dungeon RSSI: -45
My IP address is 192.168.2.25
Done!