Raspberry Pi Pico W Wi-Fi Manager Web Server using MicroPython

In this user guide, we will create Wi-Fi manager web server Raspberry Pi Pico W and MicroPython. This Wi-Fi manager let us connect Raspberry Pi Pico W to a WiFi network without having to hardcode network credentials in a script. We will be using a previous example when we learned how to build a web server (Raspberry Pi Pico W Web Server Control LED) and include the wi-fi manager in it for better functionality and ease of performing Wi-Fi provisioning.

Raspberry Pi Pico W Wi-Fi Manager Web Server using MicroPython

We have a similar guide for ESP32 and ESP8266:

Prerequisites

Before we start this tutorial, make sure you are familiar with and have the latest version Python 3 in your system, have set up MicoPython in Raspberry Pi Pico, and have a running Integrated Development Environment(IDE) in which we will be doing the programming. We will be using the same Thonny IDE as we have done previously in the getting started guide. If you have not followed our previous tutorial, you check here:

Introducing Wi-Fi Manager Web Server

With the addition of the Wi-fi manager library in MicroPython, we no longer have to separately provide the SSID and password of the local network. No hard coding would be required. We will be able to connect through other networks (Access Points) with Raspberry Pi Pico W board automatically without any hassle of manually entering the network credentials every time. Multiple SSID connections, as well as additional custom variables, can also be implemented through this.

The Process of Integrating Wi-Fi Manager

The following flow chart taken from https://github.com/tayfunulu/WiFiManager shows the process behind the execution of wi-fi managers with Raspberry Pi Pico W boards in MicroPython.

WiFi Manager micropython diagram ESP32 ESP8266
  • Initially, Raspberry Pi Pico W will be set up as an Access Point when it starts.
  • Next, to connect to your Raspberry Pi Pico W which acts as an AP, we will go to the IP address 192.164.4.1.
  • A web page containing different network will be shown from which we will choose the one which we want to configure.
  • These network credentials of the network we chose, will get saved in Raspberry Pi Pico W.
  • Then we will set up a new SSID and password for our selected network. The Raspberry Pi Pico W will restart and now will be able to connect with the network we selected. In this phase the Pic W board acts in the Station mode.
  • If the connection is not successful, Pico W board goes back to AP mode and we will set the SSID and password again.

Wi-Fi Manager Library in MicroPython

MicroPython does not contain the library for the wi-fi manager so we will need to upload it ourselves. We will learn how to do this by using two different IDE for MicroPython:

import network
import socket
import ure
import time

ap_ssid = "WifiManager"
ap_password = "tayfunulu"
ap_authmode = 3  # WPA2

NETWORK_PROFILES = 'wifi.dat'

wlan_ap = network.WLAN(network.AP_IF)
wlan_sta = network.WLAN(network.STA_IF)

server_socket = None


