| data | ||
| deploy | ||
| public | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
DVgo device emulator
Node.js ThingsBoard device emulator with a SQLite-backed web portal. Each virtual device can connect over MQTT, publish live *_s telemetry, react to shared-attribute commands from the apps, and be taken offline without deleting the ThingsBoard record.
Requires Node 22+ (uses the built-in node:sqlite module).
Python historical data generation is not included.
What it does
- Create / delete real ThingsBoard devices via the REST API (delete still removes the emulator row if the TB device is already gone)
- Persist credentials, telemetry, published-key selection, and plant settings in SQLite
- MQTT connect with the device access token as username (
v1/devices/me/telemetry, plus attributes and RPC) - Web portal (
http://localhost:3080) to:- add devices (or attach an existing MQTT access token)
- select devices (select all / deselect all) and enable, disable, or delete
- Connect / Disconnect MQTT (new devices stay idle until you connect)
- start / stop the telemetry loop (Start telemetry is disabled until connected)
- toggle which keys are included in each publish (per-card On, plus select all / deselect all under Publish now)
- edit every live
_skey from the device dashboard - run a virtual plant that follows boost and schedule commands from the apps
On startup, devices already in SQLite are restored. They reconnect only if they were left enabled (not disabled). New devices are created disconnected, with telemetry off.
Virtual Diverter
While MQTT is connected, the device polls ThingsBoard shared attributes every 5 seconds (and also receives live pushes). It understands:
| Source | Keys |
|---|---|
| One-shot boost | boostNowHw, boostNowAux (minutes; written by the apps via TB REST) |
| HW schedule | hwRlyMode 2 or 3, plus hwRlySchedMode0/1StartTime / StopTime (minutes since midnight) |
| Aux schedule | auxRlyMode 2 or 3, plus auxRlySchedMode0/1Start / Stop |
Schedule saves in the apps are SQL-only, so this emulator polls rather than waiting for a TB push.
Behaviour:
- Red / grey lamps for hot water boost and aux boost / divert
- While HW is on,
heatsink_srises at Heat rate (°C / min) (integer, default 1) - At Thermostat max (°C) (default 65) heating stops,
rlyInSel_sgoes to 0, andboostNowHwis written back to ThingsBoard as 0 - Live state is published as
heatsink_s,rlyInSel_s,auxOn_s,auxRelay_s - Battery section is display-only for now: Battery Charging lamp stays off, Charge rate (% / min) defaults to 10
- Solar, grid, home load, hot water, aux, and battery cards set the energy balance in watts. Grid is calculated. Lamps are red for draw, green for supply, and grey for zero. Hot water watts apply only while heating, and aux watts only while aux is on. Those setpoints write the live power and flow telemetry keys.
loadPower_sis home plus aux. Battery power rate is W/min and isbatteryPower_swhile the battery is on (negative when charging, positive when discharging). Charging is an extra load. Discharging is a supply.
Telemetry keys
Each device can publish the following live *_s keys on v1/devices/me/telemetry. Dashboard cards are ordered Status, Power, Energy flows, Energy today, Validity. Only keys with On checked are sent.
Status
heatsink_s— hot water / tank temperature (°C)batterySoc_s— battery state of charge, actual / capacity × 100soc_s— SoC, actual / capacity × 100batteryPercent_s— battery percent, actual / capacity × 100auxOn_s— aux on (0/1)auxRelay_s— aux relay (0/1)rlyInSel_s— control load heating (0/1)xCntlLoadState— control load state (integer; no_ssuffix)
Power (W)
solarPower_s— solar generationloadPower_s— home loadhotWaterInPower_s— hot water inhotWaterOutPower_s— hot water outbatteryPower_s— battery power (negative = charging, positive = discharging)gridPower_s— grid power (positive = importing, negative = exporting)
Energy flows (W)
solarToLoadPower_ssolarToHotWaterPower_ssolarToBatteryPower_ssolarToGridPower_sgridToLoadPower_sgridToHotWaterPower_sgridToBatteryPower_sbatteryToLoadPower_sbatteryToHotWaterPower_sbatteryToGridPower_s
Energy today (Wh)
solarToday_sloadToday_shotWaterInToday_shotWaterOutToday_ssolarToLoadToday_ssolarToHotWaterToday_ssolarToGridToday_ssolarToBatteryToday_sgridToLoadToday_sgridToHotWaterToday_sgridToBatteryToday_sbatteryToLoadToday_sbatteryToHotwaterToday_s— note the lowercasewinHotwaterbatteryToGridToday_sbatteryExportToday_sbatteryImportToday_sgridImport_sgridExport_s
Validity
vPwrReadings_s— bitmask of valid live power keys (65535= bits 0–15 all valid)
Setup
cp .env.example .env
# fill TB_USERNAME / TB_PASSWORD (tenant admin or a user who can create devices)
npm install
npm start
Open http://localhost:3080.
Deploy
Build a Docker image locally and copy it to an Ubuntu host over SSH. See deploy/README.md. From this repo: ./deploy/ship.sh (SSH host diverter-emu). The SQLite file stays on the host in /opt/diverter-device-emulator/data.
Optional env:
PORT— portal port (default3080)TB_HTTP_URL— ThingsBoard REST origin (defaulthttps://tb-dvgo.datavolt.au)TB_DEVICE_PROFILE_ID— defaults to the Diverter / DVgo profile (e6b53240-7fec-11f1-a69d-edbc0b9170ac). The add-device dropdown is preselected to a profile named Diverter or DVgo when ThingsBoard returns one.TB_DEVICE_TYPE— defaultDVgoTB_MQTT_URL— defaultmqtts://tb-dvgo.datavolt.au:8883(TLS). Port 1883 is not reachable from outside the TB host.TB_MQTT_INSECURE—trueto skip TLS certificate verificationPORTAL_HOST— public hostname for HTTPS on the Lightsail deployPORTAL_USER/PORTAL_PASSWORD— login for the portalALLOWED_IPS— comma-separated allowlist for/apiTELEMETRY_INTERVAL_MS— default5000DB_PATH— default./data/emulator.db
If a device already exists in ThingsBoard, tick Add existing device in the add modal and paste its MQTT access token. The emulator looks the device up on the server (id, name, label, type) and attaches it. You can also create by TB name with no token, or save-by-name if create times out.