Saturday, 8 October 2022

Raspberry Pi Pico W: Connecting with WiFi

 

Connectivity via WiFi

The Raspberry Pi Pico W includes an on-board 2.4GHz wireless interface which has the following features:

  • WiFi 4 (802.11n), Single-band (2.4 GHz)
  • WiFi Protected Access (WPA) 3
  • Software enabled Access Point (SoftAP) which supports up to 4 clients

The antenna is a tuned cavity design which is licensed from ABRACON (formerly ProAnt). The wireless interface is connected via a Serial Peripheral Interface (SPI) to the RP2040 microcontroller.

It’s possible to use a standard Pico connected to an ESP8266 or similar to enable WiFi connectivity, but in enabling this I found there was more heartache than I cared to endure. With the release of the Pico W with WiFi built in, this should be the go-to option for connecting to a WiFi network if you’re using a Pico.

I've written this short explanation as part of the much larger book 'Raspberry Pi Pico Tips and Tricks'. You can download it for free (or donate if you wish) from here.

Using the network and socket modules

The network module includes functionality in the form of network drivers and routing configuration which is specific to the MicroPython. Drivers for the Pico W hardware is available within this module and it can be used to configure the network interface. Network services are then available for use via the socket module.

Scan for wireless networks

As an example of how the network module provides access consider the following code;

import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
print(wlan.scan()) 

When run on a Raspberry Pi Pico W it will enable the network interface, scan for wireless networks and print them out

The import network line imports the network module.

wlan = network.WLAN(network.STA_IF) creates a WLAN network interface object with a client interface type (as opposed to an access point type, which would use network.AP_IF) .

We then activate the network interface with wlan.active(True).

Lastly we print out the results of a scan of available wireless networks with print(wlan.scan()).

The type of output that we would see might look something like the following;

