---

Monday, September 12, 2022

A Better Web Server for Raspberry Pi Pico W

The Pico W is a fantastic little device, but the examples I have found for serving web pages from it were all too low-level for my taste.


I'm big fan of Bottle and Flask as light-weight web frameworks. And was very excited to find the microdot project. This looks very much like Flask and Bottle, using the decorator syntax (@) to mark functions that are to respond to particular web requests. This leads to code as simple as this for a minimal web server.

from microdot import Microdot

app = Microdot()

@app.route('/')
def index(request):
    return 'Hello, world!'

app.run()

Now thats what I call the right level of abstraction. Not a reference to socket in sight!

The great news is that microdot works with the Raspberry Pi Pico. Although it takes care of all the web serving for you, it will not initiate your WiFi connection. Connecting to WiFi has a few subtleties such as showing connection progress and handling failure to connect and re-connection. It's definitely a wheel that doesn't need reinventing every time you want to serve a web page. So, I have put together a tiny module to wrap that process up as well. Its available here: mm_wlan.

Once installed, your entire web server code for your Pico will just become this:

from microdot import Microdot
import mm_wlan

ssid = 'my network name'
password = 'my password'

app = Microdot()  
mm_wlan.connect_to_network(ssid, password)

@app.route('/')
def index(request):
    return 'Hello, from Pico'

app.run(port=80)

Step by Step

1. Install microdot and mm_wlan. This involves copying just two files, microdot.py and mm_wlan.py onto the file system of your Pico W.

You can do this by opening these files in Thonny and, with your Pico W connected to your computer choosing Save copy from the File menu. When prompted select Raspberry Pi Pico as the destination and save the file onto the Pico's file system using its original name.

2. Start a new file in Thonny and paste in the code immediately above. Change ssid and password to match your WiFi network and run it. When you do so, you will see the IP address that the server is running on in the Shell area at the bottom of the Thonny window. 


Now open a web browser on that IP address:

Hurray! Your Pico W is being a web server!

Adding Another Page

Let's now add a second page to the web server called memory that reports how much free memory the Pico W has. The previous page returned plain text rather than HTML. The default response type of microdot is text, so even if we had put HTML tags into the response, it is displayed literally and not be interpreted by the browser as HTML.

For this new page we will need to import the gc (Garbage Collector) package:

import gc

and then add a handler like this:

@app.route('memory')
def index(request):
    response = '<h1>Free Memory={} bytes</hi>'.format(gc.mem_free())
    return response, {'Content-Type': 'text/html'}

Notice that now, as well as returning the response text, we also return the content type.

Run the program and, when you point your browser to the page memory, this is what you should see:


Try refreshing the page a few times and you should see the free memory change.

Here's the whole program:

from microdot import Microdot
import mm_wlan
import gc 
 
sid = 'my network name'
password = 'my passord'

app = Microdot()  
mm_wlan.connect_to_network(ssid, password)  
 
@app.route('/')
def index(request):
    return 'Hello, from Pico' 
 
@app.route('memory')  
 
def index(request):
    response = '<h1>Free Memory={} bytes</hi>'.format(gc.mem_free())
    return response, {'Content-Type': 'text/html'} 
 
app.run(port=80)   

Templates

As your web pages get a bit more complex, then some kind of templating will help you construct the response text. Here's an example of a multi-line example taken from an example project in the MonkMakes Plant Monitor.



from microdot import Microdot
import mm_wlan
from pmon import PlantMonitor

ssid = 'network name'
password = 'password'

html = """
    <!DOCTYPE html>
      <meta http-equiv="refresh" content="1" >
<html>
<head> <title>My Plant</title> </head>
<body>
<h1>Pico W Plant Monitor</h1>
<h2>Water: {water}</h2>
<h2>Temp (C): {temp}</h2>
<h2>Humidity: {humidity}</h2>
</body>
</html>
"""
pm = PlantMonitor()
app = Microdot()
mm_wlan.connect_to_network(ssid, password)

@app.route('/')
def index(request):
    w = pm.get_wetness()
    t = pm.get_temp()
    h = pm.get_humidity()
    response = html.format(water=w, temp=t, humidity=h)
    return response, {'Content-Type': 'text/html'}

app.run(port=80)


The neat trick here is to use the meta tag to automatically refresh the wen page every second. Perhaps a better way to do this would be to have a microdot page that serves the sensor values of JSON that Javascript then handles on the browser, with the home page including the Javascript to do that. But, that's a lot more work.

Summary

The microdot framework will take a lot of the effort out of your Pico W projects where web serving is required. It's a really impressive bit of framework code.

The microdot framework is pretty complete, supporting different request types, request parameters and pretty much anything you could expect from such a compact web server framework.





1 comment:

Stewart C. Russell said...

I suggest putting the wifi setup in a separate boot.py script that gets run automatically before main.py. This is standard on all other networked MicroPython boards, but it seems to have got lost from Pico lore - see docs on the main MicroPython.org site