devicescript-dashboard-web-integracion

How to create a web dashboard for DeviceScript

  • 4 min

A web dashboard allows you to query the device status and send commands from the browser. In this example, we will use MQTT as an intermediary between the interface and the board.

Seeing data in the VS Code console is fine for debugging, and using an external MQTT panel is practical. But often we want a custom web page, with our own buttons, our own charts, and our own organized visual chaos.

The tempting idea would be to put a complete web server inside the ESP32. It’s possible in other environments, yes. But in DeviceScript, as of today, the cleanest and most documented path is different: use an intermediary.

Solution Architecture

We are going to separate responsibilities:

The DeviceScript board: Reads sensors, controls actuators, and publishes/receives MQTT messages.

The MQTT broker: Acts as a bridge between the board and the outside world.

The web dashboard: Runs in the browser and sends commands via MQTT.

This is very common in IoT. The microcontroller doesn’t need to serve HTML, CSS, images, and JavaScript. It just needs to do what it does best: measure, act, and send small messages.

Board Code

Let’s create a very simple example. The board will publish its status and listen for commands to turn an LED on or off.

import { deviceIdentifier } from "@devicescript/core"
import { startLightBulb } from "@devicescript/servers"
import { startMQTTClient } from "@devicescript/net"
import { pins } from "@dsboard/adafruit_qt_py_c3"

const led = startLightBulb({
    pin: pins.A0_D0,
})

const deviceId = deviceIdentifier("self")
const topicState = `devs/${deviceId}/state`
const topicCommand = `devs/${deviceId}/cmd/led`

const mqtt = await startMQTTClient({
    host: "broker.hivemq.com",
    proto: "tcp",
    port: 1883,
})

await mqtt.subscribe(topicCommand, async msg => {
    const command = msg.content.toString("utf-8")

    if (command === "on") {
        await led.on()
    } else if (command === "off") {
        await led.off()
    } else if (command === "toggle") {
        await led.toggle()
    }
})

setInterval(async () => {
    const brightness = await led.intensity.read()

    await mqtt.publish(topicState, JSON.stringify({
        id: deviceId,
        led: brightness,
        time: Date.now(),
    }))
}, 2000)
Copied!

The topic includes the device identifier. This prevents two boards using the same public broker from overwriting each other’s messages.

A public broker is fine for tests and demonstrations. For a real project, use your own broker with authentication and, if the board supports it, TLS.

Web Dashboard

In the browser, we cannot use “pure” MQTT over TCP. The browser lives in its world of HTTP, WebSocket, and its security rules (which are there to protect us, even though sometimes you want to argue with them).

Therefore, the web dashboard typically connects to the broker via MQTT over WebSocket.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>DeviceScript Dashboard</title>
  <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
</head>
<body>
  <h1>DeviceScript Dashboard</h1>

  <button id="on">Turn On</button>
  <button id="off">Turn Off</button>
  <button id="toggle">Toggle</button>

  <pre id="status">Waiting for data...</pre>

  <script>
    const deviceId = "CHANGE_THIS_TO_YOUR_ID"
    const topicState = `devs/${deviceId}/state`
    const topicCommand = `devs/${deviceId}/cmd/led`

    const client = mqtt.connect("wss://broker.hivemq.com:8884/mqtt")

    client.on("connect", () => {
      client.subscribe(topicState)
    })

    client.on("message", (topic, payload) => {
      document.querySelector("#status").textContent = payload.toString()
    })

    document.querySelector("#on").onclick = () => client.publish(topicCommand, "on")
    document.querySelector("#off").onclick = () => client.publish(topicCommand, "off")
    document.querySelector("#toggle").onclick = () => client.publish(topicCommand, "toggle")
  </script>
</body>
</html>
Copied!

It’s not the prettiest HTML in the world (no one is going to put it in a museum), but it demonstrates the important idea: the browser and the board do not speak directly to each other. They coordinate through the broker.

Taking it to a Real Project

For a demo, a public broker and a standalone HTML file are sufficient. For a real project, it’s worth stepping up a level:

  • Your own broker: Mosquitto, EMQX, HiveMQ, or another alternative that fits the project.
  • Authentication: username and password as a minimum.
  • Organized topics: house/livingroom/temperature, house/livingroom/relay/cmd, etc.
  • Real frontend: React, Vue, Svelte, or a simple page with vanilla JavaScript.
  • Optional backend: Node-RED, Home Assistant, or a custom service to store history.

DeviceScript also includes a Development Gateway to communicate devices with external services. It can serve as an additional layer when you need messaging or script deployment, although you should evaluate its fit considering that the project is no longer maintained.