Wi-Fi stability on MicroPython

Hi,
I’m trying to write an application using WiFi and Pico 2W with this firmware - GitHub - zenodante/PicoCalc-micropython-driver.
I’m having quite a few problems:
wlan.scan() only works on the second or later attempt.
wlan.connect() works very randomly, sometimes it connects on the nth try.
However, none of the operations using WiFi work:
s.connect((“8.8.8.8”, 53))
addr = socket.getaddrinfo(“google.com”, 80, 0, socket.SOCK_STREAM)
nor the attempt to use request/urequest. They mostly cause the program to hang at this line.
I am considering suggestions from the repo.

Working with WIFI on picoW/2W

The wifi chip connect to the rp2040/2350 via spi1, which shared with LCD. As we autorefresh the lcd on core1, it is necessary to stop the auto refresh function first via the function: pc_terminal.stopRefresh(), after wifi finish its work, use pc_terminal.recoverRefresh() to recover the LCD refreshing.

Using Webmite or Picoware, there were no problems with wifi, so I exclude hardware damage. I’ve been struggling with this for several days without success, but I suspect that the issue is the shared SPI and it would need to be fixed in the firmware itself.
Do you have any similar experiences? Has anyone managed to resolve this?

Welcome to the forums!

I haven’t looked at zenodante’s micropython port in a bit, but I don’t think it’s a SPI issue since you’re able to connect/scan sometimes.

Did you set the WLAN mode and set WLAN to active?

And you should wait/loop until the WiFi is connected

And for your requests, use the urequests module urequests — Network Request Module — makeblock micropython docs 1.0.0 documentation or Picoware/src/MicroPython/picoware/system/drivers/urequests_2.py at main · jblanked/Picoware · GitHub

This is a script I was using to debug this issue, and the last successful stop is the WiFi connection, but sometimes it is not even able to connect (that’s why I am calling connect so many times).

"""
wifidebug2 - Debug WiFi by establishing connection AFTER stopping display
Logs all operations to /sd/wifi_debug2.log
"""
import time
import network
import sys

LOG_FILE = "/sd/wifi_debug2.log"

# WiFi credentials
SSID = "wifiname"
PASSWORD = "wifi pass"

class Logger:
    def __init__(self, filepath):
        self.filepath = filepath
        self.f = None
    
    def open(self):
        try:
            self.f = open(self.filepath, "w")
            return True
        except Exception as e:
            print("Cannot open log file: {}".format(e))
            return False
    
    def log(self, msg):
        timestamp = time.ticks_ms()
        line = "[{:>8}] {}\n".format(timestamp, msg)
        if self.f:
            self.f.write(line)
            self.f.flush()
    
    def close(self):
        if self.f:
            self.f.close()
            self.f = None