def get_connection():
    """return a working WLAN(STA_IF) instance or None"""

    # First check if there already is any connection:
    if wlan_sta.isconnected():
        return wlan_sta

    connected = False
    try:
        # ESP connecting to WiFi takes time, wait a bit and try again:
        time.sleep(3)
        if wlan_sta.isconnected():
            return wlan_sta

        # Read known network profiles from file
        profiles = read_profiles()

        # Search WiFis in range
        wlan_sta.active(True)
        networks = wlan_sta.scan()

        AUTHMODE = {0: "open", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"}
        for ssid, bssid, channel, rssi, authmode, hidden in sorted(networks, key=lambda x: x[3], reverse=True):
            ssid = ssid.decode('utf-8')
            encrypted = authmode > 0
            print("ssid: %s chan: %d rssi: %d authmode: %s" % (ssid, channel, rssi, AUTHMODE.get(authmode, '?')))
            if encrypted:
                if ssid in profiles:
                    password = profiles[ssid]
                    connected = do_connect(ssid, password)
                else:
                    print("skipping unknown encrypted network")
            else:  # open
                connected = do_connect(ssid, None)
            if connected:
                break

    except OSError as e:
        print("exception", str(e))

    # start web server for connection manager:
    if not connected:
        connected = start()

    return wlan_sta if connected else None


def read_profiles():
    with open(NETWORK_PROFILES) as f:
        lines = f.readlines()
    profiles = {}
    for line in lines:
        ssid, password = line.strip("\n").split(";")
        profiles[ssid] = password
    return profiles


def write_profiles(profiles):
    lines = []
    for ssid, password in profiles.items():
        lines.append("%s;%s\n" % (ssid, password))
    with open(NETWORK_PROFILES, "w") as f:
        f.write(''.join(lines))


def do_connect(ssid, password):
    wlan_sta.active(True)
    if wlan_sta.isconnected():
        return None
    print('Trying to connect to %s...' % ssid)
    wlan_sta.connect(ssid, password)
    for retry in range(100):
        connected = wlan_sta.isconnected()
        if connected:
            break
        time.sleep(0.1)
        print('.', end='')
    if connected:
        print('\nConnected. Network config: ', wlan_sta.ifconfig())
    else:
        print('\nFailed. Not Connected to: ' + ssid)
    return connected


def send_header(client, status_code=200, content_length=None ):
    client.sendall("HTTP/1.0 {} OK\r\n".format(status_code))
    client.sendall("Content-Type: text/html\r\n")
    if content_length is not None:
      client.sendall("Content-Length: {}\r\n".format(content_length))
    client.sendall("\r\n")


def send_response(client, payload, status_code=200):
    content_length = len(payload)
    send_header(client, status_code, content_length)
    if content_length > 0:
        client.sendall(payload)
    client.close()


def handle_root(client):
    wlan_sta.active(True)
    ssids = sorted(ssid.decode('utf-8') for ssid, *_ in wlan_sta.scan())
    send_header(client)
    client.sendall("""\
        <html>
            <h1 style="color: #5e9ca0; text-align: center;">
                <span style="color: #ff0000;">
                    Wi-Fi Client Setup
                </span>
            </h1>
            <form action="configure" method="post">
                <table style="margin-left: auto; margin-right: auto;">
                    <tbody>
    """)
    while len(ssids):
        ssid = ssids.pop(0)
        client.sendall("""\
                        <tr>
                            <td colspan="2">
                                <input type="radio" name="ssid" value="{0}" />{0}
                            </td>
                        </tr>
        """.format(ssid))
    client.sendall("""\
                        <tr>
                            <td>Password:</td>
                            <td><input name="password" type="password" /></td>
                        </tr>
                    </tbody>
                </table>
                <p style="text-align: center;">
                    <input type="submit" value="Submit" />
                </p>
            </form>
            <p>&nbsp;</p>
            <hr />
            <h5>
                <span style="color: #ff0000;">
                    Your ssid and password information will be saved into the
                    "%(filename)s" file in your ESP module for future usage.
                    Be careful about security!
                </span>
            </h5>
            <hr />
            <h2 style="color: #2e6c80;">
                Some useful infos:
            </h2>
            <ul>
                <li>
                    Original code from <a href="https://github.com/cpopp/MicroPythonSamples"
                        target="_blank" rel="noopener">cpopp/MicroPythonSamples</a>.
                </li>
                <li>
                    This code available at <a href="https://github.com/tayfunulu/WiFiManager"
                        target="_blank" rel="noopener">tayfunulu/WiFiManager</a>.
                </li>
            </ul>
        </html>
    """ % dict(filename=NETWORK_PROFILES))
    client.close()


def handle_configure(client, request):
    match = ure.search("ssid=([^&]*)&password=(.*)", request)

    if match is None:
        send_response(client, "Parameters not found", status_code=400)
        return False
    # version 1.9 compatibility
    try:
        ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!")
        password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!")
    except Exception:
        ssid = match.group(1).replace("%3F", "?").replace("%21", "!")
        password = match.group(2).replace("%3F", "?").replace("%21", "!")

    if len(ssid) == 0:
        send_response(client, "SSID must be provided", status_code=400)
        return False

    if do_connect(ssid, password):
        response = """\
            <html>
                <center>
                    <br><br>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            ESP successfully connected to WiFi network %(ssid)s.
                        </span>
                    </h1>
                    <br><br>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        try:
            profiles = read_profiles()
        except OSError:
            profiles = {}
        profiles[ssid] = password
        write_profiles(profiles)

        time.sleep(5)

        return True
    else:
        response = """\
            <html>
                <center>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            ESP could not connect to WiFi network %(ssid)s.
                        </span>
                    </h1>
                    <br><br>
                    <form>
                        <input type="button" value="Go back!" onclick="history.back()"></input>
                    </form>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        return False


def handle_not_found(client, url):
    send_response(client, "Path not found: {}".format(url), status_code=404)


def stop():
    global server_socket

    if server_socket:
        server_socket.close()
        server_socket = None


def start(port=80):
    global server_socket

    addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]

    stop()

    wlan_sta.active(True)

    wlan_ap.config(essid=ap_ssid, password=ap_password)
    wlan_ap.active(True)

    server_socket = socket.socket()
    server_socket.bind(addr)
    server_socket.listen(1)

    print('Connect to WiFi ssid ' + ap_ssid + ', default password: ' + ap_password)
    print('and access the Pic W via your favorite web browser at 192.168.4.1.')
    print('Listening on:', addr)

    while True:
        if wlan_sta.isconnected():
            return True

        client, addr = server_socket.accept()
        print('client connected from', addr)
        try:
            client.settimeout(5.0)

            request = b""
            try:
                while "\r\n\r\n" not in request:
                    request += client.recv(512)
            except OSError:
                pass

            print("Request is: {}".format(request))
            if "HTTP" not in request:  # skip invalid requests
                continue

            # version 1.9 compatibility
            try:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/")
            except Exception:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/")
            print("URL is {}".format(url))

            if url == "":
                handle_root(client)
            elif url == "configure":
                handle_configure(client, request)
            else:
                handle_not_found(client, url)

        finally:
            client.close()

