WebSocket telemetry stream¶
The dashboard subscribes to a live telemetry stream that pushes the
same snapshot the GET /api/dersim/overview endpoint returns,
but driven by simulator ticks instead of HTTP polling. Programmatic
test harnesses that want low-latency telemetry should use the
WebSocket too — it cuts the polling cost and avoids races between
your tick rate and the simulator's.
Endpoint¶
ws://<host>:<website_port>/api/dersim/ws/telemetry
The dashboard connects with browser-relative URL; for an external
client it's ws://localhost:8111/api/dersim/ws/telemetry against the
default bind.
Connection lifecycle¶
- Connect. Open a standard WebSocket against the URL above. No authentication, no handshake handshake — the simulator accepts the upgrade and immediately moves into the message loop.
- Initial payload. As soon as the connection is accepted, the
server sends the most-recent cached telemetry payload (the
value the last simulator tick computed). This gives you a known
starting point without having to also call
/overview. - Tick-driven push. Every simulator tick (~1.5 s by default,
matched to the DERSimAC
periodic_time) the server broadcasts the freshly-computed snapshot to every connected client. - Backpressure / drop policy. If a client doesn't read fast enough, the server drops the oldest payload — telemetry is sample-not-event, so missing a tick is fine and never blocks the simulator's main loop.
- Disconnect. Either side can close at any time. The server logs the disconnect at INFO. No reconnect logic is required from the server side; clients should reconnect with their own backoff if they care about gaps.
Payload shape¶
Each WebSocket message is the same JSON object as
GET /api/dersim/overview returns. The full snapshot type lives at
/api/dersim/overview for canonical schema (visible in the
Swagger UI); the relevant top-level keys:
| Key | Type | Notes |
|---|---|---|
metrics |
object | power_kw, reactive_power_kvar, apparent_power_kva, voltage_ll_v, voltage_ln_v, current_a, frequency_hz, power_factor, dc_power_kw, dc_voltage_v, dc_current_a, state_of_charge_pct, soc_reserve_min_pct, soc_reserve_max_pct, soc_min_pct, soc_max_pct, plus a timestamp string |
pq |
object | active_kw, reactive_kvar, apparent_kva, angle_deg |
status |
object | service_state, inverter_state, connection_state, optional message |
clock_time |
string | Simulator wall-clock (HH:MM:SS) |
dc_mode |
string | One of pv, battery, wind, fuelcell, hybrid-dc, none |
dc_port_count |
int | Number of DC ports populated on the SunSpec 714 side |
device_type |
string | Echoes the --device_type flag |
device_profile_name |
string | null | Profile name when --evse_profile or -f FILE was set |
asset_type |
string | Asset-type token (battery, wind, fuelcell, hybrid-dc, pv, evse) |
device_index |
int | Per-device index when -N > 1 |
For multi-device simulators (-N > 1) every connected device gets
its own broadcast — see the device filter below.
Device filter¶
Multi-device simulators publish one payload per device per tick. Most consumers only care about a single device; pass the index as a query parameter to skip the rest server-side:
ws://<host>:8111/api/dersim/ws/telemetry?device=2
device is an integer matching the simulator's index (0-based on
-N > 1 runs). Without the parameter, the client receives every
device's payload as a stream and has to filter on its end. The
parameter is server-side filtering, not subscription — the
connection remains a single WebSocket regardless.
Connection example (Python websockets)¶
import asyncio
import json
import websockets
async def main():
async with websockets.connect("ws://localhost:8111/api/dersim/ws/telemetry") as ws:
while True:
payload = json.loads(await ws.recv())
print(f"{payload['clock_time']}: {payload['metrics']['power_kw']:.2f} kW")
asyncio.run(main())
Connection example (Node.js ws)¶
const WebSocket = require("ws");
const ws = new WebSocket("ws://localhost:8111/api/dersim/ws/telemetry?device=0");
ws.on("open", () => console.log("connected"));
ws.on("message", (raw) => {
const payload = JSON.parse(raw);
console.log(`${payload.clock_time}: ${payload.metrics.power_kw.toFixed(2)} kW`);
});
Why subscribe instead of poll¶
Three reasons real harnesses move off /overview polling once they
get past prototyping:
- Lower latency. The WebSocket fires as soon as the tick computes; HTTP polling adds your polling interval to the observed latency.
- No lock contention. The HTTP handler reads the same shared snapshot the WebSocket broadcasts, so they don't race; but every request still walks the FastAPI middleware chain. The WebSocket bypasses that on every tick after the initial accept.
- No drift between samples. Polling at 250 ms vs ticking at 1.5 s means you get the same value 6× in a row, with the occasional jump. The WebSocket gives you exactly one payload per tick — no duplicates, no drift.
When polling is still the right call¶
- One-shot scripts that just want a single reading — no point opening a WebSocket for a single value.
- Long-running cron-style harnesses that sample at minute or hour cadence — TCP-keepalive maintenance cost exceeds the HTTP polling cost.
- Burst-mode scenarios where you want telemetry only around a specific action (set a wind override, see the next 30 s of rotor RPM, then go away).
For everything else — interactive dashboards, real-time control loops, anomaly detectors, charting — the WebSocket is what you want.