def main(args, shell):
    """Debug WiFi - connect after stopping display"""
    
    print("WiFi Debug 2 - logging to {}".format(LOG_FILE))
    print("Will connect to {} after stopping display".format(SSID))
    print("Please wait...")
    
    logger = Logger(LOG_FILE)
    if not logger.open():
        return 1
    
    logger.log("=" * 50)
    logger.log("WiFi Debug 2 - Connect After Display Stop")
    logger.log("SSID: {}".format(SSID))
    logger.log("=" * 50)
    logger.log("")
    
    import picocalc

    try:
        # Step 1: Stop display refresh
        logger.log("Step 1: Stopping display refresh...")
        picocalc.display.stopRefresh()
        logger.log("  OK - display refresh stopped")
        
        # Step 2: Wait 2 seconds
        logger.log("")
        logger.log("Step 2: Waiting 2 seconds for SPI to stabilize...")
        time.sleep(2)
        logger.log("  Done waiting")

        # Step 3: Deactivate WLAN
        logger.log("")
        logger.log("Step 3: Deactivating WLAN...")
        wlan = network.WLAN(network.STA_IF)
        logger.log("  Current state: active={}, connected={}".format(
            wlan.active(), wlan.isconnected()))
        wlan.active(False)
        logger.log("  WLAN deactivated")
        
        # Step 4: Wait 2 seconds
        logger.log("")
        logger.log("Step 4: Waiting 2 seconds...")
        time.sleep(2)
        logger.log("  Done waiting")

        # Step 5: Activate WLAN
        logger.log("")
        logger.log("Step 5: Activating WLAN...")
        wlan.active(True)
        logger.log("  WLAN activated")
        time.sleep(5)
        logger.log("  active={}, connected={}".format(
            wlan.active(), wlan.isconnected()))
        
        # Step 6: Connect to WiFi
        logger.log("")
        logger.log("Step 6: Connecting to {}...".format(SSID))
        wlan.connect(SSID, PASSWORD)
        time.sleep(2)
        if wlan.isconnected():
            logger.log("  Connected OK!")
        else:
            time.sleep(1)
            wlan.connect(SSID, PASSWORD)
            time.sleep(2)
        if wlan.isconnected():
            logger.log("  Connected OK!")
        else:
            time.sleep(1)
            wlan.connect(SSID, PASSWORD)
            time.sleep(2)
        if wlan.isconnected():
            logger.log("  Connected OK!")
        else:
            time.sleep(1)
            wlan.connect(SSID, PASSWORD)
            time.sleep(2)
        if wlan.isconnected():
            logger.log("  Connected OK!")
        else:
            time.sleep(1)
            wlan.connect(SSID, PASSWORD)
            time.sleep(2)
        # Wait for connection
        max_wait = 1
        for i in range(max_wait):
            status = wlan.status()
            connected = wlan.isconnected()
            logger.log("  [{}/{}] status={}, connected={}".format(
                i+1, max_wait, status, connected))
            
            if connected:
                logger.log("  SUCCESS - Connected!")
                break

            if status == 2:  # WRONG_PASSWORD
                logger.log("  FAILED - Wrong password")
                break
            elif status == -1 or status == -2:
                logger.log("  FAILED - Connection failed")
                break
            
            time.sleep(1)
        
        # Step 7: Check final state
        logger.log("")

        logger.log("Step 7: Final connection state...")
        logger.log("  isconnected(): {}".format(wlan.isconnected()))
        logger.log("  status(): {}".format(wlan.status()))
        if wlan.isconnected():
            ifconfig = wlan.ifconfig()
            logger.log("  IP: {}".format(ifconfig[0]))
            logger.log("  Gateway: {}".format(ifconfig[2]))
            logger.log("  DNS: {}".format(ifconfig[3]))

        if not wlan.isconnected():
            logger.log("")
            logger.log("WiFi connection failed - skipping network tests")
            raise Exception("WiFi not connected")


        # Step 8: Test gateway connectivity
        gateway_ip = wlan.ifconfig()[2]
        logger.log("")
        logger.log("Step 8: Testing gateway connectivity ({})...".format(gateway_ip))
        try:
            import socket
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(5)
            s.connect((gateway_ip, 80))
            logger.log("  Connected to gateway:80 OK!")
            s.close()
        except Exception as e:
            logger.log("  Gateway:80 failed: {}".format(e))
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(5)
                s.connect((gateway_ip, 53))
                logger.log("  Connected to gateway:53 OK!")
                s.close()
            except Exception as e2:
                logger.log("  Gateway:53 also failed: {}".format(e2))
        
        # Step 9: Test external IP
        logger.log("")
        logger.log("Step 9: Testing external IP (8.8.8.8:53)...")
        try:
            import socket
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(5)
            s.connect(("8.8.8.8", 53))
            logger.log("  Connected to 8.8.8.8:53 OK!")
            s.close()
        except Exception as e:
            logger.log("  External IP FAILED: {}".format(e))
        
        # Step 10: Test DNS
        logger.log("")
        logger.log("Step 10: Testing DNS resolution...")
        try:
            import socket
            addr = socket.getaddrinfo("google.com", 80, 0, socket.SOCK_STREAM)
            logger.log("  google.com resolved to: {}".format(addr[0][4]))
        except Exception as e:
            logger.log("  DNS FAILED: {}".format(e))
        
        # Step 11: Test HTTP
        logger.log("")
        logger.log("Step 11: Testing HTTP (example.com)...")
        try:
            import socket
            addr = socket.getaddrinfo("example.com", 80)[0][4]
            s = socket.socket()
            s.settimeout(10)
            s.connect(addr)
            s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
            response = s.recv(100)
            s.close()
            logger.log("  HTTP OK - received {} bytes".format(len(response)))
            logger.log("  Response: {}".format(response[:50]))
        except Exception as e:
            logger.log("  HTTP FAILED: {}".format(e))
        
        logger.log("")
        logger.log("=" * 50)
        logger.log("All tests completed!")
        logger.log("=" * 50)
    
    except Exception as e:
        logger.log("")
        logger.log("FATAL ERROR: {}".format(e))
    
    finally:
        # Recover display
        logger.log("")
        logger.log("Recovering display refresh...")
        try:
            picocalc.display.recoverRefresh()
            logger.log("  OK - display recovered")
        except Exception as e:
            logger.log("  WARNING: {}".format(e))
        
        logger.log("")
        logger.log("=" * 50)
        logger.log("WiFi Debug 2 Session Ended")
        logger.log("=" * 50)
        logger.close()
    
    print("")
    print("Debug complete. Check log file:")
    print("  cat {}".format(LOG_FILE))
    return 0