[(b'outside', b"\xb8'\xeb\x81\xb9m", 1, -76, 3, 5), (b'inside', b'l\xb0\xcel\xc0\\
xf4', 6, -37, 5, 9), (b'highway', b'\xdc\xa62*M[', 11, -31, 5, 6)]

Here there are three access points returned with the information apparently separated as ssid, bssid, channel, RSSI, security and hidden. Although, I’ll be honest and say that I know some of those networks and some of those ‘security’ and ‘hidden’ values don’t appear to be correct. More research could be required here.

bssid’ is the hardware address of an access point, in binary form, returned as a bytes object. We could use binascii.hexlify() to convert it to ASCII form if we got excited (we will do this in a future project collecting data from temperature sensors).

There are five values for security:

  • 0 – open
  • 1 – WEP
  • 2 – WPA-PSK
  • 3 – WPA2-PSK
  • 4 – WPA/WPA2-PSK

and two for whether or not the ssid is hidden:

  • 0 – visible
  • 1 – hidden

Serve a web page

Serving up a web page is well within the Pico W’s capabilities. To do this we will need to active the network interface, connect to a wireless network and then create an http server with a socket connection and then listen for connections and serve up an HTML page.

This is definitely a more complicated process and we are going to make it slightly more so by using two external files to our main.py file. Don’t worry, there’s good reasons for doing so.

Firstly, create a file called secrets.py on the Pico with contents as follows;

secrets = {
    'ssid': 'Replace-with-WiFi-ssid',
    'pw': 'Replace-with-WiFi-Password'
    } 

Edit the file and put the name of the network (ssid) that you’re going to connect to and it’s password in the appropriate places. We are doing this so that when we write our main code, we don’t have to expose things that we would rather not when and if we share our main code.

Next create a file with the contents below and save it as index.html. This will be the web page that we will go to when connecting to the Pico W via the network.

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
    <body>
        <h1>Pico W</h1>
        <p>This is a very simple web page.</p>
        <p>REALLY simple.</p>
    </body>
</html>

Lastly, create the following file on the Pico and call it main.py. This will allow it to automatically start when the Pico is connected to power.

import rp2
import network
import machine
import time
import socket
from secrets import secrets

# Set country to avoid possible errors
rp2.country('NZ')

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

# Load login data from different file for security!
ssid = secrets['ssid']
pw = secrets['pw']

wlan.connect(ssid, pw)

# Wait for connection with 10 second timeout
timeout = 10
while timeout > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    timeout -= 1
    print('Waiting for connection...')
    time.sleep(1)

wlan_status = wlan.status()

if wlan_status != 3:
    raise RuntimeError('Wi-Fi connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print('ip = ' + status[0])
    
# Function to load in html page    
def get_html(html_name):
    with open(html_name, 'r') as file:
        html = file.read()
    return html

# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    cl, addr = s.accept()
    print('Client connected from', addr)
    r = cl.recv(1024)
       
    response = get_html('index.html')
    cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
    cl.send(response)
    cl.close()

Now run the main.py program. We should see feedback from the shell showing us the connection process and it should include the ip address of the Pico.

Connected
ip = 10.1.1.41
Listening on ('0.0.0.0', 80)

Put the IP address that appears in the shell into a browser that’s on our network and we should see a page giving us the happy news that we have created a web page!

Thonny Hello World

At the same time we should see an indication in the Shell of the connection requests coming in each time we refresh the page.

Connected
ip = 10.1.1.41
Listening on ('0.0.0.0', 80)
Client connected from ('10.1.1.99', 57142)
Client connected from ('10.1.1.99', 57144)
Client connected from ('10.1.1.99', 57145)

There we go. We have just turned our Pico W into a web server! Not only that, but because we have named our program that does it main.py it will operate as soon as we plug in power.

Setting up a static IP address

Enabling network access to the Pico is a really useful thing. This has allowed us to access our device from a separate computer. But when we did it we relied on knowing what the IP address of the Pico was in order to enter that into our browser. The allocation of the address is dynamic and will be dependant on the configuration of our wireless network that is set up by our router. However, we can set up that address so that we know what it is going to be before hand. This is what is called a static IP address.

An Internet Protocol address (IP address) is a numerical label assigned to each device (e.g., computer, printer) participating in a computer network that uses the Internet Protocol for communication.

This description of setting up a static IP address makes the assumption that we have a device running on our network that is assigning IP addresses as required. This sounds complicated, but in fact it is a very common service to be running on even a small home network and most likely on an ADSL modem/router or similar. This function is run as a service called DHCP (Dynamic Host Configuration Protocol). You will need to have access to this device for the purposes of knowing what the allowable ranges are for a static IP address.

The Netmask

A common feature for home modems and routers that run DHCP devices is to allow the user to set up the range of allowable network addresses that can exist on the network. At a higher level we should be able to set a ‘netmask’ which will do the job for us. A netmask looks similar to an IP address, but it allows you to specify the range of addresses for ‘hosts’ (in our case computers) that can be connected to the network.

A very common netmask is 255.255.255.0 which means that the network in question can have any one of the combinations where the final number in the IP address varies. In other words with a netmask of 255.255.255.0, the IP addresses available for devices on the network ‘10.1.1.x’ range from 10.1.1.0 to 10.1.1.255 or in other words any one of 256 unique addresses.

Distinguish Dynamic from Static

The other service that our DHCP server will allow is the setting of a range of addresses that can be assigned dynamically. In other words we will be able to declare that the range from 10.1.1.20 to 10.1.1.255 can be dynamically assigned which leaves 10.1.1.0 to 10.1.1.19 which can be set as static addresses.

Because there are a huge range of different DHCP servers being run on different home networks, I will have to leave you with those descriptions and the advice to consult your devices manual to help you find an IP address that can be assigned as a static address. Make sure that the assigned number has not already been taken by another device. In a perfect world we would hold a list of any devices which have static addresses so that our Pico’s address does not clash with any other device.

For the sake of this exercise we will assume that the address 10.1.1.110 is available.

Default Gateway

We will also need to find out what the default gateway is for our network. A default gateway is an IP address that a device (typically a router) will use when it is asked to go to an address that it doesn’t immediately recognise. This would most commonly occur when a computer on a home network wants to contact a computer on the Internet. The default gateway is therefore typically the address of the modem / router on your home network.

We can check to find out what our default gateway is from Windows by going to the command prompt (Start > Accessories > Command Prompt) and typing;

This should present a range of information including a section that looks a little like the following;

Ethernet adapter Local Area Connection:

  IPv4 Address. . . . . . . . . . . : 10.1.1.15
  Subnet Mask . . . . . . . . . . . : 255.255.255.0
  Default Gateway . . . . . . . . . : 10.1.1.1

The default router gateway is therefore ‘10.1.1.1’.

With all that information we are now ready to configure our static IP address.

Configure the static IP

Our network module and our WLAN class include an ifconfig method that uses all our gathered information’. We will need to declare it in the format wlan.ifconfig([(ip, subnet, gateway, dns)]).

When called with no arguments, this method returns a 4-tuple with the above information. To set the above values, pass a 4-tuple with the required information. For example:

wlan.ifconfig(('10.1.1.110', '255.255.255.0', '10.1.1.1', '8.8.8.8'))

Where 8.8.8.8 is a suitable DNS server.

To include it in the example of server our web server, we would slot it into the section where we are configuring the wlan settings;

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
wlan.ifconfig(('10.1.1.110','255.255.255.0','10.1.1.1','8.8.8.8'))

Give it a try!

Don't forget, if you're looking for the book 'Raspberry Pi Pico Tips and Tricks'. You can download it for free (or donate if you wish) from here.

No comments:

Post a Comment