Serving an image on a Raspberry Pi Pico
Building on my pico web server, it's time to fix the flaw of images not working so fully fledged webpages can be shown. This also fills in a gap from the official Raspberry Pi tutorial.
- 6 minutes read time
Real-life examples were lacking
The official Raspberry Pi Pico example of creating a web server is ideal for showing how to set up a simple web server to control and display information as an introduction. But long gone are the days of static site websites. If I’m building a webpage, I want it to look good with CSS styling, images and custom fonts. But it doesn’t cover any of those things.
Looking online, I found of examples adding CSS and custom fonts, but not images. It’s why my previous post, My First Pico W server, cheated and only used SVGs as that’s all I could get to work. If you can’t make it, fake it as they say!
It turns out that more attributes are needed for the client to understand it’s an image and render it on the page.
For a client (the thing you’re looking at the webpage on) to understand it’s an image, we need to specifically tell it what we’re sending is an image file, while also defining the file size. So to make this server work, we need to extend the file header responses from the original example to not only include Content-Type, but also Content-Length.
Finding the break
This time, no cheating, time to understand the code and figure out the fix. As with all code, the first step is always the simplest. Print statements, print statements everywhere.
By doing this, I found the first part of the code was working and creating a bespoke header for the image file type. But some time after, the server hangs and we get an error.
#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)
This led me to do lots of Google searches. ‘HTTP server image’, ‘HTTP server MIME type’, ‘HTTP return image micropython’ and many more.
But then, between some useful stack overflow posts here and here, I was able to piece together problem one (and the most important) of the two. We need a file size!
sometimes, length matters
Now I knew we needed to define a content length. So it’s about how to get it. That’s where this Stackoverflow post
- defining length was the most helpful. Often I find a problem with adapting someone else’s code is not understanding the why as much as the how. This post was the most useful as it looked incredibly similar to the function section in the code already.
By seeing the similarities, I could write a new function which would pass in an image to get its length and return it as a variable. I wrote a similar function to work out the content length of the file.
# this is getting the content length FOR IMAGES
def get_image_length(request_file_name):
file_requested = open(request_file_name, 'rb').read()
image_length = str(len(file_requested))
return image_length
Next, I created a new if statement to use that function for images and return the value. A quick print function to confirm it worked, and then it was about assembling the responses back to the client.
creating a new response
To call the new function specific to getting the content length, I wrote a new if statement to the code specific for .jpg (the image type I was using). I added a print function after to validate it was pulling back a variable and looked right (it comes back as bytes, so it seems like a large number).
if '.jpg' in request:
image_length = get_image_length(request)
print(image_length)
Then I created the new response to the server which combines the response, content type and now content length. I’m learning as I go, so this took a few attempts as I didn’t know how to combine variables in micropython.
if '.jpg' in request:
image_length = get_image_length(request)
print(image_length)
file_header = 'HTTP/1.1 200 OK\r\nContent-Type: image/jpg\r\nContent-Length: ' + image_length + '\r\n\r\n'
print(file_header)
cl.send(file_header)
After sending the response it then sends the file as per normal but unfortunately, it still didn’t work.
too few bytes
An error came up when I tried the server which said ‘too few bytes mismatch’. Stackoverflow to the rescue again. Crazily, an extra space in the HTML file in the meta tag meant a byte mismatch. Coding is weird.
Spot the difference.
<meta charset="utf-8" />
<meta charset="utf-8"/>
Anyway, after fixing that, the webpage rendered as expected. Success at last.
at last, a png. So what’s next?
Well, it works, and now my webpage looks solid. It’s not without its flaws, though.
The working code is in a bit of a mess, so tidying it up is paramount (especially while I remember what I’ve done and why). You’ll be pleased to know I’ve updated the original GitHub repo already here, so if you pull the latest it should be working for images too.
It’s pretty slow at serving the page. It does one file at a time as they are requested (one for the HTML, one for the CSS and then one per image on the page) so that’s definitely contributing to the speed, or lack thereof. So I’ll look into whether these can be served asynchronously to help.
Other than that, I’m happy with the code and hope it helps you with some of your own fun projects. For me, it sets me up to do the project I wanted to do at the start. A hue-type light with its own webpage interface which can be tweaked and tinkered with by others with minimal effort. I’ll write up and publish any repos as soon as they’re done.
The Archives