My First Pico W Web Server
Still in high demand and not the easiest to get hold of, the Pico W is a fantastic microcontroller from the great folks at Raspberry Pi. Here's my adventures of setting up my first webserver and my plans.
- 8 minutes read time
Pico W you say?
The raspberry pi pico, launched in January 2021, is a dual-core ARM coretex M0 microcontroller running at 133MHZ, with 264kb of SRAM and 2MB of on-board flash memory. The Pico W now ups the ante and brings 2.4GHz wireless to the party. I’ve had a couple of Pico’s at home, with the full intention to teach myself a new coding language and further coding skills, but with the introduction of Wi-Fi, this was just what I needed to realise a little project I’ve had since the Pico was announced, without the need for packs or electronics. So as soon as it arrived in the post, I printed a fancy orange 3d case from Printables and began coding away.
My start page rabbit hole
So the project, unsurprisingly from the title, is running a webserver. No fancy inputs or controlling electronics for this one though. This is going to host a custom startpage. A startpage is something you can set your browser to default to when you open a new tab (browser permitting) or just having a url you can go to when at home (or out and about with extra setup) which has all the links and some content you might want to go back to regularly.
Theres a whole subreddit (r/startpages) dedicated to peoples designs. Really fun to look through and so now it’s time for creating one myself.
But to make things easy on myself while I learn with the Pico, I’ve decided to use a design created by someone else in the first instance before the ultimate goal of creating my own startpage.
I used a fork of the great start page by Jaredk3nt which can be found here. Other awesome startpages can be found on the AwesomeStartpage GitHub repo.
How I faired with the basic tutorials
Keeping things simple while learning micropython, I stuck to the tutorial found on the Raspberry Pi site, while also using the tutorial at Pete Codes just to see some different ways of doing things.
All things seemed to be working OK so I moved on.
Splitting out the network info
So the first thing I wanted to do is split out the network credentials so they are in their own file. Happily not too difficult an ask. I created a secrets.py file with the variables contained in and then referenced them in the main webserver.py file I had.
Secrets file content
secrets = {
'ssid': 'Your SSID here',
'pw': 'Your Password here'
}
Part of the webserver.py file importing the variables
from secrets import secrets
#import uasyncio as asyncio #test if not needed
ssid = secrets['ssid']
pw = secrets['pw']
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, pw)
The trouble injecting some css styles and javascript
Next step is using the startpage html, css and javascript I forked over. I wanted them as separate html, css and javascript files. Returning these within variables and quotations like the tutorials, is OK, but not great to maintain and change or be able to see and edit your code effectively (hence why I split out the secrets as well).
So I uploaded to the Pico W the .html, .css and .js files for my startpage. I also uploaded an svg of a raspberry to see whether you could use it for graphics or maybe the icon for the startpage.
I managed to find some code how to load and read a file. This was placed in the main webserver.py file just after the Wi-Fi section.
# this is compiling the file ready when requested
def get_request_file(request_file_name):
with open(request_file_name, 'r') as file:
file_requested = file.read()
return file_requested
In the main webserver.py file you take the request of the webserver and you run it through the function. That will then give you all the content (be it html, css or javascipt) to send back to the webserver. You need to send back what type of file it is (more on this later) so the webserver knows how to interpret it.
Figuring out the problem
The code worked (I thought) and the html page was shown. But sadly, not the css or javascript. It returned the content of those files, and I could see them in chrome web tools, but it just wasn’t working. A head scratcher I was stuck on for some time. I’ll admit, I shouldn’t have struggled on this for as long as I did. But maybe thats the fun of coding, having that ‘of course’ moment.
The problem was, regardless of the file it was reading, it was sending back either the html header type (what the webserver needs to know how to interpret the code), or nothing at all. It turns out I had a problem with my if statements not taking the right variable and checking it in the right manner to return the header it should. So the webserver just couldn’t figure out how to apply the css and javascript.
Note to self and friendly reminder, print functionality is your friend when debugging!
the final (ish) code
I’ve included the interesting snippet here. I wont show you the many, MANY iterations before I got this working.
while True:
try:
cl, addr = s.accept()
print('client connected from', addr)
#identifies the request
request = cl.recv(1024)
#turns the request into a string
request = str(request)
try:
#splits it down to the bit we're interested in (eg index.html, styles.ss)
request = request.split()[1]
except IndexError:
pass
# works out what the file type of the request is so we send back the file as the correct MIME type
if '.html' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n'
elif '.css' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: text/css\r\n\r\n'
elif '.js' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: text/javascript\r\n\r\n'
elif '.svg' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: image/svg+xml\r\n\r\n'
elif '.svgz' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: image/svg+xml\r\n\r\n'
elif '.png' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: image/png\r\n\r\n'
elif '.ico' in request:
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: image/x-icon\r\n\r\n'
# serve index if you don't know
else:
# doesnt send a header type if not extension not listed. In many cases the file will still load - but you may be better to look up the MIME type for the file and add to the above list
file_header = ''
#runs the requested file through the open bit at the top of the code to get the file contents
response = get_request_file(request)
#A little check to ensure it's getting the right MIME type for the file it needs.
print('file header = ', file_header)
#send back what type of file it is
cl.send(file_header)
#sends the content back
cl.send(response)
#finishes up
cl.close()
As you hopefully can see, the request from the webserver gets split into a string with just essentially the file it needs (eg index.html). Then the if statements work out what extension it is and therefore the correct header (type of file) to send back along with the content. This means the webserver can render the content correctly.
Finally all the css, js and even the .ico svg rendered as expected and the startpage worked like a charm.
Want the code yourself?
If you want to skip the hassle I went through, here’s a link to a GitHub repo I created which is ready for you to tweak to your hearts content.
Next steps
So now it’s working, what’s next?
Well, although this startpage is great, I’ve definitely got a startpage just for me in my head. Something a bit more visual I think. Bringing in more around weather and news and also some of the self hosted things I have at home specifically. So thats phase 2. I’d like to also make it something which would look good perhaps on a small screen like google home.
The Archives