Web dashboard on Zephyr RTOS

When a connected embedded device is being developed - and by "connected", we mean TCP/IP connectivity - it usually implements one or both of the following use cases: (a) a device hosts a web dashboard for easy configuration, status checks, and remote control, and (b) a device connects to a remote cloud for data reporting and remote control.

Having a web dashboard on a device provides handy, user-friendly local access that is much simpler than serial consoles or custom desktop applications because it is accessible through any modern browser—no drivers or installations required.

Below, we will describe in detail the principles of how to build a web dashboard on embedded devices running Zephyr RTOS.

For those who would like to see a practical application of these principles, see the video below which describes an implementation on the Nucleo-F756ZG STM32 development board. However, the same process would work for any other device with a network interface:

General architecture

The diagram below shows a high-level overview of the embedded web server architecture. A web server opens a listening HTTP/HTTPS port to the local network and has access to the application data. HTTP requests can read and write application data. If the client is a human using a browser, the web server can host a UI that displays the application state. A client can also be an automation system - for example, a health check infrastructure that periodically queries all local devices for their health, such as whether they are on or off, RAM usage, and so on.

Web UI pages

LED control example

Let's drill down into the implementation architecture using the example of LED control. Let's assume that our device has an LED.

The HTTP server should provide a way to:

  1. Read the LED state and report to the client whether the LED is on or off
  2. Write the LED state as client commands - set the LED on or off

On the HTTP side, this is implemented as a REST API. An HTTP server creates a URL, which is also called an endpoint. That URL represents an LED control. A "GET" HTTP request represents a "read" operation, and returns the current state of an LED. A "POST" request represents a "write" operation, and returns the result - for example, it can be true/false, or it also can be the result state of an LED. The table below summarizes that:

Operation HTTP request URL Input data Output data
Read GET /api/led - {"on": false}
Write POST /api/led {"on": true} {"on": true}

This simple REST API implemented by a device gives us remote control over the LED. Here is how we can control it using the curl utility from anywhere on the local network:

Reading LED state:

$ curl DEVICE_IP/api/led
{"on": false}

Setting LED state - turning LED on:

$ curl DEVICE_IP/api/led -d '{"on": true}'
{"on": true}

As you can see, this can be used by either humans or machines - like automation systems.

Now let's take a look at how that is done on the device side. The TCP/IP stack usually provides a so-called BSD socket API for reading and writing data from network connections. That means that in order to implement HTTP server functionality, a firmware developer should either use a socket API, which is low level, or use an HTTP library that provides an HTTP API.

Using a well-established, well-tested library is a reasonable way to go. In that case, a web server implementation on the device side would look like the diagram below:

Web UI pages

Let's see how that works, using the example of receiving a POST request to change LED state:

  1. The web server receives a POST request on the /api/led endpoint
  2. The application code reads the request payload, {"on": true}
  3. The application code calls a system API function gpio_pin_set_dt() to turn the LED on
  4. The application code writes a response {"on": true}

This provides a simple and efficient way to control a device remotely. However, this device control does not provide a user-friendly interface. In the next section, we will explore how to add a web dashboard.

Web Dashboard

There are multiple ways to implement a web dashboard. One approach is to generate HTML pages dynamically - either directly or using a templating engine. With this approach, the user code would look like this:

  send_http_response(connection, "<html>..... LED status: %s.... </html>",
                     gpio_pin_get_dt(&led));

This approach is quite straightforward and may work well for simple use cases. However, it has the following limitations:

The modern approach is to separate the UI and application data. In this way, the UI is served as a collection of static files, and the application data is served via a REST API. This approach decouples the UI from the application data, making it easier to maintain and update.

Let's demonstrate this with a simple web dashboard that shows the LED status and allows the user to control it. The web UI would consist of three files:

index.html:

<!DOCTYPE html>
<html>
<head>
  <link href="style.css" rel="stylesheet" />
</head>
<body>
  <div class="main">
    <h1>My Device</h1>
    <span>LED status:</span>
    <span id="status">0</span>
    <button id="btn">Toggle LED</button>
    <p><small>Note: this UI is intentionally minimal</small></p>
  </div>
  <script src="main.js"></script>
</body>
</html>

style.css:

.main  { margin: 1em; }
#status { display: inline-block; width: 2em; }

main.js:

var led = false;
var getStatus = ev => fetch('api/led')
  .then(r => r.json())
  .then(r => {
    status = r;
    document.getElementById('status').innerHTML = status;
  });

var toggle = ev => fetch('api/post', JSON.stringify({ on: !status }))
  .then(r => getStatus());

document.getElementById('btn').onclick = toggle;
window.addEventListener('load', getStatus);

Now, the web server should serve the API endpoint /api/led exactly as described in the previous section, and serve the static files for all other URLs. The time diagram would then look like this:

Web UI pages

And the UI would look like this:

Web UI pages

Real time updates

Sometimes the user wants to see device status that changes quite frequently. For example, it can be an oscilloscope-like graph display of certain real-time data.

The HTTP protocol is request-response, therefore using HTTP is not suitable for real-time updates. Instead, we can use the WebSocket protocol.

WebSocket is a protocol that allows for full-duplex communication between a client and a server over a single TCP connection. It is built on top of the HTTP protocol and provides a simple and efficient way to send and receive data in real-time. Essentially, WebSocket is a simple framing protocol that does not impose any semantics for the frames being sent or received. Both sides can send and receive data at any time. Therefore, WebSocket is a perfect fit for real-time updates. The way it works is as follows:

  1. The client sends a WebSocket request to the server.
  2. The server accepts the request and establishes a WebSocket connection.
  3. The client and server can now send and receive data in real-time over the WebSocket connection. In our case, the server can send frames containing sensor values with high frequency.

Using Web UI Wizard

The approach described in the previous section is manual - in other words, the development of the networking logic and the Web UI is completely on the firmware developer's shoulders. Oftentimes, firmware developers are not familiar with network development and/or web development, and they may not have the time or resources to learn it. In such cases, a web UI wizard can be a valuable tool to help them create a functional web interface without having to write any code.

The solution is to use a web UI wizard, which can generate the necessary networking and frontend code based on the developer's specifications. The wizard can also provide guidance on best practices for web development and network development.

An example of such an approach is Mongoose Wizard. The video linked at the top of this article provides a step-by-step guide on how to use Mongoose Wizard to create a functional web interface with remote firmware update capability. The result might look like this:

Functional Web UI

Contact us

If you have any questions or feedback, please feel free to contact us.