diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 036c220a..e44ab155 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); -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 +#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 f5004e39..fbdac301 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,830 +1,830 @@ -#include "wled.h" - -#include "palettes.h" - -/* - * JSON API (De)serialization - */ - -void deserializeSegment(JsonObject elem, byte it) -{ - byte id = elem["id"] | it; - if (id < strip.getMaxSegments()) - { - WS2812FX::Segment& seg = strip.getSegment(id); - uint16_t start = elem[F("start")] | seg.start; - int stop = elem["stop"] | -1; - - if (stop < 0) { - uint16_t len = elem[F("len")]; - stop = (len > 0) ? start + len : seg.stop; - } - uint16_t grp = elem[F("grp")] | seg.grouping; - uint16_t spc = elem[F("spc")] | seg.spacing; - strip.setSegment(id, start, stop, grp, spc); - - int segbri = elem["bri"] | -1; - if (segbri == 0) { - seg.setOption(SEG_OPTION_ON, 0, id); - } else if (segbri > 0) { - seg.setOpacity(segbri, id); - seg.setOption(SEG_OPTION_ON, 1, id); - } - - seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id); - - JsonArray colarr = elem["col"]; - if (!colarr.isNull()) - { - for (uint8_t i = 0; i < 3; i++) - { - int rgbw[] = {0,0,0,0}; - bool colValid = false; - if (colarr[i].is()) { - // unsigned long RGBW - uint32_t colX = colarr[i]; - rgbw[0] = (colX >> 16) & 0xFF; - rgbw[1] = (colX >> 8) & 0xFF; - rgbw[2] = (colX ) & 0xFF; - rgbw[3] = (colX >> 24) & 0xFF; - colValid = true; - } else { - JsonArray colX = colarr[i]; - if (colX.isNull()) { - byte brgbw[] = {0,0,0,0}; - const char* hexCol = colarr[i]; - if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400 - int kelvin = colarr[i] | -1; - if (kelvin < 0) continue; - if (kelvin == 0) seg.setColor(i, 0, id); - if (kelvin > 0) colorKtoRGB(kelvin, brgbw); - colValid = true; - } else { //HEX string, e.g. "FFAA00" - colValid = colorFromHexString(brgbw, hexCol); - } - for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c]; - } else { //Array of ints (RGB or RGBW color), e.g. [255,160,0] - byte sz = colX.size(); - if (sz == 0) continue; //do nothing on empty array - byte cp = copyArray(colX, rgbw, 4); - if (cp == 1 && rgbw[0] == 0) - seg.setColor(i, 0, id); - colValid = true; - } - } - - if (!colValid) continue; - if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment - { - if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];} - if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];} - } else { //normal case, apply directly to segment - seg.setColor(i, ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF))), id); - if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh - } - } - } - - // lx parser - #ifdef WLED_ENABLE_LOXONE - int lx = elem[F("lx")] | -1; - if (lx > 0) { - parseLxJson(lx, id, false); - } - int ly = elem[F("ly")] | -1; - if (ly > 0) { - parseLxJson(ly, id, true); - } - #endif - - //if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal); - seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED)); - seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); - seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); - - //temporary, strip object gets updated via colorUpdated() - if (id == strip.getMainSegmentId()) { - effectCurrent = elem[F("fx")] | effectCurrent; - effectSpeed = elem[F("sx")] | effectSpeed; - effectIntensity = elem[F("ix")] | effectIntensity; - effectPalette = elem[F("pal")] | effectPalette; - } else { //permanent - byte fx = elem[F("fx")] | seg.mode; - if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(id, fx); - seg.speed = elem[F("sx")] | seg.speed; - seg.intensity = elem[F("ix")] | seg.intensity; - seg.palette = elem[F("pal")] | seg.palette; - } - - JsonArray iarr = elem[F("i")]; //set individual LEDs - if (!iarr.isNull()) { - strip.setPixelSegment(id); - - //freeze and init to black - if (!seg.getOption(SEG_OPTION_FREEZE)) { - seg.setOption(SEG_OPTION_FREEZE, true); - strip.fill(0); - } - - uint16_t start = 0, stop = 0; - byte set = 0; //0 nothing set, 1 start set, 2 range set - - for (uint16_t i = 0; i < iarr.size(); i++) { - if(iarr[i].is()) { - if (!set) { - start = iarr[i]; - set = 1; - } else { - stop = iarr[i]; - set = 2; - } - } else { - JsonArray icol = iarr[i]; - if (icol.isNull()) break; - - byte sz = icol.size(); - if (sz == 0 && sz > 4) break; - - int rgbw[] = {0,0,0,0}; - copyArray(icol, rgbw); - - if (set < 2) stop = start + 1; - for (uint16_t i = start; i < stop; i++) { - strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]); - } - if (!set) start++; - set = 0; - } - } - strip.setPixelSegment(255); - strip.trigger(); - } else { //return to regular effect - seg.setOption(SEG_OPTION_FREEZE, false); - } - - } -} - -bool deserializeState(JsonObject root) -{ - strip.applyToAllSelected = false; - bool stateResponse = root[F("v")] | false; - - bri = root["bri"] | bri; - - bool on = root["on"] | (bri > 0); - if (!on != !bri) toggleOnOff(); - - int tr = root[F("transition")] | -1; - if (tr >= 0) - { - transitionDelay = tr; - transitionDelay *= 100; - transitionDelayTemp = transitionDelay; - } - - tr = root[F("tt")] | -1; - if (tr >= 0) - { - transitionDelayTemp = tr; - transitionDelayTemp *= 100; - jsonTransitionOnce = true; - } - strip.setTransition(transitionDelayTemp); - - int cy = root[F("pl")] | -2; - if (cy > -2) presetCyclingEnabled = (cy >= 0); - JsonObject ccnf = root["ccnf"]; - presetCycleMin = ccnf[F("min")] | presetCycleMin; - presetCycleMax = ccnf[F("max")] | presetCycleMax; - tr = ccnf[F("time")] | -1; - if (tr >= 2) presetCycleTime = tr; - - JsonObject nl = root["nl"]; - nightlightActive = nl["on"] | nightlightActive; - nightlightDelayMins = nl[F("dur")] | nightlightDelayMins; - nightlightMode = nl[F("fade")] | nightlightMode; //deprecated, remove for v0.12.0 - nightlightMode = nl[F("mode")] | nightlightMode; - nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri; - - JsonObject udpn = root["udpn"]; - notifyDirect = udpn["send"] | notifyDirect; - receiveNotifications = udpn["recv"] | receiveNotifications; - bool noNotification = udpn[F("nn")]; //send no notification just for this request - - unsigned long timein = root[F("time")] | UINT32_MAX; - if (timein != UINT32_MAX) { - if (millis() - ntpLastSyncTime > 50000000L) setTime(timein); - if (presetsModifiedTime == 0) presetsModifiedTime = timein; - } - - doReboot = root[F("rb")] | doReboot; - - realtimeOverride = root[F("lor")] | realtimeOverride; - if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; - - if (root.containsKey("live")) { - bool lv = root["live"]; - if (lv) realtimeLock(65000); //enter realtime without timeout - else realtimeTimeout = 0; //cancel realtime mode immediately - } - - byte prevMain = strip.getMainSegmentId(); - strip.mainSegment = root[F("mainseg")] | prevMain; - if (strip.getMainSegmentId() != prevMain) setValuesFromMainSeg(); - - int it = 0; - JsonVariant segVar = root["seg"]; - if (segVar.is()) - { - int id = segVar["id"] | -1; - - if (id < 0) { //set all selected segments - bool didSet = false; - byte lowestActive = 99; - for (byte s = 0; s < strip.getMaxSegments(); s++) - { - WS2812FX::Segment sg = strip.getSegment(s); - if (sg.isActive()) - { - if (lowestActive == 99) lowestActive = s; - if (sg.isSelected()) { - deserializeSegment(segVar, s); - didSet = true; - } - } - } - if (!didSet && lowestActive < strip.getMaxSegments()) deserializeSegment(segVar, lowestActive); - } else { //set only the segment with the specified ID - deserializeSegment(segVar, id); - } - } else { - JsonArray segs = segVar.as(); - for (JsonObject elem : segs) - { - deserializeSegment(elem, it); - it++; - } - } - - usermods.readFromJsonState(root); - - int ps = root[F("psave")] | -1; - if (ps > 0) { - savePreset(ps, true, nullptr, root); - } else { - ps = root[F("pdel")] | -1; //deletion - if (ps > 0) { - deletePreset(ps); - } - ps = root["ps"] | -1; //load preset (clears state request!) - if (ps >= 0) {applyPreset(ps); return stateResponse;} - - //HTTP API commands - const char* httpwin = root["win"]; - if (httpwin) { - String apireq = "win&"; - apireq += httpwin; - handleSet(nullptr, apireq, false); - } - } - - JsonObject playlist = root[F("playlist")]; - if (!playlist.isNull()) { - loadPlaylist(playlist); - noNotification = true; //do not notify both for this request and the first playlist entry - } - - colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE); - - return stateResponse; -} - -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset, bool segmentBounds, uint8_t versionAPI) -{ - root["id"] = id; - if (segmentBounds) { - root[F("start")] = seg.start; - root["stop"] = seg.stop; - } - if (!forPreset) root[F("len")] = seg.stop - seg.start; - root[F("grp")] = seg.grouping; - root[F("spc")] = seg.spacing; - root["on"] = seg.getOption(SEG_OPTION_ON); - byte segbri = seg.opacity; - root["bri"] = (segbri) ? segbri : 255; - - JsonArray colarr = root.createNestedArray("col"); - - if (versionAPI>1) { - for (uint8_t i = 0; i < 3; i++) - { - if (id==strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment - if (i==0) colarr.add((unsigned long)((col[0]<<16) | (col[1]<<8) | col[2] | (useRGBW?col[3]<<24:0))); - else colarr.add((unsigned long)((colSec[0]<<16) | (colSec[1]<<8) | colSec[2] | (useRGBW?colSec[3]<<24:0))); - else - colarr.add((unsigned long)seg.colors[i]); - } - } else { - for (uint8_t i = 0; i < 3; i++) - { - JsonArray colX = colarr.createNestedArray(); - if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment - { - if (i == 0) { - colX.add(col[0]); colX.add(col[1]); colX.add(col[2]); if (useRGBW) colX.add(col[3]); - } else { - colX.add(colSec[0]); colX.add(colSec[1]); colX.add(colSec[2]); if (useRGBW) colX.add(colSec[3]); - } - } else { - colX.add((seg.colors[i] >> 16) & 0xFF); - colX.add((seg.colors[i] >> 8) & 0xFF); - colX.add((seg.colors[i]) & 0xFF); - if (useRGBW) - colX.add((seg.colors[i] >> 24) & 0xFF); - } - } - } - - root[F("fx")] = seg.mode; - root[F("sx")] = seg.speed; - root[F("ix")] = seg.intensity; - root[F("pal")] = seg.palette; - root[F("sel")] = seg.isSelected(); - root["rev"] = seg.getOption(SEG_OPTION_REVERSED); - root[F("mi")] = seg.getOption(SEG_OPTION_MIRROR); -} - -void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds) -{ - if (includeBri) { - root["on"] = (bri > 0); - root["bri"] = briLast; - root[F("transition")] = transitionDelay/100; //in 100ms - } - - if (!forPreset) { - if (errorFlag) root[F("error")] = errorFlag; - - root[F("ps")] = currentPreset; - root[F("pl")] = (presetCyclingEnabled) ? 0: -1; - usermods.addToJsonState(root); - - //temporary for preset cycle - JsonObject ccnf = root.createNestedObject("ccnf"); - ccnf[F("min")] = presetCycleMin; - ccnf[F("max")] = presetCycleMax; - ccnf[F("time")] = presetCycleTime; - - JsonObject nl = root.createNestedObject("nl"); - nl["on"] = nightlightActive; - nl[F("dur")] = nightlightDelayMins; - nl[F("fade")] = (nightlightMode > NL_MODE_SET); //deprecated - nl[F("mode")] = nightlightMode; - nl[F("tbri")] = nightlightTargetBri; - if (nightlightActive) { - nl[F("rem")] = (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000; // seconds remaining - } else { - nl[F("rem")] = -1; - } - - JsonObject udpn = root.createNestedObject("udpn"); - udpn["send"] = notifyDirect; - udpn["recv"] = receiveNotifications; - - root[F("lor")] = realtimeOverride; - } - - root[F("mainseg")] = strip.getMainSegmentId(); - - uint8_t versionAPI = root["rev"] | 1; - - JsonArray seg = root.createNestedArray("seg"); - for (byte s = 0; s < strip.getMaxSegments(); s++) - { - WS2812FX::Segment sg = strip.getSegment(s); - // TODO: add logic to stop at 12 segments if using versionAPI==1 on ESP8266 - if (sg.isActive()) - { - JsonObject seg0 = seg.createNestedObject(); - serializeSegment(seg0, sg, s, forPreset, segmentBounds, versionAPI); - } else if (forPreset && segmentBounds) { //disable segments not part of preset - JsonObject seg0 = seg.createNestedObject(); - seg0["stop"] = 0; - } - } -} - -//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int getSignalQuality(int rssi) -{ - int quality = 0; - - if (rssi <= -100) - { - quality = 0; - } - else if (rssi >= -50) - { - quality = 100; - } - else - { - quality = 2 * (rssi + 100); - } - return quality; -} - -void serializeInfo(JsonObject root) -{ - root[F("ver")] = versionString; - root[F("vid")] = VERSION; - //root[F("cn")] = WLED_CODENAME; - - JsonObject leds = root.createNestedObject("leds"); - leds[F("count")] = ledCount; - leds[F("rgbw")] = useRGBW; - leds[F("wv")] = useRGBW && (strip.rgbwMode == RGBW_MODE_MANUAL_ONLY || strip.rgbwMode == RGBW_MODE_DUAL); //should a white channel slider be displayed? - - JsonArray leds_pin = leds.createNestedArray("pin"); - for (uint8_t s=0; sgetPins(pins); - leds_pin.add(pins[0]); // need to elaborate this for multipin strips - } - - leds[F("pwr")] = strip.currentMilliamps; - leds[F("fps")] = strip.getFps(); - leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; - leds[F("maxseg")] = strip.getMaxSegments(); - leds[F("seglock")] = false; //will be used in the future to prevent modifications to segment config - - root[F("str")] = syncToggleReceive; - - root[F("name")] = serverDescription; - root[F("udpport")] = udpPort; - root["live"] = (bool)realtimeMode; - - switch (realtimeMode) { - case REALTIME_MODE_INACTIVE: root["lm"] = ""; break; - case REALTIME_MODE_GENERIC: root["lm"] = ""; break; - case REALTIME_MODE_UDP: root["lm"] = F("UDP"); break; - case REALTIME_MODE_HYPERION: root["lm"] = F("Hyperion"); break; - case REALTIME_MODE_E131: root["lm"] = F("E1.31"); break; - case REALTIME_MODE_ADALIGHT: root["lm"] = F("USB Adalight/TPM2"); break; - case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break; - case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break; - case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break; - } - - if (realtimeIP[0] == 0) - { - root[F("lip")] = ""; - } else { - root[F("lip")] = realtimeIP.toString(); - } - - #ifdef WLED_ENABLE_WEBSOCKETS - root[F("ws")] = ws.count(); - #else - root[F("ws")] = -1; - #endif - - root[F("fxcount")] = strip.getModeCount(); - root[F("palcount")] = strip.getPaletteCount(); - - JsonObject wifi_info = root.createNestedObject("wifi"); - wifi_info[F("bssid")] = WiFi.BSSIDstr(); - int qrssi = WiFi.RSSI(); - wifi_info[F("rssi")] = qrssi; - wifi_info[F("signal")] = getSignalQuality(qrssi); - wifi_info[F("channel")] = WiFi.channel(); - - JsonObject fs_info = root.createNestedObject("fs"); - fs_info["u"] = fsBytesUsed / 1000; - fs_info["t"] = fsBytesTotal / 1000; - fs_info[F("pmt")] = presetsModifiedTime; - - root[F("ndc")] = nodeListEnabled ? (int)Nodes.size() : -1; - - #ifdef ARDUINO_ARCH_ESP32 - #ifdef WLED_DEBUG - wifi_info[F("txPower")] = (int) WiFi.getTxPower(); - wifi_info[F("sleep")] = (bool) WiFi.getSleep(); - #endif - root[F("arch")] = "esp32"; - root[F("core")] = ESP.getSdkVersion(); - //root[F("maxalloc")] = ESP.getMaxAllocHeap(); - #ifdef WLED_DEBUG - root[F("resetReason0")] = (int)rtc_get_reset_reason(0); - root[F("resetReason1")] = (int)rtc_get_reset_reason(1); - #endif - root[F("lwip")] = 0; - #else - root[F("arch")] = "esp8266"; - root[F("core")] = ESP.getCoreVersion(); - //root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); - #ifdef WLED_DEBUG - root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; - #endif - root[F("lwip")] = LWIP_VERSION_MAJOR; - #endif - - root[F("freeheap")] = ESP.getFreeHeap(); - root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; - if (sunrise && sunset) root[F("isday")] = daytime; - - usermods.addToJsonInfo(root); - - byte os = 0; - #ifdef WLED_DEBUG - os = 0x80; - #endif - #ifndef WLED_DISABLE_ALEXA - os += 0x40; - #endif - #ifndef WLED_DISABLE_BLYNK - os += 0x20; - #endif - #ifndef WLED_DISABLE_CRONIXIE - os += 0x10; - #endif - #ifndef WLED_DISABLE_FILESYSTEM - os += 0x08; - #endif - #ifndef WLED_DISABLE_HUESYNC - os += 0x04; - #endif - #ifdef WLED_ENABLE_ADALIGHT - os += 0x02; - #endif - #ifndef WLED_DISABLE_OTA - os += 0x01; - #endif - root[F("opt")] = os; - - root[F("brand")] = "WLED"; - root[F("product")] = F("FOSS"); - root["mac"] = escapedMac; -} - -void setPaletteColors(JsonArray json, CRGBPalette16 palette) -{ - for (int i = 0; i < 16; i++) { - JsonArray colors = json.createNestedArray(); - CRGB color = palette[i]; - colors.add((((float)i / (float)16) * 255)); - colors.add(color.red); - colors.add(color.green); - colors.add(color.blue); - } -} - -void setPaletteColors(JsonArray json, byte* tcp) -{ - TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(tcp); - TRGBGradientPaletteEntryUnion u; - - // Count entries - uint16_t count = 0; - do { - u = *(ent + count); - count++; - } while ( u.index != 255); - - u = *ent; - int indexstart = 0; - while( indexstart < 255) { - indexstart = u.index; - - JsonArray colors = json.createNestedArray(); - colors.add(u.index); - colors.add(u.r); - colors.add(u.g); - colors.add(u.b); - - ent++; - u = *ent; - } -} - -void serializePalettes(JsonObject root, AsyncWebServerRequest* request) -{ - #ifdef ESP8266 - int itemPerPage = 5; - #else - int itemPerPage = 8; - #endif - - int page = 0; - if (request->hasParam("page")) { - page = request->getParam("page")->value().toInt(); - } - - int palettesCount = strip.getPaletteCount(); - - int maxPage = (palettesCount -1) / itemPerPage; - if (page > maxPage) page = maxPage; - - int start = itemPerPage * page; - int end = start + itemPerPage; - if (end >= palettesCount) end = palettesCount; - - root[F("m")] = maxPage; - JsonObject palettes = root.createNestedObject("p"); - - for (int i = start; i < end; i++) { - JsonArray curPalette = palettes.createNestedArray(String(i)); - CRGB prim; - CRGB sec; - CRGB ter; - switch (i) { - case 0: //default palette - setPaletteColors(curPalette, PartyColors_p); - break; - case 1: //random - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); - break; - case 2: //primary color only - curPalette.add(F("c1")); - break; - case 3: //primary + secondary - curPalette.add(F("c1")); - curPalette.add(F("c1")); - curPalette.add(F("c2")); - curPalette.add(F("c2")); - break; - case 4: //primary + secondary + tertiary - curPalette.add(F("c3")); - curPalette.add(F("c2")); - curPalette.add(F("c1")); - break; - case 5: {//primary + secondary (+tert if not off), more distinct - - curPalette.add(F("c1")); - curPalette.add(F("c1")); - curPalette.add(F("c1")); - curPalette.add(F("c1")); - curPalette.add(F("c1")); - curPalette.add(F("c2")); - curPalette.add(F("c2")); - curPalette.add(F("c2")); - curPalette.add(F("c2")); - curPalette.add(F("c2")); - curPalette.add(F("c3")); - curPalette.add(F("c3")); - curPalette.add(F("c3")); - curPalette.add(F("c3")); - curPalette.add(F("c3")); - curPalette.add(F("c1")); - break;} - case 6: //Party colors - setPaletteColors(curPalette, PartyColors_p); - break; - case 7: //Cloud colors - setPaletteColors(curPalette, CloudColors_p); - break; - case 8: //Lava colors - setPaletteColors(curPalette, LavaColors_p); - break; - case 9: //Ocean colors - setPaletteColors(curPalette, OceanColors_p); - break; - case 10: //Forest colors - setPaletteColors(curPalette, ForestColors_p); - break; - case 11: //Rainbow colors - setPaletteColors(curPalette, RainbowColors_p); - break; - case 12: //Rainbow stripe colors - setPaletteColors(curPalette, RainbowStripeColors_p); - break; - - default: - if (i < 13) { - break; - } - byte tcp[72]; - memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72); - setPaletteColors(curPalette, tcp); - break; - } - } -} - -void serializeNodes(JsonObject root) -{ - JsonArray nodes = root.createNestedArray("nodes"); - - for (NodesMap::iterator it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.ip[0] != 0) - { - JsonObject node = nodes.createNestedObject(); - node[F("name")] = it->second.nodeName; - node["type"] = it->second.nodeType; - node["ip"] = it->second.ip.toString(); - node[F("age")] = it->second.age; - node[F("vid")] = it->second.build; - } - } -} - -void serveJson(AsyncWebServerRequest* request, uint8_t versionAPI) -{ - byte subJson = 0; - const String& url = request->url(); - if (url.indexOf("state") > 0) subJson = 1; - else if (url.indexOf("info") > 0) subJson = 2; - else if (url.indexOf("si") > 0) subJson = 3; - else if (url.indexOf("nodes") > 0) subJson = 4; - else if (url.indexOf("palx") > 0) subJson = 5; - else if (url.indexOf("live") > 0) { - serveLiveLeds(request); - return; - } - else if (url.indexOf(F("eff")) > 0) { - request->send_P(200, "application/json", JSON_mode_names); - return; - } - else if (url.indexOf(F("pal")) > 0) { - request->send_P(200, "application/json", JSON_palette_names); - return; - } - else if (url.length() > 6) { //not just /json - request->send( 501, "application/json", F("{\"error\":\"Not implemented\"}")); - return; - } - - AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE); - JsonObject doc = response->getRoot(); - - switch (subJson) - { - case 1: //state - if (versionAPI>1) doc["rev"] = (int)versionAPI; - serializeState(doc); break; - case 2: //info - serializeInfo(doc); break; - case 4: //node list - serializeNodes(doc); break; - case 5: //palettes - serializePalettes(doc, request); break; - default: //all - JsonObject state = doc.createNestedObject("state"); - if (versionAPI>1) state["rev"] = (int)versionAPI; - serializeState(state); - JsonObject info = doc.createNestedObject("info"); - serializeInfo(info); - if (subJson != 3) - { - doc[F("effects")] = serialized((const __FlashStringHelper*)JSON_mode_names); - doc[F("palettes")] = serialized((const __FlashStringHelper*)JSON_palette_names); - } - } - - response->setLength(); - request->send(response); -} - -#define MAX_LIVE_LEDS 180 - -bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) -{ - #ifdef WLED_ENABLE_WEBSOCKETS - AsyncWebSocketClient * wsc = nullptr; - if (!request) { //not HTTP, use 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]; - strcpy_P(buffer, PSTR("{\"leds\":[")); - obuf = buffer; - olen = 9; - - for (uint16_t i= 0; i < used; i += n) - { - olen += sprintf(obuf + olen, "\"%06X\",", strip.getPixelColor(i) & 0xFFFFFF); - } - olen -= 1; - oappend((const char*)F("],\"n\":")); - oappendi(n); - oappend("}"); - if (request) { - request->send(200, "application/json", buffer); - } - #ifdef WLED_ENABLE_WEBSOCKETS - else { - wsc->text(obuf, olen); - } - #endif - return true; -} +#include "wled.h" + +#include "palettes.h" + +/* + * JSON API (De)serialization + */ + +void deserializeSegment(JsonObject elem, byte it) +{ + byte id = elem["id"] | it; + if (id < strip.getMaxSegments()) + { + WS2812FX::Segment& seg = strip.getSegment(id); + uint16_t start = elem[F("start")] | seg.start; + int stop = elem["stop"] | -1; + + if (stop < 0) { + uint16_t len = elem[F("len")]; + stop = (len > 0) ? start + len : seg.stop; + } + uint16_t grp = elem[F("grp")] | seg.grouping; + uint16_t spc = elem[F("spc")] | seg.spacing; + strip.setSegment(id, start, stop, grp, spc); + + int segbri = elem["bri"] | -1; + if (segbri == 0) { + seg.setOption(SEG_OPTION_ON, 0, id); + } else if (segbri > 0) { + seg.setOpacity(segbri, id); + seg.setOption(SEG_OPTION_ON, 1, id); + } + + seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id); + + JsonArray colarr = elem["col"]; + if (!colarr.isNull()) + { + for (uint8_t i = 0; i < 3; i++) + { + int rgbw[] = {0,0,0,0}; + bool colValid = false; + if (colarr[i].is()) { + // unsigned long RGBW + uint32_t colX = colarr[i]; + rgbw[0] = (colX >> 16) & 0xFF; + rgbw[1] = (colX >> 8) & 0xFF; + rgbw[2] = (colX ) & 0xFF; + rgbw[3] = (colX >> 24) & 0xFF; + colValid = true; + } else { + JsonArray colX = colarr[i]; + if (colX.isNull()) { + byte brgbw[] = {0,0,0,0}; + const char* hexCol = colarr[i]; + if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400 + int kelvin = colarr[i] | -1; + if (kelvin < 0) continue; + if (kelvin == 0) seg.setColor(i, 0, id); + if (kelvin > 0) colorKtoRGB(kelvin, brgbw); + colValid = true; + } else { //HEX string, e.g. "FFAA00" + colValid = colorFromHexString(brgbw, hexCol); + } + for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c]; + } else { //Array of ints (RGB or RGBW color), e.g. [255,160,0] + byte sz = colX.size(); + if (sz == 0) continue; //do nothing on empty array + byte cp = copyArray(colX, rgbw, 4); + if (cp == 1 && rgbw[0] == 0) + seg.setColor(i, 0, id); + colValid = true; + } + } + + if (!colValid) continue; + if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment + { + if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];} + if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];} + } else { //normal case, apply directly to segment + seg.setColor(i, ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF))), id); + if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh + } + } + } + + // lx parser + #ifdef WLED_ENABLE_LOXONE + int lx = elem[F("lx")] | -1; + if (lx > 0) { + parseLxJson(lx, id, false); + } + int ly = elem[F("ly")] | -1; + if (ly > 0) { + parseLxJson(ly, id, true); + } + #endif + + //if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal); + seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED)); + seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); + seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); + + //temporary, strip object gets updated via colorUpdated() + if (id == strip.getMainSegmentId()) { + effectCurrent = elem[F("fx")] | effectCurrent; + effectSpeed = elem[F("sx")] | effectSpeed; + effectIntensity = elem[F("ix")] | effectIntensity; + effectPalette = elem[F("pal")] | effectPalette; + } else { //permanent + byte fx = elem[F("fx")] | seg.mode; + if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(id, fx); + seg.speed = elem[F("sx")] | seg.speed; + seg.intensity = elem[F("ix")] | seg.intensity; + seg.palette = elem[F("pal")] | seg.palette; + } + + JsonArray iarr = elem[F("i")]; //set individual LEDs + if (!iarr.isNull()) { + strip.setPixelSegment(id); + + //freeze and init to black + if (!seg.getOption(SEG_OPTION_FREEZE)) { + seg.setOption(SEG_OPTION_FREEZE, true); + strip.fill(0); + } + + uint16_t start = 0, stop = 0; + byte set = 0; //0 nothing set, 1 start set, 2 range set + + for (uint16_t i = 0; i < iarr.size(); i++) { + if(iarr[i].is()) { + if (!set) { + start = iarr[i]; + set = 1; + } else { + stop = iarr[i]; + set = 2; + } + } else { + JsonArray icol = iarr[i]; + if (icol.isNull()) break; + + byte sz = icol.size(); + if (sz == 0 && sz > 4) break; + + int rgbw[] = {0,0,0,0}; + copyArray(icol, rgbw); + + if (set < 2) stop = start + 1; + for (uint16_t i = start; i < stop; i++) { + strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]); + } + if (!set) start++; + set = 0; + } + } + strip.setPixelSegment(255); + strip.trigger(); + } else { //return to regular effect + seg.setOption(SEG_OPTION_FREEZE, false); + } + + } +} + +bool deserializeState(JsonObject root) +{ + strip.applyToAllSelected = false; + bool stateResponse = root[F("v")] | false; + + bri = root["bri"] | bri; + + bool on = root["on"] | (bri > 0); + if (!on != !bri) toggleOnOff(); + + int tr = root[F("transition")] | -1; + if (tr >= 0) + { + transitionDelay = tr; + transitionDelay *= 100; + transitionDelayTemp = transitionDelay; + } + + tr = root[F("tt")] | -1; + if (tr >= 0) + { + transitionDelayTemp = tr; + transitionDelayTemp *= 100; + jsonTransitionOnce = true; + } + strip.setTransition(transitionDelayTemp); + + int cy = root[F("pl")] | -2; + if (cy > -2) presetCyclingEnabled = (cy >= 0); + JsonObject ccnf = root["ccnf"]; + presetCycleMin = ccnf[F("min")] | presetCycleMin; + presetCycleMax = ccnf[F("max")] | presetCycleMax; + tr = ccnf[F("time")] | -1; + if (tr >= 2) presetCycleTime = tr; + + JsonObject nl = root["nl"]; + nightlightActive = nl["on"] | nightlightActive; + nightlightDelayMins = nl[F("dur")] | nightlightDelayMins; + nightlightMode = nl[F("fade")] | nightlightMode; //deprecated, remove for v0.12.0 + nightlightMode = nl[F("mode")] | nightlightMode; + nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri; + + JsonObject udpn = root["udpn"]; + notifyDirect = udpn["send"] | notifyDirect; + receiveNotifications = udpn["recv"] | receiveNotifications; + bool noNotification = udpn[F("nn")]; //send no notification just for this request + + unsigned long timein = root[F("time")] | UINT32_MAX; + if (timein != UINT32_MAX) { + if (millis() - ntpLastSyncTime > 50000000L) setTime(timein); + if (presetsModifiedTime == 0) presetsModifiedTime = timein; + } + + doReboot = root[F("rb")] | doReboot; + + realtimeOverride = root[F("lor")] | realtimeOverride; + if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; + + if (root.containsKey("live")) { + bool lv = root["live"]; + if (lv) realtimeLock(65000); //enter realtime without timeout + else realtimeTimeout = 0; //cancel realtime mode immediately + } + + byte prevMain = strip.getMainSegmentId(); + strip.mainSegment = root[F("mainseg")] | prevMain; + if (strip.getMainSegmentId() != prevMain) setValuesFromMainSeg(); + + int it = 0; + JsonVariant segVar = root["seg"]; + if (segVar.is()) + { + int id = segVar["id"] | -1; + + if (id < 0) { //set all selected segments + bool didSet = false; + byte lowestActive = 99; + for (byte s = 0; s < strip.getMaxSegments(); s++) + { + WS2812FX::Segment sg = strip.getSegment(s); + if (sg.isActive()) + { + if (lowestActive == 99) lowestActive = s; + if (sg.isSelected()) { + deserializeSegment(segVar, s); + didSet = true; + } + } + } + if (!didSet && lowestActive < strip.getMaxSegments()) deserializeSegment(segVar, lowestActive); + } else { //set only the segment with the specified ID + deserializeSegment(segVar, id); + } + } else { + JsonArray segs = segVar.as(); + for (JsonObject elem : segs) + { + deserializeSegment(elem, it); + it++; + } + } + + usermods.readFromJsonState(root); + + int ps = root[F("psave")] | -1; + if (ps > 0) { + savePreset(ps, true, nullptr, root); + } else { + ps = root[F("pdel")] | -1; //deletion + if (ps > 0) { + deletePreset(ps); + } + ps = root["ps"] | -1; //load preset (clears state request!) + if (ps >= 0) {applyPreset(ps); return stateResponse;} + + //HTTP API commands + const char* httpwin = root["win"]; + if (httpwin) { + String apireq = "win&"; + apireq += httpwin; + handleSet(nullptr, apireq, false); + } + } + + JsonObject playlist = root[F("playlist")]; + if (!playlist.isNull()) { + loadPlaylist(playlist); + noNotification = true; //do not notify both for this request and the first playlist entry + } + + colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE); + + return stateResponse; +} + +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset, bool segmentBounds, uint8_t versionAPI) +{ + root["id"] = id; + if (segmentBounds) { + root[F("start")] = seg.start; + root["stop"] = seg.stop; + } + if (!forPreset) root[F("len")] = seg.stop - seg.start; + root[F("grp")] = seg.grouping; + root[F("spc")] = seg.spacing; + root["on"] = seg.getOption(SEG_OPTION_ON); + byte segbri = seg.opacity; + root["bri"] = (segbri) ? segbri : 255; + + JsonArray colarr = root.createNestedArray("col"); + + if (versionAPI>1) { + for (uint8_t i = 0; i < 3; i++) + { + if (id==strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment + if (i==0) colarr.add((unsigned long)((col[0]<<16) | (col[1]<<8) | col[2] | (useRGBW?col[3]<<24:0))); + else colarr.add((unsigned long)((colSec[0]<<16) | (colSec[1]<<8) | colSec[2] | (useRGBW?colSec[3]<<24:0))); + else + colarr.add((unsigned long)seg.colors[i]); + } + } else { + for (uint8_t i = 0; i < 3; i++) + { + JsonArray colX = colarr.createNestedArray(); + if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment + { + if (i == 0) { + colX.add(col[0]); colX.add(col[1]); colX.add(col[2]); if (useRGBW) colX.add(col[3]); + } else { + colX.add(colSec[0]); colX.add(colSec[1]); colX.add(colSec[2]); if (useRGBW) colX.add(colSec[3]); + } + } else { + colX.add((seg.colors[i] >> 16) & 0xFF); + colX.add((seg.colors[i] >> 8) & 0xFF); + colX.add((seg.colors[i]) & 0xFF); + if (useRGBW) + colX.add((seg.colors[i] >> 24) & 0xFF); + } + } + } + + root[F("fx")] = seg.mode; + root[F("sx")] = seg.speed; + root[F("ix")] = seg.intensity; + root[F("pal")] = seg.palette; + root[F("sel")] = seg.isSelected(); + root["rev"] = seg.getOption(SEG_OPTION_REVERSED); + root[F("mi")] = seg.getOption(SEG_OPTION_MIRROR); +} + +void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds) +{ + if (includeBri) { + root["on"] = (bri > 0); + root["bri"] = briLast; + root[F("transition")] = transitionDelay/100; //in 100ms + } + + if (!forPreset) { + if (errorFlag) root[F("error")] = errorFlag; + + root[F("ps")] = currentPreset; + root[F("pl")] = (presetCyclingEnabled) ? 0: -1; + usermods.addToJsonState(root); + + //temporary for preset cycle + JsonObject ccnf = root.createNestedObject("ccnf"); + ccnf[F("min")] = presetCycleMin; + ccnf[F("max")] = presetCycleMax; + ccnf[F("time")] = presetCycleTime; + + JsonObject nl = root.createNestedObject("nl"); + nl["on"] = nightlightActive; + nl[F("dur")] = nightlightDelayMins; + nl[F("fade")] = (nightlightMode > NL_MODE_SET); //deprecated + nl[F("mode")] = nightlightMode; + nl[F("tbri")] = nightlightTargetBri; + if (nightlightActive) { + nl[F("rem")] = (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000; // seconds remaining + } else { + nl[F("rem")] = -1; + } + + JsonObject udpn = root.createNestedObject("udpn"); + udpn["send"] = notifyDirect; + udpn["recv"] = receiveNotifications; + + root[F("lor")] = realtimeOverride; + } + + root[F("mainseg")] = strip.getMainSegmentId(); + + uint8_t versionAPI = root["rev"] | 1; + + JsonArray seg = root.createNestedArray("seg"); + for (byte s = 0; s < strip.getMaxSegments(); s++) + { + WS2812FX::Segment sg = strip.getSegment(s); + // TODO: add logic to stop at 12 segments if using versionAPI==1 on ESP8266 + if (sg.isActive()) + { + JsonObject seg0 = seg.createNestedObject(); + serializeSegment(seg0, sg, s, forPreset, segmentBounds, versionAPI); + } else if (forPreset && segmentBounds) { //disable segments not part of preset + JsonObject seg0 = seg.createNestedObject(); + seg0["stop"] = 0; + } + } +} + +//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp +int getSignalQuality(int rssi) +{ + int quality = 0; + + if (rssi <= -100) + { + quality = 0; + } + else if (rssi >= -50) + { + quality = 100; + } + else + { + quality = 2 * (rssi + 100); + } + return quality; +} + +void serializeInfo(JsonObject root) +{ + root[F("ver")] = versionString; + root[F("vid")] = VERSION; + //root[F("cn")] = WLED_CODENAME; + + JsonObject leds = root.createNestedObject("leds"); + leds[F("count")] = ledCount; + leds[F("rgbw")] = useRGBW; + leds[F("wv")] = useRGBW && (strip.rgbwMode == RGBW_MODE_MANUAL_ONLY || strip.rgbwMode == RGBW_MODE_DUAL); //should a white channel slider be displayed? + + JsonArray leds_pin = leds.createNestedArray("pin"); + for (uint8_t s=0; sgetPins(pins); + leds_pin.add(pins[0]); // need to elaborate this for multipin strips + } + + leds[F("pwr")] = strip.currentMilliamps; + leds[F("fps")] = strip.getFps(); + leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; + leds[F("maxseg")] = strip.getMaxSegments(); + leds[F("seglock")] = false; //will be used in the future to prevent modifications to segment config + + root[F("str")] = syncToggleReceive; + + root[F("name")] = serverDescription; + root[F("udpport")] = udpPort; + root["live"] = (bool)realtimeMode; + + switch (realtimeMode) { + case REALTIME_MODE_INACTIVE: root["lm"] = ""; break; + case REALTIME_MODE_GENERIC: root["lm"] = ""; break; + case REALTIME_MODE_UDP: root["lm"] = F("UDP"); break; + case REALTIME_MODE_HYPERION: root["lm"] = F("Hyperion"); break; + case REALTIME_MODE_E131: root["lm"] = F("E1.31"); break; + case REALTIME_MODE_ADALIGHT: root["lm"] = F("USB Adalight/TPM2"); break; + case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break; + case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break; + case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break; + } + + if (realtimeIP[0] == 0) + { + root[F("lip")] = ""; + } else { + root[F("lip")] = realtimeIP.toString(); + } + + #ifdef WLED_ENABLE_WEBSOCKETS + root[F("ws")] = ws.count(); + #else + root[F("ws")] = -1; + #endif + + root[F("fxcount")] = strip.getModeCount(); + root[F("palcount")] = strip.getPaletteCount(); + + JsonObject wifi_info = root.createNestedObject("wifi"); + wifi_info[F("bssid")] = WiFi.BSSIDstr(); + int qrssi = WiFi.RSSI(); + wifi_info[F("rssi")] = qrssi; + wifi_info[F("signal")] = getSignalQuality(qrssi); + wifi_info[F("channel")] = WiFi.channel(); + + JsonObject fs_info = root.createNestedObject("fs"); + fs_info["u"] = fsBytesUsed / 1000; + fs_info["t"] = fsBytesTotal / 1000; + fs_info[F("pmt")] = presetsModifiedTime; + + root[F("ndc")] = nodeListEnabled ? (int)Nodes.size() : -1; + + #ifdef ARDUINO_ARCH_ESP32 + #ifdef WLED_DEBUG + wifi_info[F("txPower")] = (int) WiFi.getTxPower(); + wifi_info[F("sleep")] = (bool) WiFi.getSleep(); + #endif + root[F("arch")] = "esp32"; + root[F("core")] = ESP.getSdkVersion(); + //root[F("maxalloc")] = ESP.getMaxAllocHeap(); + #ifdef WLED_DEBUG + root[F("resetReason0")] = (int)rtc_get_reset_reason(0); + root[F("resetReason1")] = (int)rtc_get_reset_reason(1); + #endif + root[F("lwip")] = 0; + #else + root[F("arch")] = "esp8266"; + root[F("core")] = ESP.getCoreVersion(); + //root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); + #ifdef WLED_DEBUG + root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; + #endif + root[F("lwip")] = LWIP_VERSION_MAJOR; + #endif + + root[F("freeheap")] = ESP.getFreeHeap(); + root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; + if (sunrise && sunset) root[F("isday")] = daytime; + + usermods.addToJsonInfo(root); + + byte os = 0; + #ifdef WLED_DEBUG + os = 0x80; + #endif + #ifndef WLED_DISABLE_ALEXA + os += 0x40; + #endif + #ifndef WLED_DISABLE_BLYNK + os += 0x20; + #endif + #ifndef WLED_DISABLE_CRONIXIE + os += 0x10; + #endif + #ifndef WLED_DISABLE_FILESYSTEM + os += 0x08; + #endif + #ifndef WLED_DISABLE_HUESYNC + os += 0x04; + #endif + #ifdef WLED_ENABLE_ADALIGHT + os += 0x02; + #endif + #ifndef WLED_DISABLE_OTA + os += 0x01; + #endif + root[F("opt")] = os; + + root[F("brand")] = "WLED"; + root[F("product")] = F("FOSS"); + root["mac"] = escapedMac; +} + +void setPaletteColors(JsonArray json, CRGBPalette16 palette) +{ + for (int i = 0; i < 16; i++) { + JsonArray colors = json.createNestedArray(); + CRGB color = palette[i]; + colors.add((((float)i / (float)16) * 255)); + colors.add(color.red); + colors.add(color.green); + colors.add(color.blue); + } +} + +void setPaletteColors(JsonArray json, byte* tcp) +{ + TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(tcp); + TRGBGradientPaletteEntryUnion u; + + // Count entries + uint16_t count = 0; + do { + u = *(ent + count); + count++; + } while ( u.index != 255); + + u = *ent; + int indexstart = 0; + while( indexstart < 255) { + indexstart = u.index; + + JsonArray colors = json.createNestedArray(); + colors.add(u.index); + colors.add(u.r); + colors.add(u.g); + colors.add(u.b); + + ent++; + u = *ent; + } +} + +void serializePalettes(JsonObject root, AsyncWebServerRequest* request) +{ + #ifdef ESP8266 + int itemPerPage = 5; + #else + int itemPerPage = 8; + #endif + + int page = 0; + if (request->hasParam("page")) { + page = request->getParam("page")->value().toInt(); + } + + int palettesCount = strip.getPaletteCount(); + + int maxPage = (palettesCount -1) / itemPerPage; + if (page > maxPage) page = maxPage; + + int start = itemPerPage * page; + int end = start + itemPerPage; + if (end >= palettesCount) end = palettesCount; + + root[F("m")] = maxPage; + JsonObject palettes = root.createNestedObject("p"); + + for (int i = start; i < end; i++) { + JsonArray curPalette = palettes.createNestedArray(String(i)); + CRGB prim; + CRGB sec; + CRGB ter; + switch (i) { + case 0: //default palette + setPaletteColors(curPalette, PartyColors_p); + break; + case 1: //random + curPalette.add("r"); + curPalette.add("r"); + curPalette.add("r"); + curPalette.add("r"); + break; + case 2: //primary color only + curPalette.add(F("c1")); + break; + case 3: //primary + secondary + curPalette.add(F("c1")); + curPalette.add(F("c1")); + curPalette.add(F("c2")); + curPalette.add(F("c2")); + break; + case 4: //primary + secondary + tertiary + curPalette.add(F("c3")); + curPalette.add(F("c2")); + curPalette.add(F("c1")); + break; + case 5: {//primary + secondary (+tert if not off), more distinct + + curPalette.add(F("c1")); + curPalette.add(F("c1")); + curPalette.add(F("c1")); + curPalette.add(F("c1")); + curPalette.add(F("c1")); + curPalette.add(F("c2")); + curPalette.add(F("c2")); + curPalette.add(F("c2")); + curPalette.add(F("c2")); + curPalette.add(F("c2")); + curPalette.add(F("c3")); + curPalette.add(F("c3")); + curPalette.add(F("c3")); + curPalette.add(F("c3")); + curPalette.add(F("c3")); + curPalette.add(F("c1")); + break;} + case 6: //Party colors + setPaletteColors(curPalette, PartyColors_p); + break; + case 7: //Cloud colors + setPaletteColors(curPalette, CloudColors_p); + break; + case 8: //Lava colors + setPaletteColors(curPalette, LavaColors_p); + break; + case 9: //Ocean colors + setPaletteColors(curPalette, OceanColors_p); + break; + case 10: //Forest colors + setPaletteColors(curPalette, ForestColors_p); + break; + case 11: //Rainbow colors + setPaletteColors(curPalette, RainbowColors_p); + break; + case 12: //Rainbow stripe colors + setPaletteColors(curPalette, RainbowStripeColors_p); + break; + + default: + if (i < 13) { + break; + } + byte tcp[72]; + memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72); + setPaletteColors(curPalette, tcp); + break; + } + } +} + +void serializeNodes(JsonObject root) +{ + JsonArray nodes = root.createNestedArray("nodes"); + + for (NodesMap::iterator it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.ip[0] != 0) + { + JsonObject node = nodes.createNestedObject(); + node[F("name")] = it->second.nodeName; + node["type"] = it->second.nodeType; + node["ip"] = it->second.ip.toString(); + node[F("age")] = it->second.age; + node[F("vid")] = it->second.build; + } + } +} + +void serveJson(AsyncWebServerRequest* request, uint8_t versionAPI) +{ + byte subJson = 0; + const String& url = request->url(); + if (url.indexOf("state") > 0) subJson = 1; + else if (url.indexOf("info") > 0) subJson = 2; + else if (url.indexOf("si") > 0) subJson = 3; + else if (url.indexOf("nodes") > 0) subJson = 4; + else if (url.indexOf("palx") > 0) subJson = 5; + else if (url.indexOf("live") > 0) { + serveLiveLeds(request); + return; + } + else if (url.indexOf(F("eff")) > 0) { + request->send_P(200, "application/json", JSON_mode_names); + return; + } + else if (url.indexOf(F("pal")) > 0) { + request->send_P(200, "application/json", JSON_palette_names); + return; + } + else if (url.length() > 6) { //not just /json + request->send( 501, "application/json", F("{\"error\":\"Not implemented\"}")); + return; + } + + AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE); + JsonObject doc = response->getRoot(); + + switch (subJson) + { + case 1: //state + if (versionAPI>1) doc["rev"] = (int)versionAPI; + serializeState(doc); break; + case 2: //info + serializeInfo(doc); break; + case 4: //node list + serializeNodes(doc); break; + case 5: //palettes + serializePalettes(doc, request); break; + default: //all + JsonObject state = doc.createNestedObject("state"); + if (versionAPI>1) state["rev"] = (int)versionAPI; + serializeState(state); + JsonObject info = doc.createNestedObject("info"); + serializeInfo(info); + if (subJson != 3) + { + doc[F("effects")] = serialized((const __FlashStringHelper*)JSON_mode_names); + doc[F("palettes")] = serialized((const __FlashStringHelper*)JSON_palette_names); + } + } + + response->setLength(); + request->send(response); +} + +#define MAX_LIVE_LEDS 180 + +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) +{ + #ifdef WLED_ENABLE_WEBSOCKETS + AsyncWebSocketClient * wsc = nullptr; + if (!request) { //not HTTP, use 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]; + strcpy_P(buffer, PSTR("{\"leds\":[")); + obuf = buffer; + olen = 9; + + for (uint16_t i= 0; i < used; i += n) + { + olen += sprintf(obuf + olen, "\"%06X\",", strip.getPixelColor(i) & 0xFFFFFF); + } + olen -= 1; + oappend((const char*)F("],\"n\":")); + oappendi(n); + oappend("}"); + if (request) { + request->send(200, "application/json", buffer); + } + #ifdef WLED_ENABLE_WEBSOCKETS + else { + wsc->text(obuf, olen); + } + #endif + return true; +} diff --git a/wled00/wled.h b/wled00/wled.h index 65e34e56..b3a12036 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2103250 +#define VERSION 2103251 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 0d2626ae..26c1cb40 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -1,424 +1,424 @@ -#include "wled.h" - -/* - * Integrated HTTP web server page declarations - */ - -//Is this an IP? -bool isIp(String str) { - for (size_t i = 0; i < str.length(); i++) { - int c = str.charAt(i); - if (c != '.' && (c < '0' || c > '9')) { - return false; - } - } - return true; -} - -bool captivePortal(AsyncWebServerRequest *request) -{ - if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode - String hostH; - if (!request->hasHeader("Host")) return false; - hostH = request->getHeader("Host")->value(); - - if (!isIp(hostH) && hostH.indexOf("wled.me") < 0 && hostH.indexOf(cmDNS) < 0) { - DEBUG_PRINTLN("Captive portal"); - AsyncWebServerResponse *response = request->beginResponse(302); - response->addHeader(F("Location"), F("http://4.3.2.1")); - request->send(response); - return true; - } - return false; -} - -void initServer() -{ - //CORS compatiblity - DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), "*"); - DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Methods"), "*"); - DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), "*"); - - #ifdef WLED_ENABLE_WEBSOCKETS - server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_liveviewws); - }); - #else - server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_liveview); - }); - #endif - - //settings page - server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){ - serveSettings(request); - }); - - server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){ - if(!handleFileRead(request, "/favicon.ico")) - { - request->send_P(200, "image/x-icon", favicon, 156); - } - }); - - server.on("/sliders", HTTP_GET, [](AsyncWebServerRequest *request){ - serveIndex(request); - }); - - server.on("/welcome", HTTP_GET, [](AsyncWebServerRequest *request){ - serveSettings(request); - }); - - server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129); - doReboot = true; - }); - - server.on("/settings", HTTP_POST, [](AsyncWebServerRequest *request){ - serveSettings(request, true); - }); - - server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){ - serveJson(request); - }); - - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { - 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)); - JsonObject root = jsonBuffer.as(); - 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) - verboseResponse = deserializeState(root); - fileDoc = nullptr; - } - if (verboseResponse) { //if JSON contains "v" - serveJson(request,vAPI); return; - } - request->send(200, "application/json", F("{\"success\":true}")); - }); - server.addHandler(handler); - - server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)VERSION); - }); - - server.on("/uptime", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)millis()); - }); - - server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)ESP.getFreeHeap()); - }); - - server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_usermod); - }); - - server.on("/url", HTTP_GET, [](AsyncWebServerRequest *request){ - URL_response(request); - }); - - server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254); - }); - - //if OTA is allowed - if (!otaLock){ - #ifdef WLED_ENABLE_FS_EDITOR - #ifdef ARDUINO_ARCH_ESP32 - server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password)); - #else - server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); - #endif - #else - server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254); - }); - #endif - //init ota page - #ifndef WLED_DISABLE_OTA - server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_update); - }); - - server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ - if (Update.hasError()) - { - serveMessage(request, 500, F("Failed updating firmware!"), F("Please check your file and retry!"), 254); return; - } - serveMessage(request, 200, F("Successfully updated firmware!"), F("Please wait while the module reboots..."), 131); - doReboot = true; - },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ - if(!index){ - DEBUG_PRINTLN(F("OTA Update Start")); - #ifdef ESP8266 - Update.runAsync(true); - #endif - Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); - } - if(!Update.hasError()) Update.write(data, len); - if(final){ - if(Update.end(true)){ - DEBUG_PRINTLN(F("Update Success")); - } else { - DEBUG_PRINTLN(F("Update Failed")); - } - } - }); - - #else - server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("OTA updates are disabled in this build."), 254); - }); - #endif - } else - { - server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - 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", F("Please unlock OTA in security settings!"), 254); - }); - } - - - #ifdef WLED_ENABLE_DMX - server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor); - }); - #else - server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254); - }); - #endif - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - 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){ - DEBUG_PRINTLN("Not-Found HTTP call:"); - DEBUG_PRINTLN("URI: " + request->url()); - if (captivePortal(request)) return; - - //make API CORS compatible - if (request->method() == HTTP_OPTIONS) - { - request->send(200); return; - } - - if(handleSet(request, request->url())) return; - #ifndef WLED_DISABLE_ALEXA - if(espalexa.handleAlexaApiCall(request)) return; - #endif - if(handleFileRead(request, request->url())) return; - request->send_P(404, "text/html", PAGE_404); - }); -} - - -void serveIndexOrWelcome(AsyncWebServerRequest *request) -{ - if (!showWelcomePage){ - serveIndex(request); - } else { - serveSettings(request); - } -} - -bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request) -{ - AsyncWebHeader* header = request->getHeader("If-None-Match"); - if (header && header->value() == String(VERSION)) { - request->send(304); - return true; - } - return false; -} - -void setStaticContentCacheHeaders(AsyncWebServerResponse *response) -{ - response->addHeader(F("Cache-Control"),"max-age=2592000"); - response->addHeader(F("ETag"), String(VERSION)); -} - -void serveIndex(AsyncWebServerRequest* request) -{ - if (handleFileRead(request, "/index.htm")) return; - - if (handleIfNoneMatchCacheHeader(request)) return; - - AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_index, PAGE_index_L); - - response->addHeader(F("Content-Encoding"),"gzip"); - #ifndef WLED_DEBUG - setStaticContentCacheHeaders(response); - #endif - request->send(response); -} - - -String msgProcessor(const String& var) -{ - if (var == "MSG") { - String messageBody = messageHead; - messageBody += F(""); - messageBody += messageSub; - uint32_t optt = optionType; - - if (optt < 60) //redirect to settings after optionType seconds - { - messageBody += F(""); - } else if (optt < 120) //redirect back after optionType-60 seconds, unused - { - //messageBody += ""; - } else if (optt < 180) //reload parent after optionType-120 seconds - { - messageBody += F(""); - } else if (optt == 253) - { - messageBody += F("

