ThingsBoard Device Simulator - Design

  • Date: 2026-07-12
  • Status: Approved (pending spec review)
  • Author: Hieu Nguyen (with Claude Code)

1. Overview

A standalone local tool - tools/tb-simulator/ - providing a friendly web GUI to
simulate one or more ThingsBoard devices. It publishes telemetry and, optionally,
handles device control over real MQTT to a ThingsBoard broker.

It is a purpose-built, easier-to-use alternative to a generic MQTT client (e.g. MQTTX)
for the specific task of standing in for HMP gateway devices during testing.

Goals

  • Easy-to-use GUI for creating and running simulated devices.
  • Real MQTT transport, matching how the gateway firmware talks to ThingsBoard.
  • Run many simulated devices at once.
  • Persist device configs locally between runs.

Non-goals (deliberately cut)

  • No authentication / multi-user support.
  • No cloud hosting; runs on the developer’s machine only.
  • No packaging as an installable desktop app.
  • No non-numeric telemetry value types (numeric range only).
  • No ThingsBoard REST provisioning; the user pastes device access tokens they already have.
  • Not part of, and does not touch, the platform backend/ or frontend/.

2. Architecture

A browser page cannot open a raw MQTT/TCP connection, so the tool is two cooperating
parts inside one folder:

MQTT bridge (Bun process)

  • Holds the real mqtt.js client connections (one per simulated device).
  • Runs the per-device telemetry publish timers.
  • Handles server-side RPC auto-reply.
  • Owns the JSON persistence file (read on start, written on every change).
  • Exposes a local WebSocket endpoint (/ws) that the GUI uses to send commands and
    receive live events (status changes, RPC log entries).

GUI (React + Vite + Ant Design)

  • The control panel rendered in the browser.
  • Chosen to match the team’s existing stack (frontend/ uses React 19 + Vite + Ant
    Design) so it is polished with minimal effort and familiar to maintain.
  • Communicates with the bridge only over the local WebSocket - it has no other backend.

Run model

  • bun run dev - Vite dev server + bridge run concurrently; Vite proxies /ws to the
    bridge. Used when developing the tool itself.
  • bun run start - builds the GUI once, then a single Bun process serves the static GUI
    and the WebSocket, and auto-opens the browser. This is the normal way to use it.

Data flow

Browser GUI  --(WebSocket /ws: commands)-->  Bun bridge  --(MQTT)-->  ThingsBoard broker
Browser GUI  <--(WebSocket /ws: events)----  Bun bridge  <--(MQTT)--  ThingsBoard broker
                                                  |
                                                  +--> simulator-data.json (persistence)

3. GUI layout

  • Top bar: global broker settings - host, port (default localhost:1883);
    connect-all / disconnect-all actions.
  • Device list (left): a card per device showing name, connection status dot
    (disconnected / connecting / connected / error), a telemetry-running indicator, and
    the control on/off state.
  • Device detail (right, tabbed):
    • Connection tab: device name, access token (per-device credential),
      connect / disconnect.
    • Telemetry tab: dynamically add / remove telemetry keys. Each row: key name, min,
      max, int/float toggle, decimal precision. One publish interval (ms) per device.
      Start / Stop publishing, plus a “publish once” button.
    • Control tab (shown only when the control toggle is ON): the device subscribes to
      server-side RPC. A configurable auto-reply body (default {"success": true}).
      A live log streaming incoming RPC requests and outgoing responses. A small
      manual publish panel to push client attribute updates and client-side RPC by hand.

4. MQTT engine

Follows the ThingsBoard MQTT device API.

  • Auth: MQTT username = device access token, password empty (as the firmware and the
    existing scripts/thingsboard-test/device-simulator.mjs do).
  • Telemetry: publish JSON { key: value } to v1/devices/me/telemetry on the
    device’s interval; each value randomly generated within its configured range,
    respecting int vs float and decimal precision.
  • Control ON: subscribe to v1/devices/me/rpc/request/+. On each request, parse the
    request id from the topic, auto-reply the configured body to
    v1/devices/me/rpc/response/{id}, and emit both request and response to the GUI log.
  • Manual publish:
    • Client attributes -> v1/devices/me/attributes.
    • Client-side RPC -> v1/devices/me/rpc/request/{id}.

Per-device connection lifecycle

disconnected -> connecting -> connected (or error). Telemetry timer and RPC
subscription are (re)established on connected and torn down on disconnect. Status
transitions are pushed to the GUI over the WebSocket.

5. Persistence

A single local simulator-data.json (git-ignored), loaded on start and written on every
change. Nothing leaves the machine.

{
  "broker": { "host": "localhost", "port": 1883 },
  "devices": [
    {
      "id": "uuid",
      "name": "Pond A gateway",
      "token": "DEVICE_ACCESS_TOKEN",
      "controlEnabled": true,
      "autoReplyBody": { "success": true },
      "intervalMs": 5000,
      "telemetryKeys": [
        { "key": "temperature", "min": 27, "max": 30, "type": "float", "precision": 1 },
        { "key": "ph",          "min": 6.8, "max": 8.0, "type": "float", "precision": 2 }
      ]
    }
  ]
}

Access tokens are stored in plaintext locally. This is acceptable for a developer tool
and will be called out in the README.

6. Tech stack and layout

tools/tb-simulator/
  package.json            # own package; scripts: dev, start, build, test, lint
  server/                 # Bun bridge: MQTT engine, WebSocket, persistence, static serve
  web/                    # React + Vite + Ant Design GUI source
  README.md
  simulator-data.json     # git-ignored, created at runtime
  • Bun for install/run (repo standard; never npm/yarn).
  • Biome for lint/format (repo standard; never ESLint/Prettier).
  • mqtt (mqtt.js) for the MQTT client.
  • No dependency on the platform backend or frontend.

7. Testing

TDD for the bridge logic, using the tool’s own test runner:

  • Telemetry value generation: values fall within [min, max], integers vs floats,
    decimal precision honored.
  • RPC handling: request id correctly parsed from v1/devices/me/rpc/request/{id};
    auto-reply published to the matching .../response/{id} topic with the configured body.
  • Persistence: load / save round-trip preserves the data shape.

MQTT socket I/O is a thin adapter over mqtt.js and is verified manually against a real
ThingsBoard instance (local broker or cloud host).

8. Open items

None outstanding. React + Ant Design GUI and the manual-publish panel are both confirmed
in scope.