Uploading Wi-Fi Manager library to Raspberry Pi Pico W

Create a file in Thonny IDE with the name of wifimgr.py and copy the above MicroPython script to that file:

uploading wifi manager library to pic wireless

Click the play button to save wifimgr.py file to Raspberry Pi Pico W.

You can also save library to Pic W by using save button. Click on the Save button and set the file name as wifimgr.py

micropython wifi manager library upload thonny IDE 1

When you click on the save button, you will see the following window will appear. Select, MicroPython device to upload the library to Raspberry Pi Pico W.

Note: The Device options in the menu bar to upload scripts are available in the previous versions of Thonny IDE.

micropython wifi manager library upload thonny IDE 2

After following these steps, we have successfully installed the Wi-Fi manager library in Thonny IDE.

Raspberry Pi Pico W Wi-Fi Manager MicroPython Script

As we have set up the wi-fi manager library in our respective IDEs, we can now see how to configure the wi-fi manager with Raspberry Pi Pico W. We will be using a previous example where we created a webpage that controlled the output GPIO pins of Raspberry Pi Pico W. You can view that tutorial here.

The web page contains an ON and an OFF button indicating the status of an LED connected to the GPIO of the Raspberry Pi Pico W. Instead of manually hard coding the network credentials, we will provide the SSID and password of our WiFi router to our board with the wi-fi manager instead.

MicroPython Script

We will first open a new file and save it as main.py. Save it in the same directory as wifimgr.py. Now copy the code which is given below in your main.py file.

import wifimgr     # importing the Wi-Fi manager library
from time import sleep     
import machine
import gc
try:
  import usocket as socket
except:
  import socket
#machine.reset()
led = machine.Pin(2, machine.Pin.OUT)
wlan = wifimgr.get_connection()        #initializing wlan
if wlan is None:
    print("Could not initialize the network connection.")
    while True:
        pass  
