diff --git a/docs/ClusterBroadcast.md b/docs/ClusterBroadcast.md new file mode 100644 index 0000000..fa5027e --- /dev/null +++ b/docs/ClusterBroadcast.md @@ -0,0 +1,91 @@ +## Cluster Broadcast (CLUSTER_EVENT) + +### Overview + +Spore supports cluster-wide event broadcasting via UDP. Services publish a local event, and the core broadcasts it to peers as a `CLUSTER_EVENT`. Peers receive the event and forward it to local subscribers through the internal event bus. + +- **Local trigger**: `ctx.fire("cluster/broadcast", eventJson)` +- **UDP message**: `CLUSTER_EVENT:{json}` +- **Receiver action**: Parses `{json}` and calls `ctx.fire(event, data)` + +This centralizes network broadcast in core, so services never touch UDP directly. + +### Message format + +- UDP payload prefix: `CLUSTER_EVENT:` +- JSON body: +```json +{ + "event": "", + "data": "" // or an inline JSON object/array +} +``` + +Notes: +- The receiver accepts `data` as either a JSON string or a nested JSON object/array. Nested JSON is serialized back to a string before firing the local event. +- Keep payloads small (UDP, default buffer 512 bytes). + +### Core responsibilities + +- `ClusterManager` registers a centralized handler: + - Subscribes to `cluster/broadcast` to send the provided event JSON over UDP broadcast. + - Listens for incoming UDP `CLUSTER_EVENT` messages and forwards them to local subscribers via `ctx.fire(event, data)`. +- Broadcast target uses subnet-directed broadcast (e.g., `192.168.1.255`) for better reliability. Both nodes must share the same `udp_port`. + +### Service responsibilities + +Services send and receive events using the local event bus. + +1) Subscribe to an event name and apply state from `data`: +```cpp +ctx.on("api/neopattern", [this](void* dataPtr) { + String* jsonStr = static_cast(dataPtr); + if (!jsonStr) return; + JsonDocument doc; + if (deserializeJson(doc, *jsonStr)) return; + JsonObject obj = doc.as(); + // Parse and apply fields from obj +}); +``` + +2) Build a control payload and update locally via the same event: +```cpp +JsonDocument payload; +payload["pattern"] = "rainbow_cycle"; // example +payload["brightness"] = 100; +String payloadStr; serializeJson(payload, payloadStr); +ctx.fire("api/neopattern", &payloadStr); +``; + +3) Broadcast to peers by delegating to core: +```cpp +JsonDocument envelope; +envelope["event"] = "api/neopattern"; +envelope["data"] = payloadStr; // JSON string +String eventJson; serializeJson(envelope, eventJson); +ctx.fire("cluster/broadcast", &eventJson); +``` + +With this flow, services have a single codepath for applying state (the event handler). Broadcasting simply reuses the same payload. + +### Logging + +- Core logs source IP, payload length, and event name for received `CLUSTER_EVENT`s. +- Services can log when submitting `cluster/broadcast` and when applying control events. + +### Networking considerations + +- Ensure all nodes: + - Listen on the same `udp_port`. + - Are in the same subnet (for subnet-directed broadcast). +- Some networks may block global broadcast (`255.255.255.255`). Subnet-directed broadcast is used by default. + +### Troubleshooting + +- If peers do not react: + - Confirm logs show `CLUSTER_EVENT raw from ` on the receiver. + - Verify UDP port alignment and WiFi connection/subnet. + - Check payload size (<512 bytes by default) and JSON validity. + - Ensure the service subscribed to the correct `event` name and handles `data`. + +