diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1bbfec5a..036c220a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -1,257 +1,257 @@ -#ifndef WLED_FCN_DECLARE_H -#define WLED_FCN_DECLARE_H -#include -#include "src/dependencies/espalexa/EspalexaDevice.h" -#include "src/dependencies/e131/ESPAsyncE131.h" - -/* - * All globally accessible functions are declared here - */ - -//alexa.cpp -void onAlexaChange(EspalexaDevice* dev); -void alexaInit(); -void handleAlexa(); -void onAlexaChange(EspalexaDevice* dev); - -//blynk.cpp -void initBlynk(const char* auth, const char* host, uint16_t port); -void handleBlynk(); -void updateBlynk(); - -//button.cpp -void shortPressAction(); -bool isButtonPressed(); -void handleButton(); -void handleIO(); - -//cfg.cpp -void deserializeConfig(); -bool deserializeConfigSec(); -void serializeConfig(); -void serializeConfigSec(); - -//colors.cpp -void colorFromUint32(uint32_t in, bool secondary = false); -void colorFromUint24(uint32_t in, bool secondary = false); -uint32_t colorFromRgbw(byte* rgbw); -void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb -void colorKtoRGB(uint16_t kelvin, byte* rgb); -void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb - -void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO -void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO - -void colorFromDecOrHexString(byte* rgb, char* in); -bool colorFromHexString(byte* rgb, const char* in); -void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) - -//dmx.cpp -void initDMX(); -void handleDMX(); - -//e131.cpp -void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); - -//file.cpp -bool handleFileRead(AsyncWebServerRequest*, String path); -bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); -bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); -bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); -bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); -void updateFSInfo(); -void closeFile(); - -//hue.cpp -void handleHue(); -void reconnectHue(); -void onHueError(void* arg, AsyncClient* client, int8_t error); -void onHueConnect(void* arg, AsyncClient* client); -void sendHuePoll(); -void onHueData(void* arg, AsyncClient* client, void *data, size_t len); - -//ir.cpp -bool decodeIRCustom(uint32_t code); -void applyRepeatActions(); -void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); -void changeEffectSpeed(int8_t amount); -void changeEffectIntensity(int8_t amount); -void decodeIR(uint32_t code); -void decodeIR24(uint32_t code); -void decodeIR24OLD(uint32_t code); -void decodeIR24CT(uint32_t code); -void decodeIR40(uint32_t code); -void decodeIR44(uint32_t code); -void decodeIR21(uint32_t code); -void decodeIR6(uint32_t code); -void decodeIR9(uint32_t code); - -void initIR(); -void handleIR(); - -//json.cpp -#include "ESPAsyncWebServer.h" -#include "src/dependencies/json/ArduinoJson-v6.h" -#include "src/dependencies/json/AsyncJson-v6.h" -#include "FX.h" - -void deserializeSegment(JsonObject elem, byte it); -uint8_t deserializeState(JsonObject root); -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true, uint8_t versionAPI = 1); -void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true); -void serializeInfo(JsonObject root); -void serveJson(AsyncWebServerRequest* request, uint8_t versionAPI = 1); -bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); - -//led.cpp -void setValuesFromMainSeg(); -void resetTimebase(); -void toggleOnOff(); -void setAllLeds(); -void setLedsStandard(); -bool colorChanged(); -void colorUpdated(int callMode); -void updateInterfaces(uint8_t callMode); -void handleTransitions(); -void handleNightlight(); -byte scaledBri(byte in); - -//lx_parser.cpp -bool parseLx(int lxValue, byte* rgbw); -void parseLxJson(int lxValue, byte segId, bool secondary); - -//mqtt.cpp -bool initMqtt(); -void publishMqtt(); - -//ntp.cpp -void handleNetworkTime(); -void sendNTPPacket(); -bool checkNTPResponse(); -void updateLocalTime(); -void getTimeString(char* out); -bool checkCountdown(); -void setCountdown(); -byte weekdayMondayFirst(); -void checkTimers(); -void calculateSunriseAndSunset(); - -//overlay.cpp -void initCronixie(); -void handleOverlays(); -void handleOverlayDraw(); -void _overlayAnalogCountdown(); -void _overlayAnalogClock(); - -byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); -void setCronixie(); -void _overlayCronixie(); -void _drawOverlayCronixie(); - -//playlist.cpp -void shufflePlaylist(); -void unloadPlaylist(); -void loadPlaylist(JsonObject playlistObject); -void handlePlaylist(); - -//presets.cpp -bool applyPreset(byte index); -void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject()); -void deletePreset(byte index); - -//set.cpp -void _setRandomColor(bool _sec,bool fromButton=false); -bool isAsterisksOnly(const char* str, byte maxLen); -void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); -bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); -int getNumVal(const String* req, uint16_t pos); -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); - -//udp.cpp -void notify(byte callMode, bool followUp=false); -void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); -void handleNotifications(); -void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); -void refreshNodeList(); -void sendSysInfoUDP(); - -//um_manager.cpp -class Usermod { - public: - virtual void loop() {} - virtual void setup() {} - virtual void connected() {} - virtual void addToJsonState(JsonObject& obj) {} - virtual void addToJsonInfo(JsonObject& obj) {} - virtual void readFromJsonState(JsonObject& obj) {} - virtual void addToConfig(JsonObject& obj) {} - virtual void readFromConfig(JsonObject& obj) {} - virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} -}; - -class UsermodManager { - private: - Usermod* ums[WLED_MAX_USERMODS]; - byte numMods = 0; - - public: - void loop(); - - void setup(); - void connected(); - - void addToJsonState(JsonObject& obj); - void addToJsonInfo(JsonObject& obj); - void readFromJsonState(JsonObject& obj); - - void addToConfig(JsonObject& obj); - void readFromConfig(JsonObject& obj); - - bool add(Usermod* um); - Usermod* lookup(uint16_t mod_id); - byte getModCount(); -}; - -//usermods_list.cpp -void registerUsermods(); - -//usermod.cpp -void userSetup(); -void userConnected(); -void userLoop(); - -//wled_eeprom.cpp -void applyMacro(byte index); -void deEEP(); -void deEEPSettings(); -void clearEEPROM(); - -//wled_serial.cpp -void handleSerial(); - -//wled_server.cpp -bool isIp(String str); -bool captivePortal(AsyncWebServerRequest *request); -void initServer(); -void serveIndexOrWelcome(AsyncWebServerRequest *request); -void serveIndex(AsyncWebServerRequest* request); -String msgProcessor(const String& var); -void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); -String settingsProcessor(const String& var); -String dmxProcessor(const String& var); -void serveSettings(AsyncWebServerRequest* request, bool post = false); - -//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); -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); -void getSettingsJS(byte subPage, char* dest); - -#endif +#ifndef WLED_FCN_DECLARE_H +#define WLED_FCN_DECLARE_H +#include +#include "src/dependencies/espalexa/EspalexaDevice.h" +#include "src/dependencies/e131/ESPAsyncE131.h" + +/* + * All globally accessible functions are declared here + */ + +//alexa.cpp +void onAlexaChange(EspalexaDevice* dev); +void alexaInit(); +void handleAlexa(); +void onAlexaChange(EspalexaDevice* dev); + +//blynk.cpp +void initBlynk(const char* auth, const char* host, uint16_t port); +void handleBlynk(); +void updateBlynk(); + +//button.cpp +void shortPressAction(); +bool isButtonPressed(); +void handleButton(); +void handleIO(); + +//cfg.cpp +void deserializeConfig(); +bool deserializeConfigSec(); +void serializeConfig(); +void serializeConfigSec(); + +//colors.cpp +void colorFromUint32(uint32_t in, bool secondary = false); +void colorFromUint24(uint32_t in, bool secondary = false); +uint32_t colorFromRgbw(byte* rgbw); +void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void colorKtoRGB(uint16_t kelvin, byte* rgb); +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb + +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO + +void colorFromDecOrHexString(byte* rgb, char* in); +bool colorFromHexString(byte* rgb, const char* in); +void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) + +//dmx.cpp +void initDMX(); +void handleDMX(); + +//e131.cpp +void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); + +//file.cpp +bool handleFileRead(AsyncWebServerRequest*, String path); +bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); +bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); +bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); +bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); +void updateFSInfo(); +void closeFile(); + +//hue.cpp +void handleHue(); +void reconnectHue(); +void onHueError(void* arg, AsyncClient* client, int8_t error); +void onHueConnect(void* arg, AsyncClient* client); +void sendHuePoll(); +void onHueData(void* arg, AsyncClient* client, void *data, size_t len); + +//ir.cpp +bool decodeIRCustom(uint32_t code); +void applyRepeatActions(); +void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); +void changeEffectSpeed(int8_t amount); +void changeEffectIntensity(int8_t amount); +void decodeIR(uint32_t code); +void decodeIR24(uint32_t code); +void decodeIR24OLD(uint32_t code); +void decodeIR24CT(uint32_t code); +void decodeIR40(uint32_t code); +void decodeIR44(uint32_t code); +void decodeIR21(uint32_t code); +void decodeIR6(uint32_t code); +void decodeIR9(uint32_t code); + +void initIR(); +void handleIR(); + +//json.cpp +#include "ESPAsyncWebServer.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "FX.h" + +void deserializeSegment(JsonObject elem, byte it); +bool deserializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true, uint8_t versionAPI = 1); +void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true); +void serializeInfo(JsonObject root); +void serveJson(AsyncWebServerRequest* request, uint8_t versionAPI = 1); +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); + +//led.cpp +void setValuesFromMainSeg(); +void resetTimebase(); +void toggleOnOff(); +void setAllLeds(); +void setLedsStandard(); +bool colorChanged(); +void colorUpdated(int callMode); +void updateInterfaces(uint8_t callMode); +void handleTransitions(); +void handleNightlight(); +byte scaledBri(byte in); + +//lx_parser.cpp +bool parseLx(int lxValue, byte* rgbw); +void parseLxJson(int lxValue, byte segId, bool secondary); + +//mqtt.cpp +bool initMqtt(); +void publishMqtt(); + +//ntp.cpp +void handleNetworkTime(); +void sendNTPPacket(); +bool checkNTPResponse(); +void updateLocalTime(); +void getTimeString(char* out); +bool checkCountdown(); +void setCountdown(); +byte weekdayMondayFirst(); +void checkTimers(); +void calculateSunriseAndSunset(); + +//overlay.cpp +void initCronixie(); +void handleOverlays(); +void handleOverlayDraw(); +void _overlayAnalogCountdown(); +void _overlayAnalogClock(); + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); +void setCronixie(); +void _overlayCronixie(); +void _drawOverlayCronixie(); + +//playlist.cpp +void shufflePlaylist(); +void unloadPlaylist(); +void loadPlaylist(JsonObject playlistObject); +void handlePlaylist(); + +//presets.cpp +bool applyPreset(byte index); +void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject()); +void deletePreset(byte index); + +//set.cpp +void _setRandomColor(bool _sec,bool fromButton=false); +bool isAsterisksOnly(const char* str, byte maxLen); +void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); +int getNumVal(const String* req, uint16_t pos); +bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); + +//udp.cpp +void notify(byte callMode, bool followUp=false); +void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); +void handleNotifications(); +void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); +void refreshNodeList(); +void sendSysInfoUDP(); + +//um_manager.cpp +class Usermod { + public: + virtual void loop() {} + virtual void setup() {} + virtual void connected() {} + virtual void addToJsonState(JsonObject& obj) {} + virtual void addToJsonInfo(JsonObject& obj) {} + virtual void readFromJsonState(JsonObject& obj) {} + virtual void addToConfig(JsonObject& obj) {} + virtual void readFromConfig(JsonObject& obj) {} + virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} +}; + +class UsermodManager { + private: + Usermod* ums[WLED_MAX_USERMODS]; + byte numMods = 0; + + public: + void loop(); + + void setup(); + void connected(); + + void addToJsonState(JsonObject& obj); + void addToJsonInfo(JsonObject& obj); + void readFromJsonState(JsonObject& obj); + + void addToConfig(JsonObject& obj); + void readFromConfig(JsonObject& obj); + + bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); + byte getModCount(); +}; + +//usermods_list.cpp +void registerUsermods(); + +//usermod.cpp +void userSetup(); +void userConnected(); +void userLoop(); + +//wled_eeprom.cpp +void applyMacro(byte index); +void deEEP(); +void deEEPSettings(); +void clearEEPROM(); + +//wled_serial.cpp +void handleSerial(); + +//wled_server.cpp +bool isIp(String str); +bool captivePortal(AsyncWebServerRequest *request); +void initServer(); +void serveIndexOrWelcome(AsyncWebServerRequest *request); +void serveIndex(AsyncWebServerRequest* request); +String msgProcessor(const String& var); +void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); +String settingsProcessor(const String& var); +String dmxProcessor(const String& var); +void serveSettings(AsyncWebServerRequest* request, bool post = false); + +//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); +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void getSettingsJS(byte subPage, char* dest); + +#endif diff --git a/wled00/json.cpp b/wled00/json.cpp index 067d7492..f5004e39 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -165,11 +165,10 @@ void deserializeSegment(JsonObject elem, byte it) } } -uint8_t deserializeState(JsonObject root) +bool deserializeState(JsonObject root) { strip.applyToAllSelected = false; bool stateResponse = root[F("v")] | false; - uint8_t versionAPI = root["rev"] | 1; bri = root["bri"] | bri; @@ -279,7 +278,7 @@ uint8_t deserializeState(JsonObject root) deletePreset(ps); } ps = root["ps"] | -1; //load preset (clears state request!) - if (ps >= 0) {applyPreset(ps); return stateResponse ? versionAPI : 0;} + if (ps >= 0) {applyPreset(ps); return stateResponse;} //HTTP API commands const char* httpwin = root["win"]; @@ -298,7 +297,7 @@ uint8_t deserializeState(JsonObject root) colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE); - return stateResponse ? versionAPI : 0; + return stateResponse; } void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset, bool segmentBounds, uint8_t versionAPI) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 580dbc95..0d2626ae 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -83,7 +83,8 @@ void initServer() }); AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { - uint8_t vAPI = 0; + bool verboseResponse = false; + uint8_t vAPI = 1; { //scope JsonDocument so it releases its buffer DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); @@ -91,11 +92,15 @@ void initServer() if (error || root.isNull()) { request->send(400, "application/json", F("{\"error\":9}")); return; } + if (root.containsKey("rev")) + { + vAPI = root["rev"] | 1; + } fileDoc = &jsonBuffer; // used for applying presets (presets.cpp) - vAPI = deserializeState(root); + verboseResponse = deserializeState(root); fileDoc = nullptr; } - if (vAPI>0) { //if JSON contains "v" + if (verboseResponse) { //if JSON contains "v" serveJson(request,vAPI); return; } request->send(200, "application/json", F("{\"success\":true}")); diff --git a/wled00/ws.cpp b/wled00/ws.cpp index d8aabfa1..7824939a 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -1,117 +1,149 @@ -#include "wled.h" - -/* - * WebSockets server for bidirectional communication - */ -#ifdef WLED_ENABLE_WEBSOCKETS - -uint16_t wsLiveClientId = 0; -unsigned long wsLastLiveTime = 0; -//uint8_t* wsFrameBuffer = nullptr; -uint8_t vAPI = 2; - -#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) - { - uint8_t verboseResponse = 0; - { //scope JsonDocument so it releases its buffer - DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE); - 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) vAPI = verboseResponse; - } - 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(F("{\"error\":9}")); //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(JSON_BUFFER_SIZE); - JsonObject state = doc.createNestedObject("state"); - if (vAPI>1) state["rev"] = 2; - 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) {} +#include "wled.h" + +/* + * WebSockets server for bidirectional communication + */ +#ifdef WLED_ENABLE_WEBSOCKETS + +uint16_t wsLiveClientId = 0; +unsigned long wsLastLiveTime = 0; +//uint8_t* wsFrameBuffer = nullptr; +uint8_t vAPI = 2; +struct client_api { + uint32_t c = 0; + uint8_t vAPI = 1; +} ClientApis[DEFAULT_MAX_WS_CLIENTS]; + +#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 + for (uint8_t i=0; iid(); + ClientApis[i].vAPI = 1; + DEBUG_PRINTF("New WS client [%d]: %ld\n", (int)i, client->id()); + break; + } + sendDataWs(client); + //client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + //client disconnected + if (client->id() == wsLiveClientId) wsLiveClientId = 0; + for (uint8_t i=0; iid()) continue; + ClientApis[i].c = 0; // clear slot + ClientApis[i].vAPI = 1; + DEBUG_PRINTF("Removed WS client [%d]: %ld\n", (int)i, client->id()); + break; + } + } 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(JSON_BUFFER_SIZE); + 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; + } + if (root.containsKey("rev")) + { + for (uint8_t i=0; iid()) continue; + ClientApis[i].vAPI = root["rev"]; + DEBUG_PRINTF("API for WS client [%d]: %d\n", (int)i, (int)ClientApis[i].vAPI); + break; + } + } + 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(F("{\"error\":9}")); //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(JSON_BUFFER_SIZE); + JsonObject state = doc.createNestedObject("state"); + if (client) { + for (uint8_t i=0; iid()) continue; + state["rev"] = ClientApis[i].vAPI; + DEBUG_PRINTF("Actual API used [%d]: %d\n", (int)i, (int)ClientApis[i].vAPI); + break; + } + } + 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