print(" Raspberry Pi Pico W OK")
led_state = "OFF"
def web_page():
    html = """<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
     integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <style>
        html {
            font-family: Arial;
            display: inline-block;
            margin: 0px auto;
            text-align: center;
        }

        .button {
            background-color: #ce1b0e;
            border: none;
            color: white;
            padding: 16px 40px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
        }

        .button1 {
            background-color: #000000;
        }
    </style>
</head>

<body>
    <h2> Raspberry Pi Pico Web Server</h2>
    <p>LED state: <strong>""" + led_state + """</strong></p>
    <p>
        <i class="fas fa-lightbulb fa-3x" style="color:#c81919;"></i>
        <a href=\"?led_2_on\"><button class="button">LED ON</button></a>
    </p>
    <p>
        <i class="far fa-lightbulb fa-3x" style="color:#000000;"></i>
        <a href=\"?led_2_off\"><button class="button button1">LED OFF</button></a>
    </p>
</body>

</html>"""
    return html


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
    try:
        if gc.mem_free() < 102000:
            gc.collect()
        conn, addr = s.accept()
        conn.settimeout(3.0)
        print('Received HTTP GET connection request from %s' % str(addr))
        request = conn.recv(1024)
        conn.settimeout(None)
        request = str(request)
        print('GET Rquest Content = %s' % request)
        led_on = request.find('/?led_2_on')
        led_off = request.find('/?led_2_off')
        if led_on == 6:
            print('LED ON -> GPIO2')
            led_state = "ON"
            led.on()
        if led_off == 6:
            print('LED OFF -> GPIO2')
            led_state = "OFF"
            led.off()
        response = web_page()
        conn.send('HTTP/1.1 200 OK\n')
        conn.send('Content-Type: text/html\n')
        conn.send('Connection: close\n\n')
        conn.sendall(response)
        conn.close()
    except OSError as e:
        conn.close()
        print('Connection closed')

similarly, create a main.py file and save the above MicroPython script to that file:

Raspberry Pi Pico W wifi manager web server MicroPython script

How the Code works?

The web server part of this code is taken from our previously published project here (Raspberry Pi Pico W Web Server Control LED). You can refer to that for a code explanation of the web server part.

We will be importing the Wi-Fi manager library in the main.py file so that we will be able to use the wi-fi manager functionality.

import wifimgr

Then, we will be handling the Wi-fi manager by initializing WLAN. This will automatically set Raspberry Pi Pico W board in the station mode as it uses the instance ‘network.WLAN(STA_IF).’ The get_connection() function waits for the user to enter the ssid and password of your network.

wlan = wifimgr.get_connection()
if wlan is None:
    print("Could not initialize the network connection.")
    while True:
        pass  

We will be using try and except statements so that we are not left with an open socket. This occurs when the Raspberry Pi Pico W boots initially. Next, we will bind the socket to an IP address and a port number. This is achieved by using the bind() method. In our case, we will pass an empty string followed by ‘80’ to the bind() function. The empty string portrays the default IP address of Raspberry Pi Pico W board and ‘80’ specifies the port number set for the web servers.

try:
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  s.bind(('', 80))
  s.listen(5)

 The Raspberry Pi Pico W board would reset if there is a failure and we get an OS error. This shows that there was an open socket left. With the help of the reset() function, the open socket would be deleted.

except OSError as e:
machine.reset()

Once we set the network credentials using the Wifi manager, the Raspberry Pi Pico W sets as a station instead of an access point and connects through the wi-fi manager.

MicroPython Wi-Fi Manager Demo

To see Wi-Fi manager demo, after upload wifimgr.py and main.py files to Raspberry Pi Pico W, run main.py script by clicking on run current script button in Thonny IDE:

After that, you will see this message on shell console. Thats means Raspberry Pi Pico W board has setup as a access point and started broadcasting WifiManager ssid.

Raspberry Pi Pico W wifi manager ip address

Now connect to soft access point using either your desktop computer or mobile phone. Open Wi-Fi settings in your mobile and you will see WifiManager as wireless connection.

Connect to soft access point 2

On laptop:

Raspberry Pi Pico W wifi manager broadcasting ssid

