Web Device Dashboard
A web device dashboard is a browser-based interface served directly by an embedded device. It lets users monitor device state, change settings, view logs, upload files, run firmware updates, and control hardware without installing a separate desktop or mobile application.
This guide explains how web device dashboards work, how the frontend and embedded backend communicate, and which implementation strategy is best for production devices. We will compare simple HTML generation, CGI/SSI templates, static HTML with REST APIs, and real-time WebSocket updates. We will also cover static file serving, dashboard-to-firmware glue code, authentication, live status, graphs, logs, and OTA firmware updates.
The recommended architecture is simple: serve static HTML, CSS, and JavaScript from the device, expose device state through REST APIs, and use WebSocket when the dashboard needs real-time updates. This keeps presentation separate from firmware logic, makes the dashboard easier to maintain, and gives both humans and automation tools a clean interface to the device.
Web Device Dashboard Architecture
Implementing a web device dashboard starts with an embedded web server running on the device. The device serves web content that is visible in a browser.
The web server side is called the backend, and what the customer sees is the frontend.
Running a web server means the device must run a TCP/IP stack.
Web Device Dashboard Implementation Strategies
We assume the TCP/IP stack is already integrated on the device, together with an embedded web server.
| Strategy | Best use | Main limitation |
|---|---|---|
| Printf | Tiny diagnostic pages | HTML is mixed with firmware logic |
| CGI/SSI | Simple static pages | Data and presentation are not cleanly split |
| REST API | Production dashboards | Requires JSON handling on the device |
| REST + WebSocket | Live dashboards | Requires a persistent connection |
Printf
The simplest strategy is to let the device directly print HTML content with generated information. For example, a web server can print this when a dashboard is requested by the browser:
net_printf(conn, "<html>Ticks since boot: %lu<html>", HAL_GetTick());
It is pretty simple and clear, but has the following weak points:
- Generating HTML content in C is pretty tedious. It can work for the simplest pages, but can quickly become overwhelming
- No Data/Presentation decoupling. Ideally, C code should not bother with HTML formatting and styling - and with this approach, it does.
- No decoupling makes remote control harder, because exposing the dashboard information to machines would require either HTML parsing, or creating separate pages with no HTML formatting, just data
- Generating a web page is only one of the dashboard's tasks. Other tasks, like submitting settings forms, make it difficult and messy quickly, because the developer has to deal with parsing form data received from the browser.
CGI/SSI templates
It is possible to enhance this strategy slightly by creating static HTML pages as regular HTML, and inserting SSI markers in it. The previous approach would transform into a static HTML page like this:
<html>
Ticks since boot: <!--#ticks -->
</html>
The C code would look something like this:
void print_current_tick(void) {
net_printf("%lu", HAL_GetTick());
}
// Mapping between CGI "variable" name and its printing function
struct ssi { const char *name; void (*func)(void); } substitutions[] = {
{"ticks", print_current_tick},
{NULL, NULL},
};
...
ssi_printf(conn, DASHBOARD_HTML, substitutions);
With this approach, we decouple HTML formatting and data output, though not in the cleanest way - because presenting data to machines would still require extra effort. But creating formatted pages is now easy.
This approach is used by the
httpdbuilt-in example of the well-known lwIP TCP/IP stack.
- Generating pages becomes less painful, as HTML formatting can be done in static HTML files
- Automation and remote control are still painful, because data and presentation are not yet clearly decoupled
- Passing information from the dashboard to the C code can be done via the CGI interface which is not optimal, as it does not impose a clear data format
Static HTML/CSS/JS + REST API
This is the best approach. A web server serves static HTML content to the browser, and does not rewrite it in any way - so the presentation side is clearly separated.
When a browser loads static HTML, it executes JavaScript, and the JavaScript then makes an additional query to load dynamic content from the device. In other words, it performs REST API calls to load device state.
As device state is loaded, JavaScript updates the HTML with the received data. So static content in our example becomes this:
<!-- index.html -->
<html>
Ticks since boot: <span id="ticks"></span>
<script src="app.js"></script>
</html>
// app.js - executes when static content is loaded
const elem = document.getElementById('ticks'); // HTML element
fetch('api/state') // Make REST API call to get device state
.then(resp => resp.json()) // Convert response string to JSON object
.then(json => elem.innerHTML = json.ticks); // Update HTML
And the server part becomes like this:
if (strcmp(request.uri, "/api/ticks")) {
net_printf(conn, "{\"ticks\": %lu}", HAL_GetTick()); // REST API handler
} else {
serve_static_content(conn); // Serve index.html, app.js, etc
}
In this approach:
- Presentation and data are clearly separated - notice that the REST API handler reports only data, which is easily digestible by the dashboard's JS and automation tools
- Dashboard design can be done separately - even by separate teams
- Allows for fast, responsive interface, because only small data changes can be communicated without re-fetching the whole page
- Submitting data to the device can use JSON format - which defines a clear data presentation contract but requires JSON parsing and printing on the device side
- Sending change notification from a device to the dashboard is still an issue. Consider an oscilloscope-type graph that sends many small chunks of data per second. HTTP would require a separate request/response for each update, which is not optimal. The solution is to establish an additional bidirectional WebSocket connection which can send update notifications from device to dashboard
A WebSocket is a long-lived, bidirectional connection. It starts as an HTTP request and then upgrades to a persistent TCP connection, so the device can push data to the browser without constant polling. It is a good fit for live graphs, logs, and instant change notifications.
Serving static files
There are multiple ways of serving static files. First and most obvious is to store static files on a device's filesystem.
There are a couple of issues with this approach:
- A device may not have a filesystem at all
- Reliability. A filesystem may fail (for example, SD card failure) and if a dashboard serves an emergency recovery interface, it will defeat its purpose
- Backend/frontend synchronisation. If static files are using SSI templates, or the REST approach, then server code must be in sync with the HTML/JS. Imagine a scenario when a device gets updated, but web files are not - or vice versa. That can happen for a variety of reasons. In this case, the dashboard becomes not functional.
So the easiest and most reliable way is to embed static files into the firmware binary: transform each file into a C array, include in the build, and then, when requested, send its data directly from flash memory.
This approach is described in Serve HTML, CSS, JS from Flash on Embedded Web Server. It is used, for example, by Mongoose and by lwIP httpd.
Implementing dashboard / server glue
As we can see, the server code must serve REST APIs or SSI substitutions in response to dashboard requests. Likewise, the code in the dashboard must know what data the device exposes. From now on, we assume the dashboard is implemented using the REST approach.
That means implementing an embedded device dashboard primarily consists of creating glue between the dashboard's JS code and the server's C code, exchanging specific device data in some format - for example, JSON.
Of course, that can be done manually. In this case, the firmware developer must be familiar with network programming and implement the HTTP and WebSocket part of the glue. They also need enough HTML/CSS/JS knowledge to implement the web UI.
There are ways to make this job easier.
Web UI builders
One way is using a dashboard generation tool. An example of such tool is Mongoose Wizard - a visual dashboard builder which allows developers to visually construct the web UI without frontend experience. The tool generates both web UI and backend code, hiding much of the network programming complexity. The firmware developer only needs to implement 'hardware glue' functions that tie generated REST API handlers to firmware.
The good side is a handy pre-built dashboard which can be adopted quickly.
The bad side is that it can be difficult to customise the UI - add your own controls, JS libraries, and so on.
Custom Web UI + glue
Another way is to develop the web UI manually, and have full flexibility in chosen frameworks, look and feel. Using AI, it is possible to create quality, production-ready web UI quickly using plain English, since AI is particularly good with web interfaces and web development.
The issue is that when such UI is created, how exactly is it bound to the firmware code?
The answer is the 'glue' approach:
- The custom web UI includes a carefully designed JS library that instruments specific HTML elements and binds them to the device
- On the device side, a carefully designed C layer can expose a simple interface to exchange data with a dashboard
For our simple dashboard example, it can look like this:
<html>
Ticks since boot: ${state.ticks}
<script src="dashboard.js"></script>
<script>
Dashboard.init({data: {ticks: 0}});
</script>
</html>
The dashboard.js library would create a connection with a device, request
device data, and update instrumented HTML elements accordingly.
The ${state.ticks} may look like the old SSI approach, but it is not.
SSI processes HTML on the server side. Here, dashboard.js processes HTML on
the browser side.
The C code may look like this:
struct dash_data_fields fields[] = {
"ticks", TYPE_INT, get_ticks
};
dash_init_listener(fields, DASHBOARD_HTML);
This is clean since it lets the firmware developer concentrate on what data is exposed to the dashboard, and not bother with the networking part, since it is implemented by the C glue layer.
The example of this approach is described in Mongoose Web Device Dashboard Guide, together with its live demo.
Dashboard Usability
In order for the dashboard to be high quality, production-ready, usable and reliable, take the following points into consideration.
Online/offline status
When customers look at the device dashboard, they should clearly see whether it displays the actual state of the device, or stale data.
Synchronised changes
When multiple browser windows are opened, a change in one should reflect in the other. A dashboard should always show the latest data.
User login
For many use cases, customers have to be authenticated before using the dashboard. Use best practices and existing libraries.
Access levels
In many cases, the dashboard should provide access control. For example, only users with 'administrator' login can edit settings, the rest can only view.
Real-time updates
A common requirement is frequently updated device metrics.
Form edits
When a user edits a settings form, the dashboard should clearly indicate that edited fields differ from the values on the device. In other words, they are edited but not saved yet.
Ideally, a form should have 'Save' and 'Cancel' buttons that activate only on form edits.
Graphs
Many dashboards require graphs - static or real-time; pie charts, sparklines, and other visual elements.
Firmware updates
Almost every firmware with networking support requires firmware update. A dashboard should provide a way to update firmware using push method (upload a new firmware file) or pull method (specify a firmware URL).
File uploads and logs
Looking at device logs, and optionally uploading/downloading files, is common.
Conditional displays
Certain elements should be shown differently depending on device state. For example, a 'Free RAM' metric can display red if free memory goes too low.
FAQ
How do I implement a web device dashboard?
To implement a web device dashboard, first integrate a TCP/IP stack into the firmware. Then choose an embedded web server, serve static HTML, CSS, and JavaScript, expose device data through REST APIs, and add WebSocket support when the dashboard needs real-time updates.
What does an embedded device need to serve a web dashboard?
The device needs a working TCP/IP stack and an embedded web server. The web server serves the dashboard frontend to the browser and handles API requests that read or update device state.
What is the best architecture for a web device dashboard?
The best architecture is to serve static frontend files from the device and expose device state through REST APIs. This keeps presentation separate from firmware logic and makes the dashboard easier to maintain.
When should a web device dashboard use WebSocket?
Use WebSocket when the dashboard needs live updates from the device. Common examples include live graphs, logs, online status, instant notifications, and state changes that must appear without browser polling.
What data format should a web device dashboard use?
JSON is usually the best data format for a web device dashboard. It is easy for JavaScript to parse, works well with REST APIs, and gives the firmware and dashboard a clear data exchange contract.
What firmware code is needed for a web device dashboard?
The firmware needs glue code that serves static files, handles REST API requests, sends WebSocket updates, and connects dashboard fields to actual device state.
How is a web device dashboard different from a generic web dashboard?
A generic web dashboard often runs on a cloud server or desktop backend. A web device dashboard is served by the embedded device itself, so it must account for firmware limits, static file storage, TCP/IP integration, and direct access to device state.