diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c9e0ee4..63271ad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Development versions after 0.10.0 release +#### Build 2007020 + #### Build 2006251 - Added `SV=2` to HTTP API, allow selecting single segment only diff --git a/platformio.ini b/platformio.ini index 3a5383be..dfee3989 100644 --- a/platformio.ini +++ b/platformio.ini @@ -165,7 +165,7 @@ lib_deps = ESPAsyncTCP@1.2.0 ESPAsyncUDP@697c75a025 AsyncTCP@1.0.3 - Esp Async WebServer@1.2.0 + https://github.com/Aircoookie/ESPAsyncWebServer IRremoteESP8266@2.7.3 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 35ea3f80..c74c113b 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1599,7 +1599,7 @@ uint16_t WS2812FX::mode_oscillate(void) uint16_t WS2812FX::mode_lightning(void) { uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash - uint16_t ledlen = random16(SEGLEN -1 -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); if (SEGENV.step == 0) @@ -2247,7 +2247,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) { CRGB WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) - uint16_t ticks = ms / (32 - (SEGMENT.speed >> 3)); + uint16_t ticks = ms / SEGENV.aux0; uint8_t fastcycle8 = ticks; uint16_t slowcycle16 = (ticks >> 8) + salt; slowcycle16 += sin8(slowcycle16); @@ -2312,10 +2312,11 @@ uint16_t WS2812FX::twinklefox_base(bool cat) // numbers that it generates is (paradoxically) stable. uint16_t PRNG16 = 11337; + // Calculate speed + if (SEGMENT.speed > 100) SEGENV.aux0 = 3 + ((255 - SEGMENT.speed) >> 3); + else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1); + // Set up the background color, "bg". - // if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of - // the current palette are identical, then a deeply faded version of - // that color is used for the background color CRGB bg; bg = col_to_crgb(SEGCOLOR(1)); uint8_t bglight = bg.getAverageLight(); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index da8643d4..0982fd62 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -87,7 +87,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); void serializeState(JsonObject root); void serializeInfo(JsonObject root); void serveJson(AsyncWebServerRequest* request); -void serveLiveLeds(AsyncWebServerRequest* request); +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); //led.cpp void setValuesFromMainSeg(); @@ -210,6 +210,11 @@ String settingsProcessor(const String& var); String dmxProcessor(const String& var); void serveSettings(AsyncWebServerRequest* request); +//ws.cpp +void handleWs(); +void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); +void sendDataWs(AsyncWebSocketClient * client = nullptr); + //xml.cpp void XML_response(AsyncWebServerRequest *request, char* dest = nullptr); void URL_response(AsyncWebServerRequest *request); diff --git a/wled00/json.cpp b/wled00/json.cpp index ebd7da91..0a64f192 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -333,6 +333,12 @@ void serializeInfo(JsonObject root) root["lip"] = realtimeIP.toString(); } + #ifdef WLED_ENABLE_WEBSOCKETS + root["ws"] = ws.count(); + #else + root["ws"] = -1; + #endif + root["fxcount"] = strip.getModeCount(); root["palcount"] = strip.getPaletteCount(); @@ -454,21 +460,37 @@ void serveJson(AsyncWebServerRequest* request) #define MAX_LIVE_LEDS 180 -void serveLiveLeds(AsyncWebServerRequest* request) +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) { + AsyncWebSocketClient * wsc; + if (!request) { //not HTTP, use Websockets + #ifdef WLED_ENABLE_WEBSOCKETS + wsc = ws.client(wsClient); + if (!wsc || wsc->queueLength() > 0) return false; //only send if queue free + #endif + } + uint16_t used = ledCount; uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS char buffer[2000] = "{\"leds\":["; - olen = 9; obuf = buffer; + olen = 9; for (uint16_t i= 0; i < used; i += n) { - olen += sprintf(buffer + olen, "\"%06X\",", strip.getPixelColor(i)); + olen += sprintf(obuf + olen, "\"%06X\",", strip.getPixelColor(i)); } olen -= 1; oappend("],\"n\":"); oappendi(n); oappend("}"); - request->send(200, "application/json", buffer); + if (request) { + request->send(200, "application/json", buffer); + } + #ifdef WLED_ENABLE_WEBSOCKETS + else { + wsc->text(obuf, olen); + } + #endif + return true; } diff --git a/wled00/led.cpp b/wled00/led.cpp index 2fd23526..29baae8a 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -3,7 +3,6 @@ /* * LED methods */ - void setValuesFromMainSeg() { WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId()); @@ -167,6 +166,7 @@ void colorUpdated(int callMode) void updateInterfaces(uint8_t callMode) { + sendDataWs(); #ifndef WLED_DISABLE_ALEXA if (espalexaDevice != nullptr && callMode != NOTIFIER_CALL_MODE_ALEXA) { espalexaDevice->setValue(bri); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 484130af..594c9225 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -14,8 +14,11 @@ WLED::WLED() void WLED::reset() { briT = 0; + #ifdef WLED_ENABLE_WEBSOCKETS + ws.closeAll(1012); + #endif long dly = millis(); - while (millis() - dly < 250) { + while (millis() - dly < 450) { yield(); // enough time to send response to client } setAllLeds(); @@ -94,7 +97,8 @@ void WLED::loop() if (lastMqttReconnectAttempt > millis()) rolloverMillis++; //millis() rolls over every 50 days initMqtt(); } - + yield(); + handleWs(); // DEBUG serial logging #ifdef WLED_DEBUG @@ -283,6 +287,10 @@ void WLED::initAP(bool resetAP) void WLED::initConnection() { + #ifdef WLED_ENABLE_WEBSOCKETS + ws.onEvent(wsEvent); + #endif + WiFi.disconnect(true); // close old connections #ifdef ESP8266 WiFi.setPhyMode(WIFI_PHY_MODE_11N); diff --git a/wled00/wled.h b/wled00/wled.h index 5c34955d..fda93b52 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2006251 +#define VERSION 2007020 // ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). @@ -30,6 +30,7 @@ #endif #define WLED_ENABLE_ADALIGHT // saves 500b only //#define WLED_ENABLE_DMX // uses 3.5kb (use LEDPIN other than 2) +//#define WLED_ENABLE_WEBSOCKETS #define WLED_DISABLE_FILESYSTEM // SPIFFS is not used by any WLED feature yet //#define WLED_ENABLE_FS_SERVING // Enable sending html file from SPIFFS before serving progmem version @@ -480,6 +481,9 @@ WLED_GLOBAL bool doPublishMqtt _INIT(false); // server library objects WLED_GLOBAL AsyncWebServer server _INIT_N(((80))); +#ifdef WLED_ENABLE_WEBSOCKETS +WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws"))); +#endif WLED_GLOBAL AsyncClient* hueClient _INIT(NULL); WLED_GLOBAL AsyncMqttClient* mqtt _INIT(NULL); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index cd623bc4..6c981afb 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -205,10 +205,10 @@ void initServer() } else { server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); }); server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); }); } @@ -226,6 +226,10 @@ void initServer() if (captivePortal(request)) return; serveIndexOrWelcome(request); }); + + #ifdef WLED_ENABLE_WEBSOCKETS + server.addHandler(&ws); + #endif //called when the url is not defined here, ajax-in; get-settings server.onNotFound([](AsyncWebServerRequest *request){ @@ -374,7 +378,7 @@ void serveSettings(AsyncWebServerRequest* request) if (subPage == 1 && wifiLock && otaLock) { - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); return; + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); return; } #ifdef WLED_DISABLE_MOBILE_UI //disable welcome page if not enough storage diff --git a/wled00/ws.cpp b/wled00/ws.cpp new file mode 100644 index 00000000..affe0439 --- /dev/null +++ b/wled00/ws.cpp @@ -0,0 +1,114 @@ +#include "wled.h" + +/* + * WebSockets server for bidirectional communication + */ +#ifdef WLED_ENABLE_WEBSOCKETS + +uint16_t wsLiveClientId = 0; +unsigned long wsLastLiveTime = 0; +//uint8_t* wsFrameBuffer = nullptr; + +#define WS_LIVE_INTERVAL 40 + +void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) +{ + if(type == WS_EVT_CONNECT){ + //client connected + sendDataWs(client); + //client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + //client disconnected + if (client->id() == wsLiveClientId) wsLiveClientId = 0; + } else if(type == WS_EVT_DATA){ + //data packet + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data (max. 1450byte) + if(info->opcode == WS_TEXT) + { + bool verboseResponse = false; + { //scope JsonDocument so it releases its buffer + DynamicJsonDocument jsonBuffer(8192); + DeserializationError error = deserializeJson(jsonBuffer, data, len); + JsonObject root = jsonBuffer.as(); + if (error || root.isNull()) return; + + if (root.containsKey("lv")) + { + wsLiveClientId = root["lv"] ? client->id() : 0; + } + + verboseResponse = deserializeState(root); + } + if (verboseResponse || millis() - lastInterfaceUpdate < 1900) sendDataWs(client); //update if it takes longer than 100ms until next "broadcast" + } + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + //if(info->index == 0){ + //if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096]; + //} + + //if (wsFrameBuffer && len < 4096 && info->index + info->) + //{ + + //} + + if((info->index + len) == info->len){ + if(info->final){ + if(info->message_opcode == WS_TEXT) { + client->text("{\"error\":10}"); //we do not handle split packets right now + } + } + } + } + } else if(type == WS_EVT_ERROR){ + //error was received from the other end + + } else if(type == WS_EVT_PONG){ + //pong message was received (in response to a ping request maybe) + + } +} + +void sendDataWs(AsyncWebSocketClient * client) +{ + if (!ws.count()) return; + AsyncWebSocketMessageBuffer * buffer; + + { //scope JsonDocument so it releases its buffer + DynamicJsonDocument doc(8192); + JsonObject state = doc.createNestedObject("state"); + serializeState(state); + JsonObject info = doc.createNestedObject("info"); + serializeInfo(info); + size_t len = measureJson(doc); + buffer = ws.makeBuffer(len); + if (!buffer) return; //out of memory + + serializeJson(doc, (char *)buffer->get(), len +1); + } + if (client) { + client->text(buffer); + } else { + ws.textAll(buffer); + } +} + +void handleWs() +{ + if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL) + { + ws.cleanupClients(); + bool success = true; + if (wsLiveClientId) + success = serveLiveLeds(nullptr, wsLiveClientId); + wsLastLiveTime = millis(); + if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue + } +} + +#else +void handleWs() {} +void sendDataWs(AsyncWebSocketClient * client) {} +#endif \ No newline at end of file