Connect to Wi-Fi manager by using a default password tayfunulu.

Connect to soft access point 1

After successfully connecting with WiFiManager, open any web browser and type the IP address 192.168.4.1. You will see the following page of Wi-Fi manage which shows SSIDs of all near by access points. You can choose the one which belongs to your router and enter password to connect.

Raspberry Pi Pico W wifi manager web server

Select your network and enter the password in password window. After that press enter button. You will get the message that the Raspberry Pi Pico W has successfully connected to a Wi-Fi network.

Raspberry Pi Pico W wifi manager successfull connection

That means our Raspberry Pi Pico W board has successfully connected to Wi-Fi network and it has setup in station mode. Now Raspberry Pi Pico W will work in station mode.

thonny ide shell output

Now again connect your desktop computer or mobile phone with your local network that is router and paste the IP address in your web browser.

You will see this web page in your web browser which control GPIO25 of Raspberry Pi Pico W.

Raspberry Pi Pico W Control LED Web Server laptop view

Video demo:

In summary

By using the Wi-Fi manager library, we do not have to hardcode our WIFI credentials in MicroPython script. WiFi-Manager library sets the Raspberry Pi Pico W in soft access point mode and displays available network connections on a web page. It will let you select your network and connect to it using its name and password. Once your Raspberry Pi Pico W board connected to the network, it will automatically work in station mode.

If you find this tutorial useful, you can read our MicroPython web server project here:

10 thoughts on “Raspberry Pi Pico W Wi-Fi Manager Web Server using MicroPython”

  1. On a Pi Pico W I get this error message:

    >>> %Run -c $EDITOR_CONTENT
    exception [Errno 2] ENOENT
    Traceback (most recent call last):
    File “”, line 12, in
    File “wifimgr.py”, line 61, in get_connection
    File “wifimgr.py”, line 267, in start
    ValueError: unknown config param
    >>>

    What did I do wrong?

    Reply
    • From this line remove authmode=ap_authmode and try again

      wlan_ap.config(essid=ap_ssid, password=ap_password, authmode=ap_authmode)

      Reply
  2. Thanks, now I can access the 192.168.4.1 Wi-Fi Client Setup web page. My router also appears, but after entering the password I always get this message:
    “ESP could not connect to WiFi network”
    Where am I making a mistake?

    Reply
    • Thonny outputs this message:

      exception [Errno 2] ENOENT
      Connect to WiFi ssid WifiManager, default password: tayfunulu
      and access the Pic W via your favorite web browser at 192.168.4.1.
      Listening on: (‘0.0.0.0’, 80)
      client connected from (‘192.168.4.16′, 59105)
      Request is: b’GET / HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\r\n\r\n’
      URL is
      client connected from (‘192.168.4.16’, 59106)
      Request is: b’GET /favicon.ico HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\r\nAccept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\r\nReferer: http://192.168.4.1/\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\r\n\r\n’
      URL is favicon.ico
      client connected from (‘192.168.4.16’, 59113)
      Request is: b’POST /configure HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nContent-Length: 51\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nOrigin: http://192.168.4.1\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer: http://192.168.4.1/\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\r\n\r\nssid=FRITZ%21Box+7490&password=49650940463438741703′
      URL is configure
      Trying to connect to FRITZ!Box+7490…
      ……………………………………………………………………………………….
      Failed. Not Connected to: FRITZ!Box+7490
      client connected from (‘192.168.4.16’, 59114)
      Request is: b”

      Reply
  3. The code still has issues with special characters in SSIDs and passwords, which – unfortunately – makes it unusable in mose secure environments, where special characters are used for SSIDs and keys.

    There are some other issues, too, but character handling is the most annoying…

    Reply
  4. Hi,
    Your sample code uses the following to define the LED pin on the PICO W:
    led = machine.Pin(2, machine.Pin.OUT)

    That did not work for me. I used the following code which worked to turn the LED on:
    led = machine.Pin(“LED”, machine.Pin.OUT)

    Reply

Leave a Comment