"); //button to settings - } else if (optt == 254) - { - messageBody += F("

"); - } - return messageBody; - } - return String(); -} - - -void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl, byte optionT) -{ - messageHead = headl; - messageSub = subl; - optionType = optionT; - - request->send_P(code, "text/html", PAGE_msg, msgProcessor); -} - - -String settingsProcessor(const String& var) -{ - if (var == "CSS") { - char buf[2048]; - getSettingsJS(optionType, buf); - return String(buf); - } - - #ifdef WLED_ENABLE_DMX - - if (var == "DMXMENU") { - return String(F("
")); - } - - #endif - if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); - return String(); -} - -String dmxProcessor(const String& var) -{ - String mapJS; - #ifdef WLED_ENABLE_DMX - if (var == "DMXVARS") { - mapJS += "\nCN=" + String(DMXChannels) + ";\n"; - mapJS += "CS=" + String(DMXStart) + ";\n"; - mapJS += "CG=" + String(DMXGap) + ";\n"; - mapJS += "LC=" + String(ledCount) + ";\n"; - mapJS += "var CH=["; - for (int i=0;i<15;i++) { - mapJS += String(DMXFixtureMap[i]) + ","; - } - mapJS += "0];"; - } - #endif - - return mapJS; -} - - -void serveSettings(AsyncWebServerRequest* request, bool post) -{ - byte subPage = 0; - const String& url = request->url(); - if (url.indexOf("sett") >= 0) - { - if (url.indexOf("wifi") > 0) subPage = 1; - else if (url.indexOf("leds") > 0) subPage = 2; - else if (url.indexOf("ui") > 0) subPage = 3; - else if (url.indexOf("sync") > 0) subPage = 4; - else if (url.indexOf("time") > 0) subPage = 5; - else if (url.indexOf("sec") > 0) subPage = 6; - #ifdef WLED_ENABLE_DMX // include only if DMX is enabled - else if (url.indexOf("dmx") > 0) subPage = 7; - #endif - } else subPage = 255; //welcome page - - if (subPage == 1 && wifiLock && otaLock) - { - serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); return; - } - - if (post) { //settings/set POST request, saving - if (subPage != 1 || !(wifiLock && otaLock)) handleSettingsSet(request, subPage); - - char s[32]; - char s2[45] = ""; - - switch (subPage) { - case 1: strcpy_P(s, PSTR("WiFi")); strcpy_P(s2, PSTR("Please connect to the new IP (if changed)")); forceReconnect = true; break; - case 2: strcpy_P(s, PSTR("LED")); break; - case 3: strcpy_P(s, PSTR("UI")); break; - case 4: strcpy_P(s, PSTR("Sync")); break; - case 5: strcpy_P(s, PSTR("Time")); break; - case 6: strcpy_P(s, PSTR("Security")); strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; - case 7: strcpy_P(s, PSTR("DMX")); break; - } - - strcat_P(s, PSTR(" settings saved.")); - if (!s2[0]) strcpy_P(s2, PSTR("Redirecting...")); - - if (!doReboot) serveMessage(request, 200, s, s2, (subPage == 1 || subPage == 6) ? 129 : 1); - if (subPage == 6) doReboot = true; - - return; - } - - #ifdef WLED_DISABLE_MOBILE_UI //disable welcome page if not enough storage - if (subPage == 255) {serveIndex(request); return;} - #endif - - optionType = subPage; - - switch (subPage) - { - case 1: request->send_P(200, "text/html", PAGE_settings_wifi, settingsProcessor); break; - case 2: request->send_P(200, "text/html", PAGE_settings_leds, settingsProcessor); break; - case 3: request->send_P(200, "text/html", PAGE_settings_ui , settingsProcessor); break; - case 4: request->send_P(200, "text/html", PAGE_settings_sync, settingsProcessor); break; - case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break; - case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break; - case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break; - case 255: request->send_P(200, "text/html", PAGE_welcome); break; - default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor); - } -} +#include "wled.h" + +/* + * Integrated HTTP web server page declarations + */ + +//Is this an IP? +bool isIp(String str) { + for (size_t i = 0; i < str.length(); i++) { + int c = str.charAt(i); + if (c != '.' && (c < '0' || c > '9')) { + return false; + } + } + return true; +} + +bool captivePortal(AsyncWebServerRequest *request) +{ + if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode + String hostH; + if (!request->hasHeader("Host")) return false; + hostH = request->getHeader("Host")->value(); + + if (!isIp(hostH) && hostH.indexOf("wled.me") < 0 && hostH.indexOf(cmDNS) < 0) { + DEBUG_PRINTLN("Captive portal"); + AsyncWebServerResponse *response = request->beginResponse(302); + response->addHeader(F("Location"), F("http://4.3.2.1")); + request->send(response); + return true; + } + return false; +} + +void initServer() +{ + //CORS compatiblity + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), "*"); + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Methods"), "*"); + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), "*"); + + #ifdef WLED_ENABLE_WEBSOCKETS + server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_liveviewws); + }); + #else + server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_liveview); + }); + #endif + + //settings page + server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){ + serveSettings(request); + }); + + server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!handleFileRead(request, "/favicon.ico")) + { + request->send_P(200, "image/x-icon", favicon, 156); + } + }); + + server.on("/sliders", HTTP_GET, [](AsyncWebServerRequest *request){ + serveIndex(request); + }); + + server.on("/welcome", HTTP_GET, [](AsyncWebServerRequest *request){ + serveSettings(request); + }); + + server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129); + doReboot = true; + }); + + server.on("/settings", HTTP_POST, [](AsyncWebServerRequest *request){ + serveSettings(request, true); + }); + + server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){ + serveJson(request); + }); + + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { + 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)); + JsonObject root = jsonBuffer.as(); + 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) + verboseResponse = deserializeState(root); + fileDoc = nullptr; + } + if (verboseResponse) { //if JSON contains "v" + serveJson(request,vAPI); return; + } + request->send(200, "application/json", F("{\"success\":true}")); + }); + server.addHandler(handler); + + server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", (String)VERSION); + }); + + server.on("/uptime", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", (String)millis()); + }); + + server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", (String)ESP.getFreeHeap()); + }); + + server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_usermod); + }); + + server.on("/url", HTTP_GET, [](AsyncWebServerRequest *request){ + URL_response(request); + }); + + server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254); + }); + + //if OTA is allowed + if (!otaLock){ + #ifdef WLED_ENABLE_FS_EDITOR + #ifdef ARDUINO_ARCH_ESP32 + server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password)); + #else + server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); + #endif + #else + server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254); + }); + #endif + //init ota page + #ifndef WLED_DISABLE_OTA + server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_update); + }); + + server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ + if (Update.hasError()) + { + serveMessage(request, 500, F("Failed updating firmware!"), F("Please check your file and retry!"), 254); return; + } + serveMessage(request, 200, F("Successfully updated firmware!"), F("Please wait while the module reboots..."), 131); + doReboot = true; + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index){ + DEBUG_PRINTLN(F("OTA Update Start")); + #ifdef ESP8266 + Update.runAsync(true); + #endif + Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); + } + if(!Update.hasError()) Update.write(data, len); + if(final){ + if(Update.end(true)){ + DEBUG_PRINTLN(F("Update Success")); + } else { + DEBUG_PRINTLN(F("Update Failed")); + } + } + }); + + #else + server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, "Not implemented", F("OTA updates are disabled in this build."), 254); + }); + #endif + } else + { + server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ + 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", F("Please unlock OTA in security settings!"), 254); + }); + } + + + #ifdef WLED_ENABLE_DMX + server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor); + }); + #else + server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254); + }); + #endif + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + 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){ + DEBUG_PRINTLN("Not-Found HTTP call:"); + DEBUG_PRINTLN("URI: " + request->url()); + if (captivePortal(request)) return; + + //make API CORS compatible + if (request->method() == HTTP_OPTIONS) + { + request->send(200); return; + } + + if(handleSet(request, request->url())) return; + #ifndef WLED_DISABLE_ALEXA + if(espalexa.handleAlexaApiCall(request)) return; + #endif + if(handleFileRead(request, request->url())) return; + request->send_P(404, "text/html", PAGE_404); + }); +} + + +void serveIndexOrWelcome(AsyncWebServerRequest *request) +{ + if (!showWelcomePage){ + serveIndex(request); + } else { + serveSettings(request); + } +} + +bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request) +{ + AsyncWebHeader* header = request->getHeader("If-None-Match"); + if (header && header->value() == String(VERSION)) { + request->send(304); + return true; + } + return false; +} + +void setStaticContentCacheHeaders(AsyncWebServerResponse *response) +{ + response->addHeader(F("Cache-Control"),"max-age=2592000"); + response->addHeader(F("ETag"), String(VERSION)); +} + +void serveIndex(AsyncWebServerRequest* request) +{ + if (handleFileRead(request, "/index.htm")) return; + + if (handleIfNoneMatchCacheHeader(request)) return; + + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_index, PAGE_index_L); + + response->addHeader(F("Content-Encoding"),"gzip"); + #ifndef WLED_DEBUG + setStaticContentCacheHeaders(response); + #endif + request->send(response); +} + + +String msgProcessor(const String& var) +{ + if (var == "MSG") { + String messageBody = messageHead; + messageBody += F(""); + messageBody += messageSub; + uint32_t optt = optionType; + + if (optt < 60) //redirect to settings after optionType seconds + { + messageBody += F(""); + } else if (optt < 120) //redirect back after optionType-60 seconds, unused + { + //messageBody += ""; + } else if (optt < 180) //reload parent after optionType-120 seconds + { + messageBody += F(""); + } else if (optt == 253) + { + messageBody += F("

