adafruit / Adafruit_CircuitPython_ESP32SPI

ESP32 as wifi with SPI interface
MIT License
103 stars 75 forks source link

esp.disconnect leaves the ESP32 in a bad state #133

Open mikejc58 opened 3 years ago

mikejc58 commented 3 years ago

I am writing an application on a PyPortal that needs to talk to two access points. After connecting to the first and communicating via socket with a host there, I close the socket and do an esp.disconnect() to disconnect from the access point. This seems to work. The esp's status goes to WL_DISCONNECTED. I then connect to the second access point with the esp's status going back to WL_CONNECTED. But, at that point the ESP seems to be in a bad state. Any function requiring communication with the AP fails, in one way or another. I wrote a simple test case that demonstrates the problem:

import board
from digitalio import DigitalInOut
import busio
import sys
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_socket

esp32_cs_pin = DigitalInOut(board.ESP_CS)
esp32_ready_pin = DigitalInOut(board.ESP_BUSY)
esp32_reset_pin = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)

esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs_pin, esp32_ready_pin, esp32_reset_pin)

ssids = (('linkme', '***********'), ('corrigan', '**********'))
fwstr = str(esp.firmware_version, 'utf-8')

major, minor, sub = sys.implementation.version
print("Circuitpython {}.{}.{},  ESP32 firmware {}".format(major, minor, sub, fwstr))

test_ip = esp.unpretty_ip('192.168.43.148')
port = 65432

for ssid, password in ssids:
    print("Testing ssid = {}".format(ssid))

    # connect to an AP
    esp.connect_AP(ssid, password, timeout_s = 30)
    print("Create socket")

    # create a socket
    adafruit_esp32spi_socket.set_interface(esp)
    my_sock = adafruit_esp32spi_socket.socket()
    print("Connect socket")

    # connect to socket server on other system        
    my_sock.connect((test_ip, port))
    print("close socket")

    # close socket
    my_sock.close()
    print("disconnect from AP")

    # disconnect from AP
    esp.disconnect()    

The output is:

code.py output:
Circuitpython 6.1.0,  ESP32 firmware 1.7.3
Testing ssid = linkme
Create socket
Connect socket
close socket
disconnect from AP
Testing ssid = corrigan
Create socket
Connect socket
Traceback (most recent call last):
  File "code.py", line 35, in <module>
  File "adafruit_esp32spi/adafruit_esp32spi_socket.py", line 75, in connect
  File "/lib/adafruit_esp32spi/adafruit_esp32spi.py", line 783, in socket_connect
  File "/lib/adafruit_esp32spi/adafruit_esp32spi.py", line 685, in socket_open
  File "/lib/adafruit_esp32spi/adafruit_esp32spi.py", line 345, in _send_command_get_response
  File "/lib/adafruit_esp32spi/adafruit_esp32spi.py", line 296, in _wait_response_cmd
  File "/lib/adafruit_esp32spi/adafruit_esp32spi.py", line 199, in _wait_for_ready
RuntimeError: ESP32 not responding

Code done running.

Everything works for the first access point, and seems to work for the second until the attempt to connect the socket. It also fails in the same way if I re-connect to the first access point instead. Other functions also fail. For example, esp.get_host_by_name(host) fails with RuntimeError: Failed to request hostname.

A more elaborate test shows that after connecting to the second access point, the ESP32's internal state is inconsistent. Asking the ESP32 for the bssid and ssid that it is connected to show the second access point, but asking the ESP32 for its 'network data', gives back the ip address, and gateway of the first access point.

If I change the timeout in _wait_for_ready, I find that the socket.connect request actual finishes after 18 seconds (waiting for the response, the normal timeout is 10). But, when the socket.connect finishes, it is with a RuntimeError: Expected 01 but got 00, which is what you get when the connection fails.

If you do an esp.reset(), everything works again.

mikejc58 commented 3 years ago

The more elaborate test and its output: elaborate.txt

anecdata commented 3 years ago

ESP32SPI .disconnect simply calls disconnect in the NINA firmware, which uses the Arduino function WiFi.disconnect(). Similarly, ESP32SPI .connect calls into NINA, which does a WiFi.begin(ssid, pass). So I think we'd have to dig into the ESP32 implementation of the Arduino functions.

I suspect WiFiNINA would behave similarly, it would be interesting if it was different.

If you can incur the <1 second penalty of esp.reset(), that's probably the best workaround.