Files
spore/docs/StreamingAPI.md
Patrick Balsiger 2aa887a037 docs(streaming): add 'Things to consider' on high-frequency WS usage and throttling
Notes on heap fragmentation, UDP saturation, throttling/coalescing, buffer reuse, yielding, and payload sizing for reliable streaming/broadcast.
2025-09-28 22:10:18 +02:00

3.1 KiB
Raw Blame History

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://<device-ip>/ws

Message Format

  • Client → Device
{
  "event": "<event-name>",
  "payload": "<json-string>" | { /* inline JSON */ }
}
  • Device → Client
{
  "event": "<event-name>",
  "payload": "<json-string>"
}

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:<clientId>"
  • 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("<name>", ...).

Examples

  1. Set a solid color on NeoPattern:
{
  "event": "api/neopattern/color",
  "payload": { "color": "#FF0000", "brightness": 128 }
}
  1. Broadcast a cluster event (delegated to core):
{
  "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:

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 ≥50100 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.