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/orfrontend/.
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.jsclient 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/wsto 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.
- Connection tab: device name, access token (per-device credential),
4. MQTT engine
Follows the ThingsBoard MQTT device API.
- Auth: MQTT username = device access token, password empty (as the firmware and the
existingscripts/thingsboard-test/device-simulator.mjsdo). - Telemetry: publish JSON
{ key: value }tov1/devices/me/telemetryon 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}.
- Client attributes ->
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.