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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.