## Streaming API (WebSocket) ### Overview The streaming API exposes an event-driven WebSocket at `/ws`. It bridges between external clients and the internal event bus: - Incoming WebSocket JSON `{ event, payload }` → `ctx.fire(event, payload)` - Local events → broadcasted to all connected WebSocket clients as `{ event, payload }` This allows real-time control and observation of the system without polling. ### URL - `ws:///ws` ### Message Format - Client → Device ```json { "event": "", "payload": "" | { /* inline JSON */ } } ``` - Device → Client ```json { "event": "", "payload": "" } ``` Notes: - The device accepts `payload` as a string or a JSON object/array. Objects are serialized into a string before dispatching to local subscribers to keep a consistent downstream contract. - A minimal ack `{ "ok": true }` is sent after a valid inbound message. #### Echo suppression (origin tagging) - To prevent the sender from receiving an immediate echo of its own message, the server injects a private field into JSON payloads: - `_origin: "ws:"` - When re-broadcasting local events to WebSocket clients, the server: - Strips the `_origin` field from the outgoing payload - Skips the originating `clientId` so only other clients receive the message - If a payload is not valid JSON (plain string), no origin tag is injected and the message may be echoed ### Event Bus Integration - The WebSocket registers an `onAny` subscriber to `NodeContext` so that all local events are mirrored to clients. - Services should subscribe to specific events via `ctx.on("", ...)`. ### Examples 1) Set a solid color on NeoPattern: ```json { "event": "api/neopattern/color", "payload": { "color": "#FF0000", "brightness": 128 } } ``` 2) Broadcast a cluster event (delegated to core): ```json { "event": "cluster/broadcast", "payload": { "event": "api/neopattern/color", "data": { "color": "#00FF00", "brightness": 128 } } } ``` ### Reference Implementation - WebSocket setup and bridging are implemented in `ApiServer`. - Global event subscription uses `NodeContext::onAny`. Related docs: - [`ClusterBroadcast.md`](./ClusterBroadcast.md) — centralized UDP broadcasting and CLUSTER_EVENT format ### Things to consider - High-frequency updates can overwhelm ESP8266: - Frequent JSON parse/serialize and `String` allocations fragment heap and may cause resets (e.g., Exception(3)). - UDP broadcast on every message amplifies load; WiFi/UDP buffers can back up. - Prefer ≥50–100 ms intervals; microbursts at 10 ms are risky. - Throttle and coalesce: - Add a minimum interval in the core `cluster/broadcast` handler. - Optionally drop redundant updates (e.g., same color as previous). - Reduce allocations: - Reuse `StaticJsonDocument`/preallocated buffers in hot paths. - Avoid re-serializing when possible; pass-through payload strings. - Reserve `String` capacity when reuse is needed. - Yielding: - Call `yield()` in long-running or bursty paths to avoid WDT. - Packet size: - Keep payloads small to fit `ClusterProtocol::UDP_BUF_SIZE` and reduce airtime.