diverter-device-emulator/public/index.html
2026-10-19 17:02:27 +01:00

382 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DVgo Device Emulator</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Outfit:wght@500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<div id="login-overlay" class="overlay hidden">
<form id="login-form" class="login-card" autocomplete="on">
<p class="eyebrow">Control portal</p>
<h1>Sign in</h1>
<p class="muted">Basic auth for the emulator API. Credentials stay in this browser session.</p>
<label>
Username
<input id="login-user" name="username" required />
</label>
<label>
Password
<input id="login-pass" name="password" type="password" required />
</label>
<p id="login-error" class="error hidden"></p>
<button type="submit">Continue</button>
</form>
</div>
<header class="topbar">
<div>
<h1>DVgo device emulator</h1>
</div>
<div class="topbar-meta">
<div id="local-time" class="local-time">Current local time: --:--:--</div>
<div id="tb-status" class="status-pill off">ThingsBoard</div>
<div id="mqtt-status" class="status-pill off">MQTT</div>
</div>
</header>
<main class="layout">
<aside class="sidebar">
<div class="panel-head">
<div class="panel-title">
<h2>Devices</h2>
<span id="device-count" class="device-count">0/100</span>
</div>
<div class="device-menu">
<button
id="device-menu-toggle"
type="button"
class="device-menu-button"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="device-menu"
aria-label="Device options"
>
<span class="device-menu-icon" aria-hidden="true"></span>
</button>
<div id="device-menu" class="device-menu-panel hidden" role="menu">
<button id="add-open" type="button" class="primary" role="menuitem">Add</button>
<button id="select-all" type="button" role="menuitem">Select all</button>
<button id="deselect-all" type="button" role="menuitem">Deselect all</button>
<button id="bulk-disable" type="button" role="menuitem">Disable selected</button>
<button id="bulk-enable" type="button" role="menuitem">Enable selected</button>
<button id="bulk-delete" type="button" class="danger" role="menuitem">Delete selected</button>
</div>
</div>
</div>
<p id="bulk-status" class="muted"></p>
<div id="device-list" class="device-list"></div>
</aside>
<section class="workspace">
<div id="empty-state" class="empty-state">
<h2>No device selected</h2>
<p>Add a virtual DVgo device, then use the dashboard to publish live *_s telemetry to ThingsBoard.</p>
</div>
<div id="dashboard" class="dashboard hidden">
<div class="dash-head">
<div>
<p id="dash-type" class="eyebrow"></p>
<h2 id="dash-name"></h2>
<p id="dash-id" class="mono muted"></p>
</div>
<button id="delete-one" type="button" class="danger">Delete</button>
</div>
<div class="control-block">
<button id="toggle-mqtt" type="button" class="primary">Connect</button>
<span id="mqtt-spinner" class="spinner spinner-inline hidden" aria-hidden="true"></span>
<p class="connection-response">
connection response:
<span id="connection-response">—</span>
</p>
</div>
<div class="control-block">
<button id="toggle-telemetry" type="button" class="primary">Start telemetry</button>
</div>
<section class="group plant-panel">
<h3>Virtual Diverter</h3>
<p class="muted">Follows ThingsBoard shared attributes from the apps: one-shot boost (boostNowHw / boostNowAux) and HW/aux schedules. Schedule saves are polled every 5s while MQTT is connected.</p>
<section class="plant-card">
<div class="plant-card-head">
<h4>Solar</h4>
<span class="lamp" data-power-lamp="solar" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="solar"></output>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Power Supply (W)</span>
<output data-power-value="solar">0</output>
</div>
<label>
New value (W)
<input id="power-solar" data-power-input="solar" type="number" min="0" max="100000" step="1" value="0" />
</label>
<button type="button" data-power-update="solar">Update</button>
</div>
</section>
<section class="plant-card">
<div class="plant-card-head">
<h4>Grid</h4>
<span class="lamp" data-power-lamp="grid" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="grid"></output>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Power Draw (W)</span>
<output data-power-value="grid">0</output>
</div>
<p class="muted plant-note">Calculated from solar, home load, hot water, aux, and battery.</p>
</div>
</section>
<section class="plant-card">
<div class="plant-card-head">
<h4>Home load</h4>
<span class="lamp" data-power-lamp="home" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="home"></output>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Power Draw (W)</span>
<output data-power-value="home">0</output>
</div>
<label>
New value (W)
<input id="power-home" data-power-input="home" type="number" min="0" max="100000" step="1" value="0" />
</label>
<button type="button" data-power-update="home">Update</button>
</div>
</section>
<section class="plant-card">
<div class="plant-card-head">
<h4>Hot water</h4>
<span id="hw-lamp" class="lamp" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="hotWater"></output>
</div>
<div class="schedule-board" id="hw-schedule">
<div class="schedule-line" data-schedule-line="hw-0">
<span class="schedule-label">Schedule 0</span>
<span class="schedule-times" data-schedule-times="hw-0">— – —</span>
</div>
<div class="schedule-line" data-schedule-line="hw-1">
<span class="schedule-label">Schedule 1</span>
<span class="schedule-times" data-schedule-times="hw-1">— – —</span>
</div>
<p class="schedule-note" data-schedule-note="hw"></p>
</div>
<button type="button" id="hw-boost" class="plant-boost primary">Hot Water Boost Start</button>
<div class="plant-setting">
<div class="plant-current">
<span>Tank Temp °C</span>
<output id="tank-temp">—</output>
</div>
<label>
New value
<input id="tank-temp-input" type="number" min="0" max="99" step="0.1" />
</label>
<button type="button" data-plant-update="tank">Update</button>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Heat Rate (°C/min)</span>
<output id="heat-rate-current">1</output>
</div>
<label>
New value
<input id="heat-rate" type="number" min="0" max="20" step="1" />
</label>
<button type="button" data-plant-update="heat">Update</button>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Thermostat max (°C)</span>
<output id="thermostat-current">65</output>
</div>
<label>
New value
<input id="thermostat" type="number" min="0" max="99" step="1" />
</label>
<button type="button" data-plant-update="thermostat">Update</button>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Power Draw (W)</span>
<output data-power-value="hotWater">0</output>
</div>
<label>
New value
<input id="power-hot-water" data-power-input="hotWater" type="number" min="0" max="100000" step="1" value="1000" />
</label>
<button type="button" data-power-update="hotWater">Update</button>
</div>
</section>
<section class="plant-card">
<div class="plant-card-head">
<h4>Aux</h4>
<span id="aux-lamp" class="lamp" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="aux"></output>
</div>
<div class="schedule-board" id="aux-schedule">
<div class="schedule-line" data-schedule-line="aux-0">
<span class="schedule-label">Schedule 0</span>
<span class="schedule-times" data-schedule-times="aux-0">— – —</span>
</div>
<div class="schedule-line" data-schedule-line="aux-1">
<span class="schedule-label">Schedule 1</span>
<span class="schedule-times" data-schedule-times="aux-1">— – —</span>
</div>
<p class="schedule-note" data-schedule-note="aux"></p>
</div>
<button type="button" id="aux-boost" class="plant-boost primary">Aux Boost Start</button>
<div class="plant-setting">
<div class="plant-current">
<span>Power Draw (W)</span>
<output data-power-value="aux">0</output>
</div>
<label>
New value
<input id="power-aux" data-power-input="aux" type="number" min="0" max="100000" step="1" value="1000" />
</label>
<button type="button" data-power-update="aux">Update</button>
</div>
</section>
<section class="plant-card">
<div class="plant-card-head">
<h4>Battery</h4>
<span id="batt-lamp" class="lamp" aria-hidden="true"></span>
<output class="plant-status" data-plant-status="battery"></output>
</div>
<button type="button" id="batt-toggle" class="plant-boost primary">Battery Off</button>
<div class="plant-setting">
<div class="plant-current">
<span>Power rate (W / min)</span>
<output id="charge-rate-current">500</output>
</div>
<label>
New value
<input id="charge-rate" type="number" min="0" max="100000" step="1" />
</label>
<button type="button" data-plant-update="charge">Update</button>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Capacity (W)</span>
<output id="battery-total-current">5000</output>
</div>
<label>
New value
<input id="battery-total" type="number" min="0" max="100000" step="1" value="5000" />
</label>
<button type="button" data-plant-update="battery-total">Update</button>
</div>
<div class="plant-setting">
<div class="plant-current">
<span>Actual (W)</span>
<output id="battery-actual-current">0</output>
</div>
<label>
New value
<input id="battery-actual" type="number" min="0" max="100000" step="1" value="0" />
</label>
<button type="button" data-plant-update="battery-actual">Update</button>
</div>
</section>
</section>
<section class="group publish-panel">
<h3>Publish</h3>
<div class="meta-row publish-fields">
<label>
Publish interval (ms)
<input id="interval-input" type="number" min="5000" step="500" />
</label>
<p id="last-published" class="muted"></p>
</div>
<div class="publish-actions">
<button id="publish-now" type="button">Publish now</button>
<button id="publish-select-all" type="button">Select all</button>
<button id="publish-deselect-all" type="button">Deselect all</button>
</div>
<p class="muted publish-note">Publish now sends a single telemetry payload. It does not start the repeating loop.</p>
</section>
<div id="telemetry-groups"></div>
<details class="rpc-log">
<summary>Last RPC / shared attributes</summary>
<pre id="rpc-pre">No RPC yet.</pre>
</details>
</div>
</section>
</main>
<div id="add-modal" class="overlay hidden">
<form id="add-form" class="login-card">
<div id="add-busy" class="busy hidden" aria-live="polite" aria-busy="false">
<span class="spinner" aria-hidden="true"></span>
<p id="add-busy-text">Waiting for ThingsBoard…</p>
</div>
<p class="eyebrow">New virtual device</p>
<h2 id="add-title">Add to emulator</h2>
<p id="add-device-count" class="device-count">0/100 devices</p>
<p id="add-help" class="muted">Creates a new device in ThingsBoard and this emulator.</p>
<label class="add-existing-toggle">
<input id="add-existing" class="device-check" type="checkbox" />
Add existing device
</label>
<div id="add-create-fields">
<label>
Name / serial
<input id="add-name" name="name" placeholder="EMU-AABBCCDDEEFF" />
</label>
<label>
Label
<input id="add-label" name="label" placeholder="Kitchen emulator" />
</label>
<label>
Type
<input id="add-type" name="type" placeholder="DVgo" value="DVgo" />
</label>
<label>
Device profile
<select id="add-profile"></select>
</label>
</div>
<label id="add-token-field" class="hidden">
Access token
<input id="add-token" name="accessToken" placeholder="MQTT access token from ThingsBoard" />
</label>
<p id="add-error" class="error hidden"></p>
<div class="row-actions">
<button type="button" id="add-cancel">Cancel</button>
<button type="submit" id="add-submit" class="primary">Create device</button>
</div>
</form>
</div>
<div id="confirm-modal" class="overlay hidden">
<div class="login-card">
<p class="eyebrow">ThingsBoard timeout</p>
<h2 id="confirm-title">Save device anyway?</h2>
<p id="confirm-message" class="muted"></p>
<div class="row-actions">
<button type="button" id="confirm-cancel">Don't save</button>
<button type="button" id="confirm-ok" class="primary">Save to emulator</button>
</div>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>