# 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 `_s` key 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 plant 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_s` rises at **Heat rate (°C / min)** (integer, default 1) - At **Thermostat max (°C)** (default 65) heating stops, `rlyInSel_s` goes to 0, and `boostNowHw` is written back to ThingsBoard as 0 - Live state is published as `heatsink_s`, `rlyInSel_s`, `auxOn_s`, `auxRelay_s` ## 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 (%) - `soc_s` — SoC fallback (%) - `batteryPercent_s` — battery percent fallback (%) - `auxOn_s` — aux on (0/1) - `auxRelay_s` — aux relay (0/1) - `rlyInSel_s` — control load heating (0/1) - `xCntlLoadState` — control load state (integer; no `_s` suffix) ### Power (W) - `solarPower_s` — solar generation - `loadPower_s` — home load - `hotWaterInPower_s` — hot water in - `hotWaterOutPower_s` — hot water out - `batteryPower_s` — battery power (negative = charging, positive = discharging) - `gridPower_s` — grid power (positive = importing, negative = exporting) ### Energy flows (W) - `solarToLoadPower_s` - `solarToHotWaterPower_s` - `solarToBatteryPower_s` - `solarToGridPower_s` - `gridToLoadPower_s` - `gridToHotWaterPower_s` - `gridToBatteryPower_s` - `batteryToLoadPower_s` - `batteryToHotWaterPower_s` - `batteryToGridPower_s` ### Energy today (Wh) - `solarToday_s` - `loadToday_s` - `hotWaterInToday_s` - `hotWaterOutToday_s` - `solarToLoadToday_s` - `solarToHotWaterToday_s` - `solarToGridToday_s` - `solarToBatteryToday_s` - `gridToLoadToday_s` - `gridToHotWaterToday_s` - `gridToBatteryToday_s` - `batteryToLoadToday_s` - `batteryToHotwaterToday_s` — note the lowercase `w` in `Hotwater` - `batteryToGridToday_s` - `batteryExportToday_s` - `batteryImportToday_s` - `gridImport_s` - `gridExport_s` ### Validity - `vPwrReadings_s` — bitmask of valid live power keys (`65535` = bits 0–15 all valid) ## Setup ```bash 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](http://localhost:3080). Optional env: - `PORT` — portal port (default `3080`) - `TB_HTTP_URL` — ThingsBoard REST origin (default `https://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` — default `DVgo` - `TB_MQTT_URL` — default `mqtts://tb-dvgo.datavolt.au:8883` (TLS). Port 1883 is not reachable from outside the TB host. - `TB_MQTT_INSECURE` — `true` to skip TLS certificate verification - `PORTAL_USER` / `PORTAL_PASSWORD` — basic auth for the control API - `ALLOWED_IPS` — comma-separated allowlist for `/api` - `TELEMETRY_INTERVAL_MS` — default `5000` - `DB_PATH` — default `./data/emulator.db` If a device already exists in ThingsBoard, paste its MQTT access token in the add form and skip REST provisioning. If create times out, the portal can look the device up by name and adopt it.