DOSBox Full Installation, Game Download and Setup – Everything you need to start DOS gaming
11th May 2023Turn your old PC into a DOS gaming machine using DOSBox-X and Linux
30th May 2023Web Control Panel – Building the Web Page – Raspberry Pi Pico, ESP32, Arduino
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.
For this we’ll be looking at web page programming using a mixture of HTML, CSS and JavaScript. So, let’s get started.
An Overview of the Network
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!
Serving The Control Panel
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.
This HTML code allows us to both show the control panel, but also to kick off a whole process of loading in whatever other software we need to run it. This can include CSS files to help make the display look better, but also JavaScript files with the code that drives the graphical interface elements and control logic.
All of these HTML, CSS and JavaScript files can be stored on the Pico and sent over to the client so that the project becomes self-contained. The client only needs to visit the project default page and it then receives all the software.
But we’re not limited to our own code files that we store on the microcontroller. If we assume that the client will also be connected to the Internet, we then have access to a vast range of software libraries that can be directly downloaded from their own websites. These could be JavaScript helper libraries such as jQuery, which we’ll be using in this tutorial, or even full AI packages such as TensorFlow. All of these packages run within the client browser using the data from the Pico.
Let’s Build the Control Panel
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.
You also need some way of editing HTML and JavaScript files. If you’re using PyCharm as I am, that’s all built into the main editor, but if not you’ll need at least a plain text editor to be able to create and manipulate these files.
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.
Control Panel Webpage
I’ve split the code for the Control Panel webpage into three parts. The HTML file is the first file that will be served when the client requests the webpage. This HTML code will then tell the browser to request the JavaScript code file and the CSS file containing some page styling. You can put everything into a single HTML file, but splitting it out into the separate parts makes it easy to see what’s going on, especially as a project gets bigger and more complex.
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.
If we first of all look at the read potentiometer button you’ll see that we are using an onclick event handler to call a JavaScript function, getReadingFromPico. The code for this function will be stored in our JavaScript file.
Underneath the button I’m using an HTML H2 tag to display the potentiometer reading. In my JavaScript I will need to be able to reference this element so I’m using the ID attribute of the H2 tag to effectively label this element which will allow me to select it in my code. There are a range of ways in which you can label elements using CSS styles and data attributes, so just use the one that best fits your needs.
Moving down the listing we can see another four buttons, each of which is using its onclick event to call the setLedColour JavaScript function, passing in the desired colour as a function parameter.
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.
As we mentioned earlier this HTML file instructs the client web browser to request the CSS style file and the JavaScript code file. You can see these being included in the link tag at the top of the code listing and the script tag at the bottom.
So, let’s move over to the JavaScript file.
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.
We then create an AJAX call to our web API. If you haven’t come across AJAX before this is a way in which our JavaScript code can communicate with the Web server in the background while our browser is displaying the webpage. It will generate a web request, send its data to the Web server, receive a response from the Web server and then allow us to run a block of code which can basically do whatever we want. In our instance we’re going to decode the potentiometer reading and update our webpage display with that value. The great advantage of using AJAX techniques is that our webpage does not need to be refreshed. We can simply change the contents of the page using code.
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.
Our second setLedColour function is going to use JSON to both send the data to a web API and to receive it back. Our code starts by building a JavaScript object with the data we want to send into the API. You can see that we again set an action variable with our setLedColour command and then attach a second variable to specify the colour. Again, because we are using JSON we can use complex objects, arrays and whatever other variable types we want within this data package and that data structure will be rebuilt within our MicroPython code giving us full access.
We then build up our AJAX call in a very similar way to before. Our onload callback function is a bit more complex. Again, we are using the response body but this time we know that it contains a JSON data structure. We use the JSON.parse method to translate this string version of our data into an actual JavaScript object. This object will have the same structure as the dictionary object built within our MicroPython API action handler. So, in our onload function we first turn off the background colour of our coloured LED display button. As you can see, I’m removing all the coloured CSS style settings and adding a CSS style to make the button look like it’s turned off.
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.
The last part of this function turns this AJAX request into a JSON AJAX request. Again, we’re using the POST method to our /api endpoint, but this time we need to set the Content-Type request header to specify that we are sending JSON data. This we picked up by our MicroPython API code so that it knows to decode the request body is a JSON object. The final line of our function takes our JavaScript data object, jsonData, and turns it into JSON text which is then sent as the body of our request.
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.
Using The Web
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.
But normally we’ll be using an Internet enabled Wi-Fi network. This suddenly opens up our project to the full power of the web. There are a large range of powerful software packages that can be pulled into our browser to make our site look better, add graphic gauges and controls, or even to do in depth analysis of our MicroPython data. All we need to do is to link to them from our html web page and then run the code sent over to the browser in our JavaScript file. All of this processing then takes place on the client without putting any load on the Pico.
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.
Have Fun!