diff --git a/src/spore/core/ApiServer.cpp b/src/spore/core/ApiServer.cpp index 5397ce3..767ceb6 100644 --- a/src/spore/core/ApiServer.cpp +++ b/src/spore/core/ApiServer.cpp @@ -109,7 +109,7 @@ void ApiServer::setupWebSocket() { JsonDocument doc; DeserializationError err = deserializeJson(doc, (const char*)data, len); if (!err) { - LOG_INFO("API", "Received event: " + String(doc["event"].as())); + LOG_DEBUG("API", "Received event: " + String(doc["event"].as())); String eventName = doc["event"].as(); String payloadStr; if (doc["payload"].is()) { diff --git a/src/spore/core/ClusterManager.cpp b/src/spore/core/ClusterManager.cpp index 1d6e2e9..e86982e 100644 --- a/src/spore/core/ClusterManager.cpp +++ b/src/spore/core/ClusterManager.cpp @@ -19,7 +19,7 @@ ClusterManager::ClusterManager(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx IPAddress ip = WiFi.localIP(); IPAddress mask = WiFi.subnetMask(); IPAddress bcast(ip[0] | ~mask[0], ip[1] | ~mask[1], ip[2] | ~mask[2], ip[3] | ~mask[3]); - LOG_INFO("Cluster", String("Broadcasting CLUSTER_EVENT to ") + bcast.toString() + " len=" + String(jsonStr->length())); + LOG_DEBUG("Cluster", String("Broadcasting CLUSTER_EVENT to ") + bcast.toString() + " len=" + String(jsonStr->length())); this->ctx.udp->beginPacket(bcast, this->ctx.config.udp_port); String msg = String(ClusterProtocol::CLUSTER_EVENT_MSG) + ":" + *jsonStr; this->ctx.udp->write(msg.c_str()); @@ -204,7 +204,7 @@ void ClusterManager::onNodeInfo(const char* msg) { } } } else { - LOG_DEBUG("Cluster", String("Failed to parse NODE_INFO JSON from ") + senderIP.toString()); + LOG_WARN("Cluster", String("Failed to parse NODE_INFO JSON from ") + senderIP.toString()); } } } @@ -216,11 +216,11 @@ void ClusterManager::onClusterEvent(const char* msg) { LOG_DEBUG("Cluster", "CLUSTER_EVENT received with empty payload"); return; } - LOG_INFO("Cluster", String("CLUSTER_EVENT raw from ") + ctx.udp->remoteIP().toString() + " len=" + String(strlen(jsonStart))); + LOG_DEBUG("Cluster", String("CLUSTER_EVENT raw from ") + ctx.udp->remoteIP().toString() + " len=" + String(strlen(jsonStart))); JsonDocument doc; DeserializationError err = deserializeJson(doc, jsonStart); if (err) { - LOG_DEBUG("Cluster", String("Failed to parse CLUSTER_EVENT JSON from ") + ctx.udp->remoteIP().toString()); + LOG_ERROR("Cluster", String("Failed to parse CLUSTER_EVENT JSON from ") + ctx.udp->remoteIP().toString()); return; } // Robust extraction of event and data @@ -249,7 +249,7 @@ void ClusterManager::onClusterEvent(const char* msg) { } std::string eventKey(eventStr.c_str()); - LOG_INFO("Cluster", String("Firing event '") + eventStr + "' with dataLen=" + String(data.length())); + LOG_DEBUG("Cluster", String("Firing event '") + eventStr + "' with dataLen=" + String(data.length())); ctx.fire(eventKey, &data); } diff --git a/test/README b/test/README index a065e60..6c9555f 100644 --- a/test/README +++ b/test/README @@ -48,3 +48,17 @@ Example: node test/ws-cluster-broadcast-color.js ws://10.0.1.53/ws ``` +### 4. WS Cluster Broadcast Rainbow (`test/ws-cluster-broadcast-rainbow.js`) + +Broadcasts a smooth rainbow color transition over WebSocket using `cluster/broadcast` and the `api/neopattern/color` event. Update rate defaults to `UPDATE_RATE` in the script (e.g., 100 ms). + +**Usage:** +``` +node test/ws-cluster-broadcast-rainbow.js ws:///ws +``` +Example: +``` +node test/ws-cluster-broadcast-rainbow.js ws://10.0.1.53/ws +``` +Note: Very fast update intervals (e.g., 10 ms) may saturate links or the device. + diff --git a/test/ws-cluster-broadcast-rainbow.js b/test/ws-cluster-broadcast-rainbow.js new file mode 100644 index 0000000..f0c307f --- /dev/null +++ b/test/ws-cluster-broadcast-rainbow.js @@ -0,0 +1,71 @@ +// WebSocket client to broadcast smooth rainbow color changes across the cluster +// Usage: node ws-cluster-broadcast-rainbow.js ws:///ws + +const WebSocket = require('ws'); + +const url = process.argv[2] || 'ws://127.0.0.1/ws'; +const ws = new WebSocket(url); + +function hsvToRgb(h, s, v) { + const c = v * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m = v - c; + let r = 0, g = 0, b = 0; + if (h < 60) { r = c; g = x; b = 0; } + else if (h < 120) { r = x; g = c; b = 0; } + else if (h < 180) { r = 0; g = c; b = x; } + else if (h < 240) { r = 0; g = x; b = c; } + else if (h < 300) { r = x; g = 0; b = c; } + else { r = c; g = 0; b = x; } + const R = Math.round((r + m) * 255); + const G = Math.round((g + m) * 255); + const B = Math.round((b + m) * 255); + return { r: R, g: G, b: B }; +} + +function toHex({ r, g, b }) { + const h = (n) => n.toString(16).padStart(2, '0').toUpperCase(); + return `#${h(r)}${h(g)}${h(b)}`; +} + +let hue = 0; +const SAT = 1.0; // full saturation +const VAL = 1.0; // full value +const BRIGHTNESS = 128; +const UPDATE_RATE = 100; // 100 ms + +let timer = null; + +ws.on('open', () => { + console.log('Connected to', url); + // UPDATE_RATE ms updates (10 Hz). Be aware this can saturate slow links. + timer = setInterval(() => { + const rgb = hsvToRgb(hue, SAT, VAL); + const color = toHex(rgb); + const envelope = { + event: 'api/neopattern/color', + data: { color, brightness: BRIGHTNESS } + }; + const msg = { event: 'cluster/broadcast', payload: envelope }; + try { + ws.send(JSON.stringify(msg)); + } catch (_) {} + hue = (hue + 2) % 360; // advance hue (adjust for speed) + }, UPDATE_RATE); +}); + +ws.on('message', (data) => { + // Optionally throttle logs: comment out for quieter output + // console.log('WS:', data.toString()); +}); + +ws.on('error', (err) => { + console.error('WebSocket error:', err.message); +}); + +ws.on('close', () => { + if (timer) clearInterval(timer); + console.log('WebSocket closed'); +}); + +