docs: add ClusterBroadcast guide for CLUSTER_EVENT and cluster/broadcast flow

Documents centralized cluster broadcasting: service -> ctx.fire("cluster/broadcast", eventJson) -> UDP CLUSTER_EVENT -> peer ctx.fire(event, data). Includes message format, service/core responsibilities, logging, networking notes, and troubleshooting.
This commit is contained in:
2025-09-28 13:45:10 +02:00
parent 0e6adc999f
commit b9b91d71b5

91
docs/ClusterBroadcast.md Normal file
View File

@@ -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": "<event-name>",
"data": "<json-string>" // 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<String*>(dataPtr);
if (!jsonStr) return;
JsonDocument doc;
if (deserializeJson(doc, *jsonStr)) return;
JsonObject obj = doc.as<JsonObject>();
// 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 <ip>` 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`.