diff --git a/include/webserver.h b/include/webserver.h index 8ff10d0de..fdc5a030f 100644 --- a/include/webserver.h +++ b/include/webserver.h @@ -99,6 +99,7 @@ class CWebServer static const std::map settingValidators; AsyncWebServer _server; + AsyncWebSocket _socket; StaticStatistics _staticStats; // Helper functions/templates @@ -199,14 +200,18 @@ class CWebServer }); } + static void onSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, + uint8_t *data, size_t len); + public: CWebServer() - : _server(80), _staticStats() + : _server(80), _socket("/ws"), _staticStats() {} // begin - register page load handlers and start serving pages void begin(); + void EffectsUpdate(); }; // Set value in lambda using a forwarding function. Always returns true diff --git a/platformio.ini b/platformio.ini index 130540b2d..099c85d8a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ ; mesmerizer HUB75 info panel with audio effects, weather, info, etc, [platformio] -default_envs = +default_envs = mesmerizer build_cache_dir = .pio/build_cache ; ================== diff --git a/site/src/context/effectsContext.jsx b/site/src/context/effectsContext.jsx index 53184e5c0..6e9d03b7a 100644 --- a/site/src/context/effectsContext.jsx +++ b/site/src/context/effectsContext.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; const EffectsContext = createContext(undefined); const effectsEndpoint = `${httpPrefix !== undefined ? httpPrefix : ""}/effects`; +const gateway = `ws://${httpPrefix !== undefined ? httpPrefix.replace('http://', '') : window.location.hostname}/ws`; const refreshInterval = 30000; //30 Seconds const EffectsProvider = ({ children }) => { @@ -14,6 +15,28 @@ const EffectsProvider = ({ children }) => { const [activeEffect, setActiveEffect] = useState(0); const [effectTrigger, setEffectTrigger] = useState(false); const [currentEffect, setCurrentEffect] = useState(Number(0)); + + useEffect(() => { + const ws = new WebSocket(gateway); + ws.onopen = () => { + console.log('connected to ws'); + }; + ws.onclose = () => { + console.log('connection to ws closed'); + } + ws.onmessage = (event) => { + try { + console.log('Websocket message'); + const json = JSON.parse(event.data); + console.log('ws data: ', json); + } catch(err) { + console.log('error proccessing ws message', err); + console.log('data:', event.data) + } + } + return () => ws.close(); + }, []) + useEffect(() => { const getDataFromDevice = async (params) => { try { diff --git a/site/src/espaddr.jsx b/site/src/espaddr.jsx index bad3b6270..65fca405d 100644 --- a/site/src/espaddr.jsx +++ b/site/src/espaddr.jsx @@ -1,2 +1,2 @@ -const httpPrefix = process.env.NODE_ENV === "development" ? "http://255.255.255.0" : undefined; +const httpPrefix = process.env.NODE_ENV === "development" ? "http://10.1.5.1" : undefined; export default httpPrefix; \ No newline at end of file diff --git a/src/remotecontrol.cpp b/src/remotecontrol.cpp index 27a8a3897..3ff76a787 100644 --- a/src/remotecontrol.cpp +++ b/src/remotecontrol.cpp @@ -92,12 +92,14 @@ void RemoteControl::handle() { effectManager.ClearRemoteColor(); effectManager.NextEffect(); + g_ptrSystem->WebServer().EffectsUpdate(); return; } else if (IR_BMINUS == result) { effectManager.ClearRemoteColor(); effectManager.PreviousEffect(); + g_ptrSystem->WebServer().EffectsUpdate(); return; } else if (IR_SMOOTH == result) diff --git a/src/webserver.cpp b/src/webserver.cpp index ded871785..2f2c7d5dc 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -191,7 +191,8 @@ void CWebServer::begin() }); _server.begin(); - + _socket.onEvent(onSocketEvent); + _server.addHandler(&_socket); debugI("HTTP server started"); } @@ -212,31 +213,29 @@ long CWebServer::GetEffectIndexFromParam(AsyncWebServerRequest * pRequest, bool return strtol(pRequest->getParam("effectIndex", post, false)->value().c_str(), NULL, 10); } -void CWebServer::GetEffectListText(AsyncWebServerRequest * pRequest) +std::unique_ptr buildEffectListJson() { static size_t jsonBufferSize = JSON_BUFFER_BASE_SIZE; bool bufferOverflow; - debugV("GetEffectListText"); - do { bufferOverflow = false; auto response = std::make_unique(false, jsonBufferSize); - auto& j = response->getRoot(); - auto& effectManager = g_ptrSystem->EffectManager(); + auto &j = response->getRoot(); + auto &effectManager = g_ptrSystem->EffectManager(); - j["currentEffect"] = effectManager.GetCurrentEffectIndex(); + j["currentEffect"] = effectManager.GetCurrentEffectIndex(); j["millisecondsRemaining"] = effectManager.GetTimeRemainingForCurrentEffect(); - j["eternalInterval"] = effectManager.IsIntervalEternal(); - j["effectInterval"] = effectManager.GetInterval(); + j["eternalInterval"] = effectManager.IsIntervalEternal(); + j["effectInterval"] = effectManager.GetInterval(); for (auto effect : effectManager.EffectsList()) { StaticJsonDocument<256> effectDoc; - effectDoc["name"] = effect->FriendlyName(); + effectDoc["name"] = effect->FriendlyName(); effectDoc["enabled"] = effect->IsEnabled(); - effectDoc["core"] = effect->IsCoreEffect(); + effectDoc["core"] = effect->IsCoreEffect(); if (!j["Effects"].add(effectDoc)) { @@ -248,9 +247,19 @@ void CWebServer::GetEffectListText(AsyncWebServerRequest * pRequest) } if (!bufferOverflow) - AddCORSHeaderAndSendResponse(pRequest, response.release()); + return response; } while (bufferOverflow); + return nullptr; +} + +void CWebServer::GetEffectListText(AsyncWebServerRequest * pRequest) +{ + debugV("GetEffectListText"); + std::unique_ptr response = buildEffectListJson(); + if(response) { + AddCORSHeaderAndSendResponse(pRequest, response.release()); + } } void CWebServer::GetStatistics(AsyncWebServerRequest * pRequest) @@ -727,4 +736,35 @@ void CWebServer::Reset(AsyncWebServerRequest * pRequest) debugW("Resetting device at API request!"); throw new std::runtime_error("Resetting device at API request"); } +} + +void CWebServer::EffectsUpdate() +{ + JsonVariant message = buildEffectListJson().release()->getRoot(); + size_t len = measureJson(message); + char buffer[len]; + if(buffer) { + serializeJson(message, buffer, len); + _socket.textAll(buffer, len); + } +} + +void CWebServer::onSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, + uint8_t *data, size_t len) +{ + switch (type) + { + case WS_EVT_CONNECT: + Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + break; + case WS_EVT_DISCONNECT: + Serial.printf("WebSocket client #%u disconnected\n", client->id()); + break; + case WS_EVT_DATA: + // TODO Add any updates that need to fire when the web socket sends a message here. + break; + case WS_EVT_PONG: + case WS_EVT_ERROR: + break; + } } \ No newline at end of file