Well, you’re not waiting for it to connect correctly. It may take more than the 2 seconds you’re waiting for, and the for loop you added only tries once.

You should just use a while loop until it’s connected

That’s not the issue. I have other scripts that do it the right way and they are not working either. Connect is random. Very often ends with `CYW43_LINK_JOIN (1) or CYW43_LINK_NOIP (2)

Show me your other scripts then. Waiting in a while loop is the approach I’ve seen in most MicroPython examples (and is what works with Picoware)

Sure. This is part of a bigger script I am working on:

def _get_wlan():
    """Get WLAN interface - call after _stop_refresh()"""
    time.sleep(2)
    wlan = network.WLAN(network.STA_IF)
    if not wlan.active():
        wlan.active(True)

    time.sleep(2)
    return wlan

def _connect(ssid, password, shell):
    """Connect to a WiFi network"""
    print("Connecting to {}...".format(ssid))
    time.sleep(1)
    _stop_refresh()
    try:
        time.sleep(1)
        wlan = _get_wlan()

        wlan.connect(ssid, password)

        # Wait for connection using isconnected()
        max_wait = 30
        while max_wait > 0 and not wlan.isconnected():
            max_wait -= 1
            time.sleep(1)

        if wlan.isconnected():
            config = wlan.ifconfig()
            print("Connected to {}".format(ssid))
            print("  IP:      {}".format(config[0]))
            print("  Netmask: {}".format(config[1]))
            print("  Gateway: {}".format(config[2]))
            print("  DNS:     {}".format(config[3]))

            # Save to known networks
            _save_network(ssid, password)

            # Store in shell environment
            shell.env["WIFI_SSID"] = ssid
            shell.env["WIFI_IP"] = config[0]
            shell.env["WIFI_CONNECTED"] = "1"
            _recover_refresh()
            return True
        else:
            # Connection failed - check status for reason
            status = wlan.status()
            if status == -1:
                print("wifi: connection failed")
            elif status == -2:
                print("wifi: no network found")
            elif status == -3:
                print("wifi: wrong password")
            elif status == 1:
                print("wifi: joined but no IP (DHCP timeout)")
            else:
                print("wifi: connection failed (status={})".format(status))

            _recover_refresh()
            return False

    except Exception as e:
        _recover_refresh()
        print("wifi: {}".format(e))
        return False
1 Like

Thanks for sharing, that looks good to my knowledge. I wonder, are you trying to connect to a 5GHz network? and when you run that script, which error/status are you receiving?

I tried connecting to 3 different networks, each of which is 2.4GHz. Other firmware connects to them without any problem.
I wrote earlier about errors with the connection - that is, status 1 or 2 instead of 3.
As for the errors when using the socket, if the timeout is set, I get TCP/HTTP FAILED: [Errno 110] ETIMEDOUT, and if it isn’t, the script hangs indefinitely at that line.

Has anyone tried using WiFi with MicroPython and it worked without any problems?

Im sorry I wasn’t much help. But if you can’t solve your issue, you can just use Picoware’s MicroPython.

1 Like

In Picoware, have you implemented any synchronization between the screen and Wi-Fi? Is the screen running in a separate thread, or is everything single-threaded?

In Picoware, you don’t have to stop and start display refresh, if that’s what your concern is. And the system will auto-connect to your WiFi when the device powers on,

The following could be very irrelevant or not.

This time last year I got into trouble because my wife complained that the lights in the christmas tree stopped working (Pimoroni Plasma RP2040W). It took me some time to remember that I changed the 2.4Ghz access point to channel 13 (legal in my area) and the country code was set correctly. In the end I found that 1) another channel did work and 2) the Pico could connect on channel 13 when the country code is set to “XX”. A few weeks ago I tried install another Plasma and ran into the same problem, after 30 minutes wasted time I remembered what I did originally.

It looks that MicroPython (CircuitPython) on the Pico may have problems with some country codes and the upper WiFi channels . WebMite on the PicoCalc does not show a problem but it does not ask for a country code either.

I love it when I encounter a problem that I feel I’ve had before and solved, but I can’t remember how. By now, I take notes on many things, just as I now comment on code almost excessively, but even so, I have to remember that I took notes in the first place. :joy:

3 Likes

At the moment, the only clue is that the UF2 Loader is causing some issues. I built the latest version of clean (without screen or other libs) MicroPython from the sources with a simple script that connects to WiFi, hits an endpoint, and saves the logs of the whole process on the SD card.
Conclusion:

  • uf2 loaded via UF2 Loader 2.3 - the script does not connect to WiFi :stop_sign:
  • uf2 loaded directly onto the Pico via USB bootsel - success :white_check_mark:

I’m surprised because other firmware also loads through the uf2 loader and the Wi-Fi works normally with them.

Has anyone had something similar? Is this a bug in the UF2 Loader itself, or does the new version of MicroPython use some kind of illegal operation? Has anyone noticed similar behavior?

I still think you’re better off using WebMite or Picoware.

Zenodante’s micropython fork appears to be abandoned.

Zerodante’s MicroPython has never worked on uf2loader, either.

It’s probably the one linked from Clockwork’s github though, but they rarely update their code and it’s generally a bad idea to run their suggested software unless you’re just trying out the device for the first time. It’s almost always out of date or potentially broken.

There have been lots of other MicroPython ports, but you’ll have to go digging on github and guess which one might be the most stable, since it seems like everyone has just rolled their own and then abandoned it (except for PicoWare, which is still very actively developed). This search is a good start but may not be comprehensive:

I’ve not messed much with MicroPython myself, outside of PicoWare, but at one point I think this one was the most up to date:

This one also seemed promising, but had issues that are listed:

I’m not sure any of these MicroPython ports work with uf2loader though. PicoWare is known to work with the uf2loader.

1 Like

As I wrote earlier, this is not related to any specific driver for PicoCalc, but to MicroPython itself. I’m wondering whether something like PicoWare, if it were built on the latest version of MicroPython, would still work via the UF2 loader (unless this is handled in some different way there). I don’t want to rely on any existing system as I am creating my own.