How to Install Single MAME Games
14th January 2023Web Control Panel – Building the Web Server – Raspberry Pi Pico, ESP32, Arduino
1st April 2023WiFi Control Your Micropython Project Using a Web Interface – Raspberry Pi Pico, ESP32, Arduino
A number of microcontroller boards now come with a built in Wi-Fi interface. This opens up a vast range of opportunities for your electronics projects. With Wi-Fi you can connect to the Internet to upload and download data to various web services and you can serve requests from other devices on your network to allow them to both control and monitor your project.
In this tutorial I’ll be coding in MicroPython on a Raspberry Pi Pico W, but it will work equally well on any Wi-Fi enabled microcontroller board that supports MicroPython such as the ESP32 and a range of Arduino boards.
Connecting to the Network
The first stage in getting Wi-Fi working is to connect to a Wi-Fi network. This can either be an existing network such as your home Wi-Fi, or, as we’ll see a second, we can get the Raspberry Pi Pico to generate its own independent network.
Network handling in MicroPython is handled by the network library. So the first part of our code needs to make sure that this is imported into our source file.
import network
To connect to an existing Wi-Fi network we need the SSID and password credentials to be able to logon. We can then initiate the connection with the following lines of code.
ssid = "ssid" password = "password" # set WiFi to station interface wlan = network.WLAN(network.STA_IF) # activate the network interface wlan.active(True) # connect to wifi network wlan.connect(ssid, password)
The network.WLAN method call sets up the type of interface we want to create. In this instance we are creating a station interface which means that we want to connect to an existing Wi-Fi network. After that we activate the wireless circuitry and then try to initiate a connection to the specified Wi-Fi network.
After this the Pico will try to logon to the router so we simply need to wait for it to either get a connection or fail.
max_wait = 10 # wait for connection while max_wait > 0: """ 0 STAT_IDLE -- no connection and no activity, 1 STAT_CONNECTING -- connecting in progress, -3 STAT_WRONG_PASSWORD -- failed due to incorrect password, -2 STAT_NO_AP_FOUND -- failed because no access point replied, -1 STAT_CONNECT_FAIL -- failed due to other problems, 3 STAT_GOT_IP -- connection successful. """ if wlan.status() < 0 or wlan.status() >= 3: # connection successful break max_wait -= 1 print('waiting for connection... ' + str(max_wait)) utime.sleep(1)
We can then check the status code return by the connection attempt. A value of 3 is a successful connection. Any negative number is an error code.
At this point our Pi Pico should be successfully connected to the router. We can check the connection details by calling the ifconfig method on the network object. This will return four values.
# check connection if wlan.status() != 3: # No connection raise RuntimeError('network connection failed') else: # connection successful print('wlan connected') status = wlan.ifconfig() print('IP address = ' + status[0]) print('subnet mask = ' + status[1]) print('gateway = ' + status[2]) print('DNS server = ' + status[3])
If everything is run correctly our Raspberry Pi Pico has now joined the Wi-Fi network and has been assigned an IP address. This IP address can then be used to communicate with the Pico.
Using Access Point Mode
If there is no available Wi-Fi network, or we want to make a direct connection to the Pi Pico we can set the Wi-Fi chip into access point mode.
import network import time ssid = 'PiPicoAp' password = 'password' ap = network.WLAN(network.AP_IF) ap.config(essid=ssid, password=password) ap.active(True) # wait for wifi to go active wait_counter = 0 while ap.active() == False: print("waiting " + str(wait_counter)) time.sleep(0.5) pass print('WiFi active') status = ap.ifconfig() print('IP address = ' + status[0]) print('subnet mask = ' + status[1]) print('gateway = ' + status[2]) print('DNS server = ' + status[3])
As you can see there are a few changes to the connection code but at the end of this process we are in exactly the same position as before. The Pico is connected to a Wi-Fi network and has an assigned IP address.
Accessing Web Services
If our Wi-Fi network has access to the Internet we can use various web services to collect information for use in our projects. For instance we can use the timeapi.io web service to get the current time and date.
This is a REST API where we simply need to send an HTTP GET request in the correct format for its return back a block of JSON data with our date and time information. To achieve this in MicroPython we need to use the urequests library.
import urequests
We can then call the get method to send the HTTP GET request to the server and catch the response in a local variable. This local variable is a response object which has a number of attributes and methods. The content tribute contains the raw data sent back by the API. Quite often this will be in JSON format so we can use the json method to translate this into a Python dictionary from which we can then pull the variables we need.
response = urequests.get('https://timeapi.io/api/Time/current/zone?timeZone=Europe/London') print(response.content) print(response.json()['dateTime'])
Building a Web Based Front End For Our Project
The flipside of being able to send GET requests is that we are also able to respond to them. Other devices on our network can send these requests to our Pi Pico’s network IP address and if it is listening out for these messages we can decode them and take the appropriate action.
The first partners process is to create a network socket that allows the Pico to receive HTTP GET requests and capture the data ready for us to process. To do this we need to use the socket library.
import socket
# Open socket # addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] addr = (pico_ip, 80) s = socket.socket() s.bind(addr) s.listen(1) print('listening on', addr)
A socket is basically a data stream object that connects our code to a port on the network. This port is basically a doorway that we open to allow data in and out. Different port numbers are used for different network services. Port 80 is the default port for HTTP traffic.
So we first need to create an address tuple that contains our Pico’s network IP address and port number we want to use. We then create a socket object and bind it to this address combination. Once a socket is bound to a port we can tell it to start listening for other devices connecting to it.
As soon as the socket is listening we can ask it to accept the connection. This method call is a blocking method meaning that our code will sit at this line until the accept method finishes, i.e. a connection is made. So make sure that if you are using this in your own code having it stop processing and sitting, waiting for a device to connect does not mess up any other processing.
Once another device connects the accept method will return back a client connection object as well as reporting the IP address of the client device.
We can then collect the data being sent by the device using the recv method. At this point the variable raw_request will hold the raw data sent by the client device.
client, client_addr = s.accept() raw_request = client.recv(1024)
This raw_request variable will usually hold the HTTP GET request data which will look something like this.
GET /ledoff HTTP/1.1 Host: 192.168.1.246 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/w ebp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.1.246/ledon Accept-Encoding: gzip, deflate Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
For our basic web interface we only need to concern ourselves with the very first line. Looking at that we can say that it is made from three parts separated by spaces.
The first part is our HTTP method which in this case is a GET request.
The second part is the actual resource that the client device is trying to access. In effect this is the webpage address that the client’s browser is requesting. This is what we will use to specify the command or data being sent from the client device to our MicroPython project.
The third part is the HTTP protocol being used which we don’t need to worry about at this point.
So our code now needs to decode this HTTP request and pull out the requested resource so that we can take the appropriate action. In this example I’ve set up 2 endpoints that our project will respond to. These will allow us to control an LED using buttons on a webpage control panel that the Pi Pico will send to the client’s browser.
request_parts = raw_request.split() http_method = request_parts[0] request_url = request_parts[1] if request_url.find("/ledon") != -1: # turn LED on led_state = True led.on() elif request_url.find("/ledoff") != -1: # turn LED off led_state = False led.off() else: # do nothing pass
Displaying a Webpage in the Client’s Browser
To create a webpage we need to send HTML code back to the client’s browser. This is basically a string of text so we can either code the text directly into our MicroPython source file, or embedded into a separate HTML file that we can simply read and then send back using our client connection object. The advantage of using a file is that it makes editing our HTML much easier and we can actually test the file by simply viewing it in a browser.
There are some performance penalties in using this route as our system has to read the file in each time it wants to use it, but it does make the whole process of developing a more complicated control panel that much easier. We can always convert the file to Python code once we know that it is correct.
file = open("simpleled.html") html = file.read() file.close() client.send(html) client.close()
Conclusion
This really covers the basics of creating a web based front end for your projects. We seen the basic process of both requesting data from a web service and responding to client requests from other devices on the network.
The code developed in this tutorial is very basic. For more advanced REST API development we need to take a much deeper look into how HTTP requests are sent and handled. So please look out for my next tutorial where I’ll show you how to do just that.