"); //button to settings + } else if (optt == 254) + { + messageBody += F("

"); + } + return messageBody; + } + return String(); +} + + +void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl, byte optionT) +{ + messageHead = headl; + messageSub = subl; + optionType = optionT; + + request->send_P(code, "text/html", PAGE_msg, msgProcessor); +} + + +String settingsProcessor(const String& var) +{ + if (var == "CSS") { + char buf[2048]; + getSettingsJS(optionType, buf); + return String(buf); + } + + #ifdef WLED_ENABLE_DMX + + if (var == "DMXMENU") { + return String(F("
")); + } + + #endif + if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); + return String(); +} + +String dmxProcessor(const String& var) +{ + String mapJS; + #ifdef WLED_ENABLE_DMX + if (var == "DMXVARS") { + mapJS += "\nCN=" + String(DMXChannels) + ";\n"; + mapJS += "CS=" + String(DMXStart) + ";\n"; + mapJS += "CG=" + String(DMXGap) + ";\n"; + mapJS += "LC=" + String(ledCount) + ";\n"; + mapJS += "var CH=["; + for (int i=0;i<15;i++) { + mapJS += String(DMXFixtureMap[i]) + ","; + } + mapJS += "0];"; + } + #endif + + return mapJS; +} + + +void serveSettings(AsyncWebServerRequest* request, bool post) +{ + byte subPage = 0; + const String& url = request->url(); + if (url.indexOf("sett") >= 0) + { + if (url.indexOf("wifi") > 0) subPage = 1; + else if (url.indexOf("leds") > 0) subPage = 2; + else if (url.indexOf("ui") > 0) subPage = 3; + else if (url.indexOf("sync") > 0) subPage = 4; + else if (url.indexOf("time") > 0) subPage = 5; + else if (url.indexOf("sec") > 0) subPage = 6; + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + else if (url.indexOf("dmx") > 0) subPage = 7; + #endif + } else subPage = 255; //welcome page + + if (subPage == 1 && wifiLock && otaLock) + { + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); return; + } + + if (post) { //settings/set POST request, saving + if (subPage != 1 || !(wifiLock && otaLock)) handleSettingsSet(request, subPage); + + char s[32]; + char s2[45] = ""; + + switch (subPage) { + case 1: strcpy_P(s, PSTR("WiFi")); strcpy_P(s2, PSTR("Please connect to the new IP (if changed)")); forceReconnect = true; break; + case 2: strcpy_P(s, PSTR("LED")); break; + case 3: strcpy_P(s, PSTR("UI")); break; + case 4: strcpy_P(s, PSTR("Sync")); break; + case 5: strcpy_P(s, PSTR("Time")); break; + case 6: strcpy_P(s, PSTR("Security")); strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; + case 7: strcpy_P(s, PSTR("DMX")); break; + } + + strcat_P(s, PSTR(" settings saved.")); + if (!s2[0]) strcpy_P(s2, PSTR("Redirecting...")); + + if (!doReboot) serveMessage(request, 200, s, s2, (subPage == 1 || subPage == 6) ? 129 : 1); + if (subPage == 6) doReboot = true; + + return; + } + + #ifdef WLED_DISABLE_MOBILE_UI //disable welcome page if not enough storage + if (subPage == 255) {serveIndex(request); return;} + #endif + + optionType = subPage; + + switch (subPage) + { + case 1: request->send_P(200, "text/html", PAGE_settings_wifi, settingsProcessor); break; + case 2: request->send_P(200, "text/html", PAGE_settings_leds, settingsProcessor); break; + case 3: request->send_P(200, "text/html", PAGE_settings_ui , settingsProcessor); break; + case 4: request->send_P(200, "text/html", PAGE_settings_sync, settingsProcessor); break; + case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break; + case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break; + case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break; + case 255: request->send_P(200, "text/html", PAGE_welcome); break; + default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor); + } +} diff --git a/wled00/ws.cpp b/wled00/ws.cpp index ec56e6be..c018aefc 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -1,156 +1,156 @@ -#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; - break; - } - } else { - uint8_t minAPI = 2; - for (uint8_t i=0; i ClientApis[i].vAPI) minAPI = ClientApis[i].vAPI; - } - state["rev"] = minAPI; - } - DEBUG_PRINTF("Actual API used: %d\n", (int)state["rev"]); - 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; + break; + } + } else { + uint8_t minAPI = 2; + for (uint8_t i=0; i ClientApis[i].vAPI) minAPI = ClientApis[i].vAPI; + } + state["rev"] = minAPI; + } + DEBUG_PRINTF("Actual API used: %d\n", (int)state["rev"]); + 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