Web Dashboard Guide For Microcontrollers

This is a step-by-step guide that shows a simple, practical way to create production-ready embedded web dashboards on microcontrollers using a custom-built HTML web UI and the Mongoose library.

By the end of this guide, you will:

  • run a local web dashboard at http://localhost:8000
  • bind UI controls to firmware variables
  • update device state via browser
  • deploy the dashboard to a microcontroller

Step 1. Install tools

On Windows, enable WSL (Windows Subsystem for Linux) and start a WSL shell.

Tools we need to install are git, make, gcc, and nodejs. To install them on Linux or WSL, execute:

sudo apt -y update
sudo apt -y install build-essential make git nodejs

To install them on macOS, execute:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install gcc make git node

Step 2. Clone the Mongoose repository

git clone https://github.com/cesanta/mongoose
cd mongoose

Step 3. make watch

Change "minimal" to "full" if you want to use a full example:

cd tutorials/device-dashboard/minimal
make watch

This command builds and runs the project, then watches for changes. It starts a listener on port 8000. Open a browser and go to http://localhost:8000. You should see a dashboard Web UI.

When you make changes, make watch rebuilds and restarts the server. This enables a fast modify-build loop. From this point on, you should edit dashboard.c and dashboard.html until you're happy with the result

Step 4. Make changes

You can make changes to both dashboard.c and dashboard.html manually - or, you can use AI for that. The easy way is AI spec-driven development.

The quick way to do it is to run a prompt like this:

Read and apply https://github.com/cesanta/mongoose/blob/master/resources/AGENTS.md

Change dashboard.html, make HTML theme dark

If you want to make changes manually, Follow the C API reference and example implementation for dashboard.c edits.

For dashboard.html edits,

  • checkboxes, inputs, selects: add data-bind="FIELDSET.FIELD" to to bind them to a specific field. Optionally, specify data-autosave="1" to send a change immediately after modification, without pressing a "save" button
  • any other element: add data-bind="FIELDSET.FIELD" to replace its contents with the curent value of the field. You can achieve the same with the ${...} expression
  • buttons: add data-save="FIELDSET" to create a "save" button for a given field set. If any value in the field set is edited by the bound control, a button would activate, otherwise it'll stay disabled
  • buttons: add data-cancel="FIELDSET" to create a "cancel" button, similar to "save"
  • ${...} expressions: add those anywhere

Here is the example of binding a toggle button to a device variable:

<input type="checkbox" data-bind="leds.led1" data-autosave="1" class="toggle" />

Here is the example of using ${...} expressions:

<div>LED ${status.led1 ? 'ON' : 'OFF'}</div>

When the expression is evaluated, its evaluation context contains your Dashboard data, and all global JS variables like window. For example, ${settings.volume + 1} will be evaluated exactly as you think.

Expressions are useful for the conditional display: expression can add CSS classes, or CSS styles depending on some conditions. For example, this shows or hides the warning depedning on the metrics.ram condition:

<span class="${metrics.ram <= 30 ? 'alert': 'hidden'}">low RAM !</span>

Step 5. Build for the embedded target

Integrate Mongoose into your firmware project, see Integration Guide for details. If you are using STM32, a detailed guide is available from st.com Community Knowledge Base article at STM32H7 Ethernet with Mongoose: Getting started guide

Copy dashboard.c and file_data.c to your firmware project.

In your main.c file, make your Mongoose event loop to look like this:

#include "mongoose.h"

void run_mongoose(void) {
  struct mg_mgr mgr;

  mg_mgr_init(&mgr);
  mg_dash_init(&mgr);

  for (;;) {
    mg_mgr_poll(&mgr, 10);
    mg_dash_poll(&mgr);
  }
}

In the reader / writer functions, add your hardware-specific snippets, for example

#if MG_ARCH == MG_ARCH_CUBE
  s_led1 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);  // Read LED state into field
#endif

Build, flash, and you dashboard should be running on your microcontroller.

References

FAQ

Does this work on STM32?
Yes, you can integrate the dashboard into STM32 firmware using Mongoose. In fact, you can integrate it on any other microcontroller, too. See, for example, Mongoose running on Nano Every with 6k RAM.

Does this require a frontend framework?
No. The dashboard uses plain HTML with data bindings.

How does UI communicate with firmware?
Via JSON-RPC over WebSocket.

Can I use AI to modify the dashboard?
Yes. Modify ai.spec.md and use an AI editor to apply changes.

Contact

If you have questions, contact us at tech-support at cesanta dot com.