In my last 3 tutorials I’ve been looking at how we can connect our Micropython powered, Wi-Fi enabled, microcontroller board to a browser. I’ve already covered the connection and web server parts, so please have a look at the tutorials in the description if you haven’t already seen them.
So, at this point we now have a working web server on our microcontroller board, and for this series I’m using a Raspberry Pi Pico W, so we’re ready to build the graphical front end.
First, we need to get our heads around how our network and code is divided between the Micropython board and the client’s browser.
We’ve built up a web server on the Pi Pico. This is able to receive HTTP requests, decode them and respond back with either data or static files. It also allows the Pico to run the actual control and monitoring tasks required for our project with the web requests effectively being handled in the background. Everything on this side of the network is ready to go!
We now need to look at the client side – the actual web browser or software package you’re using to view the project’s control panel.
The first thing to get to grips with is that a web browser running on our PC or phone is an order of magnitude more powerful than our microcontroller board. The more processing we can offload to it the better. Our Pico will have to read the sensors and run the motors and outputs but we can design our code so that it only needs to perform basic tasks when it comes to the web control panel.
For example. On my test circuit I have a potentiometer being read by the Pico. This value needs to be shown as a graphic, moving dial on the control web page.
We could have the Pico generate a new dial image and feed that into a continually refreshed web page being sent to the client. But it makes much more sense for the Pico just to report back the sensor reading when asked (a single number) and have the browser do all the fancy graphics calculations and rendering.
Similarly, we can control some RGB LEDs using slider controls. These simply send three numbers to the Pico which then turns on the LEDs again leaving the browser to manage the slider displays, colour blocks and so on.
By keeping the data exchanges as simple as possible we ensure that our microcontroller board is not overloaded running the control panel rather than processing our project.
Although we’ll not be going this far in this tutorial, I hope you start to get the idea that we can offload a lot of the more powerful computing tasks to the browser, or indeed whatever piece of software is connected, a Python script for example. As the microcontroller gathers data the client software can gather this and start to do some very complex analysis. Perhaps looking at trends so it can predict problems, doing image analysis or anything else.
Do bear in mind though that any calculations performed on the client are an HTTP message away from the microcontroller so there will be a delay before any calculation results reach the board. Don’t use this technique for time critical control tasks. Also, the system relies on the client being connected. If you close your browser window the processing stops!
Let’s now start looking at where the actual software sits.
We already know that our microcontroller board is going to connect to an IP address on our network. If we send a simple GET request to that IP address it should respond back with the control panel HTML code.
To create our web control panel, we need a number of files that will be stored on the MicroPython device. First, we need the Python file, or files, that contain our Web server code together with any application code. Then we need a range of files that create a mini website that our Pi Pico will serve back to our client browser.
The files I’m creating for this tutorial will all be available as a GitHub repository. So please make sure you check the description or visit the main project page on my website for a link to that code.
The first we need to look at is the Python code file. This contains the code for our Web server, the API within it and our project code. Naturally you can spread this over a number of files but in this tutorial I just put everything in the one place.
Looking inside the file you’ll see that there are two main code blocks. The first is our Web server request handler. This is identical to the code we developed in the previous tutorial, so again if you haven’t watched that video please have a look. In this request handler we use a number of the helper classes we’ve been developing. Initially we get the raw request sent by the client browser over our Wi-Fi connection. We then run this through our request parser class which will decode all of the information and present it to us in a standardised format. This allows us to send our API requests from our client in a range of formats, for example as a simple GET request, a POST request with form data, or a POST request with JSON data.
Once we have that request, we prepare a response object which will contain the data we need to return back to the client browser.
We then need to work out what the client has requested and take the appropriate action. In this setup I’ve really created two types of requests. One will be a call to our API endpoint where our Pico will need to interact with our test circuit and the other will be a request for one of the static code files that make up the actual web control panel.
In this first part of the code, you can see that we are testing for a call to the API endpoint. Our API expects to be sent a data variable called action that specifies what API code we want to run. The first action I’ve coded is readPot. This is a request from the client for the current setting of the potentiometer. As you can see in the code it simply reads the voltage from the potentiometer and places that value as a simple string in the body of the response.
There are a range of ways in which you can package your data to be sent back to the client. This is the simplest way.
In our second action, setLedColour, we have a more complex call. Here our client needs to send a data value specifying the colour of the LED that needs to be turned on. Once we have that colour value we build a small object using a Python dictionary that holds the current state of all three LEDs. We then turn on the correct LED and log this in our LED colour state structure.
This example is designed to show how we can return complex data back to our client web browser. Our response data is a further Python dictionary inside which we can embed whatever variables we want. The first data attribute is status which is used to hold a simple string to indicate if the API call ran OK or caused an error. The second data attribute embeds our whole LED state variable into our return data. This is going to be returned to the client as a JSON object which is designed to allow complex data structures to be sent as simple text messages between servers and clients.
You can see at the end of this API action handler we call the set_body_from_dict method which takes our Python dictionary, translates it into a JSON string, and then embeds that into the body of our response.
At the moment these are the only two API actions we’ve implemented. If we get an API call for an unrecognised action, we will return an HTTP error code.
If our request was not to the API endpoint our code now assumes that the client is asking for one of the static files that make up our webpage. We make a call to the serve_static_file method, passing the requested URL and specifying what file we should use if no URL is requested. This is called the default page and is basically the home page for our application website.
The rest of the code in this file handles the actual setting up of the asynchronous tasks as well as launching some application code running on the second core of our Raspberry Pi Pico. For this tutorial it basically toggles the red LED on and off to show that we have some application software running in parallel with our web API.
So that really takes care of the MicroPython end of our web control panel. As we saw earlier it’s serving static code files and then handling our API requests with very simple data transfers between the browser and our Web server. We now need to have a look at the code that will be served to our client browser.
I’m not really going to go into great detail on how to code webpages, but I will take you through the specific techniques that we need to use to communicate with our MicroPython web API and display and control aspects of our project.
So, looking through our HTML code I’m using some tables and buttons to create the control panel interface. One of the good things about using PyCharm is that I can preview what this looks like by opening up the code in a browser.
As you can see, we’ve got a button which will take a reading from the Pico potentiometer and display it on the control panel and four buttons to turn on the relevant coloured LED along with an orange button that should return back an error. Underneath these buttons is a coloured block which will show the colour of the currently on LED. This colour will be set using the return data from our API call rather than using the button press information.
In the code then we need some way of identifying and linking with the objects on our control panel.
Finally, I’m using another button to show the colour of the currently active LED. Again, I’ve used the ID attribute so I can identify this element, but this time I’m using CSS classes to control the state and colour of the button.
In this I’ve defined the two functions that were referenced in our HTML code. I’ve used two different techniques for accessing the web API so you can see how the calls are structured.
In the first getReadingFromPico function we’re going to send some form data to the Pico. You can see this being built in the first two lines of the function where we create an object to hold the form data and then append an action attribute with the value readPot.
Our AJAX request is implemented using an XMLHttpRequest object. There is a newer fetch API which does a similar job so it’s really personal choice as to which one you use. But in our XMLHttpRequest we need to set a few properties. The onload property lets us set a function that will be called when we receive the response back from the Web server. In the code you can see we are using the document.getElementById method call to select the H2 tag that we labelled as reading using its ID attribute. We then set its innerHTML property to the body of the response returned by the web API. In this onload callback function the this object refers to the response returned. The responseText attribute gives us the body of the response. If you remember this API call simply places a string representation of our potentiometer reading in the response body.
We then use the cled_states object embedded in our web API response to check each of the individual colour LED states. If a colour status turned on we then set the correct CSS style for our coloured LED display button.
The big if statement in this callback function checks the status value returned in our response object. If it is set to OK we handle the LED colour states otherwise we raise an error message.
So that’s the code running within our browser.
The final part of our webpage is the CSS style file. If we have a quick look through that you’ll see that I’m simply defining the various background colours for the coloured LED display button. By applying and removing these we can very easily control the look of objects on our webpage.
So far all of the code files we’ve used have been stored on the Pi Pico and served to the browser. This is great as it means that our system is very self-contained so we can even run it from the Pico’s internal Wi-Fi network.
The full API demo code makes use of a CSS framework, Bootstrap, to make the pages look a lot better, gauge.js to provide graphic dials and jQuery to make dealing with web page manipulation and AJAX much easier.
But in reality, it’s now done to your imagination as to what you can do with this system.