From 8dd17451495fa96cc87fe6f9a145d7c041b35e86 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Thu, 5 Jan 2023 19:48:53 +0100 Subject: [PATCH 001/694] =?UTF-8?q?Add=20base=20battery=20=F0=9F=94=8B=20c?= =?UTF-8?q?lass,=20Add=20Lipo,=20Lion=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/battery.h | 149 +++++++++++++++++ usermods/Battery/battery_defaults.h | 11 +- usermods/Battery/lion.h | 37 +++++ usermods/Battery/lipo.h | 51 ++++++ usermods/Battery/usermod_v2_Battery.h | 221 +++++--------------------- 5 files changed, 290 insertions(+), 179 deletions(-) create mode 100644 usermods/Battery/battery.h create mode 100644 usermods/Battery/lion.h create mode 100644 usermods/Battery/lipo.h diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h new file mode 100644 index 000000000..f3daf05c4 --- /dev/null +++ b/usermods/Battery/battery.h @@ -0,0 +1,149 @@ +#ifndef UMBBattery_h +#define UMBBattery_h + +#include "battery_defaults.h" + +/** + * Battery base class + * all other battery classes should inherit from this + */ +class Battery +{ + private: + + protected: + float minVoltage = USERMOD_BATTERY_MIN_VOLTAGE; + float maxVoltage = USERMOD_BATTERY_MAX_VOLTAGE; + unsigned int capacity = USERMOD_BATTERY_TOTAL_CAPACITY; // current capacity + float voltage = this->maxVoltage; // current voltage + int8_t level = 100; // current level + float calibration = USERMOD_BATTERY_CALIBRATION; // offset or calibration value to fine tune the calculated voltage + + float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f) + { + return (v-min) * (oMax-oMin) / (max-min) + oMin; + } + + public: + Battery() + { + + } + + /** + * Corresponding battery curves + * calculates the capacity in % (0-100) with given voltage and possible voltage range + */ + virtual float mapVoltage(float v, float min, float max) = 0; + // { + // example implementation, linear mapping + // return (v-min) * 100 / (max-min); + // }; + + virtual void calculateAndSetLevel(float voltage) = 0; + + + + /* + * + * Getter and Setter + * + */ + + /* + * Get lowest configured battery voltage + */ + virtual float getMinVoltage() + { + return this->minVoltage; + } + + /* + * Set lowest battery voltage + * can't be below 0 volt + */ + virtual void setMinVoltage(float voltage) + { + this->minVoltage = max(0.0f, voltage); + } + + /* + * Get highest configured battery voltage + */ + virtual float getMaxVoltage() + { + return this->maxVoltage; + } + + /* + * Set highest battery voltage + * can't be below minVoltage + */ + virtual void setMaxVoltage(float voltage) + { + #ifdef USERMOD_BATTERY_USE_LIPO + this->maxVoltage = max(getMinVoltage()+0.7f, voltage); + #else + this->maxVoltage = max(getMinVoltage()+1.0f, voltage); + #endif + } + + /* + * Get the capacity of all cells in parralel sumed up + * unit: mAh + */ + unsigned int getCapacity() + { + return this->capacity; + } + + void setCapacity(unsigned int capacity) + { + this->capacity = capacity; + } + + float getVoltage() + { + return this->voltage; + } + + /** + * check if voltage is within specified voltage range, allow 10% over/under voltage + */ + void setVoltage(float voltage) + { + this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) ) + ? -1.0f + : voltage; + } + + float getLevel() + { + return this->level; + } + + void setLevel(float level) + { + this->level = constrain(level, 0.0f, 110.0f);; + } + + /* + * Get the configured calibration value + * a offset value to fine-tune the calculated voltage. + */ + virtual float getCalibration() + { + return calibration; + } + + /* + * Set the voltage calibration offset value + * a offset value to fine-tune the calculated voltage. + */ + virtual void setCalibration(float offset) + { + calibration = offset; + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index c682cb45d..73f14f62a 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -14,6 +14,15 @@ #define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000 #endif + +/* Default Battery Type + * 1 = Lipo + * 2 = Lion + */ +#ifndef USERMOB_BATTERY_DEFAULT_TYPE + #define USERMOB_BATTERY_DEFAULT_TYPE 1 +#endif + // default for 18650 battery // https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop // Discharge voltage: 2.5 volt + .1 for personal safety @@ -69,4 +78,4 @@ #ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION #define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5 -#endif \ No newline at end of file +#endif diff --git a/usermods/Battery/lion.h b/usermods/Battery/lion.h new file mode 100644 index 000000000..e8d78cc72 --- /dev/null +++ b/usermods/Battery/lion.h @@ -0,0 +1,37 @@ +#ifndef UMBLion_h +#define UMBLion_h + +#include "battery_defaults.h" +#include "battery.h" + +/** + * Lion Battery + * + */ +class Lion : public Battery +{ + private: + + public: + Lion() : Battery() + { + + } + + float mapVoltage(float v, float min, float max) override + { + return 0.0f; + }; + + void calculateAndSetLevel(float voltage) override + { + + }; + + virtual void setMaxVoltage(float voltage) override + { + this->maxVoltage = max(getMinVoltage()+1.0f, voltage); + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/lipo.h b/usermods/Battery/lipo.h new file mode 100644 index 000000000..4e9b0be7c --- /dev/null +++ b/usermods/Battery/lipo.h @@ -0,0 +1,51 @@ +#ifndef UMBLipo_h +#define UMBLipo_h + +#include "battery_defaults.h" +#include "battery.h" + +/** + * Lipo Battery + * + */ +class Lipo : public Battery +{ + private: + + public: + Lipo() : Battery() + { + + } + + /** + * LiPo batteries have a differnt dischargin curve, see + * https://blog.ampow.com/lipo-voltage-chart/ + */ + float mapVoltage(float v, float min, float max) override + { + float lvl = 0.0f; + lvl = this->linearMapping(v, min, max); // basic mapping + + if (lvl < 40.0f) + lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly + else { + if (lvl < 90.0f) + lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop + else // level > 90% + lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly + } + }; + + void calculateAndSetLevel(float voltage) override + { + this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage())); + }; + + virtual void setMaxVoltage(float voltage) override + { + this->maxVoltage = max(getMinVoltage()+0.7f, voltage); + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index ac34a7e4d..4c77ca5dd 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -2,6 +2,9 @@ #include "wled.h" #include "battery_defaults.h" +#include "battery.h" +#include "lion.h" +#include "lipo.h" /* * Usermod by Maximilian Mewes @@ -15,28 +18,12 @@ class UsermodBattery : public Usermod private: // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; + // Battery object + Battery* bat; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; - // battery min. voltage - float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE; - // battery max. voltage - float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE; - // all battery cells summed up - unsigned int totalBatteryCapacity = USERMOD_BATTERY_TOTAL_CAPACITY; - // raw analog reading - float rawValue = 0.0f; - // calculated voltage - float voltage = maxBatteryVoltage; - // mapped battery level based on voltage - int8_t batteryLevel = 100; - // offset or calibration value to fine tune the calculated voltage - float calibration = USERMOD_BATTERY_CALIBRATION; - - // time left estimation feature - // bool calculateTimeLeftEnabled = USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED; - // float estimatedTimeLeft = 0.0; // auto shutdown/shutoff/master off feature bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED; @@ -63,14 +50,6 @@ class UsermodBattery : public Usermod static const char _preset[]; static const char _duration[]; static const char _init[]; - - - // custom map function - // https://forum.arduino.cc/t/floating-point-using-map-function/348113/2 - double mapf(double x, double in_min, double in_max, double out_min, double out_max) - { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; - } float dot2round(float x) { @@ -94,8 +73,8 @@ class UsermodBattery : public Usermod { if (!lowPowerIndicatorEnabled) return; if (batteryPin < 0) return; // no measurement - if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= batteryLevel) lowPowerIndicationDone = false; - if (lowPowerIndicatorThreshold <= batteryLevel) return; + if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= bat->getLevel()) lowPowerIndicationDone = false; + if (lowPowerIndicatorThreshold <= bat->getLevel()) return; if (lowPowerIndicationDone) return; if (lowPowerActivationTime <= 1) { lowPowerActivationTime = millis(); @@ -139,6 +118,16 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); #endif + // this could also be handled with a factory class but for only 2 types now it should be sufficient + if(USERMOB_BATTERY_DEFAULT_TYPE == 1) { + bat = new Lipo(); + } else + if(USERMOB_BATTERY_DEFAULT_TYPE == 2) { + bat = new Lion(); + } else { + bat = new Lipo(); + } + nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -174,8 +163,9 @@ class UsermodBattery : public Usermod if (batteryPin < 0) return; // nothing to read - initializing = false; - + initializing = false; + float voltage = -1.0f; + float rawValue = 0.0f; #ifdef ARDUINO_ARCH_ESP32 // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) rawValue = analogReadMilliVolts(batteryPin); @@ -188,40 +178,15 @@ class UsermodBattery : public Usermod rawValue = analogRead(batteryPin); // calculate the voltage - voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; + voltage = ((rawValue / getAdcPrecision()) * bat->getMaxVoltage()) + bat->getCalibration(); #endif - // check if voltage is within specified voltage range, allow 10% over/under voltage - voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; + bat->setVoltage(voltage); // translate battery voltage into percentage - /* - the standard "map" function doesn't work - https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom - */ - #ifdef USERMOD_BATTERY_USE_LIPO - batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); // basic mapping - // LiPo batteries have a differnt dischargin curve, see - // https://blog.ampow.com/lipo-voltage-chart/ - if (batteryLevel < 40.0f) - batteryLevel = mapf(batteryLevel, 0, 40, 0, 12); // last 45% -> drops very quickly - else { - if (batteryLevel < 90.0f) - batteryLevel = mapf(batteryLevel, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop - else // level > 90% - batteryLevel = mapf(batteryLevel, 90, 105, 95, 100); // highest 15% -> drop slowly - } - #else - batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); - #endif - if (voltage > -1.0f) batteryLevel = constrain(batteryLevel, 0.0f, 110.0f); - - // if (calculateTimeLeftEnabled) { - // float currentBatteryCapacity = totalBatteryCapacity; - // estimatedTimeLeft = (currentBatteryCapacity/strip.currentMilliamps)*60; - // } + bat->calculateAndSetLevel(voltage); // Auto off -- Master power off - if (autoOffEnabled && (autoOffThreshold >= batteryLevel)) + if (autoOffEnabled && (autoOffThreshold >= bat->getLevel())) turnOff(); // SmartHome stuff @@ -254,16 +219,6 @@ class UsermodBattery : public Usermod // info modal display names JsonArray infoPercentage = user.createNestedArray(F("Battery level")); JsonArray infoVoltage = user.createNestedArray(F("Battery voltage")); - // if (calculateTimeLeftEnabled) - // { - // JsonArray infoEstimatedTimeLeft = user.createNestedArray(F("Estimated time left")); - // if (initializing) { - // infoEstimatedTimeLeft.add(FPSTR(_init)); - // } else { - // infoEstimatedTimeLeft.add(estimatedTimeLeft); - // infoEstimatedTimeLeft.add(F(" min")); - // } - // } JsonArray infoNextUpdate = user.createNestedArray(F("Next update")); infoNextUpdate.add((nextReadTime - millis()) / 1000); @@ -275,17 +230,17 @@ class UsermodBattery : public Usermod return; } - if (batteryLevel < 0) { + if (bat->getLevel() < 0) { infoPercentage.add(F("invalid")); } else { - infoPercentage.add(batteryLevel); + infoPercentage.add(bat->getLevel()); } infoPercentage.add(F(" %")); - if (voltage < 0) { + if (bat->getVoltage() < 0) { infoVoltage.add(F("invalid")); } else { - infoVoltage.add(dot2round(voltage)); + infoVoltage.add(dot2round(bat->getVoltage())); } infoVoltage.add(F(" V")); } @@ -298,7 +253,7 @@ class UsermodBattery : public Usermod /* void addToJsonState(JsonObject& root) { - + // TBD } */ @@ -310,6 +265,7 @@ class UsermodBattery : public Usermod /* void readFromJsonState(JsonObject& root) { + // TBD } */ @@ -356,18 +312,17 @@ class UsermodBattery : public Usermod battery[F("pin")] = batteryPin; #endif - // battery[F("time-left")] = calculateTimeLeftEnabled; - battery[F("min-voltage")] = minBatteryVoltage; - battery[F("max-voltage")] = maxBatteryVoltage; - battery[F("capacity")] = totalBatteryCapacity; - battery[F("calibration")] = calibration; + battery[F("min-voltage")] = bat->getMinVoltage(); + battery[F("max-voltage")] = bat->getMaxVoltage(); + battery[F("capacity")] = bat->getCapacity(); + battery[F("calibration")] = bat->getCalibration(); battery[FPSTR(_readInterval)] = readingInterval; - JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section + JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section ao[FPSTR(_enabled)] = autoOffEnabled; ao[FPSTR(_threshold)] = autoOffThreshold; - JsonObject lp = battery.createNestedObject(F("indicator")); // low power section + JsonObject lp = battery.createNestedObject(F("indicator")); // low power section lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled; lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset; lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; @@ -432,11 +387,11 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; - setMinBatteryVoltage(battery[F("min-voltage")] | minBatteryVoltage); - setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); - setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity); - setCalibration(battery[F("calibration")] | calibration); + + bat->setMinVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); + bat->setMaxVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); + bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); + bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; @@ -479,7 +434,8 @@ class UsermodBattery : public Usermod } /* - * Generate a preset sample for low power indication + * TBD: Generate a preset sample for low power indication + * a button on the config page would be cool, currently not possible */ void generateExamplePreset() { @@ -539,60 +495,6 @@ class UsermodBattery : public Usermod readingInterval = max((unsigned long)3000, newReadingInterval); } - - /* - * Get lowest configured battery voltage - */ - float getMinBatteryVoltage() - { - return minBatteryVoltage; - } - - /* - * Set lowest battery voltage - * can't be below 0 volt - */ - void setMinBatteryVoltage(float voltage) - { - minBatteryVoltage = max(0.0f, voltage); - } - - /* - * Get highest configured battery voltage - */ - float getMaxBatteryVoltage() - { - return maxBatteryVoltage; - } - - /* - * Set highest battery voltage - * can't be below minBatteryVoltage - */ - void setMaxBatteryVoltage(float voltage) - { - #ifdef USERMOD_BATTERY_USE_LIPO - maxBatteryVoltage = max(getMinBatteryVoltage()+0.7f, voltage); - #else - maxBatteryVoltage = max(getMinBatteryVoltage()+1.0f, voltage); - #endif - } - - - /* - * Get the capacity of all cells in parralel sumed up - * unit: mAh - */ - unsigned int getTotalBatteryCapacity() - { - return totalBatteryCapacity; - } - - void setTotalBatteryCapacity(unsigned int capacity) - { - totalBatteryCapacity = capacity; - } - /* * Get the choosen adc precision * esp8266 = 10bit resolution = 1024.0f @@ -609,43 +511,6 @@ class UsermodBattery : public Usermod #endif } - /* - * Get the calculated voltage - * formula: (adc pin value / adc precision * max voltage) + calibration - */ - float getVoltage() - { - return voltage; - } - - /* - * Get the mapped battery level (0 - 100) based on voltage - * important: voltage can drop when a load is applied, so its only an estimate - */ - int8_t getBatteryLevel() - { - return batteryLevel; - } - - /* - * Get the configured calibration value - * a offset value to fine-tune the calculated voltage. - */ - float getCalibration() - { - return calibration; - } - - /* - * Set the voltage calibration offset value - * a offset value to fine-tune the calculated voltage. - */ - void setCalibration(float offset) - { - calibration = offset; - } - - /* * Get auto-off feature enabled status * is auto-off enabled, true/false From 4c8b490c89635a647b9b7e30a519b8a358a1fc47 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Thu, 5 Jan 2023 20:38:55 +0100 Subject: [PATCH 002/694] minor changes --- usermods/Battery/battery.h | 6 +----- usermods/Battery/battery_defaults.h | 7 +------ usermods/Battery/lion.h | 4 ++-- usermods/Battery/lipo.h | 2 ++ usermods/Battery/usermod_v2_Battery.h | 18 ++++++++++++++---- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index f3daf05c4..c678f775d 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -81,11 +81,7 @@ class Battery */ virtual void setMaxVoltage(float voltage) { - #ifdef USERMOD_BATTERY_USE_LIPO - this->maxVoltage = max(getMinVoltage()+0.7f, voltage); - #else - this->maxVoltage = max(getMinVoltage()+1.0f, voltage); - #endif + this->maxVoltage = max(getMinVoltage()+.5f, voltage); } /* diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 73f14f62a..4a04ac352 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -27,7 +27,7 @@ // https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop // Discharge voltage: 2.5 volt + .1 for personal safety #ifndef USERMOD_BATTERY_MIN_VOLTAGE - #ifdef USERMOD_BATTERY_USE_LIPO + #if USERMOB_BATTERY_DEFAULT_TYPE == 1 // LiPo "1S" Batteries should not be dischared below 3V !! #define USERMOD_BATTERY_MIN_VOLTAGE 3.2f #else @@ -49,11 +49,6 @@ #define USERMOD_BATTERY_CALIBRATION 0 #endif -// calculate remaining time / the time that is left before the battery runs out of power -// #ifndef USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED -// #define USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED false -// #endif - // auto-off feature #ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED #define USERMOD_BATTERY_AUTO_OFF_ENABLED true diff --git a/usermods/Battery/lion.h b/usermods/Battery/lion.h index e8d78cc72..69095ac09 100644 --- a/usermods/Battery/lion.h +++ b/usermods/Battery/lion.h @@ -20,12 +20,12 @@ class Lion : public Battery float mapVoltage(float v, float min, float max) override { - return 0.0f; + return this->linearMapping(v, min, max); // basic mapping }; void calculateAndSetLevel(float voltage) override { - + this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage())); }; virtual void setMaxVoltage(float voltage) override diff --git a/usermods/Battery/lipo.h b/usermods/Battery/lipo.h index 4e9b0be7c..92ede8f81 100644 --- a/usermods/Battery/lipo.h +++ b/usermods/Battery/lipo.h @@ -35,6 +35,8 @@ class Lipo : public Battery else // level > 90% lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly } + + return lvl; }; void calculateAndSetLevel(float voltage) override diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 4c77ca5dd..87b73ec6d 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -18,8 +18,11 @@ class UsermodBattery : public Usermod private: // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; + + int8_t batteryType = USERMOB_BATTERY_DEFAULT_TYPE; // Battery object Battery* bat; + // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; @@ -118,14 +121,14 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); #endif - // this could also be handled with a factory class but for only 2 types now it should be sufficient - if(USERMOB_BATTERY_DEFAULT_TYPE == 1) { + // this could also be handled with a factory class but for only 2 types it should be sufficient for now + if(batteryType == 1) { bat = new Lipo(); } else - if(USERMOB_BATTERY_DEFAULT_TYPE == 2) { + if(batteryType == 2) { bat = new Lion(); } else { - bat = new Lipo(); + bat = new Lipo(); // in the future one could create a nullObject } nextReadTime = millis() + readingInterval; @@ -317,6 +320,10 @@ class UsermodBattery : public Usermod battery[F("capacity")] = bat->getCapacity(); battery[F("calibration")] = bat->getCalibration(); battery[FPSTR(_readInterval)] = readingInterval; + + // JsonArray type = battery[F("Type")]; + // type[0] = 1; + // type[1] = 2; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section ao[FPSTR(_enabled)] = autoOffEnabled; @@ -393,6 +400,9 @@ class UsermodBattery : public Usermod bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); + + // JsonArray type = battery[F("Type")]; + // batteryType = type["bt"] | batteryType; JsonObject ao = battery[F("auto-off")]; setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); From 85d59945a0ffaaa8907ce69cc1cb3f7548de2775 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Fri, 6 Jan 2023 00:19:16 +0100 Subject: [PATCH 003/694] =?UTF-8?q?runtime=20exception=20fix=20?= =?UTF-8?q?=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/usermod_v2_Battery.h | 38 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 87b73ec6d..f9bfc96fb 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -20,9 +20,15 @@ class UsermodBattery : public Usermod int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; int8_t batteryType = USERMOB_BATTERY_DEFAULT_TYPE; - // Battery object - Battery* bat; - + + float minVoltage = USERMOD_BATTERY_MIN_VOLTAGE; + float maxVoltage = USERMOD_BATTERY_MAX_VOLTAGE; + unsigned int capacity = USERMOD_BATTERY_TOTAL_CAPACITY; // current capacity + float voltage = this->maxVoltage; // current voltage + int8_t level = 100; // current level + float calibration = USERMOD_BATTERY_CALIBRATION; // offset or calibration value to fine tune the calculated voltage + Battery* bat = nullptr; + // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; @@ -121,7 +127,7 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); #endif - // this could also be handled with a factory class but for only 2 types it should be sufficient for now + //this could also be handled with a factory class but for only 2 types it should be sufficient for now if(batteryType == 1) { bat = new Lipo(); } else @@ -315,15 +321,13 @@ class UsermodBattery : public Usermod battery[F("pin")] = batteryPin; #endif - battery[F("min-voltage")] = bat->getMinVoltage(); - battery[F("max-voltage")] = bat->getMaxVoltage(); - battery[F("capacity")] = bat->getCapacity(); - battery[F("calibration")] = bat->getCalibration(); + if(bat) { + battery[F("min-voltage")] = bat->getMinVoltage(); + battery[F("max-voltage")] = bat->getMaxVoltage(); + battery[F("capacity")] = bat->getCapacity(); + battery[F("calibration")] = bat->getCalibration(); + } battery[FPSTR(_readInterval)] = readingInterval; - - // JsonArray type = battery[F("Type")]; - // type[0] = 1; - // type[1] = 2; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section ao[FPSTR(_enabled)] = autoOffEnabled; @@ -395,10 +399,12 @@ class UsermodBattery : public Usermod newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - bat->setMinVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); - bat->setMaxVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); - bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); - bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); + if(bat) { + bat->setMinVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); + bat->setMaxVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); + bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); + bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); + } setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); // JsonArray type = battery[F("Type")]; From 375907144966e37e9a8dad3efc92343fec892052 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Fri, 6 Jan 2023 17:00:29 +0100 Subject: [PATCH 004/694] =?UTF-8?q?Fix=20previous=20bug=20again=20?= =?UTF-8?q?=F0=9F=90=9B,=20Add=20Type=20Dropdown=20to=20config=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/battery.h | 21 +++-- usermods/Battery/battery_defaults.h | 111 +++++++++++++++++++------- usermods/Battery/lion.h | 8 +- usermods/Battery/lipo.h | 10 ++- usermods/Battery/unkown.h | 36 +++++++++ usermods/Battery/usermod_v2_Battery.h | 62 +++++++------- 6 files changed, 178 insertions(+), 70 deletions(-) create mode 100644 usermods/Battery/unkown.h diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index c678f775d..ad3fb2703 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -12,12 +12,12 @@ class Battery private: protected: - float minVoltage = USERMOD_BATTERY_MIN_VOLTAGE; - float maxVoltage = USERMOD_BATTERY_MAX_VOLTAGE; - unsigned int capacity = USERMOD_BATTERY_TOTAL_CAPACITY; // current capacity - float voltage = this->maxVoltage; // current voltage - int8_t level = 100; // current level - float calibration = USERMOD_BATTERY_CALIBRATION; // offset or calibration value to fine tune the calculated voltage + float minVoltage; + float maxVoltage; + unsigned int capacity; + float voltage; + int8_t level = 100; + float calibration; // offset or calibration value to fine tune the calculated voltage float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f) { @@ -30,6 +30,15 @@ class Battery } + virtual void update(batteryConfig cfg) + { + if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); + if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); + if(cfg.calibration) this->setCapacity(cfg.calibration); + if(cfg.level) this->setLevel(cfg.level); + if(cfg.calibration) this->setCalibration(cfg.calibration); + } + /** * Corresponding battery curves * calculates the capacity in % (0-100) with given voltage and possible voltage range diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 4a04ac352..f4060ca60 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -1,3 +1,6 @@ +#ifndef UMBDefaults_h +#define UMBDefaults_h + // pin defaults // for the esp32 it is best to use the ADC1: GPIO32 - GPIO39 // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html @@ -16,38 +19,70 @@ /* Default Battery Type + * 0 = unkown * 1 = Lipo * 2 = Lion */ -#ifndef USERMOB_BATTERY_DEFAULT_TYPE - #define USERMOB_BATTERY_DEFAULT_TYPE 1 +#ifndef USERMOD_BATTERY_DEFAULT_TYPE + #define USERMOD_BATTERY_DEFAULT_TYPE 0 +#endif +/* + * + * Unkown 'Battery' defaults + * + */ +#ifndef USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE + #define USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE 3.3f +#endif +#ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE + #define USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE 4.2f +#endif +#ifndef USERMOD_BATTERY_UNKOWN_CAPACITY + #define USERMOD_BATTERY_UNKOWN_CAPACITY 2500 +#endif +#ifndef USERMOD_BATTERY_UNKOWN_CALIBRATION + // offset or calibration value to fine tune the calculated voltage + #define USERMOD_BATTERY_UNKOWN_CALIBRATION 0 +#endif +/* + * + * Lithium polymer (Li-Po) defaults + * + */ +#ifndef USERMOD_BATTERY_LIPO_MIN_VOLTAGE + // LiPo "1S" Batteries should not be dischared below 3V !! + #define USERMOD_BATTERY_LIPO_MIN_VOLTAGE 3.2f +#endif +#ifndef USERMOD_BATTERY_LIPO_MAX_VOLTAGE + #define USERMOD_BATTERY_LIPO_MAX_VOLTAGE 4.2f +#endif +#ifndef USERMOD_BATTERY_LIPO_CAPACITY + #define USERMOD_BATTERY_LIPO_CAPACITY 5000 +#endif +#ifndef USERMOD_BATTERY_LIPO_CALIBRATION + #define USERMOD_BATTERY_LIPO_CALIBRATION 0 +#endif +/* + * + * Lithium-ion (Li-Ion) defaults + * + */ +#ifndef USERMOD_BATTERY_LION_MIN_VOLTAGE + // default for 18650 battery + #define USERMOD_BATTERY_LION_MIN_VOLTAGE 2.6f +#endif +#ifndef USERMOD_BATTERY_LION_MAX_VOLTAGE + #define USERMOD_BATTERY_LION_MAX_VOLTAGE 4.2f +#endif +#ifndef USERMOD_BATTERY_LION_CAPACITY + // a common capacity for single 18650 battery cells is between 2500 and 3600 mAh + #define USERMOD_BATTERY_LION_CAPACITY 3100 +#endif +#ifndef USERMOD_BATTERY_LION_CALIBRATION + // offset or calibration value to fine tune the calculated voltage + #define USERMOD_BATTERY_LION_CALIBRATION 0 #endif -// default for 18650 battery -// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop -// Discharge voltage: 2.5 volt + .1 for personal safety -#ifndef USERMOD_BATTERY_MIN_VOLTAGE - #if USERMOB_BATTERY_DEFAULT_TYPE == 1 - // LiPo "1S" Batteries should not be dischared below 3V !! - #define USERMOD_BATTERY_MIN_VOLTAGE 3.2f - #else - #define USERMOD_BATTERY_MIN_VOLTAGE 2.6f - #endif -#endif - -#ifndef USERMOD_BATTERY_MAX_VOLTAGE - #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f -#endif - -// a common capacity for single 18650 battery cells is between 2500 and 3600 mAh -#ifndef USERMOD_BATTERY_TOTAL_CAPACITY - #define USERMOD_BATTERY_TOTAL_CAPACITY 3100 -#endif - -// offset or calibration value to fine tune the calculated voltage -#ifndef USERMOD_BATTERY_CALIBRATION - #define USERMOD_BATTERY_CALIBRATION 0 -#endif // auto-off feature #ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED @@ -74,3 +109,25 @@ #ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION #define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5 #endif + +typedef enum +{ + unknown=0, + lipo=1, + lion=2 +} batteryType; + +// used for initial configuration after boot +typedef struct bconfig_t +{ + batteryType type; + float minVoltage; + float maxVoltage; + unsigned int capacity; // current capacity + float voltage; // current voltage + int8_t level; // current level + float calibration; // offset or calibration value to fine tune the calculated voltage +} batteryConfig; + + +#endif \ No newline at end of file diff --git a/usermods/Battery/lion.h b/usermods/Battery/lion.h index 69095ac09..4016af7e3 100644 --- a/usermods/Battery/lion.h +++ b/usermods/Battery/lion.h @@ -13,9 +13,13 @@ class Lion : public Battery private: public: - Lion() : Battery() + Lion() { - + this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE); + this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); + this->setCapacity(USERMOD_BATTERY_LION_CAPACITY); + this->setVoltage(this->getVoltage()); + this->setCalibration(USERMOD_BATTERY_LION_CALIBRATION); } float mapVoltage(float v, float min, float max) override diff --git a/usermods/Battery/lipo.h b/usermods/Battery/lipo.h index 92ede8f81..03eed7b86 100644 --- a/usermods/Battery/lipo.h +++ b/usermods/Battery/lipo.h @@ -5,7 +5,7 @@ #include "battery.h" /** - * Lipo Battery + * Lipo Battery * */ class Lipo : public Battery @@ -13,9 +13,13 @@ class Lipo : public Battery private: public: - Lipo() : Battery() + Lipo() { - + this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE); + this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); + this->setCapacity(USERMOD_BATTERY_LIPO_CAPACITY); + this->setVoltage(this->getVoltage()); + this->setCalibration(USERMOD_BATTERY_LIPO_CALIBRATION); } /** diff --git a/usermods/Battery/unkown.h b/usermods/Battery/unkown.h new file mode 100644 index 000000000..63b2674f3 --- /dev/null +++ b/usermods/Battery/unkown.h @@ -0,0 +1,36 @@ +#ifndef UMBUnkown_h +#define UMBUnkown_h + +#include "battery_defaults.h" +#include "battery.h" + +/** + * Lion Battery + * + */ +class Unkown : public Battery +{ + private: + + public: + Unkown() + { + this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); + this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); + this->setCapacity(USERMOD_BATTERY_UNKOWN_CAPACITY); + this->setVoltage(this->getVoltage()); + this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); + } + + float mapVoltage(float v, float min, float max) override + { + return this->linearMapping(v, min, max); // basic mapping + }; + + void calculateAndSetLevel(float voltage) override + { + this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage())); + }; +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index f9bfc96fb..ab2ab908d 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -3,6 +3,7 @@ #include "wled.h" #include "battery_defaults.h" #include "battery.h" +#include "unkown.h" #include "lion.h" #include "lipo.h" @@ -18,16 +19,9 @@ class UsermodBattery : public Usermod private: // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; - - int8_t batteryType = USERMOB_BATTERY_DEFAULT_TYPE; - - float minVoltage = USERMOD_BATTERY_MIN_VOLTAGE; - float maxVoltage = USERMOD_BATTERY_MAX_VOLTAGE; - unsigned int capacity = USERMOD_BATTERY_TOTAL_CAPACITY; // current capacity - float voltage = this->maxVoltage; // current voltage - int8_t level = 100; // current level - float calibration = USERMOD_BATTERY_CALIBRATION; // offset or calibration value to fine tune the calculated voltage + Battery* bat = nullptr; + batteryConfig bcfg; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; @@ -127,16 +121,17 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); #endif - //this could also be handled with a factory class but for only 2 types it should be sufficient for now - if(batteryType == 1) { - bat = new Lipo(); - } else - if(batteryType == 2) { - bat = new Lion(); - } else { - bat = new Lipo(); // in the future one could create a nullObject - } + //this could also be handled with a factory class but for only 2 types it should be sufficient for now + if(bcfg.type == (batteryType)lipo) { + bat = new Lipo(); + } else + if(bcfg.type == (batteryType)lion) { + bat = new Lion(); + } else { + bat = new Unkown(); // nullObject + } + bat->update(bcfg); nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -321,12 +316,11 @@ class UsermodBattery : public Usermod battery[F("pin")] = batteryPin; #endif - if(bat) { - battery[F("min-voltage")] = bat->getMinVoltage(); - battery[F("max-voltage")] = bat->getMaxVoltage(); - battery[F("capacity")] = bat->getCapacity(); - battery[F("calibration")] = bat->getCalibration(); - } + battery[F("type")] = (String)bcfg.type; + battery[F("min-voltage")] = bat->getMinVoltage(); + battery[F("max-voltage")] = bat->getMaxVoltage(); + battery[F("capacity")] = bat->getCapacity(); + battery[F("calibration")] = bat->getCalibration(); battery[FPSTR(_readInterval)] = readingInterval; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section @@ -344,6 +338,11 @@ class UsermodBattery : public Usermod void appendConfigData() { + oappend(SET_F("td=addDropdown('Battery', 'type');")); + oappend(SET_F("addOption(td, 'Unkown', '0');")); + oappend(SET_F("addOption(td, 'LiPo', '1');")); + oappend(SET_F("addOption(td, 'LiOn', '2');")); + oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); oappend(SET_F("addInfo('Battery:capacity', 1, 'mAh');")); @@ -399,16 +398,15 @@ class UsermodBattery : public Usermod newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - if(bat) { - bat->setMinVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); - bat->setMaxVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); - bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); - bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); - } + getJsonValue(battery[F("type")], bcfg.type); + getJsonValue(battery[F("min-voltage")], bcfg.minVoltage); + getJsonValue(battery[F("max-voltage")], bcfg.maxVoltage); + getJsonValue(battery[F("capacity")], bcfg.capacity); + getJsonValue(battery[F("calibration")], bcfg.calibration); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); // JsonArray type = battery[F("Type")]; - // batteryType = type["bt"] | batteryType; + // batteryType = type["bt"] | btype; JsonObject ao = battery[F("auto-off")]; setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); @@ -446,7 +444,7 @@ class UsermodBattery : public Usermod } #endif - return !battery[FPSTR(_readInterval)].isNull(); + return !battery[F("min-voltage")].isNull(); } /* From 8ba5dfe447ac73111ff2a244be44a6ec08389417 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Fri, 6 Jan 2023 19:07:39 +0100 Subject: [PATCH 005/694] =?UTF-8?q?Another=20Bugfx=20=F0=9F=A7=91=E2=80=8D?= =?UTF-8?q?=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/battery.h | 9 +-------- usermods/Battery/lion.h | 9 +++++++++ usermods/Battery/lipo.h | 9 +++++++++ usermods/Battery/usermod_v2_Battery.h | 11 ++++++----- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index ad3fb2703..3a792aadb 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -30,14 +30,7 @@ class Battery } - virtual void update(batteryConfig cfg) - { - if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); - if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); - if(cfg.calibration) this->setCapacity(cfg.calibration); - if(cfg.level) this->setLevel(cfg.level); - if(cfg.calibration) this->setCalibration(cfg.calibration); - } + virtual void update(batteryConfig cfg) = 0; /** * Corresponding battery curves diff --git a/usermods/Battery/lion.h b/usermods/Battery/lion.h index 4016af7e3..17a4b3593 100644 --- a/usermods/Battery/lion.h +++ b/usermods/Battery/lion.h @@ -22,6 +22,15 @@ class Lion : public Battery this->setCalibration(USERMOD_BATTERY_LION_CALIBRATION); } + void update(batteryConfig cfg) + { + if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); + if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); + if(cfg.calibration) this->setCapacity(cfg.calibration); + if(cfg.level) this->setLevel(cfg.level); + if(cfg.calibration) this->setCalibration(cfg.calibration); + } + float mapVoltage(float v, float min, float max) override { return this->linearMapping(v, min, max); // basic mapping diff --git a/usermods/Battery/lipo.h b/usermods/Battery/lipo.h index 03eed7b86..dcd44567f 100644 --- a/usermods/Battery/lipo.h +++ b/usermods/Battery/lipo.h @@ -22,6 +22,15 @@ class Lipo : public Battery this->setCalibration(USERMOD_BATTERY_LIPO_CALIBRATION); } + void update(batteryConfig cfg) + { + if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); + if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); + if(cfg.calibration) this->setCapacity(cfg.calibration); + if(cfg.level) this->setLevel(cfg.level); + if(cfg.calibration) this->setCalibration(cfg.calibration); + } + /** * LiPo batteries have a differnt dischargin curve, see * https://blog.ampow.com/lipo-voltage-chart/ diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index ab2ab908d..5cf6ac792 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -132,6 +132,7 @@ class UsermodBattery : public Usermod } bat->update(bcfg); + nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -316,7 +317,7 @@ class UsermodBattery : public Usermod battery[F("pin")] = batteryPin; #endif - battery[F("type")] = (String)bcfg.type; + battery[F("type")] = (String)bcfg.type; // has to be a String otherwise it won't get converted to a Dropdown battery[F("min-voltage")] = bat->getMinVoltage(); battery[F("max-voltage")] = bat->getMaxVoltage(); battery[F("capacity")] = bat->getCapacity(); @@ -404,9 +405,6 @@ class UsermodBattery : public Usermod getJsonValue(battery[F("capacity")], bcfg.capacity); getJsonValue(battery[F("calibration")], bcfg.calibration); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); - - // JsonArray type = battery[F("Type")]; - // batteryType = type["bt"] | btype; JsonObject ao = battery[F("auto-off")]; setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); @@ -444,7 +442,10 @@ class UsermodBattery : public Usermod } #endif - return !battery[F("min-voltage")].isNull(); + if(initDone) + bat->update(bcfg); + + return !battery[FPSTR(_readInterval)].isNull(); } /* From d16f9efeecd9f182a150354479f885eb9e2d9bee Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Fri, 6 Jan 2023 19:09:12 +0100 Subject: [PATCH 006/694] =?UTF-8?q?Added=20forgotten=20file=20=F0=9F=98=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/unkown.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/usermods/Battery/unkown.h b/usermods/Battery/unkown.h index 63b2674f3..f36c3195e 100644 --- a/usermods/Battery/unkown.h +++ b/usermods/Battery/unkown.h @@ -22,6 +22,14 @@ class Unkown : public Battery this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); } + void update(batteryConfig cfg) + { + if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); else this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); + if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); else this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); + if(cfg.calibration) this->setCapacity(cfg.calibration); else this->setCapacity(USERMOD_BATTERY_UNKOWN_CAPACITY); + if(cfg.calibration) this->setCalibration(cfg.calibration); else this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); + } + float mapVoltage(float v, float min, float max) override { return this->linearMapping(v, min, max); // basic mapping From bb82bf762fd4becdf6dc1ed92b4e97eb03e18aed Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Thu, 12 Jan 2023 21:50:46 +0100 Subject: [PATCH 007/694] Update Readme, my_config type config options with examples --- usermods/Battery/battery_defaults.h | 2 ++ usermods/Battery/readme.md | 16 +++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index f4060ca60..fbdaf4877 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -1,6 +1,8 @@ #ifndef UMBDefaults_h #define UMBDefaults_h +#include "wled.h" + // pin defaults // for the esp32 it is best to use the ADC1: GPIO32 - GPIO39 // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index d55573abe..1ca229763 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -36,13 +36,12 @@ define `USERMOD_BATTERY` in `wled00/my_config.h` | Name | Unit | Description | | ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- | | `USERMOD_BATTERY` | | define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | -| `USERMOD_BATTERY_USE_LIPO` | | define this (in `my_config.h`) if you use LiPo rechargeables (1S) | | `USERMOD_BATTERY_MEASUREMENT_PIN` | | defaults to A0 on ESP8266 and GPIO35 on ESP32 | | `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds | -| `USERMOD_BATTERY_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | -| `USERMOD_BATTERY_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | -| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parralel sumed up | -| `USERMOD_BATTERY_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller | +| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | the capacity of all cells in parralel sumed up | +| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller | | Auto-Off | --- | --- | | `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off | | `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | when this threshold is reached master power turns off | @@ -54,6 +53,13 @@ define `USERMOD_BATTERY` in `wled00/my_config.h` All parameters can be configured at runtime via the Usermods settings page. +**NOTICE:** Each Battery type can be pre-configured individualy (in `my_config.h`) + +| Name | Alias | `my_config.h` example | +| --------------- | ------------- | ------------------------------------- | +| Lithium Polymer | lipo (Li-Po) | `USERMOD_BATTERY_lipo_MIN_VOLTAGE` | +| Lithium Ionen | lion (Li-Ion) | `USERMOD_BATTERY_lion_TOTAL_CAPACITY` | + ## ⚠️ Important - Make sure you know your battery specifications! All batteries are **NOT** the same! From f97b79bc16a5027d35219e015162c7b58e154ef9 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Sat, 21 Jan 2023 00:39:51 +0100 Subject: [PATCH 008/694] Exposing the Battery state to JSON API - Part 1 --- usermods/Battery/battery_defaults.h | 9 +-- usermods/Battery/usermod_v2_Battery.h | 99 +++++++++++++++------------ 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index fbdaf4877..092e3dd36 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -34,6 +34,7 @@ * */ #ifndef USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE + // Extra save defaults #define USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE 3.3f #endif #ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE @@ -125,10 +126,10 @@ typedef struct bconfig_t batteryType type; float minVoltage; float maxVoltage; - unsigned int capacity; // current capacity - float voltage; // current voltage - int8_t level; // current level - float calibration; // offset or calibration value to fine tune the calculated voltage + unsigned int capacity; // current capacity + float voltage; // current voltage + int8_t level; // current level + float calibration; // offset or calibration value to fine tune the calculated voltage } batteryConfig; diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index bf123a79d..390bd96b4 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -30,17 +30,17 @@ class UsermodBattery : public Usermod // auto shutdown/shutoff/master off feature bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED; - int8_t autoOffThreshold = USERMOD_BATTERY_AUTO_OFF_THRESHOLD; + uint8_t autoOffThreshold = USERMOD_BATTERY_AUTO_OFF_THRESHOLD; // low power indicator feature bool lowPowerIndicatorEnabled = USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED; - int8_t lowPowerIndicatorPreset = USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET; - int8_t lowPowerIndicatorThreshold = USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD; - int8_t lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; - int8_t lowPowerIndicatorDuration = USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION; + uint8_t lowPowerIndicatorPreset = USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET; + uint8_t lowPowerIndicatorThreshold = USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD; + uint8_t lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; + uint8_t lowPowerIndicatorDuration = USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION; bool lowPowerIndicationDone = false; unsigned long lowPowerActivationTime = 0; // used temporary during active time - int8_t lastPreset = 0; + uint8_t lastPreset = 0; bool initDone = false; bool initializing = true; @@ -128,7 +128,7 @@ class UsermodBattery : public Usermod if(bcfg.type == (batteryType)lion) { bat = new Lion(); } else { - bat = new Unkown(); // nullObject + bat = new Unkown(); // nullObject pattern } bat->update(bcfg); @@ -181,7 +181,6 @@ class UsermodBattery : public Usermod #else // read battery raw input rawValue = analogRead(batteryPin); - // calculate the voltage voltage = ((rawValue / getAdcPrecision()) * bat->getMaxVoltage()) + bat->getCalibration(); #endif @@ -252,17 +251,42 @@ class UsermodBattery : public Usermod infoVoltage.add(F(" V")); } + void addBatteryToJsonObject(JsonObject& battery, bool forJsonState) + { + if(forJsonState) { battery[F("type")] = bcfg.type; } else {battery[F("type")] = (String)bcfg.type; } // has to be a String otherwise it won't get converted to a Dropdown + battery[F("min-voltage")] = bat->getMinVoltage(); + battery[F("max-voltage")] = bat->getMaxVoltage(); + battery[F("capacity")] = bat->getCapacity(); + battery[F("calibration")] = bat->getCalibration(); + battery[FPSTR(_readInterval)] = readingInterval; + + JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section + ao[FPSTR(_enabled)] = autoOffEnabled; + ao[FPSTR(_threshold)] = autoOffThreshold; + + JsonObject lp = battery.createNestedObject(F("indicator")); // low power section + lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled; + lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset; + lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; + lp[FPSTR(_duration)] = lowPowerIndicatorDuration; + } /* * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - /* void addToJsonState(JsonObject& root) { - // TBD + JsonObject battery = root.createNestedObject(FPSTR(_name)); + + if (battery.isNull()) { + battery = root.createNestedObject(FPSTR(_name)); + } + + addBatteryToJsonObject(battery, true); + + DEBUG_PRINTLN(F("Battery state exposed in JSON API.")); } - */ /* @@ -314,48 +338,39 @@ class UsermodBattery : public Usermod */ void addToConfig(JsonObject& root) { - JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname + JsonObject battery = root.createNestedObject(FPSTR(_name)); + + if (battery.isNull()) { + battery = root.createNestedObject(FPSTR(_name)); + } + #ifdef ARDUINO_ARCH_ESP32 battery[F("pin")] = batteryPin; #endif - - battery[F("type")] = (String)bcfg.type; // has to be a String otherwise it won't get converted to a Dropdown - battery[F("min-voltage")] = bat->getMinVoltage(); - battery[F("max-voltage")] = bat->getMaxVoltage(); - battery[F("capacity")] = bat->getCapacity(); - battery[F("calibration")] = bat->getCalibration(); - battery[FPSTR(_readInterval)] = readingInterval; - JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section - ao[FPSTR(_enabled)] = autoOffEnabled; - ao[FPSTR(_threshold)] = autoOffThreshold; - - JsonObject lp = battery.createNestedObject(F("indicator")); // low power section - lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled; - lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset; - lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; - lp[FPSTR(_duration)] = lowPowerIndicatorDuration; + addBatteryToJsonObject(battery, false); DEBUG_PRINTLN(F("Battery config saved.")); } void appendConfigData() { - oappend(SET_F("td=addDropdown('Battery', 'type');")); - oappend(SET_F("addOption(td, 'Unkown', '0');")); - oappend(SET_F("addOption(td, 'LiPo', '1');")); - oappend(SET_F("addOption(td, 'LiOn', '2');")); - oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); - oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); - oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); - oappend(SET_F("addInfo('Battery:capacity', 1, 'mAh');")); - oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); - oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); - oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); - oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');")); + // Total: 501 Bytes + oappend(SET_F("td=addDropdown('Battery', 'type');")); // 35 Bytes + oappend(SET_F("addOption(td, 'Unkown', '0');")); // 30 Bytes + oappend(SET_F("addOption(td, 'LiPo', '1');")); // 28 Bytes + oappend(SET_F("addOption(td, 'LiOn', '2');")); // 28 Bytes + oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes + oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); // 40 Bytes + oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); // 40 Bytes + oappend(SET_F("addInfo('Battery:capacity', 1, 'mAh');")); // 39 Bytes + oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); // 38 Bytes + oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); // 47 Bytes + oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); // 48 Bytes + oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');")); // 47 Bytes - // cannot quite get this mf to work. its exeeding some buffer limit i think - // what i wanted is a list of all presets to select one from + // this option list would exeed the oappend() buffer + // a list of all presets to select one from // oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); // the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);")); // for(int8_t i=1; i < 42; i++) { From f78f8b6b127203295cc0e22965c51a1f71415915 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Sat, 21 Jan 2023 01:44:50 +0100 Subject: [PATCH 009/694] Exposing the Battery state to JSON API - Part 2 --- usermods/Battery/usermod_v2_Battery.h | 59 +++++++++++++++------------ 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 390bd96b4..bd4d7778c 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -271,6 +271,30 @@ class UsermodBattery : public Usermod lp[FPSTR(_duration)] = lowPowerIndicatorDuration; } + void getUsermodConfigFromJsonObject(JsonObject& battery) + { + getJsonValue(battery[F("type")], bcfg.type); + getJsonValue(battery[F("min-voltage")], bcfg.minVoltage); + getJsonValue(battery[F("max-voltage")], bcfg.maxVoltage); + getJsonValue(battery[F("capacity")], bcfg.capacity); + getJsonValue(battery[F("calibration")], bcfg.calibration); + setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); + + JsonObject ao = battery[F("auto-off")]; + setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); + setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold); + + JsonObject lp = battery[F("indicator")]; + setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled); + setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset); + setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold); + lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; + setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration); + + if(initDone) + bat->update(bcfg); + } + /* * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients @@ -296,7 +320,15 @@ class UsermodBattery : public Usermod /* void readFromJsonState(JsonObject& root) { - // TBD + if (!initDone) return; // prevent crash on boot applyPreset() + + JsonObject battery = root[FPSTR(_name)]; + + if (!battery.isNull()) { + getUsermodConfigFromJsonObject(battery); + + DEBUG_PRINTLN(F("Battery state read from JSON API.")); + } } */ @@ -416,25 +448,7 @@ class UsermodBattery : public Usermod newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - getJsonValue(battery[F("type")], bcfg.type); - getJsonValue(battery[F("min-voltage")], bcfg.minVoltage); - getJsonValue(battery[F("max-voltage")], bcfg.maxVoltage); - getJsonValue(battery[F("capacity")], bcfg.capacity); - getJsonValue(battery[F("calibration")], bcfg.calibration); - setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); - - JsonObject ao = battery[F("auto-off")]; - setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); - setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold); - - JsonObject lp = battery[F("indicator")]; - setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled); - setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset); // dropdown trickery (int)lp["preset"] - setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold); - lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; - setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration); - - DEBUG_PRINT(FPSTR(_name)); + getUsermodConfigFromJsonObject(battery); #ifdef ARDUINO_ARCH_ESP32 if (!initDone) @@ -459,9 +473,6 @@ class UsermodBattery : public Usermod } #endif - if(initDone) - bat->update(bcfg); - return !battery[FPSTR(_readInterval)].isNull(); } @@ -578,7 +589,6 @@ class UsermodBattery : public Usermod autoOffThreshold = lowPowerIndicatorEnabled /*&& autoOffEnabled*/ ? min(lowPowerIndicatorThreshold-1, (int)autoOffThreshold) : autoOffThreshold; } - /* * Get low-power-indicator feature enabled status * is the low-power-indicator enabled, true/false @@ -648,7 +658,6 @@ class UsermodBattery : public Usermod lowPowerIndicatorDuration = duration; } - /* * Get low-power-indicator status when the indication is done thsi returns true */ From b8c61b52366a567d57860ada080c30ced9725777 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Sat, 9 Sep 2023 21:01:55 +0200 Subject: [PATCH 010/694] =?UTF-8?q?Move=20battery=20types=20to=20a=20separ?= =?UTF-8?q?ate=20folder=20=F0=9F=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/battery.h | 17 +---- usermods/Battery/battery_defaults.h | 16 +--- usermods/Battery/{ => types}/lion.h | 6 +- usermods/Battery/{ => types}/lipo.h | 14 ++-- usermods/Battery/{ => types}/unkown.h | 6 +- usermods/Battery/usermod_v2_Battery.h | 105 ++++++-------------------- 6 files changed, 37 insertions(+), 127 deletions(-) rename usermods/Battery/{ => types}/lion.h (87%) rename usermods/Battery/{ => types}/lipo.h (82%) rename usermods/Battery/{ => types}/unkown.h (83%) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index 3a792aadb..8a8042ff3 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -14,7 +14,6 @@ class Battery protected: float minVoltage; float maxVoltage; - unsigned int capacity; float voltage; int8_t level = 100; float calibration; // offset or calibration value to fine tune the calculated voltage @@ -34,7 +33,7 @@ class Battery /** * Corresponding battery curves - * calculates the capacity in % (0-100) with given voltage and possible voltage range + * calculates the level in % (0-100) with given voltage and possible voltage range */ virtual float mapVoltage(float v, float min, float max) = 0; // { @@ -86,20 +85,6 @@ class Battery this->maxVoltage = max(getMinVoltage()+.5f, voltage); } - /* - * Get the capacity of all cells in parralel sumed up - * unit: mAh - */ - unsigned int getCapacity() - { - return this->capacity; - } - - void setCapacity(unsigned int capacity) - { - this->capacity = capacity; - } - float getVoltage() { return this->voltage; diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 668009680..199ee3432 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -40,9 +40,6 @@ #ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE #define USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_UNKOWN_CAPACITY - #define USERMOD_BATTERY_UNKOWN_CAPACITY 2500 -#endif #ifndef USERMOD_BATTERY_UNKOWN_CALIBRATION // offset or calibration value to fine tune the calculated voltage #define USERMOD_BATTERY_UNKOWN_CALIBRATION 0 @@ -59,9 +56,6 @@ #ifndef USERMOD_BATTERY_LIPO_MAX_VOLTAGE #define USERMOD_BATTERY_LIPO_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_LIPO_CAPACITY - #define USERMOD_BATTERY_LIPO_CAPACITY 5000 -#endif #ifndef USERMOD_BATTERY_LIPO_CALIBRATION #define USERMOD_BATTERY_LIPO_CALIBRATION 0 #endif @@ -77,10 +71,6 @@ #ifndef USERMOD_BATTERY_LION_MAX_VOLTAGE #define USERMOD_BATTERY_LION_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_LION_CAPACITY - // a common capacity for single 18650 battery cells is between 2500 and 3600 mAh - #define USERMOD_BATTERY_LION_CAPACITY 3100 -#endif #ifndef USERMOD_BATTERY_LION_CALIBRATION // offset or calibration value to fine tune the calculated voltage #define USERMOD_BATTERY_LION_CALIBRATION 0 @@ -109,11 +99,6 @@ #define USERMOD_BATTERY_CALIBRATION 0 #endif -// calculate remaining time / the time that is left before the battery runs out of power -// #ifndef USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED -// #define USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED false -// #endif - // auto-off feature #ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED #define USERMOD_BATTERY_AUTO_OFF_ENABLED true @@ -157,6 +142,7 @@ typedef struct bconfig_t float voltage; // current voltage int8_t level; // current level float calibration; // offset or calibration value to fine tune the calculated voltage + float voltageMultiplier; } batteryConfig; diff --git a/usermods/Battery/lion.h b/usermods/Battery/types/lion.h similarity index 87% rename from usermods/Battery/lion.h rename to usermods/Battery/types/lion.h index 17a4b3593..2ff54a1ea 100644 --- a/usermods/Battery/lion.h +++ b/usermods/Battery/types/lion.h @@ -1,8 +1,8 @@ #ifndef UMBLion_h #define UMBLion_h -#include "battery_defaults.h" -#include "battery.h" +#include "../battery_defaults.h" +#include "../battery.h" /** * Lion Battery @@ -17,7 +17,6 @@ class Lion : public Battery { this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); - this->setCapacity(USERMOD_BATTERY_LION_CAPACITY); this->setVoltage(this->getVoltage()); this->setCalibration(USERMOD_BATTERY_LION_CALIBRATION); } @@ -26,7 +25,6 @@ class Lion : public Battery { if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); - if(cfg.calibration) this->setCapacity(cfg.calibration); if(cfg.level) this->setLevel(cfg.level); if(cfg.calibration) this->setCalibration(cfg.calibration); } diff --git a/usermods/Battery/lipo.h b/usermods/Battery/types/lipo.h similarity index 82% rename from usermods/Battery/lipo.h rename to usermods/Battery/types/lipo.h index dcd44567f..264d3251e 100644 --- a/usermods/Battery/lipo.h +++ b/usermods/Battery/types/lipo.h @@ -1,8 +1,8 @@ #ifndef UMBLipo_h #define UMBLipo_h -#include "battery_defaults.h" -#include "battery.h" +#include "../battery_defaults.h" +#include "../battery.h" /** * Lipo Battery @@ -17,7 +17,6 @@ class Lipo : public Battery { this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); - this->setCapacity(USERMOD_BATTERY_LIPO_CAPACITY); this->setVoltage(this->getVoltage()); this->setCalibration(USERMOD_BATTERY_LIPO_CALIBRATION); } @@ -26,13 +25,12 @@ class Lipo : public Battery { if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); - if(cfg.calibration) this->setCapacity(cfg.calibration); if(cfg.level) this->setLevel(cfg.level); if(cfg.calibration) this->setCalibration(cfg.calibration); } /** - * LiPo batteries have a differnt dischargin curve, see + * LiPo batteries have a differnt discharge curve, see * https://blog.ampow.com/lipo-voltage-chart/ */ float mapVoltage(float v, float min, float max) override @@ -41,12 +39,12 @@ class Lipo : public Battery lvl = this->linearMapping(v, min, max); // basic mapping if (lvl < 40.0f) - lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly + lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly else { if (lvl < 90.0f) - lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop + lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop else // level > 90% - lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly + lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly } return lvl; diff --git a/usermods/Battery/unkown.h b/usermods/Battery/types/unkown.h similarity index 83% rename from usermods/Battery/unkown.h rename to usermods/Battery/types/unkown.h index f36c3195e..2b38da96c 100644 --- a/usermods/Battery/unkown.h +++ b/usermods/Battery/types/unkown.h @@ -1,8 +1,8 @@ #ifndef UMBUnkown_h #define UMBUnkown_h -#include "battery_defaults.h" -#include "battery.h" +#include "../battery_defaults.h" +#include "../battery.h" /** * Lion Battery @@ -17,7 +17,6 @@ class Unkown : public Battery { this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); - this->setCapacity(USERMOD_BATTERY_UNKOWN_CAPACITY); this->setVoltage(this->getVoltage()); this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); } @@ -26,7 +25,6 @@ class Unkown : public Battery { if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); else this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); else this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); - if(cfg.calibration) this->setCapacity(cfg.calibration); else this->setCapacity(USERMOD_BATTERY_UNKOWN_CAPACITY); if(cfg.calibration) this->setCalibration(cfg.calibration); else this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); } diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 7f6738e10..a91331cb7 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -3,9 +3,9 @@ #include "wled.h" #include "battery_defaults.h" #include "battery.h" -#include "unkown.h" -#include "lion.h" -#include "lipo.h" +#include "types/unkown.h" +#include "types/lion.h" +#include "types/lipo.h" /* * Usermod by Maximilian Mewes @@ -28,7 +28,7 @@ class UsermodBattery : public Usermod unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; // battery min. voltage - float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE; + float minBatteryVoltage = 3.3f; // battery max. voltage float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE; // all battery cells summed up @@ -39,16 +39,10 @@ class UsermodBattery : public Usermod float voltage = maxBatteryVoltage; // between 0 and 1, to control strength of voltage smoothing filter float alpha = 0.05f; - // multiplier for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266 - float voltageMultiplier = USERMOD_BATTERY_VOLTAGE_MULTIPLIER; // mapped battery level based on voltage int8_t batteryLevel = 100; // offset or calibration value to fine tune the calculated voltage float calibration = USERMOD_BATTERY_CALIBRATION; - - // time left estimation feature - // bool calculateTimeLeftEnabled = USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED; - // float estimatedTimeLeft = 0.0; // auto shutdown/shutoff/master off feature bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED; @@ -114,18 +108,22 @@ class UsermodBattery : public Usermod } } - float readVoltage() - { - #ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value - return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; - #else - // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value - return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; - #endif - } + // float readVoltage() + // { + // #ifdef ARDUINO_ARCH_ESP32 + // // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value + // return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; + // #else + // // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value + // return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; + // #endif + // } public: + UsermodBattery() + { + bat = new Unkown(); + } //Functions called by WLED /* @@ -152,14 +150,13 @@ class UsermodBattery : public Usermod } #else //ESP8266 boards have only one analog input pin A0 pinMode(batteryPin, INPUT); - voltage = readVoltage(); + // voltage = readVoltage(); #endif //this could also be handled with a factory class but for only 2 types it should be sufficient for now if(bcfg.type == (batteryType)lipo) { bat = new Lipo(); - } else - if(bcfg.type == (batteryType)lion) { + } else if(bcfg.type == (batteryType)lion) { bat = new Lion(); } else { bat = new Unkown(); // nullObject pattern @@ -218,14 +215,8 @@ class UsermodBattery : public Usermod // calculate the voltage voltage = ((rawValue / getAdcPrecision()) * bat->getMaxVoltage()) + bat->getCalibration(); #endif - // initializing = false; - - // rawValue = readVoltage(); - // // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout - // voltage = voltage + alpha * (rawValue - voltage); - - // check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage - //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; + // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout + voltage = voltage + alpha * (rawValue - voltage); bat->setVoltage(voltage); // translate battery voltage into percentage @@ -298,7 +289,6 @@ class UsermodBattery : public Usermod if(forJsonState) { battery[F("type")] = bcfg.type; } else {battery[F("type")] = (String)bcfg.type; } // has to be a String otherwise it won't get converted to a Dropdown battery[F("min-voltage")] = bat->getMinVoltage(); battery[F("max-voltage")] = bat->getMaxVoltage(); - battery[F("capacity")] = bat->getCapacity(); battery[F("calibration")] = bat->getCalibration(); battery[FPSTR(_readInterval)] = readingInterval; @@ -318,8 +308,8 @@ class UsermodBattery : public Usermod getJsonValue(battery[F("type")], bcfg.type); getJsonValue(battery[F("min-voltage")], bcfg.minVoltage); getJsonValue(battery[F("max-voltage")], bcfg.maxVoltage); - getJsonValue(battery[F("capacity")], bcfg.capacity); getJsonValue(battery[F("calibration")], bcfg.calibration); + setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; @@ -421,26 +411,18 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 battery[F("pin")] = batteryPin; #endif - - // battery[F("time-left")] = calculateTimeLeftEnabled; - battery[F("min-voltage")] = minBatteryVoltage; - battery[F("max-voltage")] = maxBatteryVoltage; - battery[F("capacity")] = totalBatteryCapacity; - battery[F("calibration")] = calibration; - battery[F("voltage-multiplier")] = voltageMultiplier; - battery[FPSTR(_readInterval)] = readingInterval; addBatteryToJsonObject(battery, false); // read voltage in case calibration or voltage multiplier changed to see immediate effect - voltage = readVoltage(); + // voltage = readVoltage(); DEBUG_PRINTLN(F("Battery config saved.")); } void appendConfigData() { - // Total: 501 Bytes + // Total: 462 Bytes oappend(SET_F("td=addDropdown('Battery', 'type');")); // 35 Bytes oappend(SET_F("addOption(td, 'Unkown', '0');")); // 30 Bytes oappend(SET_F("addOption(td, 'LiPo', '1');")); // 28 Bytes @@ -448,7 +430,6 @@ class UsermodBattery : public Usermod oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); // 40 Bytes oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); // 40 Bytes - oappend(SET_F("addInfo('Battery:capacity', 1, 'mAh');")); // 39 Bytes oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); // 38 Bytes oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); // 47 Bytes oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); // 48 Bytes @@ -503,9 +484,7 @@ class UsermodBattery : public Usermod // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; setMinBatteryVoltage(battery[F("min-voltage")] | minBatteryVoltage); setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); - setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity); setCalibration(battery[F("calibration")] | calibration); - setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); getUsermodConfigFromJsonObject(battery); @@ -655,22 +634,6 @@ class UsermodBattery : public Usermod } - /* - * Get the capacity of all cells in parralel sumed up - * unit: mAh - */ - unsigned int getTotalBatteryCapacity() - { - return totalBatteryCapacity; - } - - void setTotalBatteryCapacity(unsigned int capacity) - { - totalBatteryCapacity = capacity; - } - - - /* * Get the calculated voltage * formula: (adc pin value / adc precision * max voltage) + calibration @@ -707,24 +670,6 @@ class UsermodBattery : public Usermod calibration = offset; } - /* - * Set the voltage multiplier value - * A multiplier that may need adjusting for different voltage divider setups - */ - void setVoltageMultiplier(float multiplier) - { - voltageMultiplier = multiplier; - } - - /* - * Get the voltage multiplier value - * A multiplier that may need adjusting for different voltage divider setups - */ - float getVoltageMultiplier() - { - return voltageMultiplier; - } - /* * Get auto-off feature enabled status * is auto-off enabled, true/false From a9d6a1592412a6f901afecc1e9e4e8934ef46b13 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Sat, 9 Sep 2023 21:50:30 +0200 Subject: [PATCH 011/694] Update Classes --- usermods/Battery/battery.h | 23 ++- usermods/Battery/battery_defaults.h | 25 +--- usermods/Battery/types/lion.h | 3 +- usermods/Battery/types/lipo.h | 3 +- usermods/Battery/types/unkown.h | 4 +- usermods/Battery/usermod_v2_Battery.h | 208 ++++++++++---------------- 6 files changed, 111 insertions(+), 155 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index 8a8042ff3..4cdfb035f 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -17,6 +17,7 @@ class Battery float voltage; int8_t level = 100; float calibration; // offset or calibration value to fine tune the calculated voltage + float voltageMultiplier; // ratio for the voltage divider float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f) { @@ -26,7 +27,9 @@ class Battery public: Battery() { - + this->setVoltage(this->getVoltage()); + this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER); + this->setCalibration(USERMOD_BATTERY_CALIBRATION); } virtual void update(batteryConfig cfg) = 0; @@ -127,6 +130,24 @@ class Battery { calibration = offset; } + + /* + * Get the configured calibration value + * a value to set the voltage divider ratio + */ + virtual float getVoltageMultiplier() + { + return voltageMultiplier; + } + + /* + * Set the voltage multiplier value + * a value to set the voltage divider ratio. + */ + virtual void setVoltageMultiplier(float multiplier) + { + voltageMultiplier = voltageMultiplier; + } }; #endif \ No newline at end of file diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 199ee3432..6d0a95dc4 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -40,10 +40,7 @@ #ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE #define USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_UNKOWN_CALIBRATION - // offset or calibration value to fine tune the calculated voltage - #define USERMOD_BATTERY_UNKOWN_CALIBRATION 0 -#endif + /* * * Lithium polymer (Li-Po) defaults @@ -56,9 +53,7 @@ #ifndef USERMOD_BATTERY_LIPO_MAX_VOLTAGE #define USERMOD_BATTERY_LIPO_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_LIPO_CALIBRATION - #define USERMOD_BATTERY_LIPO_CALIBRATION 0 -#endif + /* * * Lithium-ion (Li-Ion) defaults @@ -71,12 +66,8 @@ #ifndef USERMOD_BATTERY_LION_MAX_VOLTAGE #define USERMOD_BATTERY_LION_MAX_VOLTAGE 4.2f #endif -#ifndef USERMOD_BATTERY_LION_CALIBRATION - // offset or calibration value to fine tune the calculated voltage - #define USERMOD_BATTERY_LION_CALIBRATION 0 -#endif -//the default ratio for the voltage divider +// the default ratio for the voltage divider #ifndef USERMOD_BATTERY_VOLTAGE_MULTIPLIER #ifdef ARDUINO_ARCH_ESP32 #define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 2.0f @@ -85,13 +76,8 @@ #endif #endif -#ifndef USERMOD_BATTERY_MAX_VOLTAGE - #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f -#endif - -// a common capacity for single 18650 battery cells is between 2500 and 3600 mAh -#ifndef USERMOD_BATTERY_TOTAL_CAPACITY - #define USERMOD_BATTERY_TOTAL_CAPACITY 3100 +#ifndef USERMOD_BATTERY_AVERAGING_ALPHA + #define USERMOD_BATTERY_AVERAGING_ALPHA 0.1f #endif // offset or calibration value to fine tune the calculated voltage @@ -138,7 +124,6 @@ typedef struct bconfig_t batteryType type; float minVoltage; float maxVoltage; - unsigned int capacity; // current capacity float voltage; // current voltage int8_t level; // current level float calibration; // offset or calibration value to fine tune the calculated voltage diff --git a/usermods/Battery/types/lion.h b/usermods/Battery/types/lion.h index 2ff54a1ea..0d2325386 100644 --- a/usermods/Battery/types/lion.h +++ b/usermods/Battery/types/lion.h @@ -14,11 +14,10 @@ class Lion : public Battery public: Lion() + : Battery() { this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); - this->setVoltage(this->getVoltage()); - this->setCalibration(USERMOD_BATTERY_LION_CALIBRATION); } void update(batteryConfig cfg) diff --git a/usermods/Battery/types/lipo.h b/usermods/Battery/types/lipo.h index 264d3251e..f65ab12c5 100644 --- a/usermods/Battery/types/lipo.h +++ b/usermods/Battery/types/lipo.h @@ -14,11 +14,10 @@ class Lipo : public Battery public: Lipo() + : Battery() { this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); - this->setVoltage(this->getVoltage()); - this->setCalibration(USERMOD_BATTERY_LIPO_CALIBRATION); } void update(batteryConfig cfg) diff --git a/usermods/Battery/types/unkown.h b/usermods/Battery/types/unkown.h index 2b38da96c..edf220040 100644 --- a/usermods/Battery/types/unkown.h +++ b/usermods/Battery/types/unkown.h @@ -14,18 +14,16 @@ class Unkown : public Battery public: Unkown() + : Battery() { this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); - this->setVoltage(this->getVoltage()); - this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); } void update(batteryConfig cfg) { if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); else this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); else this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); - if(cfg.calibration) this->setCalibration(cfg.calibration); else this->setCalibration(USERMOD_BATTERY_UNKOWN_CALIBRATION); } float mapVoltage(float v, float min, float max) override diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index a91331cb7..9b980d557 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -20,27 +20,15 @@ class UsermodBattery : public Usermod // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; - Battery* bat = nullptr; - batteryConfig bcfg; + Battery* bat = new Unkown(); + batteryConfig cfg; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; - // battery min. voltage - float minBatteryVoltage = 3.3f; - // battery max. voltage - float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE; - // all battery cells summed up - unsigned int totalBatteryCapacity = USERMOD_BATTERY_TOTAL_CAPACITY; - // raw analog reading - float rawValue = 0.0f; - // calculated voltage - float voltage = maxBatteryVoltage; // between 0 and 1, to control strength of voltage smoothing filter - float alpha = 0.05f; - // mapped battery level based on voltage - int8_t batteryLevel = 100; + float alpha = USERMOD_BATTERY_AVERAGING_ALPHA; // offset or calibration value to fine tune the calculated voltage float calibration = USERMOD_BATTERY_CALIBRATION; @@ -70,13 +58,16 @@ class UsermodBattery : public Usermod static const char _duration[]; static const char _init[]; + /** + * Helper for rounding floating point values + */ float dot2round(float x) { float nx = (int)(x * 100 + .5); return (float)(nx / 100); } - /* + /** * Turn off all leds */ void turnOff() @@ -85,7 +76,7 @@ class UsermodBattery : public Usermod stateUpdated(CALL_MODE_DIRECT_CHANGE); } - /* + /** * Indicate low power by activating a configured preset for a given time and then switching back to the preset that was selected previously */ void lowPowerIndicator() @@ -108,25 +99,24 @@ class UsermodBattery : public Usermod } } - // float readVoltage() - // { - // #ifdef ARDUINO_ARCH_ESP32 - // // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value - // return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; - // #else - // // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value - // return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; - // #endif - // } + /** + * read the battery voltage in different ways depending on the architecture + */ + float readVoltage() + { + #ifdef ARDUINO_ARCH_ESP32 + // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value + return (analogReadMilliVolts(batteryPin) / 1000.0f) * bat->getVoltageMultiplier() + bat->getCalibration(); + #else + // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value + return (analogRead(batteryPin) / 1023.0f) * bat->getVoltageMultiplier() + bat->getCalibration(); + #endif + } public: - UsermodBattery() - { - bat = new Unkown(); - } //Functions called by WLED - /* + /** * setup() is called once at boot. WiFi is not yet connected at this point. * You can use it to initialize variables, sensors or similar. */ @@ -153,16 +143,15 @@ class UsermodBattery : public Usermod // voltage = readVoltage(); #endif - //this could also be handled with a factory class but for only 2 types it should be sufficient for now - if(bcfg.type == (batteryType)lipo) { + // plug in the right battery type + if(cfg.type == (batteryType)lipo) { bat = new Lipo(); - } else if(bcfg.type == (batteryType)lion) { + } else if(cfg.type == (batteryType)lion) { bat = new Lion(); - } else { - bat = new Unkown(); // nullObject pattern } - bat->update(bcfg); + // update the choosen battery type with configured values + bat->update(cfg); nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -171,7 +160,7 @@ class UsermodBattery : public Usermod } - /* + /** * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ @@ -199,28 +188,15 @@ class UsermodBattery : public Usermod if (batteryPin < 0) return; // nothing to read - initializing = false; - float voltage = -1.0f; - float rawValue = 0.0f; -#ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) - rawValue = analogReadMilliVolts(batteryPin); - // calculate the voltage - voltage = (rawValue / 1000.0f) + calibration; - // usually a voltage divider (50%) is used on ESP32, so we need to multiply by 2 - voltage *= 2.0f; -#else - // read battery raw input - rawValue = analogRead(batteryPin); - // calculate the voltage - voltage = ((rawValue / getAdcPrecision()) * bat->getMaxVoltage()) + bat->getCalibration(); -#endif - // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout - voltage = voltage + alpha * (rawValue - voltage); + initializing = false; + float rawValue = readVoltage(); - bat->setVoltage(voltage); + // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout + float filteredVoltage = bat->getVoltage() + alpha * (rawValue - bat->getVoltage()); + + bat->setVoltage(filteredVoltage); // translate battery voltage into percentage - bat->calculateAndSetLevel(voltage); + bat->calculateAndSetLevel(filteredVoltage); // Auto off -- Master power off if (autoOffEnabled && (autoOffThreshold >= bat->getLevel())) @@ -232,13 +208,13 @@ class UsermodBattery : public Usermod if (WLED_MQTT_CONNECTED) { char buf[64]; // buffer for snprintf() snprintf_P(buf, 63, PSTR("%s/voltage"), mqttDeviceTopic); - mqtt->publish(buf, 0, false, String(voltage).c_str()); + mqtt->publish(buf, 0, false, String(bat->getVoltage()).c_str()); } #endif } - /* + /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor @@ -286,7 +262,7 @@ class UsermodBattery : public Usermod void addBatteryToJsonObject(JsonObject& battery, bool forJsonState) { - if(forJsonState) { battery[F("type")] = bcfg.type; } else {battery[F("type")] = (String)bcfg.type; } // has to be a String otherwise it won't get converted to a Dropdown + if(forJsonState) { battery[F("type")] = cfg.type; } else {battery[F("type")] = (String)cfg.type; } // has to be a String otherwise it won't get converted to a Dropdown battery[F("min-voltage")] = bat->getMinVoltage(); battery[F("max-voltage")] = bat->getMaxVoltage(); battery[F("calibration")] = bat->getCalibration(); @@ -305,10 +281,10 @@ class UsermodBattery : public Usermod void getUsermodConfigFromJsonObject(JsonObject& battery) { - getJsonValue(battery[F("type")], bcfg.type); - getJsonValue(battery[F("min-voltage")], bcfg.minVoltage); - getJsonValue(battery[F("max-voltage")], bcfg.maxVoltage); - getJsonValue(battery[F("calibration")], bcfg.calibration); + getJsonValue(battery[F("type")], cfg.type); + getJsonValue(battery[F("min-voltage")], cfg.minVoltage); + getJsonValue(battery[F("max-voltage")], cfg.maxVoltage); + getJsonValue(battery[F("calibration")], cfg.calibration); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); @@ -324,10 +300,10 @@ class UsermodBattery : public Usermod setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration); if(initDone) - bat->update(bcfg); + bat->update(cfg); } - /* + /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ @@ -345,7 +321,7 @@ class UsermodBattery : public Usermod } - /* + /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ @@ -365,7 +341,7 @@ class UsermodBattery : public Usermod */ - /* + /** * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * If you want to force saving the current state, use serializeConfig() in your loop(). @@ -449,7 +425,7 @@ class UsermodBattery : public Usermod } - /* + /** * readFromConfig() can be used to read back the custom settings you added with addToConfig(). * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) * @@ -482,8 +458,8 @@ class UsermodBattery : public Usermod newBatteryPin = battery[F("pin")] | newBatteryPin; #endif // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; - setMinBatteryVoltage(battery[F("min-voltage")] | minBatteryVoltage); - setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); + setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); + setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); setCalibration(battery[F("calibration")] | calibration); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); @@ -515,7 +491,7 @@ class UsermodBattery : public Usermod return !battery[FPSTR(_readInterval)].isNull(); } - /* + /** * TBD: Generate a preset sample for low power indication * a button on the config page would be cool, currently not possible */ @@ -554,7 +530,7 @@ class UsermodBattery : public Usermod * */ - /* + /** * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). * This could be used in the future for the system to determine whether your usermod is installed. */ @@ -569,7 +545,7 @@ class UsermodBattery : public Usermod return readingInterval; } - /* + /** * minimum repetition is 3000ms (3s) */ void setReadingInterval(unsigned long newReadingInterval) @@ -577,100 +553,78 @@ class UsermodBattery : public Usermod readingInterval = max((unsigned long)3000, newReadingInterval); } - /* - * Get the choosen adc precision - * esp8266 = 10bit resolution = 1024.0f - * esp32 = 12bit resolution = 4095.0f - */ - float getAdcPrecision() - { - #ifdef ARDUINO_ARCH_ESP32 - // esp32 - return 4096.0f; - #else - // esp8266 - return 1024.0f; - #endif - } - - /* - - /* + /** * Get lowest configured battery voltage */ float getMinBatteryVoltage() { - return minBatteryVoltage; + return bat->getMinVoltage(); } - /* + /** * Set lowest battery voltage - * can't be below 0 volt + * cant be below 0 volt */ void setMinBatteryVoltage(float voltage) { - minBatteryVoltage = max(0.0f, voltage); + bat->setMinVoltage(voltage); } - /* + /** * Get highest configured battery voltage */ float getMaxBatteryVoltage() { - return maxBatteryVoltage; + return bat->getMaxVoltage(); } - /* + /** * Set highest battery voltage * can't be below minBatteryVoltage */ void setMaxBatteryVoltage(float voltage) { - #ifdef USERMOD_BATTERY_USE_LIPO - maxBatteryVoltage = max(getMinBatteryVoltage()+0.7f, voltage); - #else - maxBatteryVoltage = max(getMinBatteryVoltage()+1.0f, voltage); - #endif + bat->setMaxVoltage(voltage); } - /* + /** * Get the calculated voltage * formula: (adc pin value / adc precision * max voltage) + calibration */ float getVoltage() { - return voltage; + return bat->getVoltage(); } - /* + /** * Get the mapped battery level (0 - 100) based on voltage * important: voltage can drop when a load is applied, so its only an estimate */ int8_t getBatteryLevel() { - return batteryLevel; + return bat->getLevel(); } - /* + /** * Get the configured calibration value * a offset value to fine-tune the calculated voltage. */ float getCalibration() { - return calibration; + return bat->getCalibration(); } - /* + /** * Set the voltage calibration offset value * a offset value to fine-tune the calculated voltage. */ void setCalibration(float offset) { - calibration = offset; + bat->setCalibration(offset); } - /* + /** * Get auto-off feature enabled status * is auto-off enabled, true/false */ @@ -679,7 +633,7 @@ class UsermodBattery : public Usermod return autoOffEnabled; } - /* + /** * Set auto-off feature status */ void setAutoOffEnabled(bool enabled) @@ -687,7 +641,7 @@ class UsermodBattery : public Usermod autoOffEnabled = enabled; } - /* + /** * Get auto-off threshold in percent (0-100) */ int8_t getAutoOffThreshold() @@ -695,7 +649,7 @@ class UsermodBattery : public Usermod return autoOffThreshold; } - /* + /** * Set auto-off threshold in percent (0-100) */ void setAutoOffThreshold(int8_t threshold) @@ -705,7 +659,7 @@ class UsermodBattery : public Usermod autoOffThreshold = lowPowerIndicatorEnabled /*&& autoOffEnabled*/ ? min(lowPowerIndicatorThreshold-1, (int)autoOffThreshold) : autoOffThreshold; } - /* + /** * Get low-power-indicator feature enabled status * is the low-power-indicator enabled, true/false */ @@ -714,7 +668,7 @@ class UsermodBattery : public Usermod return lowPowerIndicatorEnabled; } - /* + /** * Set low-power-indicator feature status */ void setLowPowerIndicatorEnabled(bool enabled) @@ -722,7 +676,7 @@ class UsermodBattery : public Usermod lowPowerIndicatorEnabled = enabled; } - /* + /** * Get low-power-indicator preset to activate when low power is detected */ int8_t getLowPowerIndicatorPreset() @@ -730,7 +684,7 @@ class UsermodBattery : public Usermod return lowPowerIndicatorPreset; } - /* + /** * Set low-power-indicator preset to activate when low power is detected */ void setLowPowerIndicatorPreset(int8_t presetId) @@ -748,7 +702,7 @@ class UsermodBattery : public Usermod return lowPowerIndicatorThreshold; } - /* + /** * Set low-power-indicator threshold in percent (0-100) */ void setLowPowerIndicatorThreshold(int8_t threshold) @@ -758,7 +712,7 @@ class UsermodBattery : public Usermod lowPowerIndicatorThreshold = autoOffEnabled /*&& lowPowerIndicatorEnabled*/ ? max(autoOffThreshold+1, (int)lowPowerIndicatorThreshold) : max(5, (int)lowPowerIndicatorThreshold); } - /* + /** * Get low-power-indicator duration in seconds */ int8_t getLowPowerIndicatorDuration() @@ -766,7 +720,7 @@ class UsermodBattery : public Usermod return lowPowerIndicatorDuration; } - /* + /** * Set low-power-indicator duration in seconds */ void setLowPowerIndicatorDuration(int8_t duration) @@ -774,7 +728,7 @@ class UsermodBattery : public Usermod lowPowerIndicatorDuration = duration; } - /* + /** * Get low-power-indicator status when the indication is done thsi returns true */ bool getLowPowerIndicatorDone() From 26ab1bfd4f54d2107aba37c4e9819f8fbdce00d1 Mon Sep 17 00:00:00 2001 From: skinnyquiver Date: Mon, 4 Dec 2023 19:17:15 -0600 Subject: [PATCH 012/694] [Feat] Adds new esp32s3dev_16MB_PSRAM_opi dev board to work with LilyGo T7_s3 ESP32-S3-WROOM-1-N16R8 --- platformio.ini | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index f3caa585e..b7cd10684 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,8 +11,8 @@ # CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi - +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3dev_16MB_PSRAM_opi +; default_envs = esp32s3dev_16MB_PSRAM_opi # Release binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB @@ -524,6 +524,29 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32s3dev_16MB_PSRAM_opi] +board = esp32-s3-devkitc-1 +board_build.arduino.memory_type = qio_opi +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +upload_speed = 921600 +framework = arduino + +# Configure options for the N16R8V variant +build_flags= ${common.build_flags} ${esp32s3.build_flags} + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM + -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D BOARD_HAS_PSRAM +lib_deps = ${esp32s3.lib_deps} +board_build.partitions = tools/WLED_ESP32_16MB.CSV +board_upload.flash_size = 16MB +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + [env:esp32s3dev_8MB_PSRAM_qspi] ;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi) extends = env:esp32s3dev_8MB_PSRAM_opi From aeb9e2ad9fd79f1352475185a2927025c6192836 Mon Sep 17 00:00:00 2001 From: skinnyquiver Date: Mon, 4 Dec 2023 19:24:35 -0600 Subject: [PATCH 013/694] [fix] Removes additional default envs line --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b7cd10684..fd5a27702 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ # CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3dev_16MB_PSRAM_opi -; default_envs = esp32s3dev_16MB_PSRAM_opi + # Release binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB From 90d696d8263a8d47a8b73962dc794b11ee709b87 Mon Sep 17 00:00:00 2001 From: David Rasch Date: Tue, 5 Dec 2023 10:54:32 -0500 Subject: [PATCH 014/694] fix(esp32c3-2mb): correct flash size for c3 board with only 2MB --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index f3caa585e..7d65ac07d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -642,6 +642,8 @@ upload_speed = 115200 lib_deps = ${esp32c3.lib_deps} board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv board_build.flash_mode = dio +board_upload.flash_size = 2MB +board_upload.maximum_size = 2097152 [env:wemos_shield_esp32] board = esp32dev From 84802d9065789d8f2938648ecbf617780e5a6e82 Mon Sep 17 00:00:00 2001 From: skinnyquiver Date: Tue, 5 Dec 2023 10:36:31 -0600 Subject: [PATCH 015/694] [fix] Uses extends as per softhack007 comment --- platformio.ini | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/platformio.ini b/platformio.ini index fd5a27702..21ca6ea97 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ # CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3dev_16MB_PSRAM_opi +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi # Release binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB @@ -42,6 +42,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32d ; default_envs = esp32s2_saola ; default_envs = esp32c3dev ; default_envs = lolin_s2_mini +; default_envs = esp32s3dev_16MB_PSRAM_opi src_dir = ./wled00 data_dir = ./wled00/data @@ -525,27 +526,9 @@ board_build.flash_mode = qio monitor_filters = esp32_exception_decoder [env:esp32s3dev_16MB_PSRAM_opi] -board = esp32-s3-devkitc-1 -board_build.arduino.memory_type = qio_opi -platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} -upload_speed = 921600 -framework = arduino - -# Configure options for the N16R8V variant -build_flags= ${common.build_flags} ${esp32s3.build_flags} - -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 - -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip - ;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM - -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used - -D BOARD_HAS_PSRAM -lib_deps = ${esp32s3.lib_deps} -board_build.partitions = tools/WLED_ESP32_16MB.CSV +extends = env:esp32s3dev_8MB_PSRAM_opi +board_build.partitions = tools/WLED_ESP32_16MB.csv board_upload.flash_size = 16MB -board_build.f_flash = 80000000L -board_build.flash_mode = qio -monitor_filters = esp32_exception_decoder [env:esp32s3dev_8MB_PSRAM_qspi] ;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi) From 809a294f9d80463b8073d30c5c4978ae2924c9cd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 22 Dec 2023 15:43:31 +0100 Subject: [PATCH 016/694] Fix Pixel Magic button. --- wled00/data/index.htm | 2 +- wled00/html_ui.h | 2665 ++++++++++++++++++++--------------------- 2 files changed, 1333 insertions(+), 1334 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e6ce04896..7665a6e5d 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -200,7 +200,7 @@
- +
diff --git a/wled00/html_ui.h b/wled00/html_ui.h index 941869572..ededcc1e9 100644 --- a/wled00/html_ui.h +++ b/wled00/html_ui.h @@ -7,9 +7,9 @@ */ // Autogenerated from wled00/data/index.htm, do not edit!! -const uint16_t PAGE_index_L = 32025; +const uint16_t PAGE_index_L = 32008; const uint8_t PAGE_index[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xcc, 0xbd, 0xe9, 0x76, 0xe2, 0xca, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xcc, 0xbd, 0xe9, 0x76, 0xe2, 0xca, 0xb2, 0x30, 0xf8, 0xdf, 0x4f, 0xa1, 0x52, 0xed, 0xe3, 0x82, 0x8d, 0x0c, 0x62, 0x34, 0x43, 0x61, 0x5f, 0x8c, 0xe7, 0xd9, 0xc6, 0x73, 0xdd, 0x5a, 0xb7, 0x04, 0x24, 0x20, 0x5b, 0x48, 0xb2, 0x24, 0x06, 0x9b, 0xe2, 0x3e, 0x46, 0xaf, 0xd5, 0x2f, 0xd0, 0x3f, 0xba, 0xdf, 0xaa, 0x9f, 0xa4, 0x23, @@ -680,1335 +680,1334 @@ const uint8_t PAGE_index[] PROGMEM = { 0x09, 0xf5, 0x92, 0x21, 0x9f, 0x1a, 0xe7, 0x17, 0x1a, 0xe2, 0xcc, 0x86, 0x21, 0x3d, 0xcf, 0x73, 0x9f, 0x83, 0x0c, 0xf7, 0xc8, 0x2c, 0x9d, 0x4e, 0x0b, 0x0b, 0x22, 0x3b, 0x85, 0x61, 0x41, 0x84, 0xe7, 0xb7, 0xb8, 0x88, 0xfd, 0x99, 0x1e, 0x87, 0x53, 0x97, 0x07, 0x8e, 0xcc, 0x63, 0x48, 0x9b, - 0x4d, 0x34, 0xcb, 0x5f, 0x3c, 0x6c, 0xb8, 0xc4, 0x48, 0x51, 0x67, 0x4a, 0x5f, 0xed, 0xdc, 0x80, - 0x90, 0x18, 0x73, 0xea, 0x10, 0xa1, 0x7a, 0xff, 0x98, 0xc5, 0xa3, 0xfa, 0xb5, 0xbf, 0xd1, 0xf7, - 0x07, 0x47, 0x10, 0x1d, 0x98, 0xba, 0xf4, 0xc0, 0x19, 0xba, 0x47, 0x10, 0x4b, 0xa0, 0x61, 0x67, - 0x4d, 0x65, 0x25, 0xba, 0x08, 0xff, 0x3a, 0x34, 0xee, 0x9a, 0xb2, 0x77, 0x81, 0x45, 0xd6, 0xf5, - 0x91, 0xa6, 0x49, 0x08, 0x98, 0xd6, 0x72, 0x60, 0x67, 0xeb, 0x93, 0xb4, 0x45, 0x86, 0xc6, 0x98, - 0x1c, 0x39, 0x64, 0x98, 0x10, 0x27, 0x1a, 0x41, 0x2a, 0x98, 0x8a, 0x49, 0xd8, 0xfa, 0x5e, 0x47, - 0xc4, 0x76, 0x8e, 0x6d, 0x43, 0x4f, 0xcc, 0xac, 0x21, 0x02, 0x5d, 0xfd, 0x22, 0x87, 0xbf, 0x38, - 0xcb, 0x13, 0x8d, 0x94, 0x2b, 0xca, 0x12, 0x9f, 0x80, 0xdd, 0xc1, 0x08, 0xd7, 0x56, 0x23, 0x5c, - 0xce, 0x6f, 0x2e, 0x61, 0x33, 0x11, 0x7a, 0xdf, 0x03, 0xb9, 0xb1, 0xe3, 0xd8, 0x22, 0x7f, 0x36, - 0x12, 0x63, 0xca, 0xee, 0x4d, 0xc5, 0x38, 0x7e, 0xdf, 0x65, 0x5c, 0x0a, 0x3f, 0x2c, 0xe2, 0x72, - 0xfc, 0x35, 0xd6, 0xa0, 0x30, 0x74, 0x0f, 0x55, 0x96, 0xb2, 0x14, 0xb7, 0x59, 0xf4, 0x39, 0xf8, - 0xf7, 0xd9, 0x0b, 0xf3, 0x7d, 0x5d, 0xc1, 0x5d, 0xbc, 0x02, 0x52, 0xff, 0xa8, 0x9b, 0x10, 0x99, - 0xa7, 0xa3, 0x2d, 0x26, 0xd3, 0x14, 0x30, 0xfc, 0x70, 0x31, 0x7e, 0xc5, 0x18, 0x32, 0x14, 0xd8, - 0x37, 0x58, 0x2b, 0x6d, 0x6d, 0x64, 0xd5, 0xbf, 0x2d, 0x2d, 0xce, 0xa6, 0xd7, 0xaf, 0xf1, 0x3f, - 0xcb, 0xb3, 0x38, 0xaa, 0x5b, 0x0a, 0x10, 0x5b, 0x98, 0x09, 0x11, 0xbd, 0xde, 0x70, 0x08, 0xee, - 0xba, 0x0f, 0x7b, 0xb2, 0x7b, 0x67, 0x90, 0x1e, 0xfb, 0x8b, 0x52, 0x83, 0x8b, 0xa6, 0x7f, 0x90, - 0xf9, 0xed, 0x3f, 0xfc, 0xff, 0xc3, 0xf6, 0x02, 0xfd, 0xda, 0x0e, 0xd1, 0xb1, 0x8b, 0x39, 0x0f, - 0x16, 0xf6, 0x1e, 0x0c, 0x28, 0x28, 0x73, 0x89, 0x9a, 0xbd, 0x07, 0x16, 0x73, 0xb7, 0xa4, 0x17, - 0x3b, 0xe8, 0xb4, 0x65, 0xf7, 0xf3, 0x8d, 0x72, 0x2d, 0x4c, 0xb5, 0xb4, 0x4c, 0xdb, 0x98, 0xba, - 0x02, 0x54, 0x4f, 0x73, 0xea, 0xa2, 0x5f, 0x34, 0x64, 0xd8, 0x60, 0x1d, 0xec, 0x4f, 0x3d, 0x3a, - 0x08, 0x9b, 0x21, 0x7c, 0x2f, 0x4e, 0x14, 0x9f, 0xbc, 0x71, 0x7b, 0x03, 0x76, 0xd1, 0x1e, 0x80, - 0x29, 0xef, 0x8a, 0xc2, 0x22, 0x90, 0x02, 0xa5, 0x02, 0x80, 0xb4, 0x9c, 0xcb, 0x95, 0x96, 0xc2, - 0xb9, 0xc6, 0x03, 0x4a, 0x4b, 0xf2, 0xa6, 0x87, 0xbf, 0x0c, 0xe5, 0x5a, 0x14, 0xcc, 0x00, 0xca, - 0x6c, 0x2c, 0x94, 0x14, 0xc0, 0x4a, 0x21, 0xe7, 0x03, 0xb8, 0xb6, 0x0a, 0x93, 0xb4, 0xe4, 0x27, - 0x00, 0x5c, 0xfb, 0x3c, 0x1e, 0x03, 0x00, 0x73, 0x4b, 0x00, 0x84, 0xe5, 0x52, 0x29, 0xe5, 0x3f, - 0x37, 0xd5, 0xb4, 0xe0, 0x4a, 0x00, 0xd7, 0xfe, 0xea, 0x44, 0x07, 0x00, 0xde, 0x19, 0xcb, 0xc9, - 0xb1, 0x52, 0xce, 0x17, 0x3e, 0x07, 0x21, 0x16, 0xfc, 0x1f, 0x23, 0xc5, 0x7d, 0xd8, 0xdb, 0x96, - 0x4d, 0x33, 0xf4, 0x5c, 0xfc, 0x1c, 0x1d, 0xd2, 0x92, 0xff, 0x14, 0x1d, 0x46, 0x8f, 0xf5, 0x28, - 0x43, 0x90, 0x63, 0x2c, 0x76, 0x51, 0xe6, 0xcb, 0x9f, 0xc3, 0xaf, 0xf1, 0x32, 0x36, 0x0c, 0x92, - 0xbc, 0x13, 0x4f, 0xcc, 0xce, 0xe7, 0x8a, 0x1f, 0x9f, 0xb0, 0xf3, 0x4a, 0x8b, 0x7b, 0x32, 0x68, - 0x12, 0x34, 0x78, 0xad, 0x34, 0xfb, 0xd1, 0x32, 0xae, 0x45, 0xf4, 0x33, 0xc7, 0xee, 0xff, 0xa8, - 0x59, 0xf4, 0x93, 0xa7, 0xee, 0xcc, 0x5c, 0x18, 0x8c, 0x8b, 0xca, 0x00, 0x01, 0x7a, 0x43, 0x06, - 0x44, 0x57, 0x2e, 0xb0, 0x71, 0x58, 0x71, 0xf6, 0x43, 0x0e, 0x3d, 0xd9, 0xbf, 0x38, 0x41, 0xfc, - 0xfc, 0x50, 0x10, 0xa8, 0xa8, 0x4d, 0x77, 0xba, 0x82, 0x5c, 0xf9, 0xf4, 0x04, 0x05, 0x00, 0x1c, - 0xe1, 0xe6, 0x64, 0xab, 0xce, 0x5b, 0xec, 0x79, 0x10, 0x37, 0x49, 0x7e, 0xc1, 0xbf, 0xea, 0x1e, - 0x21, 0xff, 0xaf, 0x75, 0x8f, 0x58, 0x66, 0xe6, 0xe6, 0xb1, 0x4e, 0x31, 0x97, 0xf5, 0x27, 0x4a, - 0xf5, 0x86, 0xb6, 0xf4, 0x8c, 0x9c, 0xd6, 0xca, 0x45, 0x0f, 0x00, 0xdc, 0xad, 0x67, 0xe5, 0x8c, - 0x85, 0x74, 0x82, 0xbf, 0x78, 0xe2, 0xd2, 0xcc, 0xc6, 0x1e, 0x46, 0xf1, 0x87, 0x06, 0x23, 0xdb, - 0x31, 0x86, 0xd4, 0xac, 0xf2, 0xd7, 0x96, 0xce, 0x5a, 0xbc, 0x6f, 0xc3, 0x5f, 0x31, 0xa0, 0xbb, - 0xf3, 0xb1, 0xf6, 0xb7, 0x27, 0x24, 0x32, 0x1f, 0x39, 0xa0, 0x63, 0x36, 0x1e, 0x21, 0xbb, 0x7a, - 0x22, 0xf2, 0x91, 0x45, 0xc3, 0xcd, 0xc3, 0xda, 0xea, 0x89, 0xf0, 0x3c, 0xb3, 0xfe, 0x22, 0x13, - 0x6b, 0xe6, 0x3e, 0xe0, 0x60, 0xee, 0x3c, 0xe4, 0xfe, 0x19, 0x1e, 0x26, 0xff, 0x0f, 0x72, 0xb0, - 0x4f, 0x4c, 0x44, 0x5e, 0xdc, 0x72, 0xe7, 0x21, 0xb7, 0x7a, 0x1e, 0x0a, 0x7f, 0x7b, 0x41, 0xc8, - 0xa4, 0xfc, 0xb7, 0x16, 0x44, 0xfe, 0x93, 0x0b, 0x22, 0xff, 0x99, 0x05, 0x91, 0xcf, 0xfe, 0x6f, - 0xbd, 0x1e, 0x0a, 0xc1, 0x7a, 0xc8, 0x2f, 0x9b, 0x87, 0xde, 0xd4, 0x30, 0x03, 0x1d, 0xc6, 0xbd, - 0x86, 0x4d, 0xf5, 0xb4, 0x90, 0x5c, 0x05, 0x19, 0x72, 0x54, 0xa4, 0x32, 0x38, 0xe1, 0x39, 0xde, - 0x99, 0xd0, 0xb5, 0xab, 0x7d, 0xc0, 0x51, 0xa1, 0x6d, 0x77, 0x2f, 0xdc, 0x6a, 0xd2, 0x86, 0x83, - 0xc5, 0x1b, 0x4c, 0x1f, 0xed, 0xf2, 0xc2, 0x77, 0x94, 0xe3, 0xe4, 0xa1, 0xd0, 0xec, 0x5d, 0xd0, - 0x01, 0x24, 0xb2, 0x12, 0x3d, 0x00, 0x74, 0x6f, 0x5d, 0xfd, 0x25, 0x51, 0x6d, 0x2d, 0x3c, 0xea, - 0x6c, 0x44, 0x48, 0xfb, 0x70, 0xd0, 0xf9, 0x42, 0x9b, 0x1b, 0xf4, 0xda, 0xb2, 0x69, 0xf2, 0x06, - 0x9d, 0xf5, 0x06, 0x9d, 0x5b, 0x3a, 0xe8, 0x9c, 0xb8, 0x28, 0xeb, 0xc7, 0x0d, 0x3a, 0xf7, 0xe9, - 0x41, 0xaf, 0xad, 0x12, 0xa1, 0x01, 0xb2, 0xdc, 0x5f, 0x19, 0x34, 0x53, 0x92, 0x0b, 0x9d, 0x0f, - 0xa6, 0x9a, 0x1f, 0x74, 0xce, 0x1b, 0x74, 0x3e, 0x32, 0xe8, 0xb5, 0x60, 0xd4, 0xf9, 0xc5, 0xa9, - 0x8e, 0x1b, 0x74, 0x7e, 0xc9, 0xa0, 0x3f, 0xa5, 0xd8, 0x2c, 0xd5, 0x88, 0x11, 0x8c, 0x16, 0xe9, - 0x0f, 0x31, 0xac, 0xf9, 0x47, 0xd6, 0x1e, 0xf7, 0xee, 0x47, 0x54, 0x09, 0x8f, 0xb0, 0x3b, 0x76, - 0x1d, 0x84, 0x63, 0x77, 0xec, 0x6b, 0x4a, 0xe2, 0xd6, 0xb2, 0xa2, 0xb9, 0xe5, 0x26, 0x47, 0xdb, - 0x5d, 0x38, 0x96, 0x0d, 0x6f, 0x9c, 0x44, 0x67, 0x01, 0xc8, 0x54, 0x9a, 0xbb, 0x26, 0x80, 0x20, - 0xc1, 0x76, 0x07, 0x10, 0x63, 0x3d, 0xdf, 0xba, 0x09, 0x62, 0x02, 0x84, 0xbc, 0x20, 0x1d, 0xc7, - 0x43, 0x3b, 0xbb, 0x23, 0x11, 0xf0, 0x37, 0xca, 0xf0, 0x4a, 0xc5, 0x74, 0xd1, 0x73, 0xf6, 0x92, - 0xd3, 0xd9, 0x80, 0xcf, 0xa5, 0x37, 0x81, 0xfa, 0xf5, 0xb6, 0x6d, 0xd6, 0x98, 0x6f, 0x34, 0x9a, - 0xbf, 0xa8, 0x47, 0x29, 0xfd, 0x18, 0x99, 0x4f, 0x4e, 0x2e, 0x09, 0xd1, 0x02, 0x91, 0x61, 0x5f, - 0x5a, 0x08, 0xf4, 0x87, 0xd8, 0x36, 0x5f, 0x35, 0x71, 0xc5, 0x99, 0x4a, 0x57, 0xdc, 0x72, 0x1b, - 0xe2, 0x4e, 0x53, 0x96, 0x19, 0xd4, 0xd8, 0x25, 0x9e, 0x78, 0x83, 0x5a, 0xac, 0x3d, 0x6d, 0x6d, - 0x89, 0xbd, 0x9e, 0xce, 0x7f, 0xf2, 0x9b, 0xb0, 0xc4, 0x58, 0xef, 0x66, 0xc7, 0x88, 0x17, 0xbc, - 0xdd, 0xeb, 0x73, 0x66, 0xaf, 0xb5, 0x4f, 0xda, 0xea, 0x17, 0x4c, 0xf5, 0x14, 0x88, 0x88, 0xad, - 0xca, 0x55, 0xb1, 0xc3, 0x06, 0x78, 0x86, 0x3e, 0x24, 0xa3, 0x80, 0x9e, 0x97, 0xed, 0x1e, 0xe6, - 0x47, 0x44, 0xbd, 0xd4, 0x53, 0x98, 0x7e, 0x88, 0xd1, 0x70, 0xf9, 0x82, 0x5b, 0xc7, 0xf3, 0x61, - 0x0f, 0x8a, 0x68, 0xaa, 0xfe, 0xc2, 0x1f, 0x1d, 0x19, 0x26, 0xd1, 0x6f, 0x94, 0x76, 0x62, 0xb9, - 0xff, 0xba, 0xb7, 0xe5, 0xc4, 0xfa, 0xaf, 0x33, 0x47, 0xe4, 0x78, 0xcf, 0xf9, 0x85, 0x4e, 0xd7, - 0x16, 0x7a, 0xcd, 0x7e, 0xc2, 0x6b, 0x7e, 0xb1, 0x53, 0xd7, 0x68, 0xbc, 0xf6, 0xc9, 0x6e, 0x17, - 0x7a, 0xcd, 0x2d, 0xbd, 0x1d, 0xe1, 0x6f, 0x35, 0xb1, 0xb7, 0x40, 0xfc, 0xd5, 0xff, 0x37, 0x47, - 0x9b, 0x5f, 0x36, 0x5a, 0x9f, 0xd7, 0xc7, 0xde, 0xb1, 0x61, 0xe4, 0xb3, 0xb6, 0xfa, 0x8e, 0x80, - 0xfb, 0x71, 0x96, 0xf0, 0x21, 0x3d, 0xbb, 0x45, 0xa4, 0xa0, 0x49, 0x35, 0x6c, 0x03, 0xb6, 0xf6, - 0xf0, 0x8b, 0x3c, 0x37, 0x98, 0x85, 0x77, 0xc6, 0x92, 0x8b, 0xd5, 0xfc, 0x08, 0x67, 0xab, 0xae, - 0x7a, 0x44, 0xea, 0xe0, 0xa5, 0x4e, 0x7f, 0x41, 0xd0, 0x5b, 0xea, 0x31, 0x34, 0x18, 0x9c, 0x6d, - 0x08, 0xf4, 0xcb, 0x38, 0x9f, 0xbd, 0xbf, 0x22, 0xd0, 0xf8, 0x47, 0x0c, 0x5f, 0x78, 0x80, 0xb2, - 0x16, 0x39, 0x5f, 0xf0, 0x81, 0x18, 0xf6, 0xd9, 0x21, 0xef, 0xb0, 0xef, 0xd5, 0x9f, 0xa8, 0xa2, - 0xa0, 0x68, 0x8e, 0xeb, 0x82, 0x8f, 0xd6, 0x9e, 0x2a, 0xfd, 0xc2, 0x48, 0xc6, 0xd4, 0xfb, 0xb5, - 0xb6, 0x62, 0x93, 0x52, 0x41, 0x52, 0xef, 0x76, 0x2e, 0xae, 0x27, 0xf2, 0xc9, 0x41, 0xdf, 0x68, - 0xc0, 0x7f, 0xe7, 0xad, 0xdb, 0xc1, 0xde, 0x6d, 0x1f, 0x9e, 0x76, 0x64, 0x7c, 0xdf, 0x6f, 0x36, - 0x1e, 0xe1, 0xa7, 0x59, 0xdc, 0x1f, 0xf5, 0x8a, 0x98, 0xd0, 0x78, 0x38, 0x6f, 0x5d, 0xcb, 0x47, - 0x0d, 0xcb, 0x2e, 0x74, 0x4a, 0x57, 0x98, 0x70, 0xad, 0x5f, 0xdd, 0x66, 0x77, 0xa0, 0xcc, 0xf4, - 0x79, 0x32, 0x2e, 0x3f, 0x5e, 0xdd, 0x62, 0xe2, 0x71, 0x67, 0x6f, 0xf0, 0xd4, 0x99, 0x34, 0x1a, - 0xbb, 0xf6, 0x19, 0xbc, 0x6e, 0xee, 0x36, 0x3a, 0xdd, 0xf1, 0xeb, 0x01, 0x56, 0xd8, 0x69, 0xb7, - 0x6e, 0xaf, 0x77, 0xee, 0x9a, 0x83, 0x1b, 0xed, 0xb1, 0xd2, 0xde, 0x35, 0x1a, 0x93, 0xdd, 0xb3, - 0xf3, 0xfb, 0x4d, 0xbd, 0xa2, 0x4f, 0x9a, 0xaa, 0xf9, 0xe6, 0x5c, 0x9d, 0x17, 0x9e, 0xca, 0x4e, - 0xdb, 0xba, 0x39, 0x1c, 0xee, 0x0e, 0xf7, 0x0b, 0xc6, 0xe5, 0xfb, 0x9b, 0xd6, 0x9d, 0x5c, 0xbf, - 0x9a, 0xd9, 0x56, 0xab, 0xab, 0xdf, 0x65, 0xce, 0x47, 0x4f, 0xa3, 0xf7, 0x57, 0x62, 0x35, 0x76, - 0xde, 0xa6, 0x0f, 0xef, 0xfa, 0xce, 0x24, 0xaf, 0xf6, 0x5f, 0xc8, 0xfe, 0x5e, 0xef, 0xe1, 0xed, - 0x76, 0x34, 0x38, 0xc9, 0xbc, 0xed, 0x9f, 0xc9, 0xcd, 0xe9, 0x71, 0xef, 0xed, 0xf5, 0xe1, 0x69, - 0xef, 0xa2, 0x53, 0xca, 0xb4, 0xac, 0x4a, 0xa6, 0xdd, 0xdb, 0x1c, 0x1d, 0x35, 0x8b, 0xe7, 0x93, - 0xee, 0xa6, 0x61, 0x9d, 0x8d, 0x1b, 0x97, 0x74, 0x2c, 0x7b, 0xda, 0xfe, 0xcd, 0x4b, 0x6b, 0x74, - 0x35, 0x6c, 0x36, 0x45, 0x3c, 0x1b, 0x8b, 0xba, 0x04, 0x8f, 0xc3, 0x9c, 0x2b, 0xe4, 0x21, 0x13, - 0xbb, 0xa7, 0xba, 0x77, 0x7e, 0x79, 0xda, 0xe1, 0x4f, 0x9e, 0xe8, 0x86, 0xda, 0x03, 0xba, 0x1e, - 0xac, 0x3c, 0x02, 0x8b, 0x69, 0x25, 0x42, 0x81, 0x47, 0x3a, 0x30, 0x48, 0xbd, 0x43, 0x04, 0x3c, - 0x83, 0xf8, 0x8b, 0x6d, 0x79, 0xe7, 0x79, 0xb8, 0x3a, 0x13, 0xfe, 0x31, 0x1e, 0x53, 0x4c, 0xc4, - 0xa4, 0x24, 0xfe, 0x97, 0x4d, 0x34, 0xf4, 0xc0, 0xd9, 0xba, 0xa5, 0x49, 0xf4, 0x9e, 0x5f, 0xcc, - 0xf5, 0xa0, 0xb8, 0x3e, 0xa8, 0x28, 0x81, 0xab, 0x36, 0x2c, 0x4d, 0x74, 0xf4, 0x1e, 0x95, 0x23, - 0xd8, 0xf8, 0xdb, 0x86, 0xe1, 0x44, 0x1a, 0xf5, 0x8f, 0x83, 0x29, 0x72, 0x79, 0xc1, 0x6f, 0x20, - 0x6e, 0x9d, 0x81, 0x0a, 0x21, 0x4c, 0x54, 0x67, 0xe0, 0xe6, 0x30, 0xe7, 0x00, 0xc5, 0x72, 0x70, - 0x4d, 0xc0, 0x22, 0x2e, 0x17, 0x6a, 0xb0, 0x36, 0xf6, 0xf7, 0xe4, 0xbd, 0x9a, 0xbb, 0xb9, 0xac, - 0x09, 0xed, 0x37, 0xa1, 0xa1, 0x5a, 0x1d, 0xc3, 0x30, 0x5e, 0x54, 0x42, 0x2f, 0xe4, 0x3a, 0x03, - 0x22, 0x7c, 0x57, 0x04, 0x7a, 0x76, 0x29, 0x0e, 0x1c, 0xc7, 0xb4, 0xab, 0x99, 0x0c, 0x1e, 0x10, - 0xa6, 0x41, 0x87, 0xea, 0x18, 0x23, 0xcb, 0x26, 0x69, 0xf4, 0x9f, 0x31, 0x33, 0x20, 0xb6, 0x28, - 0x16, 0xe0, 0xa5, 0x2e, 0xfe, 0x97, 0x7b, 0x5f, 0x65, 0x8d, 0x5e, 0x75, 0xec, 0x18, 0xc3, 0xe1, - 0x48, 0xa7, 0x96, 0x19, 0x65, 0x6b, 0xd9, 0x36, 0xa6, 0xb3, 0x5b, 0x62, 0xff, 0x2e, 0x2f, 0x58, - 0x76, 0xab, 0xec, 0xb3, 0xcc, 0x00, 0x3f, 0x89, 0x25, 0x6e, 0x51, 0xb0, 0x55, 0x97, 0x54, 0xec, - 0x05, 0xea, 0xd6, 0x17, 0xa9, 0xdb, 0xf5, 0x41, 0xf2, 0x0e, 0xaf, 0x97, 0x7c, 0x73, 0x53, 0xfc, - 0x2c, 0xd5, 0xa2, 0x14, 0xe0, 0x0f, 0x65, 0x91, 0xf2, 0xe3, 0xa5, 0xe5, 0xa1, 0x36, 0xc6, 0x63, - 0x81, 0xb5, 0x28, 0x0e, 0x7d, 0xb8, 0x59, 0xbe, 0x0b, 0xe3, 0x62, 0xf0, 0x19, 0x7c, 0x8e, 0x1d, - 0x59, 0xd4, 0x9b, 0x1d, 0xef, 0xb8, 0x2e, 0x4e, 0x55, 0x04, 0xe1, 0x83, 0x51, 0x9f, 0x44, 0x0e, - 0xd3, 0x91, 0x48, 0xd7, 0xfc, 0x66, 0x34, 0x60, 0x12, 0xdb, 0x01, 0x6f, 0xc0, 0x7f, 0x6b, 0xc2, - 0x8d, 0x21, 0x8c, 0x6c, 0x22, 0xb4, 0x47, 0xaa, 0x86, 0xa1, 0x77, 0x05, 0xc2, 0x76, 0x6e, 0x89, - 0xa6, 0xa2, 0x9c, 0x04, 0x5d, 0x5b, 0x20, 0xc1, 0xba, 0x17, 0xd9, 0x05, 0xd8, 0x6f, 0x60, 0x45, - 0xb2, 0xba, 0x8f, 0xc6, 0x48, 0xe8, 0x40, 0x19, 0x8b, 0x38, 0x23, 0x4b, 0x17, 0xd0, 0x7b, 0x8b, - 0x00, 0x17, 0x57, 0x87, 0x84, 0x9e, 0xee, 0x22, 0x6d, 0x63, 0x64, 0x0e, 0x1b, 0xaf, 0xf3, 0x22, - 0x55, 0xe3, 0xb7, 0x71, 0x00, 0xf9, 0xf4, 0x19, 0x85, 0x52, 0xbc, 0x6a, 0x0c, 0xc4, 0x6a, 0xe9, - 0xc4, 0x4a, 0xaf, 0xb1, 0x15, 0xb5, 0x30, 0x59, 0x21, 0xdf, 0x24, 0xe7, 0xd4, 0xb0, 0xa8, 0x44, - 0x72, 0xe1, 0x41, 0x65, 0x50, 0x67, 0xd3, 0x15, 0x4b, 0x7e, 0xb1, 0x7e, 0x8e, 0xaf, 0x3f, 0xd2, - 0xf1, 0x86, 0xb9, 0x45, 0x97, 0xba, 0xdf, 0x0e, 0xb7, 0xb8, 0xd7, 0x82, 0xd5, 0xbd, 0xb6, 0x6f, - 0x58, 0x30, 0x7c, 0xdb, 0x11, 0x4c, 0x62, 0xa1, 0xe3, 0x19, 0xd2, 0xaa, 0x24, 0xa8, 0xa0, 0x44, - 0xe0, 0xe7, 0xf4, 0x70, 0xd1, 0x11, 0x1a, 0x63, 0x04, 0xf0, 0x40, 0xf1, 0x61, 0xf4, 0x7a, 0xee, - 0xb0, 0x01, 0x2d, 0x43, 0x44, 0x82, 0x0d, 0xab, 0x17, 0x58, 0xe1, 0x64, 0x40, 0x74, 0x1a, 0xf0, - 0x02, 0x70, 0x01, 0x68, 0x4e, 0x2f, 0x78, 0x0f, 0xab, 0xc1, 0xb4, 0x23, 0xce, 0xc4, 0x98, 0x89, - 0x5e, 0x18, 0x96, 0x9c, 0x0c, 0x26, 0x7f, 0xcd, 0x9f, 0xfd, 0xef, 0xee, 0xc5, 0xe6, 0xb5, 0x31, - 0xa0, 0x5e, 0x33, 0x3a, 0xaa, 0x29, 0x4d, 0xee, 0x25, 0xde, 0x33, 0x41, 0x9a, 0xd8, 0x52, 0x07, - 0x2f, 0xa2, 0x49, 0xd4, 0x8c, 0x63, 0x4b, 0x0e, 0x73, 0x32, 0x40, 0x67, 0x85, 0xfa, 0x97, 0x2c, - 0xfe, 0x98, 0xb0, 0x96, 0x0d, 0xc6, 0x8b, 0xaa, 0xa2, 0xa4, 0xda, 0x17, 0x3a, 0x66, 0xe8, 0x5a, - 0x03, 0x7f, 0x54, 0xfb, 0x74, 0xcc, 0x7e, 0x51, 0x2c, 0x60, 0x4f, 0x74, 0x25, 0xe1, 0xa3, 0xfd, - 0xa6, 0x77, 0x5a, 0x80, 0x15, 0xef, 0xf9, 0xa6, 0xaf, 0x5d, 0x93, 0x0e, 0x94, 0x97, 0xa5, 0x81, - 0x62, 0x53, 0x8f, 0x52, 0xcc, 0x82, 0xe7, 0xeb, 0x83, 0x1d, 0xf7, 0xa9, 0xd9, 0xbc, 0x61, 0xcd, - 0xef, 0x8e, 0xac, 0x7a, 0x49, 0x86, 0x87, 0x1b, 0xc5, 0xaa, 0xe3, 0x2f, 0xde, 0xc3, 0xa4, 0x2d, - 0x91, 0xfe, 0x29, 0xaa, 0x66, 0xb2, 0xe4, 0x85, 0xa9, 0xda, 0xe7, 0x5f, 0x2e, 0x15, 0x0d, 0xde, - 0x3a, 0xf0, 0x8a, 0x3f, 0x23, 0x0b, 0xc3, 0x5c, 0x32, 0x11, 0xad, 0xbe, 0x01, 0xe3, 0x01, 0x89, - 0x8a, 0xed, 0x17, 0xb4, 0x4a, 0xbf, 0x69, 0x00, 0x0d, 0xc0, 0x23, 0x30, 0x58, 0xff, 0xd1, 0x98, - 0xc0, 0x34, 0xdf, 0xea, 0x30, 0x37, 0x5d, 0x78, 0x85, 0xae, 0x40, 0xb0, 0xc4, 0x74, 0xf6, 0x63, - 0x76, 0x3c, 0x40, 0xd8, 0x13, 0x45, 0x03, 0x36, 0x3b, 0x81, 0x4c, 0xc7, 0xaa, 0x6f, 0x4a, 0xdd, - 0x7a, 0x17, 0x74, 0x22, 0x14, 0x45, 0xa5, 0xde, 0x14, 0xa5, 0x99, 0xfa, 0x8f, 0x9f, 0x92, 0x89, - 0x1b, 0x6b, 0x7d, 0x36, 0x97, 0x88, 0xf7, 0xa0, 0x79, 0x0f, 0x66, 0xf0, 0x74, 0x5e, 0x17, 0x45, - 0xc9, 0x3c, 0xc2, 0x6e, 0xce, 0x47, 0x43, 0xfc, 0x19, 0x3a, 0xf5, 0x2c, 0xfe, 0x3d, 0x6d, 0xb1, - 0xb7, 0x53, 0xe8, 0x09, 0x81, 0x81, 0x1f, 0x64, 0x64, 0x58, 0x4b, 0xb5, 0xcf, 0x10, 0x86, 0x21, - 0x02, 0x30, 0x1c, 0xc0, 0x9f, 0x89, 0x7d, 0x6d, 0x62, 0xa1, 0x4e, 0xaf, 0x5f, 0x9f, 0x39, 0x78, - 0x35, 0xb4, 0x3a, 0x43, 0x31, 0xaa, 0x0a, 0xb2, 0x95, 0xf5, 0x22, 0x4a, 0xed, 0x7e, 0x75, 0x36, - 0xb2, 0xb4, 0xaa, 0x28, 0xce, 0x25, 0x45, 0x33, 0x07, 0x0a, 0x64, 0xf7, 0xab, 0xe9, 0x92, 0x04, - 0x52, 0x6d, 0x35, 0x5d, 0x9e, 0x4b, 0xcc, 0xef, 0x13, 0x13, 0xa1, 0x08, 0xbe, 0x0e, 0xcd, 0x2a, - 0x8b, 0x62, 0x63, 0x57, 0x67, 0xec, 0xda, 0x62, 0x15, 0x26, 0xd1, 0xea, 0xb7, 0xab, 0xd0, 0xf1, - 0xeb, 0x08, 0x52, 0xf0, 0x7d, 0x40, 0xa6, 0xf0, 0x0e, 0x23, 0xa3, 0xaa, 0x29, 0xa6, 0x98, 0x9d, - 0x21, 0x30, 0x63, 0x2c, 0x64, 0xaa, 0x5d, 0x4c, 0x00, 0x94, 0x6b, 0x44, 0xaf, 0xb2, 0x69, 0x34, - 0x27, 0x96, 0xfb, 0x44, 0xa6, 0x26, 0x3e, 0x75, 0x6c, 0x5a, 0x6b, 0xd0, 0x55, 0xde, 0x6c, 0x7c, - 0x07, 0xf4, 0x91, 0x1e, 0xa6, 0xa8, 0x5d, 0xdb, 0xb0, 0xb0, 0x1d, 0x80, 0x06, 0x54, 0xd4, 0xfa, - 0x8f, 0x1f, 0xb2, 0x94, 0xcd, 0x4a, 0xb9, 0x82, 0x54, 0x90, 0xfc, 0x5d, 0x52, 0xf1, 0x77, 0xd2, - 0x74, 0x1f, 0xb6, 0xe1, 0x51, 0x3b, 0xad, 0x1a, 0x99, 0xe9, 0x50, 0xb1, 0xd3, 0x20, 0x47, 0x8a, - 0x3f, 0x25, 0xa8, 0x93, 0x93, 0xb2, 0x9b, 0x52, 0x36, 0xa8, 0x42, 0xc5, 0x4c, 0x3b, 0x4d, 0x91, - 0xd0, 0x31, 0xd0, 0x1b, 0x20, 0x0d, 0x83, 0xcd, 0x14, 0x2a, 0x59, 0xfc, 0x97, 0xcd, 0xe5, 0xd3, - 0xcf, 0x26, 0xad, 0x9a, 0x93, 0x73, 0x45, 0x29, 0x2f, 0xe5, 0xb0, 0x89, 0xd5, 0x1d, 0x12, 0x98, - 0x19, 0xe0, 0x68, 0x6e, 0x97, 0x50, 0xaf, 0x00, 0x55, 0xf2, 0xd9, 0xbf, 0x58, 0x4f, 0x96, 0x4a, - 0x30, 0xb4, 0x8f, 0x21, 0x2d, 0x66, 0x4b, 0xf8, 0x6f, 0xb3, 0x92, 0xf3, 0x20, 0xc5, 0x0f, 0x1d, - 0x64, 0x3f, 0x51, 0x33, 0x9b, 0xad, 0xe0, 0xbf, 0x72, 0x59, 0x96, 0x59, 0xd5, 0x9f, 0xb5, 0xde, - 0x48, 0xa7, 0xb1, 0xc0, 0x85, 0x01, 0x48, 0x22, 0x1a, 0xb9, 0xf3, 0xa3, 0xea, 0x34, 0xa9, 0x11, - 0x2a, 0x91, 0x9c, 0x7d, 0xe9, 0xa6, 0x59, 0x1c, 0xc3, 0xf5, 0x75, 0x9d, 0x4c, 0x04, 0xe0, 0x20, - 0xf8, 0xe9, 0x45, 0x6f, 0x49, 0x6d, 0xe5, 0x49, 0x7e, 0x7d, 0x3d, 0x24, 0x48, 0xce, 0xfd, 0x36, - 0x6d, 0x50, 0x45, 0x13, 0x44, 0x72, 0x92, 0x33, 0x10, 0x65, 0xdc, 0xf5, 0xb1, 0xa7, 0x11, 0xfc, - 0x49, 0xd3, 0x3d, 0x32, 0x0d, 0x4b, 0xf4, 0xd2, 0x02, 0x69, 0xcf, 0x72, 0xde, 0x68, 0xc1, 0xa0, - 0x2e, 0x7a, 0xb5, 0x90, 0xe4, 0xcc, 0xdd, 0x69, 0xba, 0x69, 0x90, 0x7a, 0xdc, 0xaa, 0x3b, 0x6f, - 0x34, 0x8b, 0x2b, 0xba, 0xb7, 0xd3, 0x3c, 0x5f, 0x52, 0xd8, 0xde, 0x79, 0x6b, 0x22, 0x2b, 0x3d, - 0x07, 0xdd, 0x29, 0x54, 0x49, 0xb5, 0xf7, 0x86, 0x26, 0xf6, 0xea, 0x57, 0x93, 0xeb, 0xf5, 0xfa, - 0x45, 0xfb, 0x19, 0x3f, 0x51, 0xf0, 0x42, 0xde, 0x6c, 0xc8, 0x49, 0x33, 0x07, 0x50, 0xbe, 0x12, - 0x14, 0xe0, 0xaa, 0x90, 0xf5, 0x75, 0xd1, 0xa0, 0x55, 0xc4, 0x7a, 0x1d, 0x0d, 0x2b, 0x46, 0x0f, - 0xd3, 0xbe, 0x34, 0x2c, 0x4b, 0x79, 0x4b, 0xab, 0x36, 0xfd, 0x8d, 0x74, 0x0b, 0x8b, 0x9c, 0x58, - 0x6a, 0x27, 0x68, 0xe5, 0x0b, 0xa4, 0x29, 0xe7, 0x09, 0x53, 0x01, 0x39, 0x6f, 0x1f, 0x83, 0xd6, - 0x40, 0x56, 0x72, 0x7d, 0x5d, 0x45, 0xbb, 0x0d, 0xb0, 0xcb, 0x48, 0xf5, 0xeb, 0x7e, 0x9b, 0x7a, - 0xcd, 0x87, 0x01, 0xa7, 0x95, 0x8f, 0x74, 0xa8, 0x9a, 0xb6, 0xa0, 0x6e, 0x38, 0xa5, 0xbf, 0x90, - 0xd2, 0xe6, 0x9a, 0x84, 0x15, 0xdd, 0x72, 0xac, 0xa0, 0x39, 0xbc, 0x4a, 0x97, 0x10, 0x53, 0xd0, - 0x50, 0x4a, 0x94, 0xf0, 0xb7, 0xef, 0xfe, 0xb6, 0x53, 0x62, 0x52, 0x0c, 0xd5, 0xc3, 0x4b, 0xe0, - 0x7e, 0xbd, 0x74, 0x2e, 0x9b, 0x2b, 0xfd, 0x19, 0x02, 0x24, 0x95, 0xde, 0xcc, 0x16, 0x73, 0x7f, - 0x86, 0x40, 0x49, 0xa5, 0xe5, 0xcd, 0x5c, 0x28, 0x8d, 0x07, 0x06, 0x8f, 0x26, 0x5a, 0xa7, 0xd8, - 0x28, 0xec, 0x57, 0x82, 0x53, 0x27, 0x69, 0x64, 0xa6, 0x90, 0x9a, 0x9e, 0x6c, 0x73, 0x55, 0xfc, - 0xc4, 0x64, 0x15, 0xf8, 0x0b, 0x8a, 0xb6, 0x3a, 0x11, 0xbf, 0xd4, 0xd1, 0xf3, 0xaf, 0x09, 0x8c, - 0x6b, 0x04, 0x7b, 0x42, 0x0b, 0xe9, 0x0b, 0xe7, 0x10, 0x2d, 0x5d, 0x2d, 0x1a, 0x85, 0xb1, 0xc6, - 0x76, 0x1d, 0x98, 0x1f, 0x1e, 0x8d, 0x5e, 0x63, 0xc9, 0xed, 0x84, 0xfd, 0xfb, 0x37, 0xbc, 0x33, - 0xca, 0xa4, 0xac, 0xaf, 0xee, 0x0d, 0xd2, 0x2f, 0xb4, 0x95, 0xcd, 0x6d, 0x6e, 0xd3, 0xbb, 0x00, - 0x62, 0x95, 0x5e, 0x99, 0x00, 0xb5, 0xc4, 0xab, 0x12, 0xf8, 0xb9, 0xd7, 0xbd, 0xbd, 0x6e, 0x7d, - 0xdd, 0xd9, 0x92, 0xb7, 0x7f, 0x45, 0xdd, 0xde, 0xb3, 0x65, 0x7a, 0xab, 0x56, 0xf8, 0x63, 0xe6, - 0x61, 0xdf, 0xef, 0x60, 0x2e, 0xe4, 0xe5, 0x7f, 0x49, 0x88, 0xdd, 0xc4, 0x1f, 0x33, 0x67, 0x2e, - 0xf9, 0x7f, 0x92, 0xc9, 0x5f, 0xd5, 0x85, 0xd2, 0xc9, 0x6a, 0xc2, 0x1f, 0x54, 0xd0, 0x67, 0x02, - 0x76, 0x82, 0x58, 0xb0, 0x7e, 0xc5, 0x34, 0xfb, 0x4b, 0x5a, 0x1c, 0xb6, 0x13, 0x33, 0x4c, 0x6e, - 0x96, 0x14, 0xd3, 0xd4, 0xde, 0x9a, 0xbd, 0x3e, 0x70, 0x87, 0x0e, 0x0b, 0x43, 0x20, 0x6a, 0x28, - 0xdf, 0xc2, 0x22, 0xa8, 0xc3, 0x06, 0x94, 0xa6, 0xfb, 0x4f, 0x1a, 0xb7, 0x9f, 0x64, 0x0d, 0xc5, - 0x10, 0xc2, 0xa5, 0xd2, 0x0e, 0xd2, 0xed, 0x7e, 0x0d, 0xe0, 0xa4, 0xfc, 0x41, 0xa4, 0xa1, 0x30, - 0x45, 0xc9, 0x2d, 0xeb, 0xd0, 0xb2, 0xb8, 0xfd, 0xa4, 0xd9, 0x9e, 0x52, 0xf3, 0x4a, 0x39, 0x6d, - 0x53, 0x94, 0x9c, 0x6d, 0x31, 0x4b, 0xbf, 0x57, 0xee, 0x7f, 0xb4, 0x1c, 0x9f, 0x00, 0x48, 0xfa, - 0x8c, 0xe1, 0xb0, 0x69, 0x78, 0x25, 0x7c, 0x80, 0x99, 0xf1, 0xaa, 0xb6, 0xdd, 0xaa, 0x7e, 0x78, - 0x4a, 0x41, 0xf6, 0xaa, 0xb8, 0xe1, 0x98, 0xf8, 0xc2, 0x83, 0x2e, 0x2d, 0x4c, 0xfd, 0x62, 0xa1, - 0x18, 0x25, 0x2e, 0x2e, 0x7b, 0xe8, 0xd0, 0x6c, 0x99, 0x76, 0x5b, 0x0c, 0xf5, 0xe3, 0x6c, 0xb4, - 0x45, 0x29, 0x18, 0x2b, 0xe5, 0xbf, 0x69, 0xd8, 0x61, 0x83, 0x12, 0x76, 0xdf, 0x64, 0x25, 0xe8, - 0x08, 0xd9, 0x86, 0xb8, 0xcd, 0xba, 0xa8, 0xba, 0x3d, 0x42, 0x61, 0x15, 0xfd, 0x49, 0xc2, 0xce, - 0xa3, 0xe8, 0x5a, 0xe0, 0x7b, 0x8e, 0xde, 0xaa, 0x80, 0x7d, 0x51, 0x3a, 0x6e, 0x5d, 0x9c, 0xc3, - 0xbc, 0xe1, 0xf7, 0x58, 0xd5, 0xde, 0x5b, 0x02, 0x9a, 0x4d, 0x26, 0x7d, 0x31, 0x01, 0x98, 0x57, - 0xd7, 0x5e, 0x5f, 0x67, 0xca, 0xf3, 0xed, 0x11, 0xcf, 0x97, 0x3d, 0xc7, 0xf0, 0x99, 0x0f, 0x08, - 0xdb, 0xe8, 0xd3, 0xb0, 0x9b, 0xd7, 0xbf, 0xc4, 0x24, 0x4a, 0xc1, 0x8c, 0x87, 0x5a, 0x71, 0x63, - 0x50, 0xcc, 0xc2, 0x93, 0x5e, 0x5f, 0x46, 0x0d, 0xdb, 0x4c, 0x18, 0xa9, 0xba, 0xf9, 0xcb, 0x5a, - 0xf5, 0x1c, 0x36, 0x66, 0x11, 0x4a, 0xe0, 0x40, 0x63, 0x09, 0xcb, 0x1a, 0xa0, 0xf7, 0x04, 0x16, - 0x06, 0x07, 0xb4, 0xbf, 0x38, 0x38, 0x48, 0x8c, 0x6d, 0xc5, 0xa5, 0x6b, 0x60, 0x44, 0x04, 0xf8, - 0x02, 0x4f, 0xa7, 0xe2, 0x57, 0x42, 0x78, 0x7a, 0xe8, 0x6c, 0xf4, 0x30, 0x91, 0x5e, 0x6c, 0xe2, - 0x12, 0x73, 0x98, 0xd8, 0xed, 0x76, 0x43, 0x89, 0x79, 0x4c, 0x6c, 0xb7, 0xdb, 0xa1, 0xc4, 0x02, - 0x26, 0x2a, 0x8a, 0x12, 0x4a, 0x2c, 0x62, 0x62, 0xa5, 0x52, 0x09, 0x25, 0x96, 0xe2, 0x12, 0xcb, - 0x98, 0x58, 0x2e, 0x97, 0x43, 0x89, 0x6d, 0x4c, 0x2c, 0x14, 0x0a, 0xa1, 0xc4, 0x0e, 0x26, 0xe6, - 0xf3, 0xf9, 0x50, 0x22, 0xc1, 0xc4, 0x6c, 0x36, 0x1b, 0x4a, 0xec, 0x62, 0x62, 0x2e, 0x97, 0x0b, - 0x25, 0x5a, 0x14, 0xce, 0x5c, 0xb8, 0x64, 0x9f, 0x96, 0x54, 0xc2, 0x89, 0x1a, 0x4d, 0x2c, 0x75, - 0x42, 0x89, 0x06, 0x24, 0xd2, 0x2f, 0x3b, 0xe6, 0xe4, 0x82, 0x24, 0x04, 0x7f, 0xe4, 0x74, 0x25, - 0x19, 0x2a, 0x68, 0xb7, 0x5d, 0x7c, 0xe6, 0x23, 0xc9, 0x03, 0x37, 0xbd, 0x14, 0x4a, 0x77, 0xda, - 0x4b, 0x1a, 0x76, 0xbf, 0xa2, 0xbd, 0xd1, 0x4e, 0x26, 0x23, 0x15, 0x14, 0xaf, 0x46, 0x76, 0x53, - 0x96, 0x84, 0xe0, 0xcf, 0xf2, 0x1a, 0x83, 0x4f, 0xf5, 0x41, 0x3d, 0x71, 0xa9, 0xb1, 0x33, 0xe9, - 0xb2, 0x53, 0xe6, 0xa9, 0x86, 0x66, 0x58, 0x8c, 0xc7, 0x9d, 0x90, 0xd3, 0x65, 0x28, 0x57, 0x8d, - 0x12, 0x54, 0x14, 0xfd, 0x94, 0xa0, 0xd8, 0xde, 0x12, 0x21, 0xa8, 0xe8, 0x9c, 0xe4, 0xe3, 0xa6, - 0xb4, 0x10, 0x37, 0xf9, 0x94, 0xa0, 0x8a, 0xc5, 0xe2, 0x22, 0x41, 0x95, 0x4a, 0xa5, 0x4f, 0x12, - 0x54, 0x94, 0x72, 0x29, 0x41, 0x75, 0x3a, 0x9d, 0x45, 0x82, 0x8a, 0x2e, 0x91, 0x6e, 0xdc, 0x6a, - 0xa0, 0x04, 0x45, 0x0a, 0xb9, 0x45, 0x82, 0x2a, 0x90, 0xdc, 0x22, 0x41, 0x15, 0xca, 0x4a, 0x3c, - 0x41, 0xe5, 0x61, 0x22, 0xbc, 0x7f, 0x4b, 0xa8, 0x09, 0x90, 0x19, 0x4b, 0x4d, 0x90, 0x5e, 0x5c, - 0x42, 0x4d, 0x7c, 0xab, 0x9f, 0x21, 0x25, 0x39, 0x07, 0x54, 0xe4, 0xff, 0xf9, 0x04, 0x29, 0x15, - 0xb3, 0x92, 0xe0, 0xfd, 0xfb, 0x2c, 0x1d, 0x8d, 0x74, 0xd8, 0x07, 0x44, 0x8e, 0x4f, 0xa1, 0x19, - 0x6b, 0xa7, 0x1f, 0x08, 0x4c, 0xb4, 0x6a, 0xbb, 0x8f, 0x7d, 0xd6, 0xbb, 0xe9, 0x8e, 0x45, 0x80, - 0xf9, 0xbb, 0xa2, 0x30, 0x6d, 0x52, 0x4c, 0xd6, 0xd4, 0x5e, 0xc2, 0x4e, 0xa3, 0xd9, 0x9d, 0x48, - 0x22, 0xf0, 0x68, 0xf2, 0xfb, 0xb7, 0xaf, 0x37, 0x80, 0x9e, 0x67, 0x8f, 0x86, 0x69, 0x73, 0x00, - 0xea, 0xbf, 0x9d, 0xc9, 0x56, 0x72, 0x72, 0x26, 0x2b, 0x97, 0x65, 0xe4, 0xe4, 0xd0, 0x03, 0x6e, - 0xcf, 0x7a, 0xdd, 0x53, 0x02, 0x6a, 0x3d, 0xc3, 0x4a, 0x50, 0x23, 0x83, 0x00, 0x52, 0x2f, 0x68, - 0x68, 0xbf, 0x7f, 0xff, 0xf8, 0xc9, 0x0a, 0x29, 0x75, 0x10, 0x37, 0xb5, 0x1f, 0xf2, 0xcf, 0x6d, - 0x1d, 0x45, 0xf1, 0xfd, 0x91, 0xa6, 0x3d, 0x82, 0xfc, 0x93, 0x48, 0x56, 0x31, 0x51, 0x32, 0xfc, - 0x36, 0x12, 0x8a, 0xa4, 0xfd, 0xc8, 0xfe, 0x84, 0x3f, 0xb9, 0x9f, 0x49, 0x49, 0x0d, 0xd2, 0x0d, - 0x00, 0x13, 0xb7, 0x3c, 0xfa, 0xa2, 0x62, 0x23, 0xf4, 0x29, 0x99, 0xd2, 0x7e, 0xe4, 0xa1, 0xa4, - 0xbe, 0x55, 0x37, 0x40, 0x1d, 0xf9, 0x5e, 0x57, 0x41, 0xd8, 0x61, 0x83, 0xd1, 0x7e, 0x14, 0x7e, - 0x26, 0xe7, 0x73, 0x1b, 0xbd, 0xf6, 0xf7, 0xf0, 0x4b, 0xdd, 0x68, 0x7e, 0x26, 0x3a, 0xb1, 0x12, - 0xd4, 0xd4, 0x07, 0xf2, 0x45, 0x7d, 0xcb, 0x1d, 0x01, 0x27, 0x69, 0x47, 0xf7, 0xea, 0x76, 0x1f, - 0x3a, 0xa6, 0xe2, 0xb8, 0x0e, 0x42, 0x73, 0x42, 0xaf, 0xa7, 0x4b, 0x49, 0xc9, 0x53, 0x56, 0xdc, - 0x40, 0x73, 0x75, 0xdd, 0x4f, 0x09, 0x44, 0xab, 0x23, 0xd4, 0xb8, 0xea, 0xbf, 0x40, 0xc5, 0x06, - 0xf9, 0x8a, 0x42, 0x44, 0x25, 0x2b, 0x76, 0x0b, 0x84, 0x4e, 0x4a, 0x70, 0x7a, 0x93, 0x0c, 0xc9, - 0x5a, 0xa2, 0x17, 0x1c, 0xb5, 0x03, 0xd2, 0x75, 0x64, 0x66, 0x5b, 0x2f, 0xaa, 0xde, 0x6c, 0xb5, - 0x70, 0x7a, 0x61, 0xd6, 0xbe, 0x30, 0x9d, 0x88, 0xe1, 0xd8, 0xa9, 0x47, 0xd4, 0x9c, 0x1b, 0xa5, - 0x4f, 0x95, 0x1c, 0x34, 0x40, 0xc3, 0x3a, 0x43, 0x44, 0xc7, 0x90, 0x00, 0x1e, 0x84, 0x01, 0x0d, - 0xd8, 0x69, 0xb5, 0x0b, 0xf3, 0x0f, 0xfb, 0x1f, 0xd1, 0xf0, 0x44, 0xf3, 0x0d, 0x3f, 0xbe, 0x4c, - 0x80, 0xb4, 0x20, 0x29, 0x38, 0x20, 0xce, 0x80, 0x72, 0x8e, 0x29, 0x91, 0xc0, 0x5e, 0x00, 0x54, - 0x1a, 0x73, 0x80, 0xc6, 0xd2, 0x34, 0xe2, 0x64, 0x5d, 0xc4, 0x3b, 0x69, 0x80, 0x15, 0x8c, 0x94, - 0xa7, 0x77, 0x9b, 0x03, 0x55, 0xeb, 0x26, 0x6c, 0x98, 0x8d, 0x40, 0x45, 0x63, 0x95, 0x7d, 0x65, - 0x21, 0x01, 0xb2, 0xcd, 0xb6, 0x67, 0x68, 0x4a, 0x89, 0x99, 0x8c, 0x98, 0xa2, 0xb6, 0xaa, 0xaa, - 0x28, 0x26, 0x53, 0x24, 0xa8, 0x67, 0xe8, 0x68, 0x2c, 0x4d, 0x30, 0xea, 0x26, 0xf5, 0xc8, 0x75, - 0x1f, 0x24, 0x65, 0x74, 0x06, 0x26, 0x55, 0xa4, 0xe3, 0x34, 0x6d, 0x0c, 0x90, 0x9a, 0xa4, 0x86, - 0x2c, 0x59, 0x4a, 0xd0, 0x26, 0xeb, 0x21, 0x31, 0xaa, 0xef, 0x89, 0x51, 0x90, 0x7a, 0x64, 0x82, - 0x54, 0x0b, 0xb2, 0x2f, 0x2b, 0x06, 0xb5, 0x41, 0x21, 0x4c, 0x88, 0xfb, 0xd0, 0x1e, 0x0d, 0x00, - 0x96, 0x16, 0x2e, 0x35, 0xfc, 0x9a, 0xb5, 0x40, 0x23, 0xff, 0xb2, 0x98, 0x82, 0x47, 0x97, 0x5f, - 0xc4, 0x65, 0x82, 0x19, 0x6b, 0x51, 0xa2, 0xad, 0x25, 0x93, 0x35, 0x90, 0x4f, 0x88, 0xaf, 0xc5, - 0x98, 0x8a, 0x33, 0xa0, 0xdf, 0xc3, 0xb2, 0xeb, 0x34, 0x8e, 0x6d, 0x87, 0xa0, 0xf7, 0x4e, 0x1a, - 0x90, 0x65, 0xdf, 0xab, 0xce, 0x00, 0xb0, 0x2a, 0x26, 0xb7, 0x37, 0xb2, 0xd5, 0xb1, 0xa1, 0x76, - 0x05, 0x19, 0x08, 0xc4, 0x04, 0x1d, 0x9c, 0xa6, 0xd6, 0x7c, 0x73, 0x5c, 0x30, 0x40, 0xd6, 0x07, - 0x24, 0x0c, 0x0c, 0xdb, 0xc1, 0x66, 0x53, 0x20, 0xbf, 0x63, 0x10, 0xc9, 0x6d, 0x90, 0xad, 0x52, - 0xec, 0x11, 0x31, 0x09, 0xf3, 0xc3, 0x34, 0xd8, 0x2d, 0x19, 0x14, 0x55, 0x54, 0x90, 0x6c, 0x5c, - 0x94, 0x09, 0x17, 0x3f, 0xb4, 0x95, 0x54, 0x1d, 0x7a, 0x49, 0x61, 0x7a, 0xb2, 0xea, 0xf2, 0x01, - 0x5c, 0xee, 0x5e, 0xaf, 0xb0, 0x08, 0x58, 0xe9, 0xe4, 0x9c, 0x51, 0x5d, 0x3c, 0x36, 0x03, 0xa1, - 0x34, 0x59, 0x73, 0xa0, 0x0e, 0x9a, 0xa1, 0x40, 0xcd, 0xed, 0x93, 0x5d, 0x42, 0x4c, 0x7c, 0x63, - 0xb2, 0x2a, 0x5d, 0x79, 0x09, 0x50, 0x5b, 0xf0, 0xd6, 0x13, 0xea, 0xfe, 0xb7, 0x8e, 0xaa, 0x81, - 0xa4, 0x9b, 0x10, 0x1d, 0x6b, 0x44, 0xc4, 0xfa, 0x92, 0xd6, 0xcd, 0xce, 0x50, 0x84, 0x89, 0xfa, - 0x92, 0x39, 0x33, 0xda, 0x6a, 0x06, 0x34, 0x3a, 0xdb, 0x49, 0xe8, 0xca, 0x58, 0xed, 0x2b, 0x50, - 0x32, 0x3d, 0xb2, 0x89, 0xd5, 0xe8, 0xc3, 0x24, 0xc1, 0x8a, 0xc5, 0xe5, 0xb6, 0xb2, 0x15, 0x28, - 0x13, 0x0d, 0x12, 0xc7, 0xc9, 0x7f, 0x81, 0x64, 0x4e, 0xed, 0x51, 0xdb, 0x3d, 0xe2, 0x74, 0x06, - 0xc1, 0x41, 0xd4, 0x00, 0xe3, 0x76, 0x43, 0x7a, 0xfa, 0xd9, 0x36, 0x74, 0x40, 0xef, 0x6c, 0x48, - 0x9c, 0x81, 0xd1, 0xad, 0x8a, 0x50, 0x02, 0xd6, 0x2e, 0x72, 0x12, 0x3d, 0x01, 0x7c, 0x86, 0xd0, - 0x02, 0x89, 0x64, 0x90, 0x32, 0x8b, 0x5a, 0x04, 0x00, 0x47, 0x68, 0xd4, 0x02, 0xdd, 0x3e, 0x99, - 0x06, 0x62, 0x86, 0x5e, 0xb0, 0x14, 0x5a, 0x7b, 0x0d, 0xe0, 0x0c, 0x9a, 0xd1, 0x4f, 0x88, 0xe7, - 0x86, 0xa0, 0x60, 0x69, 0x97, 0xbd, 0xd2, 0x9e, 0xd1, 0x80, 0x1c, 0x82, 0x22, 0x2d, 0xec, 0xb2, - 0xaf, 0x77, 0xd9, 0x94, 0x61, 0x90, 0x6e, 0x5a, 0xc4, 0x26, 0x7b, 0xaa, 0x0e, 0x4b, 0xf2, 0x2d, - 0x91, 0x48, 0x42, 0xab, 0xee, 0x1e, 0xc1, 0xc9, 0xe2, 0xfd, 0x34, 0x30, 0x2a, 0x28, 0x57, 0x5d, - 0x96, 0x15, 0x20, 0x02, 0x16, 0xfa, 0xfa, 0x3a, 0xcf, 0x8b, 0x44, 0x5c, 0xff, 0x4d, 0xb6, 0xfc, - 0xf9, 0x6b, 0xd2, 0x92, 0xeb, 0x3f, 0xe8, 0x1e, 0xb6, 0x63, 0x0a, 0xb3, 0x61, 0x2e, 0xa7, 0x98, - 0xcb, 0xa1, 0x43, 0x57, 0x13, 0x77, 0x21, 0xd2, 0x07, 0x78, 0xff, 0x01, 0x6d, 0xd5, 0xfc, 0x3b, - 0x7b, 0xe6, 0x2e, 0xc5, 0xf9, 0x79, 0xfc, 0x45, 0x38, 0x96, 0x1a, 0x36, 0x48, 0x25, 0xe7, 0x12, - 0x9e, 0xa8, 0xcf, 0xe9, 0xff, 0x18, 0xe5, 0xb9, 0x84, 0xd7, 0x8d, 0xd9, 0x2e, 0x82, 0xc8, 0xd2, - 0xcc, 0x11, 0x4b, 0x94, 0xe2, 0x6d, 0x63, 0xd2, 0x97, 0xac, 0xbb, 0x55, 0x77, 0xc6, 0x3e, 0x4f, - 0xf7, 0xf6, 0x08, 0x99, 0x2a, 0xb3, 0xc8, 0x80, 0x01, 0x10, 0xeb, 0xad, 0x45, 0x31, 0x65, 0x58, - 0x0d, 0x4d, 0x4b, 0x7c, 0xe3, 0x62, 0xb4, 0xba, 0x9e, 0x94, 0x3f, 0xbf, 0x25, 0xfd, 0xed, 0x14, - 0x8f, 0x11, 0x04, 0x3b, 0xa9, 0xc7, 0x40, 0xe6, 0x18, 0xa3, 0xce, 0x00, 0xcf, 0x07, 0x50, 0x03, - 0xa5, 0x54, 0xbc, 0x43, 0xfd, 0x26, 0x61, 0x43, 0x5c, 0x56, 0x1a, 0x38, 0x4c, 0xa4, 0x6c, 0xc0, - 0x5c, 0x23, 0x13, 0x46, 0xbc, 0x8d, 0x85, 0xd9, 0xd3, 0x02, 0xb7, 0x89, 0x00, 0x36, 0x1b, 0x61, - 0x73, 0x92, 0x76, 0xcc, 0xad, 0x37, 0xa5, 0x83, 0xf1, 0xf4, 0xa1, 0x2c, 0x33, 0x91, 0xff, 0xfe, - 0xed, 0xfc, 0x20, 0x3f, 0xa3, 0x97, 0xe9, 0xbc, 0x42, 0x1c, 0x83, 0x77, 0xfd, 0x30, 0x88, 0xe4, - 0xd4, 0x01, 0x9f, 0x33, 0x56, 0x7b, 0x7d, 0xfd, 0x8b, 0x03, 0x8c, 0x59, 0x6d, 0xa1, 0x9b, 0x27, - 0xec, 0x53, 0xff, 0xd5, 0x8c, 0xb9, 0xd6, 0x66, 0x0f, 0x0d, 0xc3, 0x01, 0x69, 0x0a, 0xa7, 0x01, - 0x0a, 0x2c, 0x1a, 0x14, 0x41, 0xe0, 0x52, 0x45, 0x89, 0x35, 0xb2, 0x40, 0x9e, 0x84, 0x37, 0x68, - 0x60, 0x78, 0x7a, 0xe6, 0x79, 0xe1, 0xc1, 0x81, 0x4c, 0x8c, 0xcd, 0xad, 0xeb, 0xc4, 0x91, 0x5c, - 0xd8, 0xf2, 0x9b, 0xe1, 0xed, 0xdb, 0x02, 0xc9, 0xcd, 0x9d, 0x74, 0x5a, 0x91, 0x39, 0x79, 0xd0, - 0xad, 0x16, 0xbf, 0x6f, 0x79, 0x78, 0x73, 0x76, 0x4a, 0x77, 0xdc, 0x30, 0x4a, 0x80, 0x65, 0x13, - 0xf4, 0xfb, 0x00, 0xc6, 0x8d, 0x40, 0xc0, 0x72, 0xa0, 0xbe, 0x20, 0x1e, 0x89, 0xbb, 0x47, 0x33, - 0xc8, 0xc9, 0x59, 0xf7, 0x8a, 0xae, 0x0e, 0xe9, 0xde, 0xe7, 0x1a, 0xbd, 0xbc, 0xc3, 0x9b, 0x7a, - 0x74, 0x5d, 0xc4, 0xcd, 0x11, 0xeb, 0x61, 0x2e, 0xe5, 0x2a, 0xb0, 0x1a, 0x24, 0x18, 0x22, 0xcf, - 0x6f, 0x48, 0x04, 0x1f, 0x9c, 0x3b, 0x4a, 0x72, 0x16, 0x20, 0x48, 0x6c, 0x02, 0x42, 0x88, 0xab, - 0x6a, 0x1b, 0x02, 0xd5, 0xe4, 0x85, 0x9e, 0x02, 0x9b, 0x66, 0xf7, 0x0b, 0xcc, 0x85, 0xcc, 0xeb, - 0xd0, 0x11, 0xaf, 0x16, 0x52, 0x2f, 0x92, 0xbc, 0x4f, 0x64, 0x3c, 0x8e, 0x1c, 0x0e, 0x58, 0x37, - 0xba, 0xa2, 0x9d, 0x70, 0x11, 0x83, 0xac, 0x32, 0x1e, 0x27, 0xcb, 0x86, 0xee, 0x2c, 0x1d, 0xba, - 0x14, 0x97, 0xe5, 0x76, 0x33, 0x97, 0x42, 0x24, 0x01, 0x8c, 0xea, 0x1a, 0xcf, 0x0a, 0x87, 0xc4, - 0xb5, 0x8e, 0x32, 0xb0, 0x03, 0x23, 0x24, 0x4a, 0xd8, 0x67, 0xb0, 0xad, 0xa7, 0x7b, 0x9a, 0x01, - 0xcb, 0xc3, 0xc9, 0x94, 0x4b, 0x05, 0x44, 0xab, 0xce, 0xa7, 0x26, 0x9c, 0x0d, 0x9a, 0xfc, 0xa7, - 0x9d, 0xcc, 0xe4, 0x4b, 0x98, 0xad, 0xc5, 0x67, 0x6f, 0x60, 0xee, 0x9f, 0x7a, 0x32, 0x53, 0xc2, - 0x0d, 0xa9, 0x6e, 0x6f, 0xdb, 0x29, 0x51, 0x10, 0x53, 0x89, 0x2c, 0xec, 0xd9, 0x68, 0x36, 0x79, - 0x13, 0xf1, 0x24, 0xe7, 0x0d, 0x16, 0x63, 0x4a, 0x94, 0x04, 0x11, 0x3f, 0x92, 0xe6, 0x5a, 0x7f, - 0x95, 0x54, 0x5d, 0xff, 0xfd, 0xdb, 0xde, 0xd6, 0xfd, 0x0a, 0xfa, 0xb6, 0x38, 0x30, 0x46, 0x48, - 0x52, 0xf8, 0x03, 0x55, 0xa0, 0xb4, 0xf4, 0x05, 0xd8, 0xb8, 0x0e, 0xa8, 0x84, 0xe2, 0xd8, 0x00, - 0xa0, 0x62, 0xab, 0x58, 0x81, 0x75, 0x66, 0xb3, 0x34, 0x2d, 0x45, 0x9d, 0x20, 0x31, 0xfd, 0x3b, - 0x82, 0x82, 0x86, 0x4b, 0xcc, 0xe7, 0xca, 0xbb, 0xe9, 0x98, 0xe2, 0x6c, 0x94, 0xe4, 0x3f, 0xb1, - 0x8a, 0x4d, 0x50, 0xf9, 0x53, 0x38, 0x03, 0xb5, 0x0e, 0xbc, 0xc2, 0x98, 0xe0, 0x3a, 0x42, 0xc3, - 0xac, 0xe8, 0x09, 0x7c, 0xbf, 0xbe, 0x3b, 0xd6, 0xd6, 0x77, 0xa7, 0xeb, 0x1d, 0x6c, 0xbe, 0x90, - 0x37, 0xa7, 0x2b, 0x6e, 0xfd, 0x31, 0x23, 0xf3, 0xef, 0x19, 0xa7, 0xcb, 0x67, 0x8d, 0x15, 0x8d, - 0x65, 0x39, 0x73, 0x10, 0xa5, 0xdd, 0xec, 0x0c, 0x54, 0xff, 0x15, 0x9a, 0x9d, 0x53, 0xee, 0xcc, - 0xee, 0x32, 0xc1, 0xe6, 0x87, 0xd4, 0xb3, 0x3e, 0xaf, 0xa2, 0x87, 0xad, 0xf4, 0x08, 0x2e, 0xe9, - 0x80, 0x74, 0xb8, 0xbe, 0x4e, 0x52, 0x29, 0x0f, 0x67, 0x64, 0x2b, 0x57, 0xc4, 0xa1, 0x90, 0x3a, - 0xfc, 0x26, 0x25, 0x4e, 0xe0, 0xa4, 0x9e, 0xb4, 0xb7, 0xd0, 0x24, 0xc7, 0x0e, 0x81, 0x52, 0x7f, - 0x99, 0x08, 0xa9, 0xda, 0xfd, 0x95, 0x64, 0xf1, 0x96, 0x6a, 0x5f, 0x68, 0xcb, 0x3f, 0x9c, 0x9f, - 0xbf, 0x7f, 0xcb, 0x5f, 0xb0, 0x75, 0xec, 0x63, 0x3b, 0x28, 0x8a, 0x1f, 0x81, 0x80, 0xc2, 0xc1, - 0xd2, 0x77, 0xb0, 0xcb, 0x6d, 0x7a, 0x9b, 0xad, 0x5c, 0xae, 0x09, 0x47, 0xbb, 0xc2, 0x70, 0x64, - 0x3b, 0x42, 0x9b, 0x08, 0x90, 0x2e, 0x18, 0xa0, 0x46, 0x11, 0xdb, 0x4e, 0xe3, 0xc4, 0x56, 0x57, - 0xb4, 0xf2, 0xcb, 0xab, 0x8f, 0xe7, 0xd9, 0x13, 0x4b, 0xc5, 0x08, 0xbb, 0xc2, 0x1f, 0x33, 0x93, - 0x4a, 0xfe, 0x4e, 0x72, 0xfe, 0x85, 0xc3, 0x91, 0xe9, 0x9e, 0x79, 0xb8, 0xc3, 0x70, 0x7d, 0x4f, - 0x81, 0x46, 0x88, 0x87, 0x06, 0x3a, 0x86, 0xf5, 0x75, 0x36, 0x14, 0xf2, 0x33, 0x78, 0x4a, 0xeb, - 0xd4, 0x88, 0x1c, 0xbc, 0xc2, 0xf4, 0xf3, 0x87, 0x10, 0x97, 0x9a, 0xf2, 0x86, 0xde, 0x95, 0xdc, - 0x21, 0x84, 0x5f, 0xd6, 0x74, 0xf3, 0xb8, 0xd6, 0xbc, 0xa4, 0xb4, 0x69, 0x73, 0xe0, 0x29, 0xa6, - 0x7a, 0xa7, 0x68, 0x9e, 0x6e, 0x43, 0x0b, 0x83, 0x8c, 0xe7, 0x55, 0x4a, 0xba, 0xa7, 0x11, 0xa2, - 0x6b, 0x15, 0x76, 0x4f, 0x66, 0x80, 0x42, 0xd4, 0xbe, 0x9e, 0xc0, 0x83, 0x53, 0xaf, 0xa0, 0x37, - 0x1a, 0x27, 0x0d, 0xea, 0xc1, 0x36, 0xfd, 0x5b, 0x4d, 0x74, 0x09, 0xc6, 0xf2, 0x80, 0x34, 0x5d, - 0xf2, 0x1f, 0xcd, 0xe0, 0xf1, 0x55, 0x8b, 0x1a, 0x4f, 0x1d, 0x7e, 0xf1, 0xbf, 0x6a, 0x1e, 0xee, - 0x3e, 0xc4, 0xd4, 0xab, 0xb6, 0xcd, 0x3d, 0xe3, 0xe1, 0x69, 0x40, 0x4b, 0xe6, 0x4e, 0xe7, 0xc5, - 0xa7, 0x4c, 0xa6, 0x99, 0xa3, 0x7d, 0xb7, 0x46, 0xdc, 0xaf, 0x2d, 0x25, 0xa8, 0x91, 0x9e, 0x38, - 0x2d, 0xef, 0x8b, 0xbc, 0xd7, 0xf4, 0xcc, 0x4d, 0x96, 0x2a, 0xf4, 0x3f, 0x14, 0x4f, 0xc8, 0x94, - 0x74, 0x9a, 0xc6, 0x70, 0x08, 0x12, 0x08, 0xee, 0x45, 0xe6, 0x1b, 0x8a, 0x5d, 0x3c, 0x33, 0x36, - 0x55, 0xe6, 0x80, 0x80, 0x11, 0xd8, 0xdb, 0x86, 0x62, 0x01, 0x17, 0xe6, 0x06, 0x62, 0xd2, 0x39, - 0xa7, 0x3c, 0x38, 0xa0, 0x04, 0x3c, 0x7f, 0x85, 0xa5, 0x59, 0x73, 0xac, 0xb7, 0x59, 0xc2, 0x5e, - 0x25, 0x9f, 0x81, 0xbc, 0xec, 0x69, 0x10, 0x59, 0x99, 0x92, 0x04, 0x32, 0x78, 0x57, 0x5e, 0x4d, - 0xce, 0xe6, 0x4c, 0x9f, 0xfe, 0xc5, 0xbb, 0xbc, 0xd2, 0xaf, 0xd5, 0x74, 0x44, 0x20, 0x4a, 0xb2, - 0xfd, 0x2d, 0x1c, 0x69, 0xa2, 0x5a, 0x0e, 0x7f, 0xcf, 0x53, 0xa0, 0xdf, 0xab, 0x15, 0xbf, 0x55, - 0xbf, 0xf9, 0xae, 0x0d, 0xfe, 0x69, 0x77, 0x2d, 0xe4, 0xa5, 0x5b, 0x13, 0xe3, 0x2f, 0xaf, 0xd7, - 0x3e, 0x68, 0x79, 0xbe, 0xf5, 0xab, 0xa6, 0xa7, 0x60, 0x61, 0x8a, 0xe8, 0xb9, 0x32, 0x50, 0xc6, - 0x44, 0xd0, 0x0d, 0x17, 0x29, 0xb6, 0xf0, 0x46, 0x9c, 0x2f, 0xb0, 0xe0, 0xdc, 0x98, 0xf1, 0x20, - 0xff, 0x5a, 0x44, 0x98, 0x28, 0x36, 0x3a, 0xc1, 0xa8, 0xb6, 0x3d, 0x22, 0x54, 0xa2, 0xc6, 0x05, - 0xf6, 0x06, 0x6c, 0xd4, 0xab, 0x05, 0x9b, 0x1c, 0xca, 0x06, 0xd0, 0xaa, 0xe8, 0xf9, 0xd4, 0x88, - 0x12, 0xeb, 0xe3, 0x10, 0x38, 0x12, 0x7e, 0xfb, 0xc7, 0x6d, 0x4a, 0xb5, 0x05, 0x14, 0x16, 0x46, - 0xa6, 0x5b, 0x95, 0xde, 0xbb, 0x45, 0x01, 0x4a, 0xc1, 0x84, 0xb1, 0x6a, 0x8c, 0x6c, 0xe6, 0x01, - 0xa5, 0x69, 0x0a, 0x3b, 0x56, 0x19, 0xc3, 0x36, 0x8a, 0xdf, 0xf1, 0xa0, 0xde, 0x36, 0xc2, 0x7f, - 0xea, 0x82, 0x20, 0x24, 0x5a, 0xa0, 0xe7, 0x00, 0x08, 0x8a, 0xd7, 0xc8, 0x44, 0xd5, 0xd8, 0x25, - 0x05, 0x01, 0x7d, 0xa7, 0xa9, 0xff, 0x98, 0xe1, 0xf2, 0x02, 0x42, 0x9d, 0x4f, 0x58, 0x9f, 0x49, - 0x18, 0xd8, 0xa1, 0x0b, 0x85, 0xe2, 0xc1, 0x61, 0x30, 0xf7, 0x14, 0x3c, 0x21, 0x10, 0x5e, 0x74, - 0x63, 0x02, 0x7c, 0xd4, 0x30, 0xba, 0xe8, 0xa5, 0xe3, 0x80, 0x0a, 0x8d, 0xa3, 0xf8, 0xf6, 0xdd, - 0xfb, 0xb0, 0x00, 0x73, 0x59, 0xee, 0xd0, 0x1b, 0x05, 0x5e, 0xda, 0x56, 0x00, 0xd7, 0x4a, 0xbf, - 0x1d, 0x97, 0xf0, 0xd1, 0x15, 0xd9, 0x7c, 0x0b, 0x11, 0xa7, 0xef, 0x71, 0xf3, 0x2d, 0x29, 0x51, - 0x14, 0x52, 0xff, 0x17, 0x91, 0x89, 0xd1, 0xae, 0x0b, 0x39, 0xc7, 0xee, 0x74, 0xc9, 0x97, 0xc3, - 0xe8, 0xca, 0x61, 0xcc, 0xb7, 0x6e, 0x47, 0xcc, 0x24, 0x1e, 0x9d, 0x10, 0x6a, 0x6e, 0xa1, 0xfc, - 0x04, 0x38, 0x32, 0xfa, 0x51, 0xd4, 0xa9, 0x0a, 0x42, 0x9f, 0xb7, 0xe4, 0xa4, 0xb7, 0x98, 0x0d, - 0x73, 0x84, 0xd1, 0xa3, 0xbc, 0x6a, 0x5f, 0x5c, 0x55, 0x05, 0x09, 0x0f, 0x7e, 0x25, 0xaa, 0x84, - 0x13, 0xa0, 0xba, 0x04, 0x88, 0xb1, 0x90, 0xf0, 0xa5, 0xee, 0xe6, 0x82, 0x28, 0x12, 0xd1, 0x08, - 0x5d, 0xa2, 0xf8, 0x48, 0x21, 0x14, 0x0b, 0x72, 0x81, 0x9a, 0x28, 0x10, 0xd5, 0x23, 0x7b, 0x7b, - 0x26, 0x57, 0x67, 0xf3, 0x79, 0x35, 0x46, 0x4f, 0x64, 0x3e, 0x23, 0x24, 0x04, 0x4f, 0x14, 0xde, - 0xb0, 0xce, 0xc8, 0x2f, 0x71, 0x90, 0x5c, 0xa3, 0xca, 0x1f, 0x1e, 0xb3, 0x05, 0xf2, 0x11, 0x01, - 0xd6, 0x92, 0x8c, 0x5a, 0x99, 0x7c, 0xe5, 0xcb, 0xc3, 0x5f, 0x64, 0x90, 0x08, 0x64, 0xc6, 0xf3, - 0x20, 0x5a, 0x31, 0xca, 0x04, 0xa8, 0x3f, 0x2f, 0x20, 0x8a, 0x44, 0x84, 0x47, 0xc9, 0x1f, 0x26, - 0x37, 0x4e, 0xe6, 0xff, 0xe2, 0x32, 0x75, 0x50, 0x5b, 0x2c, 0x15, 0xfb, 0x4f, 0x06, 0x63, 0xf5, - 0x23, 0xe4, 0x84, 0x06, 0xcb, 0x0b, 0xea, 0xc8, 0x8a, 0x16, 0x07, 0x8b, 0x07, 0x67, 0xdc, 0x99, - 0x57, 0x64, 0xa8, 0xa0, 0x45, 0xae, 0x1a, 0xa4, 0xeb, 0x0b, 0xf7, 0x0f, 0x8d, 0x91, 0x7c, 0x30, - 0x46, 0xd7, 0x67, 0xfe, 0x1f, 0x1f, 0x22, 0x55, 0x83, 0x57, 0x0d, 0x93, 0xb9, 0x28, 0xfd, 0x43, - 0xa3, 0x4c, 0xb8, 0x0e, 0x4f, 0x84, 0xda, 0x7f, 0xd3, 0xf6, 0x40, 0xed, 0x61, 0x51, 0x96, 0x9a, - 0x1e, 0xe9, 0x2c, 0x41, 0xac, 0x7d, 0xa9, 0x89, 0xe1, 0x81, 0x06, 0x8e, 0x52, 0x7f, 0x63, 0xc8, - 0xb8, 0xf3, 0x20, 0x2c, 0x2e, 0x61, 0x4b, 0xe6, 0xd5, 0x29, 0xb4, 0x14, 0xb8, 0xb3, 0x78, 0x38, - 0xbe, 0x3a, 0xf5, 0x37, 0x60, 0xd8, 0xf1, 0x80, 0x31, 0x40, 0x41, 0xdf, 0x28, 0x06, 0xd8, 0xf1, - 0x85, 0x45, 0x83, 0x65, 0xa1, 0xcf, 0x51, 0x82, 0xba, 0xa2, 0xd4, 0xb7, 0xc8, 0x0f, 0xf9, 0xe7, - 0x96, 0x83, 0x96, 0x31, 0x89, 0x20, 0x6b, 0x8c, 0xbb, 0x87, 0x73, 0x85, 0x8e, 0x50, 0x14, 0xef, - 0x78, 0x17, 0xe0, 0x1b, 0xc2, 0x41, 0xf1, 0x90, 0x84, 0x1a, 0xbf, 0x96, 0x86, 0x10, 0x12, 0xf0, - 0xc3, 0xe1, 0xee, 0xf5, 0x1c, 0x90, 0x73, 0xa1, 0x8b, 0xf9, 0xab, 0xd6, 0x16, 0xbd, 0x00, 0x7a, - 0x98, 0x94, 0xfb, 0xb9, 0x8d, 0x7f, 0x50, 0xa0, 0x08, 0xbb, 0xfe, 0x31, 0x2e, 0x90, 0x70, 0xab, - 0xc1, 0xd6, 0x88, 0x92, 0xf2, 0x8f, 0xec, 0xcf, 0xb9, 0xcf, 0x5b, 0x7f, 0xd5, 0x18, 0x3b, 0x7d, - 0xd5, 0x16, 0xc3, 0xd9, 0x78, 0xdf, 0x45, 0x83, 0xb9, 0x40, 0x03, 0xa5, 0x10, 0x5b, 0xd2, 0xd7, - 0x8c, 0xfc, 0xc2, 0x7c, 0x8b, 0x9c, 0x3a, 0x3b, 0x5f, 0xc0, 0xb7, 0xcf, 0x8a, 0x3d, 0x2e, 0x9c, - 0x60, 0x1c, 0x8d, 0x33, 0xff, 0xad, 0x96, 0x37, 0x92, 0x4c, 0xb0, 0x4b, 0xce, 0x5c, 0x59, 0x8d, - 0x09, 0x57, 0xf2, 0x4f, 0x57, 0x0c, 0x04, 0x4d, 0xc6, 0x8e, 0x2e, 0x29, 0x56, 0x01, 0x14, 0x6d, - 0x3a, 0x79, 0x9d, 0xa1, 0x79, 0x99, 0xf4, 0xe8, 0x81, 0x09, 0x28, 0x48, 0x18, 0xdc, 0x61, 0x45, - 0xc2, 0x75, 0xb4, 0xb3, 0xd9, 0x4c, 0x51, 0xf1, 0x93, 0x79, 0xf6, 0xe0, 0x61, 0x44, 0x32, 0x89, - 0xdb, 0x90, 0xaa, 0x83, 0x90, 0xcf, 0x8c, 0xce, 0xbe, 0xca, 0x87, 0x87, 0x17, 0x4c, 0xc5, 0x57, - 0xea, 0x58, 0x12, 0xe4, 0xbd, 0x9a, 0x02, 0x7b, 0x0b, 0x90, 0x8d, 0x39, 0xb2, 0x07, 0x89, 0x1f, - 0x44, 0x52, 0x24, 0x4f, 0xea, 0xc6, 0x53, 0x0a, 0x96, 0x0c, 0x2b, 0xde, 0x49, 0xc5, 0x08, 0x49, - 0x34, 0x6e, 0x8e, 0x47, 0x03, 0x64, 0x6e, 0x88, 0x5b, 0xbf, 0x02, 0xb3, 0x9b, 0xa9, 0x76, 0x51, - 0xde, 0x8a, 0xd6, 0x53, 0x7d, 0x9d, 0x09, 0xf7, 0xcd, 0x5f, 0x31, 0x2d, 0xa3, 0x39, 0x58, 0xf0, - 0x63, 0xe6, 0xc4, 0x53, 0x0e, 0x99, 0x27, 0xb1, 0x99, 0x90, 0x1c, 0xbf, 0x2d, 0xfa, 0xee, 0xc5, - 0xdf, 0xc2, 0xe1, 0xa3, 0xbe, 0x31, 0x97, 0xee, 0x3c, 0x0b, 0x9b, 0x80, 0x1a, 0xca, 0xdc, 0xd3, - 0x38, 0x48, 0x72, 0x8e, 0xe2, 0x40, 0xd4, 0x31, 0xd9, 0xff, 0xa0, 0xa0, 0xd0, 0xd3, 0x2c, 0x6e, - 0x88, 0x3a, 0x66, 0x84, 0x83, 0x33, 0xb4, 0x08, 0x48, 0xf3, 0x90, 0x97, 0xca, 0xca, 0xf2, 0xdc, - 0x8b, 0x54, 0xd7, 0x71, 0xbf, 0x87, 0xc2, 0x9c, 0x63, 0xe3, 0x3a, 0x88, 0xb4, 0x8e, 0xee, 0x84, - 0x5e, 0x23, 0x5c, 0xfb, 0x8c, 0x7e, 0xa3, 0xcd, 0xe7, 0x2b, 0x6e, 0x84, 0x0e, 0x6c, 0x38, 0x32, - 0x29, 0xa0, 0x32, 0xf2, 0xb1, 0x88, 0xbc, 0xc6, 0xfd, 0xb6, 0x03, 0x98, 0xdc, 0x19, 0xa0, 0x4e, - 0x9b, 0xa9, 0xd4, 0x7c, 0x89, 0x00, 0xe3, 0xd0, 0xfc, 0x2d, 0x79, 0x3b, 0x41, 0x05, 0x11, 0x2a, - 0x49, 0xac, 0xaf, 0xcb, 0xee, 0x6f, 0x62, 0xb9, 0x87, 0x07, 0x9a, 0x46, 0x51, 0x08, 0x70, 0x97, - 0x03, 0x50, 0x1e, 0xf5, 0x1f, 0x5d, 0x5e, 0x7e, 0xc1, 0x1b, 0x84, 0xad, 0x8a, 0xa4, 0x67, 0x85, - 0xc5, 0xb6, 0xaa, 0x21, 0x59, 0xc1, 0x37, 0xd9, 0x5e, 0x36, 0x12, 0xc1, 0xa6, 0x84, 0x0c, 0x93, - 0xb1, 0x86, 0x68, 0xf8, 0x31, 0xb6, 0xb2, 0x31, 0xfa, 0x18, 0xaf, 0x60, 0xb0, 0xc5, 0xd1, 0x33, - 0xe8, 0x11, 0xa4, 0xe7, 0xa1, 0x4a, 0xdc, 0xd5, 0x4a, 0xd2, 0x48, 0x85, 0xb5, 0x85, 0xf3, 0x2f, - 0x1e, 0x41, 0x4e, 0x7a, 0x48, 0xb7, 0x84, 0xcc, 0x8f, 0xff, 0x1c, 0xe5, 0xe5, 0x82, 0xbc, 0x81, - 0x3f, 0xbd, 0x1e, 0xfc, 0x2d, 0xc8, 0xf8, 0x52, 0xe8, 0xb6, 0xe1, 0xa5, 0x40, 0xe8, 0x4b, 0xa5, - 0x87, 0x39, 0xbd, 0x0a, 0x7d, 0xe9, 0x29, 0xf4, 0xa5, 0x57, 0x2a, 0xe1, 0x4b, 0xaf, 0x82, 0x75, - 0xb2, 0xf9, 0x2c, 0xbc, 0xec, 0x6e, 0x56, 0x76, 0x7f, 0x66, 0xf0, 0x6c, 0x67, 0xc9, 0xd1, 0x9b, - 0x1f, 0x4b, 0x14, 0x9d, 0xae, 0xe9, 0x77, 0xd7, 0x99, 0x4b, 0x16, 0x9e, 0xbe, 0x8a, 0xbb, 0x14, - 0xbc, 0x36, 0x9a, 0xd7, 0xea, 0xf5, 0x3a, 0xce, 0xd3, 0x32, 0xf7, 0xc5, 0x55, 0xcd, 0x90, 0x34, - 0x7e, 0x68, 0x84, 0x2a, 0x4d, 0x62, 0x02, 0xbf, 0x3a, 0x94, 0x04, 0xd5, 0xdb, 0xa1, 0x87, 0x47, - 0x5e, 0xa2, 0x9b, 0xd2, 0x4d, 0x33, 0xbe, 0xef, 0x04, 0x2e, 0xc9, 0x84, 0x7a, 0xef, 0x00, 0x2b, - 0x80, 0x97, 0x90, 0x1f, 0x35, 0x8a, 0x90, 0x96, 0xe7, 0xa0, 0xec, 0x96, 0x82, 0x37, 0xa0, 0x51, - 0xea, 0x37, 0x4c, 0xd2, 0x3d, 0x3b, 0x8d, 0x32, 0x23, 0x93, 0x99, 0xb9, 0xaf, 0x03, 0x79, 0x23, - 0x77, 0xaf, 0xe5, 0xd7, 0x7d, 0x37, 0x21, 0xbd, 0xdb, 0x01, 0xfa, 0x44, 0x61, 0xc1, 0x75, 0x75, - 0x42, 0xaf, 0x62, 0xbf, 0x65, 0x20, 0xa7, 0xe9, 0x76, 0xe8, 0x2d, 0x3d, 0xa9, 0x52, 0x9f, 0xe3, - 0x55, 0x45, 0x06, 0x50, 0x24, 0x81, 0x3e, 0xca, 0xc3, 0x09, 0x1e, 0x3d, 0x0d, 0x71, 0xaf, 0xdd, - 0x4e, 0x70, 0x81, 0xca, 0xe4, 0xdd, 0xc5, 0xad, 0x89, 0x85, 0x29, 0xe3, 0xc3, 0xb1, 0x65, 0x77, - 0x63, 0xb7, 0xa5, 0xc5, 0x82, 0xb9, 0x55, 0x05, 0x93, 0xd5, 0x15, 0x3d, 0x2f, 0x6f, 0x33, 0xfb, - 0x29, 0x10, 0x73, 0x4b, 0x4b, 0x25, 0x17, 0x37, 0x47, 0x6f, 0x9d, 0x70, 0x5b, 0x19, 0x4c, 0x98, - 0x45, 0xc8, 0x80, 0x28, 0x66, 0x06, 0xbf, 0xd1, 0x57, 0xb3, 0xeb, 0x76, 0xda, 0x31, 0xf6, 0xd5, - 0x29, 0xe9, 0x26, 0xb2, 0x49, 0x77, 0xff, 0x72, 0x91, 0x6b, 0x4e, 0x2c, 0x49, 0xab, 0x8b, 0xe7, - 0x86, 0x23, 0xe0, 0x97, 0xe5, 0x68, 0x93, 0x5d, 0xb1, 0xa6, 0x6f, 0x65, 0x49, 0x7e, 0x5b, 0xab, - 0x27, 0x74, 0xf8, 0x7f, 0xa6, 0x0e, 0x2f, 0x49, 0xbf, 0x09, 0xc8, 0x93, 0xb7, 0xe5, 0x6a, 0x36, - 0x99, 0x4c, 0x89, 0x42, 0x43, 0xac, 0xea, 0x38, 0x1d, 0x09, 0x5a, 0xb6, 0x28, 0xff, 0x49, 0x2d, - 0x91, 0xd4, 0x96, 0x0d, 0x15, 0x61, 0x59, 0x63, 0xa1, 0x61, 0x43, 0xf4, 0xf6, 0x38, 0x26, 0x30, - 0x91, 0xf4, 0x28, 0x89, 0xbb, 0x27, 0x32, 0x5e, 0xe7, 0x07, 0x50, 0xe3, 0x4f, 0x50, 0x23, 0xa3, - 0x42, 0x2d, 0x94, 0x49, 0xda, 0xb0, 0x25, 0x6e, 0x2b, 0xa9, 0xba, 0x67, 0x02, 0x84, 0xa2, 0xf4, - 0x10, 0x1a, 0xf7, 0xd4, 0x6a, 0x38, 0x9d, 0xf5, 0x60, 0xd4, 0xc5, 0x93, 0xd1, 0x68, 0xa0, 0xbc, - 0x8c, 0xc4, 0x1a, 0x49, 0x83, 0x12, 0x9b, 0xa6, 0x67, 0x1b, 0xee, 0x89, 0xa8, 0x9c, 0xce, 0x16, - 0xd2, 0xd4, 0xfa, 0x0b, 0xe5, 0x0e, 0x0d, 0x10, 0x23, 0xe9, 0xa2, 0xea, 0xe8, 0x34, 0x05, 0x1f, - 0xd8, 0xee, 0x37, 0x06, 0xe6, 0x8c, 0x95, 0xe7, 0x02, 0xc8, 0x4d, 0xc6, 0xdc, 0xd7, 0xca, 0xbf, - 0xd3, 0x4f, 0x62, 0x22, 0xab, 0xfe, 0x63, 0xa6, 0xcc, 0xe9, 0x0f, 0xba, 0x3b, 0xd4, 0x95, 0x6d, - 0x51, 0xac, 0x7e, 0xf3, 0xcd, 0x91, 0x86, 0x86, 0x37, 0x31, 0xea, 0xb9, 0xad, 0xef, 0x03, 0xcb, - 0x33, 0x32, 0x78, 0xb7, 0x75, 0xfc, 0xaf, 0xab, 0xbb, 0x5f, 0xc3, 0x93, 0x6b, 0xcc, 0x4b, 0x1e, - 0xbf, 0x87, 0xbc, 0xf8, 0xf1, 0x56, 0x4c, 0xa5, 0xda, 0xb2, 0x6b, 0xae, 0xfc, 0xc6, 0x3a, 0xcd, - 0x15, 0x4b, 0xeb, 0x78, 0x2e, 0xe4, 0x6c, 0x7b, 0x08, 0x10, 0x77, 0x49, 0x7b, 0xd4, 0x17, 0xa5, - 0x6f, 0x2b, 0x42, 0x4c, 0xc6, 0xde, 0xd1, 0x9b, 0xfd, 0xe7, 0xb7, 0x2e, 0x56, 0xfd, 0xcf, 0x6f, - 0xd5, 0x6f, 0xa9, 0x44, 0x36, 0x57, 0x76, 0x1b, 0x16, 0x7b, 0x0a, 0x30, 0x6c, 0xd8, 0x99, 0xe9, - 0x29, 0x6a, 0x32, 0xf5, 0x8d, 0x8a, 0x85, 0xd1, 0x4d, 0x33, 0x5c, 0x05, 0xf4, 0xd5, 0x2a, 0x8d, - 0x74, 0x0b, 0xc5, 0x83, 0xcf, 0xca, 0x85, 0xee, 0x6c, 0x7d, 0x43, 0xc3, 0x31, 0x1b, 0x84, 0x0f, - 0xfa, 0xce, 0x48, 0xd5, 0xd0, 0x99, 0x22, 0x3d, 0x56, 0xbb, 0xc9, 0x48, 0x5e, 0x4b, 0xed, 0x83, - 0xd4, 0x4e, 0x6f, 0xbf, 0xa0, 0x7c, 0x8d, 0xa5, 0x26, 0x6a, 0x4f, 0x4d, 0xdb, 0x34, 0x3d, 0x25, - 0xfe, 0x4b, 0xa0, 0x7e, 0xc5, 0x34, 0xcd, 0xb2, 0x6d, 0x55, 0x12, 0x85, 0xee, 0xce, 0x10, 0x78, - 0x65, 0xa4, 0x9d, 0x5b, 0x13, 0xcd, 0xee, 0xa2, 0x14, 0x31, 0xc1, 0xa7, 0x47, 0x34, 0x3d, 0x19, - 0x2d, 0x7e, 0x43, 0x0b, 0x03, 0x33, 0xc6, 0xcc, 0x48, 0x1e, 0xc6, 0xe6, 0x12, 0x70, 0x75, 0xc1, - 0x5a, 0x83, 0xee, 0x5e, 0x76, 0xbc, 0xce, 0x48, 0xda, 0xb4, 0x2d, 0x65, 0xb8, 0x1d, 0x2e, 0x79, - 0xd9, 0xba, 0x6e, 0x9c, 0x89, 0x52, 0xc2, 0xcd, 0xa5, 0x4b, 0x32, 0xc9, 0xad, 0x47, 0xb7, 0x89, - 0x45, 0xb4, 0xec, 0x01, 0x33, 0x1d, 0xe2, 0x72, 0x14, 0xdc, 0xeb, 0x27, 0xa2, 0xa4, 0x45, 0x61, - 0x69, 0x00, 0x91, 0xc2, 0xbe, 0x2d, 0xec, 0x5f, 0xb6, 0x10, 0x5c, 0xba, 0xa4, 0x7b, 0xa6, 0x1d, - 0x2d, 0x76, 0xd6, 0x68, 0x0a, 0xc0, 0x46, 0xf0, 0x1a, 0x15, 0x16, 0x1b, 0x2a, 0x9d, 0x85, 0x41, - 0xa9, 0x1a, 0xb1, 0xdf, 0x6c, 0xd8, 0xfb, 0xb1, 0x00, 0x30, 0xfc, 0x51, 0x0a, 0xcf, 0xf0, 0xe9, - 0xa3, 0x93, 0x42, 0x10, 0x11, 0xd1, 0xdc, 0xe2, 0x06, 0xd1, 0xe5, 0x4f, 0x56, 0x30, 0xc3, 0x0a, - 0xc1, 0x42, 0xff, 0xd7, 0x22, 0xde, 0xf7, 0xf4, 0xb1, 0x6a, 0x19, 0xfa, 0x90, 0x82, 0x4f, 0xd2, - 0x78, 0xe9, 0x9e, 0x9e, 0x29, 0xa0, 0xa7, 0x8a, 0x45, 0xe0, 0x91, 0x4e, 0x9f, 0x36, 0x51, 0x4d, - 0xf4, 0x04, 0xa7, 0xb5, 0x81, 0xd0, 0xe9, 0x2a, 0xfb, 0xc5, 0x78, 0xe2, 0xcb, 0x38, 0xbc, 0xb9, - 0x53, 0xb1, 0x9b, 0x4a, 0xd1, 0x68, 0x7b, 0x0a, 0x8a, 0x2c, 0xf1, 0xcf, 0x8f, 0x44, 0x8e, 0x49, - 0x32, 0x8f, 0x22, 0xdf, 0x87, 0xc2, 0x35, 0x9a, 0xb9, 0x35, 0x5b, 0x2a, 0x9e, 0xe4, 0xf5, 0xd1, - 0xdd, 0x20, 0x14, 0xb9, 0x86, 0x77, 0x3b, 0xf1, 0x18, 0xaf, 0x77, 0x4d, 0x9c, 0x67, 0xbe, 0x54, - 0xba, 0xb7, 0xfd, 0x7d, 0x30, 0x74, 0xfe, 0x9a, 0xe0, 0xaf, 0x09, 0x85, 0xef, 0x06, 0xb9, 0x57, - 0x82, 0x50, 0xfc, 0xea, 0xbb, 0xfa, 0x83, 0x57, 0x38, 0x95, 0xa2, 0x6d, 0x6a, 0x81, 0xc6, 0xa0, - 0xa7, 0x61, 0x95, 0xd4, 0xa8, 0x47, 0x42, 0xd0, 0x08, 0x95, 0xfb, 0xb8, 0x36, 0xb5, 0x14, 0x10, - 0x96, 0xb6, 0x85, 0x0d, 0x63, 0x16, 0xb6, 0xaf, 0x25, 0x69, 0x4b, 0x0a, 0x33, 0x1a, 0x43, 0xfb, - 0x62, 0x4a, 0x4b, 0x4a, 0x46, 0xfd, 0xcb, 0x17, 0x05, 0x0f, 0x60, 0xe2, 0x4f, 0xcb, 0x7c, 0x65, - 0xed, 0xf7, 0x6f, 0xbc, 0xca, 0xa0, 0xad, 0xaf, 0xf3, 0x7e, 0xc6, 0x90, 0x8d, 0x1e, 0x56, 0x62, - 0xf0, 0x89, 0x69, 0xb1, 0x66, 0x4f, 0x54, 0x94, 0xbc, 0x74, 0x14, 0x25, 0x93, 0xb3, 0x0e, 0xfa, - 0xba, 0x64, 0xab, 0x6a, 0xe8, 0x48, 0xb3, 0xd6, 0xb6, 0x88, 0xf2, 0x52, 0xa3, 0x79, 0x39, 0x3e, - 0xaf, 0x1f, 0xce, 0xcb, 0xf3, 0x79, 0x5a, 0x52, 0x9c, 0x23, 0xfc, 0x96, 0xab, 0x9b, 0xb8, 0x52, - 0x34, 0xe8, 0x00, 0xda, 0x3c, 0x88, 0x49, 0xd5, 0xb6, 0x17, 0x79, 0x14, 0x17, 0x01, 0x49, 0x80, - 0xed, 0x4e, 0xf8, 0x63, 0xa6, 0xa7, 0x0d, 0x7d, 0x1b, 0x0f, 0x8e, 0x45, 0xa6, 0x0a, 0xfb, 0x12, - 0xb9, 0x36, 0x87, 0x02, 0x61, 0x05, 0x07, 0x50, 0x77, 0x39, 0xb1, 0x12, 0x98, 0x97, 0x8c, 0xf0, - 0xb4, 0xe5, 0x71, 0xac, 0xf8, 0xe8, 0x49, 0xac, 0x5d, 0xfa, 0x0d, 0x93, 0xd5, 0x91, 0xf8, 0x48, - 0x1f, 0xaf, 0x05, 0xb0, 0x8e, 0xfe, 0x4a, 0x48, 0xb1, 0x25, 0xdf, 0xc0, 0xc3, 0x61, 0x42, 0xaf, - 0x30, 0xbc, 0xcc, 0xd6, 0x87, 0x91, 0x94, 0xf8, 0xbf, 0xa0, 0x31, 0xd6, 0x75, 0xb6, 0x6d, 0x4a, - 0x5d, 0xfa, 0x64, 0x98, 0x92, 0xe9, 0x25, 0x3d, 0x4a, 0x7d, 0x37, 0xed, 0x51, 0x1a, 0xd5, 0x41, - 0x1e, 0x03, 0x92, 0xf8, 0x3e, 0x9c, 0xfc, 0x39, 0x1c, 0x48, 0x3d, 0x98, 0x1a, 0x16, 0x0d, 0x27, - 0x14, 0x03, 0xc7, 0xfd, 0x02, 0x2c, 0xde, 0x88, 0xc5, 0xab, 0x88, 0x44, 0x40, 0x2d, 0xf1, 0x0c, - 0x77, 0x4c, 0xb1, 0xab, 0x5a, 0xec, 0x90, 0x42, 0x9c, 0x2f, 0x09, 0xda, 0xc8, 0xa1, 0x10, 0xda, - 0x89, 0xa0, 0x0c, 0x1a, 0xf4, 0xf0, 0x85, 0xa3, 0x85, 0x02, 0xdb, 0xa2, 0x1b, 0xcf, 0x86, 0xce, - 0xec, 0xd6, 0x07, 0xb1, 0x8b, 0xbc, 0x78, 0x36, 0xbf, 0xa4, 0xf1, 0x07, 0xa0, 0x9f, 0xa9, 0xa8, - 0xdf, 0x7c, 0x0c, 0xe3, 0x30, 0xfa, 0x4d, 0xc3, 0x33, 0x95, 0x87, 0x70, 0xa8, 0xfe, 0x5d, 0x00, - 0x07, 0x28, 0xda, 0x0d, 0x91, 0xc5, 0x8c, 0xd0, 0xcd, 0xe7, 0x73, 0x98, 0xfe, 0x04, 0x4e, 0x1f, - 0x17, 0x51, 0xfa, 0x18, 0xc2, 0xe9, 0xe3, 0xdf, 0x85, 0x78, 0xf8, 0x4f, 0xa1, 0xf4, 0x71, 0x01, - 0xa5, 0x21, 0x08, 0x87, 0x7f, 0x17, 0x42, 0xc6, 0x0f, 0xdb, 0x61, 0x7e, 0x42, 0x7b, 0x54, 0x4c, - 0xbc, 0x71, 0x4d, 0xc3, 0x85, 0xc2, 0x33, 0x7e, 0x1b, 0xd1, 0xe4, 0x22, 0xb7, 0x6a, 0x6d, 0x6d, - 0xc3, 0x76, 0x63, 0x3a, 0xed, 0x51, 0x06, 0x29, 0x64, 0x77, 0x85, 0xfd, 0x07, 0x2a, 0x20, 0xf2, - 0x0b, 0x8d, 0x68, 0x1b, 0x26, 0x74, 0xcb, 0xce, 0xe5, 0xc2, 0xa9, 0xa1, 0xee, 0xb2, 0xb9, 0xe8, - 0x08, 0xb3, 0x39, 0x8f, 0xe1, 0x7c, 0x77, 0x03, 0x8b, 0x05, 0x9f, 0x40, 0xfc, 0x63, 0x06, 0x9c, - 0x18, 0x86, 0x9d, 0xcd, 0x6d, 0xe3, 0x59, 0x39, 0xbb, 0xad, 0xca, 0x46, 0x4e, 0xe3, 0xa5, 0xdb, - 0xdf, 0x33, 0xac, 0x4e, 0xb4, 0x6e, 0x16, 0xeb, 0x66, 0x97, 0xd5, 0xdd, 0x51, 0xac, 0x65, 0x15, - 0x73, 0x58, 0x31, 0xb7, 0xac, 0x62, 0xc3, 0xea, 0x2c, 0xab, 0x98, 0xc7, 0x8a, 0xf9, 0x65, 0x15, - 0x9b, 0xf4, 0xce, 0x76, 0x50, 0x37, 0xc3, 0xb2, 0x23, 0xbc, 0xe8, 0xc8, 0x9d, 0x1f, 0x3a, 0x1b, - 0xb6, 0x8e, 0x68, 0x53, 0x63, 0xa7, 0xa2, 0x85, 0x62, 0x89, 0x60, 0xab, 0xc3, 0xbf, 0x3b, 0x0f, - 0x76, 0x74, 0xed, 0xb6, 0xd4, 0x0f, 0x67, 0xc1, 0x56, 0x17, 0x11, 0x49, 0x14, 0xa8, 0xa9, 0x7f, - 0x3c, 0x0b, 0x31, 0x75, 0xef, 0xc9, 0xbd, 0xaa, 0x69, 0xd7, 0x46, 0xe7, 0xe5, 0xd1, 0x18, 0x7d, - 0x80, 0x99, 0x5a, 0xd4, 0x4c, 0x07, 0xc3, 0xa0, 0x86, 0x26, 0x68, 0x5f, 0xc3, 0x73, 0xa1, 0x21, - 0xee, 0xe1, 0xa4, 0xbf, 0x2d, 0x86, 0xfa, 0x80, 0x5c, 0x63, 0x3b, 0xd8, 0xd8, 0x17, 0x36, 0x40, - 0x97, 0xee, 0xf1, 0x9e, 0x33, 0xdd, 0x42, 0xe0, 0x01, 0x4d, 0x54, 0x31, 0x2b, 0x99, 0xc5, 0xe1, - 0xde, 0xfa, 0x78, 0x05, 0xdb, 0x44, 0x0b, 0x63, 0x16, 0xe5, 0x11, 0x7e, 0x09, 0x43, 0xca, 0xdf, - 0x5b, 0xc3, 0x91, 0xe1, 0x2f, 0x98, 0x27, 0xb1, 0xa3, 0xbd, 0xa9, 0x3f, 0x8b, 0x4b, 0x2c, 0x7e, - 0xd6, 0x7b, 0x08, 0x5c, 0xfa, 0x1e, 0xd8, 0xfb, 0xd0, 0x01, 0x8e, 0xca, 0x89, 0xf0, 0xeb, 0xfa, - 0x45, 0x26, 0x92, 0xb5, 0x20, 0x46, 0x2f, 0x6d, 0xbc, 0x46, 0x45, 0x03, 0x1c, 0x0b, 0xd4, 0xde, - 0xb6, 0x5d, 0x33, 0x0d, 0xfb, 0x85, 0x66, 0x41, 0x72, 0x02, 0x9c, 0x17, 0xb2, 0x78, 0x43, 0x0c, - 0x03, 0x30, 0xe2, 0x4f, 0x3e, 0x57, 0x14, 0xe7, 0x54, 0x90, 0xf8, 0x95, 0x02, 0x71, 0x49, 0xdf, - 0x86, 0x7f, 0x55, 0x2f, 0x76, 0x9a, 0x80, 0xc2, 0x59, 0xea, 0x5b, 0xc8, 0x94, 0x68, 0x98, 0x8c, - 0xd2, 0x3f, 0x84, 0x8d, 0x25, 0xe3, 0x16, 0x8d, 0x6e, 0x6b, 0x4a, 0x9f, 0x9e, 0x04, 0xe3, 0x81, - 0xf7, 0xd6, 0xb7, 0xd4, 0xaf, 0x28, 0x12, 0xfa, 0x6e, 0x10, 0x2d, 0xef, 0x2c, 0x9c, 0xaa, 0x9d, - 0xb0, 0x43, 0xcf, 0x6b, 0xbc, 0x51, 0x15, 0x23, 0xc5, 0xe9, 0x64, 0xea, 0x84, 0xe5, 0xe4, 0x45, - 0x07, 0xba, 0x6f, 0x08, 0xe1, 0x37, 0x17, 0x1d, 0xb9, 0xcd, 0xf2, 0x1f, 0xb3, 0x16, 0x35, 0x1e, - 0xa6, 0xf1, 0xd3, 0x25, 0xcd, 0x81, 0x62, 0x35, 0xd1, 0x41, 0x96, 0x52, 0x55, 0x4a, 0x6c, 0x88, - 0xe9, 0x8e, 0x9b, 0xd4, 0x40, 0x6f, 0xcf, 0xa4, 0x87, 0x8e, 0x5f, 0x91, 0x71, 0x6f, 0x74, 0xbc, - 0x10, 0x7a, 0x94, 0x2c, 0x42, 0xa0, 0x7a, 0xf2, 0x61, 0x2f, 0x59, 0x0b, 0x0b, 0x6e, 0x07, 0x96, - 0x49, 0xe7, 0x46, 0x92, 0x03, 0x70, 0x1a, 0x5e, 0xc8, 0x94, 0x15, 0x0d, 0x59, 0x4b, 0x1b, 0xca, - 0x06, 0x0d, 0xed, 0x7c, 0xa2, 0xa1, 0xfe, 0xd2, 0x86, 0x72, 0x41, 0x43, 0xcd, 0x4f, 0x34, 0xa4, - 0x2d, 0x6d, 0x28, 0x1f, 0x34, 0xb4, 0xeb, 0x37, 0xc4, 0x31, 0x09, 0xe1, 0xd7, 0xe2, 0x84, 0xc7, - 0x18, 0xd2, 0x5d, 0xc2, 0x5f, 0x6e, 0x4a, 0xd7, 0xe2, 0xcd, 0xe8, 0x71, 0xad, 0xc7, 0x5a, 0xd1, - 0xb5, 0x58, 0x0b, 0xba, 0x16, 0xb5, 0x9e, 0xc3, 0x42, 0x88, 0x5e, 0x63, 0xb4, 0xa8, 0xf7, 0x7e, - 0x94, 0xcf, 0xa9, 0x7a, 0x08, 0x70, 0x78, 0x65, 0xb0, 0x2c, 0x04, 0xa3, 0xf3, 0x68, 0xc8, 0x99, - 0x3a, 0xa1, 0x1a, 0x4e, 0xec, 0xf7, 0x79, 0x82, 0xcf, 0xf3, 0xfc, 0x31, 0x13, 0x89, 0x6d, 0x96, - 0x73, 0xa5, 0x92, 0x58, 0xaf, 0xdb, 0x54, 0x6d, 0xdd, 0xce, 0xe7, 0xaa, 0xa5, 0xc2, 0x9c, 0x17, - 0xaf, 0xdd, 0x35, 0x8b, 0x1c, 0x34, 0x14, 0xf5, 0x8e, 0x7e, 0x03, 0x48, 0x40, 0x6e, 0x94, 0x4e, - 0xa7, 0xc5, 0x8c, 0x6b, 0x36, 0xf2, 0x11, 0x05, 0x9a, 0x21, 0x8d, 0x31, 0xe8, 0x00, 0x41, 0xa3, - 0x0b, 0x1a, 0x40, 0xee, 0x74, 0xb7, 0xfe, 0x98, 0x8d, 0xb6, 0xc5, 0x16, 0x8a, 0xd8, 0xc2, 0x03, - 0x7a, 0x90, 0xd0, 0xa7, 0xd3, 0xbd, 0x5d, 0x91, 0x79, 0xab, 0x71, 0xa5, 0x78, 0x1c, 0x01, 0xbc, - 0xdb, 0xe2, 0x3d, 0x5a, 0x92, 0x68, 0x1d, 0xe0, 0x11, 0x50, 0x79, 0xa1, 0x00, 0x8b, 0xe7, 0x03, - 0xea, 0xa4, 0x57, 0x28, 0xb6, 0x5d, 0x94, 0xc8, 0x2f, 0x7a, 0x3d, 0xbc, 0x0d, 0xc4, 0x79, 0xc8, - 0x71, 0x20, 0xba, 0x08, 0xe6, 0x18, 0x6e, 0x98, 0xcf, 0xaf, 0x8c, 0x80, 0xf8, 0xc7, 0x2c, 0x31, - 0xda, 0x1e, 0x4e, 0xaa, 0x9e, 0x3d, 0x3a, 0xb9, 0x91, 0x9d, 0x73, 0xca, 0x4a, 0x67, 0xbe, 0xa0, - 0xf4, 0x9c, 0x12, 0x3d, 0x50, 0x87, 0xfc, 0x6f, 0x2c, 0x41, 0x67, 0xec, 0x1b, 0x4b, 0x1e, 0x57, - 0xe7, 0x46, 0xf1, 0x01, 0x80, 0xe4, 0x03, 0x00, 0x43, 0xf0, 0xf1, 0xc0, 0x75, 0x37, 0x12, 0x51, - 0x9c, 0x76, 0xaa, 0x72, 0xf2, 0x1f, 0x00, 0x59, 0xc0, 0x5e, 0x3d, 0x9f, 0x26, 0xa4, 0xd9, 0x0d, - 0x45, 0x53, 0xfb, 0x7a, 0x15, 0x79, 0xba, 0xe5, 0xa0, 0x23, 0x13, 0x6e, 0x87, 0x58, 0x68, 0x9c, - 0xa2, 0x66, 0x48, 0x31, 0xd5, 0xc3, 0xa4, 0xc5, 0x91, 0xd2, 0x96, 0x98, 0x9d, 0x78, 0x61, 0x53, - 0x37, 0x7a, 0xd1, 0x91, 0x73, 0x54, 0x6c, 0xf4, 0x56, 0x8d, 0x83, 0x33, 0x3e, 0xc2, 0xf2, 0x84, - 0x1e, 0x5c, 0x2b, 0xe7, 0x16, 0x23, 0xd0, 0x47, 0xcf, 0xd3, 0x12, 0x00, 0x5b, 0xc0, 0x91, 0x78, - 0x48, 0x8d, 0x9e, 0x1e, 0xd1, 0x3d, 0xa2, 0x5d, 0xd0, 0x2f, 0x1f, 0xa6, 0xb0, 0x0f, 0x66, 0xef, - 0x5b, 0x4a, 0x4b, 0x7d, 0xb3, 0x1f, 0x57, 0xce, 0xdf, 0xb7, 0x54, 0x62, 0x38, 0xd8, 0xc8, 0xa2, - 0xed, 0xd1, 0x1b, 0xde, 0xb7, 0x94, 0x89, 0x6f, 0x31, 0x83, 0xa3, 0x0d, 0x2e, 0x99, 0x25, 0x37, - 0x6f, 0x2b, 0x00, 0xf5, 0x03, 0xc0, 0xc8, 0x47, 0x80, 0x0d, 0x07, 0x21, 0xa0, 0x12, 0xfd, 0x45, - 0x7a, 0x32, 0xab, 0x68, 0x2b, 0xff, 0xf7, 0x81, 0x15, 0x96, 0xd3, 0x12, 0x6c, 0xff, 0x43, 0x8f, - 0x88, 0x06, 0xf0, 0xe0, 0x4f, 0x80, 0x48, 0x99, 0xac, 0x3f, 0xb3, 0x07, 0x18, 0x17, 0x0c, 0x36, - 0x6d, 0x7f, 0xfc, 0x2d, 0xbc, 0x80, 0xc0, 0xbd, 0x87, 0xa7, 0x4e, 0xfc, 0xd4, 0xda, 0xeb, 0x5b, - 0x66, 0x2c, 0x92, 0xb2, 0xbc, 0x3d, 0x83, 0xa3, 0x4a, 0x28, 0xff, 0xbf, 0x80, 0x23, 0xd8, 0x66, - 0x67, 0xe5, 0xd4, 0x45, 0xa1, 0x82, 0xf2, 0xff, 0x08, 0x54, 0x9f, 0x30, 0xd2, 0x33, 0x93, 0xd0, - 0x32, 0xd9, 0xd5, 0xff, 0x0e, 0x93, 0x1f, 0x49, 0x97, 0xee, 0xb2, 0xf1, 0x1f, 0x7f, 0xe3, 0xe6, - 0xcb, 0xb3, 0xad, 0x86, 0xb7, 0xd4, 0x81, 0xd0, 0x0e, 0xeb, 0x42, 0x40, 0x91, 0xa2, 0xbf, 0xc9, - 0x27, 0xe8, 0xd6, 0xd0, 0x4b, 0xc2, 0xc3, 0xfa, 0x7a, 0x7f, 0xc3, 0xdc, 0xca, 0xae, 0xaf, 0x77, - 0x37, 0x3a, 0x5b, 0xd9, 0xed, 0x36, 0x25, 0x9c, 0x04, 0x49, 0x37, 0x46, 0x5d, 0xd5, 0xb8, 0x26, - 0xec, 0x2e, 0xc6, 0xfa, 0x7a, 0x24, 0x81, 0x9a, 0xdb, 0xc4, 0xea, 0x11, 0x6e, 0xe4, 0x2b, 0x4c, - 0x02, 0x21, 0x00, 0xda, 0xce, 0x50, 0x74, 0xfb, 0xa6, 0x21, 0x77, 0x4d, 0x83, 0x9e, 0x46, 0x30, - 0xab, 0x81, 0x1b, 0x60, 0x0c, 0xfb, 0x06, 0xee, 0xb9, 0x5a, 0x03, 0xa1, 0x0b, 0xc4, 0x31, 0x23, - 0xba, 0xdd, 0x8d, 0xe9, 0xaf, 0xaa, 0x6f, 0x28, 0x7e, 0x3b, 0x66, 0x48, 0x03, 0x81, 0xd5, 0xb0, - 0x85, 0x27, 0x3a, 0x1f, 0xb7, 0x1c, 0x67, 0xf1, 0x09, 0xb5, 0x1c, 0xb1, 0xf9, 0xd0, 0x96, 0x91, - 0x0f, 0xfe, 0x75, 0x0d, 0xa7, 0x8b, 0x61, 0x42, 0x51, 0x84, 0x5f, 0x4e, 0x3d, 0x2e, 0x64, 0x16, - 0x13, 0xb9, 0xfc, 0xcf, 0xde, 0x9a, 0xa0, 0x93, 0xba, 0x51, 0xca, 0xf0, 0xf2, 0x0e, 0x77, 0x12, - 0x64, 0x7e, 0x8a, 0xc8, 0x42, 0x9f, 0x47, 0x0c, 0x7c, 0x82, 0x3e, 0x03, 0x4a, 0x37, 0x04, 0xca, - 0x2e, 0x75, 0xc0, 0xe1, 0x00, 0xe8, 0xf2, 0xca, 0xe0, 0xd6, 0x5f, 0xfc, 0xda, 0x58, 0x48, 0x1f, - 0x5e, 0x94, 0x1d, 0x81, 0xe4, 0x2c, 0x64, 0x6b, 0xcc, 0x1f, 0x74, 0xee, 0xd9, 0xbd, 0x17, 0x1d, - 0x2a, 0x5c, 0x13, 0x3b, 0x6f, 0x41, 0xdf, 0xaa, 0xb3, 0xf3, 0xf0, 0x5a, 0x70, 0xb3, 0x4b, 0x73, - 0x0f, 0x69, 0x41, 0xad, 0xc4, 0xab, 0xa9, 0x49, 0xcf, 0x8b, 0xd4, 0x7b, 0xf7, 0x42, 0x69, 0xa3, - 0x3f, 0xb3, 0x6f, 0xf9, 0x57, 0xea, 0x72, 0x4d, 0xf9, 0x5e, 0xc7, 0x41, 0xd6, 0x94, 0x54, 0x2a, - 0x19, 0x18, 0xdf, 0x15, 0x6c, 0x20, 0x60, 0x20, 0x8a, 0x7f, 0x9b, 0x88, 0x9a, 0x7a, 0xa9, 0xbf, - 0x3e, 0x5d, 0x0b, 0x0a, 0xda, 0x8e, 0x7f, 0x25, 0xfd, 0xce, 0xfb, 0x16, 0xd6, 0x5d, 0x72, 0x8a, - 0xfc, 0x85, 0xaf, 0x07, 0x80, 0xfd, 0xf2, 0xa1, 0x82, 0x95, 0xfb, 0x39, 0xe0, 0xb3, 0x49, 0x76, - 0x9d, 0xdb, 0x3d, 0x80, 0xf8, 0x9e, 0x0b, 0x8a, 0xf6, 0xbb, 0xa0, 0x96, 0xc2, 0x48, 0x96, 0xf5, - 0x0e, 0xdc, 0x92, 0x0b, 0x09, 0xe4, 0x55, 0x92, 0xd1, 0xf6, 0xed, 0xba, 0xd9, 0x26, 0x7d, 0x30, - 0xfa, 0xa6, 0xbc, 0xf4, 0x94, 0x5b, 0xfa, 0x42, 0xed, 0xca, 0x5f, 0x34, 0x76, 0xeb, 0x33, 0xbc, - 0x4f, 0xf2, 0xcd, 0xb3, 0x91, 0x22, 0x48, 0x73, 0xdb, 0xbb, 0x46, 0x91, 0x84, 0x8d, 0x34, 0xb5, - 0xac, 0x14, 0xf1, 0x4b, 0x7d, 0xf7, 0xe4, 0xbc, 0x00, 0x22, 0x6b, 0x71, 0x74, 0x71, 0x47, 0xf9, - 0x7e, 0x98, 0xf1, 0xa8, 0xfb, 0x83, 0x87, 0x32, 0x60, 0x8c, 0xe1, 0x40, 0x2f, 0x52, 0xf8, 0xa6, - 0x26, 0xba, 0x34, 0x98, 0x76, 0x12, 0xed, 0x03, 0xf8, 0xe0, 0x7b, 0xc3, 0x7b, 0x37, 0x92, 0xbf, - 0x9d, 0xd2, 0xd8, 0xdf, 0x55, 0x16, 0x16, 0x3c, 0xc6, 0x6e, 0x65, 0xf7, 0x79, 0x9e, 0x13, 0x39, - 0xcd, 0x65, 0x81, 0xc3, 0xff, 0xf3, 0x5b, 0xd5, 0xc7, 0x01, 0xf7, 0x49, 0xef, 0x18, 0x63, 0x56, - 0x60, 0x83, 0xda, 0xba, 0x75, 0xdb, 0xec, 0xfa, 0xa6, 0xa7, 0x6f, 0x35, 0xff, 0x98, 0x9e, 0xfa, - 0x33, 0x0a, 0x0c, 0x64, 0x7a, 0x48, 0x45, 0xbd, 0x11, 0xc3, 0x6d, 0xfd, 0x31, 0x73, 0xd2, 0x6a, - 0x77, 0x8e, 0x8e, 0x60, 0x72, 0xbd, 0x8e, 0xcf, 0xdb, 0xa2, 0x6b, 0x97, 0x10, 0xab, 0x4e, 0x1a, - 0x6f, 0x52, 0x80, 0xbe, 0xe4, 0x86, 0x36, 0x4f, 0x61, 0x7e, 0x4a, 0x64, 0x7e, 0xce, 0x73, 0xbf, - 0xcb, 0x5f, 0x35, 0x42, 0x9d, 0xb8, 0x43, 0x36, 0x2f, 0xd7, 0x9b, 0xdb, 0xad, 0x19, 0xf2, 0x17, - 0x8c, 0xe4, 0x2c, 0x9b, 0x3b, 0xce, 0x35, 0x31, 0xa6, 0x2c, 0x47, 0x7d, 0x8b, 0x27, 0x7d, 0xbe, - 0x4f, 0xad, 0xeb, 0xf4, 0x49, 0x7d, 0x70, 0x25, 0x7a, 0xde, 0x47, 0x3c, 0xdf, 0x54, 0xff, 0x54, - 0x12, 0xef, 0x19, 0x38, 0xdf, 0x89, 0x3b, 0xa9, 0x35, 0x07, 0xd6, 0x3c, 0xf9, 0xe1, 0xfc, 0xac, - 0xcf, 0xd4, 0x6e, 0x15, 0x1f, 0xd0, 0x7d, 0x01, 0x35, 0x43, 0xf6, 0x92, 0xfd, 0x39, 0xc7, 0x36, - 0x78, 0x07, 0x51, 0xea, 0xde, 0x44, 0xaf, 0xa3, 0x6b, 0x04, 0x03, 0x4f, 0x29, 0x16, 0x49, 0x38, - 0x34, 0x31, 0x89, 0xbe, 0x0a, 0x9e, 0xf3, 0x2b, 0xb6, 0x27, 0xb3, 0x96, 0xc4, 0x16, 0xde, 0xde, - 0x15, 0xe7, 0x01, 0x10, 0xf4, 0x4e, 0x25, 0xf1, 0xe8, 0x09, 0x2f, 0xe6, 0x4b, 0x3a, 0xfc, 0xd0, - 0x6b, 0xdd, 0xa2, 0x20, 0xa2, 0xc7, 0x87, 0xc8, 0x62, 0x36, 0xd0, 0xce, 0x54, 0xbd, 0x4b, 0xa6, - 0x17, 0xbd, 0x84, 0x78, 0xdd, 0xba, 0xdb, 0x15, 0x93, 0xdf, 0x65, 0xea, 0xbf, 0x18, 0x26, 0x5a, - 0xe6, 0x59, 0x8b, 0xde, 0xea, 0xcc, 0x09, 0xd7, 0x25, 0x5a, 0xec, 0x04, 0x23, 0x33, 0xb0, 0x54, - 0xbc, 0xb0, 0xc2, 0x32, 0xb6, 0xc5, 0x5a, 0xed, 0x4b, 0x2d, 0x0b, 0x62, 0x83, 0x97, 0x51, 0x63, - 0xd8, 0x41, 0x7f, 0x09, 0x6d, 0xfb, 0xc7, 0xcf, 0xaa, 0xe6, 0x5d, 0x48, 0xaf, 0xd1, 0xc0, 0x12, - 0x8e, 0x5b, 0xf1, 0x7b, 0xfe, 0xf7, 0x6f, 0xea, 0x53, 0x41, 0xdd, 0x55, 0xa1, 0x1c, 0xfe, 0x7a, - 0x45, 0x25, 0x7a, 0xf7, 0x31, 0xe6, 0xfe, 0x39, 0x8d, 0xbf, 0xe5, 0x45, 0x41, 0xa3, 0x17, 0xcf, - 0xdd, 0xfb, 0x14, 0xc1, 0xa7, 0xf1, 0xbc, 0xe3, 0x4f, 0xbf, 0xa3, 0x82, 0xdf, 0x51, 0xfe, 0xe7, - 0x36, 0xc8, 0xa1, 0x55, 0x7c, 0xa8, 0xc9, 0xf4, 0x5e, 0x57, 0x02, 0x3d, 0x55, 0x92, 0x92, 0xc2, - 0xf5, 0x95, 0x50, 0x00, 0x4f, 0x1d, 0x6d, 0x84, 0xe1, 0x5d, 0x41, 0x3e, 0x0c, 0x3a, 0x60, 0x9f, - 0xb4, 0xc3, 0xc2, 0x41, 0x7e, 0x96, 0xcf, 0xa7, 0x5f, 0x94, 0x0b, 0xe7, 0xe7, 0xb8, 0x7c, 0xf6, - 0x41, 0xb7, 0x70, 0xfe, 0x98, 0xcf, 0xa7, 0x9f, 0x53, 0x0b, 0xe7, 0xf7, 0xc2, 0xf9, 0xc5, 0x1a, - 0xfa, 0x06, 0x39, 0xa9, 0x7a, 0x1f, 0xaf, 0xe9, 0xe2, 0x8e, 0x02, 0x44, 0x8d, 0x8e, 0x7c, 0x87, - 0xce, 0x50, 0x4b, 0xe0, 0x67, 0x35, 0x25, 0x22, 0xe9, 0x12, 0x8a, 0x27, 0xfb, 0x0f, 0x80, 0x45, - 0xea, 0x49, 0xc0, 0x36, 0x44, 0xef, 0x93, 0x95, 0xfc, 0x7e, 0x18, 0xe3, 0x82, 0xeb, 0xbb, 0xce, - 0x33, 0x2f, 0x7b, 0xdf, 0x25, 0xdb, 0x7d, 0x0b, 0x79, 0x38, 0x67, 0x7f, 0x46, 0xc9, 0x97, 0xba, - 0xc0, 0xba, 0x65, 0x3d, 0x02, 0xfe, 0x21, 0x4b, 0x3e, 0x6f, 0xf8, 0xe9, 0xc5, 0xf4, 0x02, 0xb2, - 0xf4, 0x17, 0x12, 0xd2, 0x30, 0xad, 0x82, 0x9c, 0x26, 0x7e, 0x60, 0xee, 0x05, 0x02, 0x51, 0x42, - 0xbf, 0x65, 0x09, 0xbb, 0xa1, 0x63, 0xbc, 0xf4, 0x92, 0x7f, 0x45, 0x3f, 0x08, 0x69, 0xd2, 0x43, - 0x44, 0x57, 0x45, 0xfa, 0x63, 0x06, 0xad, 0x42, 0xd9, 0x4b, 0x48, 0x6c, 0xda, 0x76, 0x82, 0xba, - 0x65, 0xfb, 0xfe, 0x98, 0xbf, 0xe8, 0x36, 0xc8, 0x7c, 0x30, 0xbd, 0x8f, 0x06, 0x87, 0xf8, 0xce, - 0x17, 0x2f, 0xdc, 0x9f, 0x77, 0xdc, 0x0f, 0x53, 0xe2, 0x7b, 0xc0, 0xe1, 0x17, 0x57, 0xa9, 0xc5, - 0x25, 0xe9, 0xbb, 0x2b, 0x00, 0x63, 0x20, 0xdf, 0x17, 0x0b, 0x00, 0xbb, 0x4b, 0x79, 0x0e, 0x09, - 0x0b, 0xa1, 0x35, 0x00, 0x10, 0xdf, 0x51, 0xda, 0x07, 0x82, 0x8f, 0x88, 0x81, 0x57, 0x56, 0xd3, - 0xc6, 0xc8, 0x71, 0xa1, 0xfa, 0x00, 0x4d, 0xb0, 0x41, 0x6f, 0x10, 0x49, 0xfc, 0x6f, 0xc1, 0xfd, - 0xb8, 0x8d, 0x98, 0x42, 0x7e, 0xf0, 0xdf, 0xe2, 0xbf, 0x83, 0x35, 0xda, 0x26, 0x8f, 0x36, 0xce, - 0x57, 0xc2, 0x22, 0x5d, 0x4b, 0x99, 0xb8, 0x85, 0xfd, 0x88, 0x1b, 0x31, 0xf7, 0xd7, 0xc5, 0xaf, - 0xee, 0xf0, 0x84, 0x34, 0x75, 0x61, 0x4e, 0xd6, 0x78, 0x2f, 0x7a, 0x81, 0x04, 0x5e, 0x1b, 0x04, - 0x16, 0x70, 0xa8, 0x7a, 0x42, 0x4c, 0xfb, 0x30, 0x26, 0x31, 0x02, 0x9c, 0x17, 0x77, 0xae, 0x1e, - 0x99, 0x5d, 0x3f, 0xc2, 0x9f, 0xda, 0x4d, 0x86, 0xe3, 0x88, 0xf0, 0xc5, 0x98, 0x93, 0x39, 0x1f, - 0xdb, 0x36, 0xb8, 0xa6, 0x1b, 0xa4, 0x21, 0x3f, 0x43, 0x17, 0x6f, 0xc7, 0xbb, 0x52, 0xe8, 0xca, - 0x00, 0x55, 0x81, 0x6e, 0xf8, 0xb5, 0x6c, 0xdd, 0xe7, 0x33, 0xe8, 0x98, 0x09, 0x84, 0x59, 0xa7, - 0x24, 0x1a, 0xe6, 0xa9, 0x74, 0x5d, 0xb8, 0xf9, 0xe8, 0x8d, 0x0b, 0xa8, 0x4c, 0x7a, 0xf7, 0xbd, - 0x5d, 0xff, 0xf2, 0x80, 0x72, 0xbc, 0xf6, 0x18, 0xbd, 0xb0, 0x2d, 0x59, 0xab, 0x3b, 0x1e, 0x67, - 0x55, 0x24, 0x43, 0x52, 0x25, 0x0b, 0xe4, 0xb7, 0xda, 0x02, 0xef, 0xd6, 0x92, 0x49, 0xab, 0xce, - 0xf9, 0xee, 0xa0, 0xd3, 0x79, 0x06, 0x3a, 0xfb, 0x13, 0x23, 0x18, 0x48, 0xcc, 0xf1, 0x5c, 0x32, - 0xea, 0x18, 0x31, 0x47, 0x52, 0xeb, 0x18, 0x0e, 0x87, 0x46, 0x1a, 0x11, 0x30, 0x14, 0x8a, 0x85, - 0xfc, 0x3a, 0xa9, 0x20, 0x6c, 0xae, 0x6f, 0x9f, 0xa2, 0x77, 0x8d, 0x61, 0x02, 0x5d, 0x47, 0x16, - 0xd3, 0xd4, 0xc5, 0xb4, 0x20, 0x68, 0x09, 0xa1, 0x1d, 0x6d, 0x64, 0x5d, 0x0f, 0x44, 0x76, 0x15, - 0xde, 0x66, 0xc2, 0x27, 0xd0, 0xb2, 0x45, 0xf4, 0x9a, 0xc2, 0xf9, 0xb9, 0xe0, 0x66, 0xe2, 0x4d, - 0x99, 0x85, 0xdd, 0xc5, 0x67, 0xf5, 0xb1, 0xd7, 0xf8, 0xac, 0x76, 0x72, 0xfe, 0x05, 0x66, 0xa2, - 0x6e, 0x01, 0x8e, 0x43, 0x08, 0x20, 0x19, 0x0f, 0x9b, 0x14, 0x05, 0x78, 0x15, 0x9e, 0xba, 0xd5, - 0xbb, 0xb1, 0x0c, 0x15, 0x0c, 0x63, 0x68, 0xe0, 0x1f, 0x75, 0x9e, 0xc4, 0x80, 0x8a, 0xf3, 0x7f, - 0x01, 0x59, 0xbb, 0x97, 0x8e, 0x03, 0x1f, 0xbd, 0xaa, 0x10, 0x8d, 0xc1, 0x88, 0xc1, 0xab, 0xd1, - 0xe2, 0x25, 0x61, 0xbc, 0x9e, 0x67, 0x43, 0xa5, 0x81, 0x22, 0x6a, 0xbf, 0xc2, 0xc4, 0xb6, 0xb8, - 0x40, 0xe9, 0xc5, 0x66, 0xe0, 0xcf, 0xb8, 0x2d, 0x4b, 0x4a, 0xe8, 0x86, 0x73, 0x64, 0x25, 0x32, - 0xb9, 0x0a, 0xcf, 0x0d, 0x69, 0x88, 0x72, 0xd7, 0xd2, 0x47, 0x4f, 0xec, 0x50, 0xdf, 0xc2, 0xfb, - 0xcd, 0x78, 0xa4, 0xc6, 0x39, 0x25, 0xd2, 0x3c, 0x10, 0xad, 0xd0, 0xee, 0xa4, 0xa0, 0x7d, 0xe9, - 0xdb, 0xdc, 0xd0, 0x9b, 0x4c, 0xf9, 0xfa, 0x63, 0xa6, 0xcf, 0x69, 0xe8, 0xc6, 0xa4, 0x7f, 0xc2, - 0xe7, 0xea, 0x69, 0x6e, 0xaa, 0xb0, 0xf2, 0xc3, 0xbb, 0xf1, 0xc7, 0x4f, 0x91, 0x13, 0x41, 0x5a, - 0x95, 0xb3, 0xa1, 0x20, 0x80, 0x28, 0x74, 0xe0, 0x33, 0x99, 0x8b, 0xe1, 0x93, 0x3e, 0x5a, 0x38, - 0xac, 0x07, 0x2f, 0xfb, 0xa2, 0x6f, 0xa8, 0x5e, 0xf0, 0x3d, 0x5f, 0x76, 0xbb, 0x3b, 0x74, 0x10, - 0xe2, 0x1e, 0xf7, 0x83, 0x4e, 0xee, 0xab, 0x82, 0xfe, 0x74, 0xb4, 0x11, 0x48, 0x5c, 0xf3, 0xae, - 0xdb, 0x12, 0x71, 0x5d, 0x96, 0x72, 0x55, 0xe6, 0x81, 0x94, 0xab, 0xba, 0xeb, 0x7b, 0xaf, 0x75, - 0x99, 0xcf, 0x89, 0x9e, 0x5f, 0x12, 0xfb, 0xc9, 0x87, 0x32, 0x37, 0x5a, 0x5e, 0x7e, 0xc1, 0xcd, - 0x2f, 0x44, 0xf2, 0xf3, 0x6e, 0x7e, 0xd1, 0xcd, 0x2f, 0x86, 0xf3, 0x9b, 0x5e, 0x7e, 0x96, 0xe5, - 0x97, 0xf9, 0xce, 0xe9, 0x51, 0x84, 0x4b, 0x82, 0xe2, 0x36, 0x3f, 0x02, 0x3d, 0x74, 0xab, 0x58, - 0xc4, 0x90, 0x42, 0xf4, 0x5a, 0x20, 0xa6, 0x6f, 0x83, 0x6c, 0x67, 0x56, 0xd9, 0xf3, 0xe2, 0xa6, - 0xce, 0x42, 0xd5, 0xd3, 0x58, 0xba, 0x8c, 0xe7, 0x00, 0xf5, 0xe9, 0xf8, 0x07, 0x24, 0x3a, 0x64, - 0x22, 0x20, 0x75, 0x62, 0x89, 0xa4, 0xaf, 0xcb, 0xa2, 0x17, 0x9b, 0x9b, 0xf8, 0x17, 0x64, 0x56, - 0xaf, 0x99, 0x24, 0x34, 0xa9, 0xb0, 0x44, 0x97, 0x1d, 0xac, 0x38, 0x3d, 0xc2, 0x43, 0xf2, 0x5c, - 0x79, 0x5d, 0xa1, 0x91, 0xa3, 0xe8, 0xc9, 0x04, 0x9e, 0xd0, 0xf0, 0xc7, 0x48, 0xd6, 0xd0, 0xb9, - 0xe9, 0x6b, 0x89, 0x6f, 0xb0, 0x5c, 0x61, 0x90, 0xf3, 0x6f, 0xf4, 0x73, 0x56, 0xc9, 0x9a, 0x18, - 0x76, 0xd2, 0xa2, 0x77, 0x75, 0x3d, 0x6f, 0xc4, 0x5f, 0x71, 0x37, 0x4c, 0xfd, 0xfb, 0x51, 0xb4, - 0x99, 0xd0, 0x77, 0xa5, 0xdd, 0x0f, 0xc0, 0xbb, 0xf7, 0xc3, 0xbf, 0xd1, 0xd0, 0xe5, 0x99, 0x8c, - 0xd7, 0x21, 0xf5, 0x87, 0xe5, 0xc8, 0xb3, 0xed, 0x51, 0x20, 0x9b, 0x12, 0x25, 0xe9, 0xde, 0xa8, - 0xc1, 0xf2, 0x63, 0xb5, 0xfb, 0x3d, 0x97, 0x97, 0x37, 0xb3, 0x79, 0x19, 0x07, 0x43, 0xb8, 0x8b, - 0x55, 0xd2, 0x2f, 0xa8, 0x41, 0xc9, 0x30, 0x9b, 0xdb, 0x74, 0x07, 0x0c, 0x55, 0xd1, 0x89, 0x41, - 0x65, 0x7a, 0x14, 0xad, 0xbe, 0x2d, 0x9e, 0x67, 0x1a, 0x62, 0x95, 0x3e, 0xcf, 0xe9, 0xc8, 0x40, - 0xa0, 0x4a, 0xa5, 0xe6, 0x04, 0x9d, 0xed, 0xbf, 0xcb, 0xdb, 0x36, 0xc8, 0x82, 0xa1, 0xcf, 0x64, - 0xe0, 0x2d, 0x5d, 0xd8, 0x95, 0xd0, 0xdc, 0xd7, 0x4d, 0x8b, 0x55, 0x99, 0x7a, 0xef, 0x25, 0xb0, - 0xd8, 0xb9, 0x21, 0x18, 0x78, 0x9d, 0x38, 0xf8, 0x56, 0x82, 0xd0, 0x43, 0x7e, 0x96, 0x46, 0x41, - 0x1c, 0x95, 0x3a, 0xdf, 0xcb, 0x59, 0xe0, 0x3c, 0x39, 0x9b, 0xcc, 0x09, 0xd5, 0xaf, 0x54, 0x45, - 0x7f, 0x4e, 0x3a, 0x9b, 0x73, 0x56, 0x52, 0x67, 0xf7, 0x76, 0x22, 0xde, 0x9b, 0x7a, 0x48, 0x6a, - 0xb2, 0xc3, 0xf7, 0x0a, 0xdd, 0x8f, 0x25, 0xc4, 0x5e, 0x28, 0x64, 0x9f, 0x96, 0xf8, 0xd4, 0x7d, - 0x42, 0xff, 0xe2, 0xfa, 0x48, 0xeb, 0xd2, 0x90, 0xf8, 0xd8, 0xb6, 0x80, 0x8d, 0x0b, 0x28, 0x53, - 0xb0, 0x18, 0x22, 0xb1, 0x17, 0x0d, 0xe3, 0x3e, 0x92, 0x23, 0x85, 0xd7, 0x87, 0x27, 0xb1, 0x49, - 0xe4, 0x83, 0x3b, 0x95, 0x0b, 0x41, 0x70, 0xa8, 0x21, 0x87, 0xc9, 0x13, 0x2c, 0x86, 0x14, 0x71, - 0xa5, 0x85, 0x5a, 0x10, 0xc2, 0x0b, 0x51, 0x8a, 0xfd, 0x7c, 0xd6, 0x7d, 0x15, 0x6f, 0xad, 0xe1, - 0xd2, 0x04, 0xdd, 0x67, 0x69, 0x80, 0x62, 0x87, 0x3a, 0xc3, 0x7a, 0x21, 0x6b, 0xee, 0x90, 0xf3, - 0xd2, 0x20, 0xb3, 0x7d, 0x31, 0xe9, 0xad, 0x74, 0x92, 0x1e, 0x28, 0x76, 0xc3, 0x71, 0x2c, 0x15, - 0xc8, 0x10, 0x72, 0x87, 0xca, 0x54, 0x4c, 0x02, 0xb7, 0x50, 0xbc, 0x24, 0x7a, 0x03, 0x84, 0x19, - 0x0d, 0xaa, 0xb0, 0xa5, 0x7b, 0x11, 0x48, 0x78, 0xdf, 0x5f, 0x9a, 0x99, 0x81, 0x25, 0xa7, 0x7f, - 0xa7, 0x81, 0x2f, 0x60, 0xa1, 0xe5, 0x98, 0xf4, 0xa2, 0xd5, 0x17, 0xa2, 0x13, 0x57, 0x58, 0x70, - 0x62, 0x76, 0xa4, 0xdd, 0xee, 0x27, 0x29, 0xbd, 0xfc, 0xcb, 0x4b, 0xe8, 0x6c, 0x94, 0xdc, 0x94, - 0xe4, 0xaf, 0xda, 0xb2, 0x40, 0x78, 0xda, 0x9c, 0x19, 0xd4, 0x42, 0x68, 0x5b, 0x86, 0x41, 0x3f, - 0x72, 0x1d, 0xfb, 0xf6, 0x23, 0x43, 0x9c, 0xe2, 0x6a, 0x71, 0xbe, 0x0c, 0xef, 0x59, 0x45, 0x82, - 0x40, 0xaf, 0x5c, 0x54, 0x23, 0x1a, 0xa8, 0x35, 0xcd, 0x3e, 0x8e, 0xf2, 0x77, 0xbb, 0x8c, 0x0b, - 0x31, 0xc4, 0x7d, 0x9c, 0xd2, 0x0d, 0x9e, 0x13, 0x21, 0x1c, 0x34, 0x07, 0x72, 0xd1, 0xf9, 0x98, - 0xf9, 0x8a, 0xa0, 0x85, 0xcb, 0xa3, 0x9f, 0x48, 0xdc, 0x98, 0x48, 0x29, 0x77, 0x54, 0xa0, 0x54, - 0xc7, 0x96, 0x08, 0x6c, 0x60, 0x8b, 0x27, 0x76, 0x4e, 0x95, 0x46, 0x9b, 0xb1, 0x37, 0x3c, 0x9b, - 0x68, 0xa8, 0xe9, 0xc7, 0x5f, 0x28, 0x2d, 0x86, 0xda, 0xc2, 0x24, 0xa3, 0x1e, 0x01, 0x00, 0x04, - 0xb2, 0x48, 0x87, 0x20, 0x98, 0xf2, 0x29, 0x46, 0x0f, 0x92, 0x3a, 0x4c, 0x02, 0x1c, 0xbe, 0xb4, - 0x1e, 0x0f, 0x61, 0x9d, 0x77, 0x83, 0xd7, 0x5d, 0x16, 0x51, 0x52, 0xb5, 0xcf, 0x70, 0xcf, 0x70, - 0xb6, 0xea, 0xd4, 0xbf, 0x34, 0x89, 0xfc, 0x4b, 0x73, 0xaf, 0xeb, 0xcb, 0x92, 0x96, 0x66, 0x5f, - 0x6f, 0x80, 0x5f, 0x55, 0xc7, 0xd8, 0xd4, 0x6c, 0x76, 0x59, 0x76, 0x16, 0xb4, 0x65, 0xcc, 0xa6, - 0xbf, 0x2c, 0xdb, 0xa0, 0x0f, 0xcc, 0x53, 0xd5, 0xa0, 0x99, 0x9e, 0x05, 0x70, 0x23, 0x2b, 0xa9, - 0x41, 0x66, 0x8a, 0xbe, 0x71, 0xd9, 0x52, 0x67, 0x7d, 0xbd, 0xb3, 0xc4, 0xe2, 0xd9, 0x5d, 0x5f, - 0xef, 0x2e, 0xc9, 0x02, 0xa1, 0xd3, 0x5a, 0x6a, 0x8d, 0x62, 0x42, 0x71, 0xa4, 0xe1, 0x88, 0xb1, - 0x31, 0xd2, 0x76, 0x24, 0x37, 0xd2, 0x3c, 0xd7, 0x33, 0x9d, 0x39, 0x4e, 0x5a, 0xd6, 0x3c, 0x7a, - 0xe0, 0xc4, 0x64, 0x65, 0x29, 0x05, 0x18, 0x40, 0x01, 0x35, 0xfd, 0xcf, 0xba, 0xba, 0x61, 0xd0, - 0x86, 0xcc, 0xd0, 0xc4, 0xe1, 0xb1, 0x0c, 0x53, 0x8c, 0x81, 0x44, 0xf0, 0x1c, 0x08, 0x8a, 0xa1, - 0xe1, 0xd2, 0x5c, 0x5f, 0x37, 0x97, 0x0f, 0x96, 0x0b, 0x23, 0xea, 0x9b, 0x1b, 0xc2, 0x3a, 0xdc, - 0x2f, 0xaa, 0xc3, 0xfd, 0xe0, 0x04, 0xd9, 0xe0, 0xa3, 0x1c, 0x73, 0xf1, 0x27, 0xeb, 0xd2, 0x61, - 0x72, 0x83, 0xbd, 0x44, 0x03, 0xa4, 0xdb, 0xaf, 0xbb, 0xd9, 0xdc, 0x90, 0xa9, 0x23, 0xe9, 0x21, - 0xd0, 0xa9, 0x13, 0x27, 0x34, 0xa4, 0xd3, 0xf0, 0x9e, 0xbe, 0xc9, 0xeb, 0xff, 0xfd, 0x3f, 0xfe, - 0x2f, 0xb4, 0x78, 0x6d, 0xeb, 0x4b, 0xc1, 0xaf, 0xea, 0xcb, 0x2e, 0x63, 0x31, 0x9b, 0xa2, 0x89, - 0x17, 0x92, 0x57, 0xdc, 0xea, 0xf2, 0x00, 0x70, 0xcc, 0x5f, 0x21, 0x33, 0x3b, 0x65, 0x67, 0x26, - 0xc8, 0xda, 0xec, 0x56, 0x72, 0x92, 0x5a, 0xe4, 0xf4, 0xad, 0xec, 0xb6, 0x59, 0xc7, 0xb8, 0x4b, - 0x20, 0xc7, 0xd9, 0x62, 0x15, 0x3d, 0x05, 0xb1, 0xfd, 0xba, 0x98, 0xa5, 0x4e, 0x1b, 0xb0, 0x3a, - 0x61, 0x2b, 0xf9, 0x12, 0x1a, 0x59, 0xdf, 0x82, 0x96, 0x19, 0x67, 0xef, 0xc7, 0x2f, 0x79, 0x5a, - 0xc2, 0x23, 0x83, 0xd1, 0x12, 0xc6, 0x61, 0x76, 0xfc, 0x32, 0x68, 0x05, 0xc3, 0x8b, 0x03, 0xfd, - 0xba, 0x7b, 0x5f, 0xac, 0xc7, 0xd8, 0x7f, 0x87, 0xc0, 0x8e, 0xa6, 0x67, 0x12, 0xfd, 0xd4, 0x08, - 0x14, 0x55, 0xf7, 0x23, 0x03, 0x3d, 0x7a, 0x98, 0xb0, 0x95, 0xfd, 0xfd, 0x7b, 0xb4, 0x25, 0xe3, - 0xb3, 0x09, 0x32, 0x84, 0x00, 0x1a, 0x44, 0x6f, 0x2e, 0x8c, 0x55, 0xcb, 0x19, 0x29, 0x5a, 0x12, - 0xd4, 0x27, 0x6a, 0xd2, 0xf7, 0xd6, 0x32, 0x2b, 0x25, 0x0a, 0x3f, 0xf0, 0x6e, 0xa9, 0xf9, 0x33, - 0x82, 0x29, 0xa0, 0xc3, 0x50, 0x84, 0x22, 0x33, 0xca, 0x21, 0xf1, 0x76, 0x29, 0x93, 0x21, 0x6b, - 0xc4, 0x0b, 0x2d, 0x87, 0xf7, 0x50, 0xc5, 0x64, 0xd8, 0x7c, 0x44, 0x92, 0xb1, 0x71, 0xbb, 0x3c, - 0x7f, 0xc8, 0x24, 0x57, 0x1b, 0x2f, 0xcf, 0xff, 0xd5, 0xda, 0x30, 0x5f, 0xa1, 0x2f, 0xc3, 0xe0, - 0xed, 0xff, 0x70, 0x7c, 0xa7, 0x50, 0xf6, 0xdc, 0xf8, 0x45, 0x23, 0x59, 0x3a, 0x51, 0x62, 0x09, - 0x9a, 0x94, 0x12, 0xcb, 0xea, 0xbe, 0x6a, 0xed, 0x5f, 0x34, 0xd2, 0xe4, 0xf2, 0xca, 0xf3, 0x28, - 0x9a, 0x30, 0xae, 0xc1, 0x8c, 0xbb, 0xb5, 0x79, 0x69, 0x4c, 0x88, 0xe5, 0xd9, 0xbd, 0x71, 0xaf, - 0xaa, 0xe3, 0x67, 0x78, 0xb6, 0xbd, 0x10, 0x7a, 0x18, 0xd2, 0x8b, 0xbf, 0xe3, 0xa9, 0x85, 0x8a, - 0xea, 0x5a, 0x63, 0x59, 0xc9, 0xd6, 0x9b, 0xde, 0x09, 0x95, 0xf5, 0xbe, 0xd8, 0x13, 0xaa, 0xc0, - 0x60, 0x6a, 0xf9, 0xeb, 0xda, 0x0f, 0xbf, 0xd0, 0x0a, 0x3e, 0xb9, 0x83, 0x06, 0x96, 0x04, 0xf7, - 0x09, 0x9e, 0xc5, 0x43, 0x31, 0x77, 0xff, 0xdc, 0xc1, 0x33, 0xa5, 0xa5, 0xb9, 0x2d, 0x93, 0x20, - 0x42, 0x96, 0xe6, 0x1f, 0x79, 0x5f, 0x9d, 0x5f, 0x51, 0xa6, 0x99, 0x5d, 0x95, 0x99, 0x5b, 0x95, - 0x99, 0xc7, 0x4c, 0xef, 0x1b, 0x0a, 0x89, 0x25, 0xa5, 0xae, 0x57, 0xb4, 0x70, 0xb0, 0x22, 0x6f, - 0x87, 0x06, 0x34, 0x08, 0x3e, 0xcc, 0xb0, 0xa4, 0xd8, 0xbd, 0x98, 0xe4, 0x3f, 0x94, 0xc0, 0xc5, - 0xa7, 0x67, 0xe6, 0xc4, 0x09, 0x5e, 0x56, 0x59, 0x38, 0xc2, 0xf2, 0x9a, 0x8d, 0x1e, 0x60, 0xb1, - 0x2a, 0x6d, 0x45, 0x8b, 0xab, 0xd1, 0x6c, 0xde, 0xc4, 0x96, 0x1f, 0x90, 0xe9, 0x64, 0xa1, 0x3c, - 0xc1, 0xd8, 0xff, 0xb1, 0xc5, 0xd9, 0x07, 0x81, 0xe2, 0x3a, 0xa0, 0x78, 0x04, 0xc1, 0x8b, 0x16, - 0x88, 0xef, 0x6a, 0xd9, 0x68, 0xd8, 0x67, 0x2c, 0x56, 0xd6, 0xb5, 0xff, 0x8d, 0xba, 0xe3, 0x15, - 0x75, 0x63, 0x2b, 0xbc, 0xac, 0xee, 0x6c, 0x05, 0x36, 0xad, 0x7e, 0x7b, 0x65, 0x5d, 0x82, 0xdf, - 0x1d, 0x88, 0xad, 0xf9, 0xda, 0xb1, 0x37, 0x26, 0x2b, 0xea, 0xd1, 0x0f, 0x2f, 0x45, 0x6b, 0x72, - 0xd7, 0xf8, 0xdd, 0x47, 0x1a, 0xf3, 0xd2, 0xb2, 0x13, 0x0b, 0xf2, 0x6a, 0x74, 0xfd, 0x86, 0xa2, - 0xdc, 0x05, 0x76, 0x6b, 0x69, 0x71, 0xe3, 0xfe, 0xc5, 0xc2, 0x96, 0x32, 0x7b, 0x90, 0x67, 0xa5, - 0xfe, 0xf9, 0xc3, 0x37, 0x16, 0x11, 0xb6, 0xf7, 0xdb, 0x74, 0xcb, 0xe6, 0x8f, 0xd7, 0x99, 0x05, - 0x71, 0x51, 0x0e, 0xe0, 0x98, 0x33, 0x7e, 0x5b, 0x68, 0x25, 0xfb, 0x96, 0x9c, 0x0f, 0x85, 0x10, - 0xd6, 0x3f, 0x32, 0xdd, 0xcf, 0x95, 0x5c, 0xce, 0x9b, 0x39, 0x71, 0x3a, 0x6a, 0xf3, 0xc4, 0xfb, - 0xd0, 0x28, 0x4a, 0x91, 0xef, 0xa5, 0xa4, 0x16, 0xd6, 0x19, 0x22, 0x32, 0x1f, 0xd9, 0xca, 0xad, - 0xaf, 0xd3, 0x28, 0x21, 0x2b, 0x8a, 0xe4, 0xb1, 0x48, 0xee, 0xe7, 0x6a, 0xd1, 0x53, 0x08, 0x5d, - 0xa2, 0xd4, 0x92, 0x62, 0x96, 0xda, 0x8c, 0x3c, 0xc3, 0x29, 0x96, 0x44, 0xca, 0xf8, 0xe0, 0x80, - 0x74, 0x91, 0xa3, 0xf3, 0x71, 0xe9, 0x3c, 0x51, 0x4f, 0x42, 0x4d, 0x77, 0xc5, 0xbc, 0xf7, 0xa6, - 0xfc, 0x94, 0x47, 0xe5, 0x3e, 0x0c, 0x95, 0xe0, 0x2c, 0xcc, 0xbd, 0xbd, 0xd0, 0x64, 0x68, 0xee, - 0x5d, 0x82, 0x59, 0x35, 0xf9, 0x5e, 0xf0, 0x53, 0xf6, 0x91, 0x47, 0x16, 0xd8, 0xc8, 0xbf, 0x1a, - 0xff, 0x37, 0x84, 0x53, 0x3d, 0x39, 0xd3, 0x57, 0xec, 0xeb, 0x18, 0x22, 0x83, 0x1e, 0x23, 0x5f, - 0x2a, 0x16, 0x8c, 0xda, 0xc1, 0x85, 0x14, 0xb4, 0xe2, 0xa9, 0xcd, 0x24, 0xe6, 0x64, 0x84, 0x3f, - 0x10, 0x61, 0x56, 0x7f, 0x77, 0xce, 0xa8, 0x82, 0x18, 0xcc, 0x99, 0x61, 0x3a, 0xe1, 0x28, 0x33, - 0xfc, 0x97, 0x8e, 0x20, 0x93, 0x3b, 0x6e, 0xad, 0x11, 0xee, 0x2c, 0x84, 0x9e, 0x6d, 0x82, 0xbc, - 0xe7, 0x7e, 0x0f, 0xef, 0xf7, 0xef, 0xac, 0xff, 0x0c, 0x19, 0x5f, 0xf0, 0x50, 0x14, 0x03, 0xef, - 0xe6, 0x7f, 0x06, 0x32, 0xb3, 0x4c, 0xcf, 0x88, 0x7f, 0xff, 0x66, 0x6e, 0x1a, 0x98, 0xc7, 0xfe, - 0x06, 0x25, 0x60, 0x7b, 0xdc, 0xaa, 0xcb, 0xd1, 0xd4, 0x2c, 0x95, 0xb4, 0x97, 0x90, 0x55, 0x95, - 0x2c, 0x8f, 0xb6, 0xe0, 0x6a, 0xfe, 0xfa, 0xe7, 0xe4, 0x7e, 0xa3, 0x1e, 0x71, 0xf5, 0x59, 0x0c, - 0x97, 0xdc, 0x55, 0xc7, 0x3f, 0xa2, 0xf7, 0xbb, 0xbc, 0x80, 0xc9, 0x1c, 0x92, 0x8d, 0xa4, 0xb2, - 0xa8, 0x29, 0x2c, 0x87, 0xb3, 0xba, 0x64, 0x68, 0xb5, 0xbf, 0x02, 0x91, 0x77, 0xc7, 0xe9, 0x43, - 0x70, 0xfe, 0xcf, 0xff, 0x1b, 0xc1, 0x59, 0x5f, 0x0f, 0xa5, 0xfd, 0x3f, 0x7f, 0x13, 0xc5, 0x9c, - 0xec, 0xe8, 0x6e, 0x10, 0xd7, 0x18, 0x02, 0x90, 0x59, 0x87, 0xd9, 0xfe, 0x43, 0xbf, 0x1f, 0xba, - 0x18, 0xec, 0xc4, 0x8b, 0x49, 0x42, 0x1d, 0x3e, 0x40, 0xbe, 0x40, 0xbe, 0xe1, 0x5e, 0x7b, 0x01, - 0x48, 0x44, 0x5a, 0x10, 0xad, 0x69, 0x8f, 0x09, 0xd9, 0x9c, 0x26, 0x69, 0x40, 0x01, 0x3f, 0x05, - 0xbf, 0x5d, 0x9a, 0x14, 0xfd, 0x15, 0xad, 0xa9, 0xe6, 0x36, 0xfd, 0x8b, 0xa1, 0x68, 0xfd, 0x73, - 0x7e, 0x74, 0x40, 0xb2, 0x41, 0x57, 0xc0, 0x2b, 0x24, 0x82, 0x98, 0xb2, 0x5d, 0x37, 0x1c, 0x2d, - 0x7c, 0x2f, 0xfc, 0x17, 0xfb, 0xfa, 0x2b, 0xfd, 0x88, 0x26, 0x51, 0x69, 0x7c, 0x43, 0x84, 0x02, - 0x63, 0x46, 0xa6, 0xb5, 0xe1, 0x9c, 0x9e, 0x91, 0xe0, 0xe1, 0x80, 0x6b, 0x97, 0x0c, 0x3e, 0x8b, - 0xb9, 0x28, 0xa0, 0xb0, 0xf1, 0xd0, 0xc1, 0x44, 0x76, 0x45, 0x2e, 0x0e, 0xe7, 0xd0, 0xbc, 0x64, - 0xc8, 0x41, 0x25, 0xc0, 0x13, 0xb0, 0xd8, 0xf7, 0x05, 0x61, 0x61, 0xd0, 0x43, 0x70, 0xdd, 0x0f, - 0x0e, 0xe8, 0x07, 0xf8, 0xc5, 0x13, 0xe6, 0xad, 0xc0, 0x31, 0x87, 0x06, 0x7f, 0x62, 0x13, 0x0c, - 0xe3, 0xa7, 0x75, 0xc2, 0x57, 0x62, 0x40, 0x87, 0x75, 0x62, 0x92, 0x6b, 0xd3, 0xba, 0xbd, 0x55, - 0xd8, 0x5c, 0x5f, 0xb7, 0xbf, 0x17, 0xcb, 0xbf, 0x7f, 0xdb, 0x5b, 0xa5, 0x02, 0x3e, 0x57, 0xb2, - 0xf8, 0x5c, 0x29, 0xe1, 0x73, 0x36, 0x97, 0xc7, 0x97, 0x5c, 0xb1, 0xb8, 0x2d, 0xd6, 0x01, 0xfe, - 0x2d, 0x51, 0x7a, 0xab, 0xeb, 0xb4, 0x92, 0x4e, 0x2b, 0xe9, 0xb4, 0x92, 0x4e, 0x2b, 0xe9, 0xb4, - 0x92, 0xce, 0x2a, 0xe9, 0x7c, 0x25, 0x37, 0x10, 0x6a, 0x22, 0x41, 0xa1, 0xf3, 0x82, 0xae, 0x6e, - 0x8b, 0xdf, 0xc5, 0xea, 0x34, 0x99, 0x72, 0x87, 0x19, 0xb1, 0xfc, 0xd3, 0xf3, 0xcc, 0x70, 0xd9, - 0xb7, 0x64, 0x8a, 0x8d, 0x83, 0x05, 0x42, 0x94, 0xa5, 0x99, 0xce, 0x9c, 0x35, 0xaa, 0x5f, 0x64, - 0xde, 0x72, 0x3a, 0x54, 0x5e, 0xc8, 0x7d, 0x2b, 0x41, 0xb1, 0x3a, 0xb1, 0x7f, 0xff, 0xf6, 0x8f, - 0xcb, 0x27, 0xf6, 0x77, 0x99, 0xb7, 0x9e, 0x12, 0x0c, 0x31, 0xba, 0xed, 0x5b, 0x8c, 0x27, 0x18, - 0x8c, 0xc6, 0x22, 0xf4, 0x16, 0xc6, 0xff, 0x57, 0xdc, 0x95, 0x3f, 0xb7, 0x6d, 0x64, 0xe9, 0xdf, - 0xf7, 0xaf, 0xa0, 0x90, 0xc4, 0x02, 0x22, 0x48, 0x06, 0x29, 0xcb, 0xe3, 0x80, 0x82, 0x58, 0x8e, - 0x8f, 0x1d, 0xd5, 0x38, 0x8e, 0x36, 0xf2, 0x24, 0xe3, 0x72, 0xa9, 0x56, 0x24, 0xd5, 0x94, 0x58, - 0x86, 0x00, 0x06, 0x80, 0x0e, 0x87, 0xe2, 0xff, 0xbe, 0xef, 0xe8, 0x13, 0x00, 0x0f, 0xc5, 0x99, - 0xd9, 0xaa, 0xc4, 0x22, 0x1a, 0xdd, 0x8d, 0xbe, 0xfb, 0xbd, 0xd7, 0xaf, 0xbf, 0xcf, 0x27, 0xca, - 0x01, 0x2f, 0xf4, 0xee, 0x08, 0xa6, 0xf8, 0x0e, 0x49, 0x48, 0xbc, 0x9d, 0x1a, 0xcd, 0x83, 0xa1, - 0x39, 0xa0, 0xc4, 0x7d, 0xf8, 0x1a, 0x31, 0x85, 0xfc, 0x26, 0x46, 0xa7, 0xd0, 0xe1, 0x82, 0x08, - 0xf3, 0xf6, 0x46, 0xd3, 0x6c, 0x58, 0x7c, 0xf9, 0x40, 0x07, 0x59, 0x84, 0x9b, 0x3f, 0xba, 0x81, - 0x45, 0xbc, 0xf0, 0xc2, 0xbb, 0x72, 0x0f, 0x41, 0x0d, 0xca, 0x12, 0x0d, 0x93, 0x68, 0x0d, 0xc6, - 0xb5, 0x58, 0x72, 0xa8, 0x69, 0x0b, 0x39, 0xcc, 0x58, 0x3a, 0x7f, 0xfd, 0x91, 0x12, 0x69, 0x8e, - 0x0d, 0x0b, 0xb6, 0x8c, 0x13, 0x04, 0x7c, 0x94, 0x4c, 0xe8, 0x0d, 0xc1, 0xdc, 0xc1, 0xa5, 0xb6, - 0xe0, 0xe8, 0x82, 0xd0, 0x7a, 0x60, 0x0e, 0x10, 0x8b, 0x85, 0x54, 0xb1, 0x9c, 0x84, 0x75, 0x8b, - 0xb6, 0x82, 0x93, 0xdf, 0x14, 0x72, 0x3c, 0xd5, 0xf3, 0x11, 0x36, 0x07, 0xc4, 0x30, 0x28, 0x07, - 0xbe, 0x01, 0x64, 0x82, 0xc9, 0xc7, 0x14, 0xad, 0x4f, 0x9e, 0x38, 0x00, 0x34, 0x65, 0x10, 0xc4, - 0x16, 0xee, 0x81, 0x12, 0xcf, 0x08, 0x05, 0x74, 0x20, 0xff, 0xc6, 0x55, 0xdf, 0x59, 0x5a, 0xca, - 0x30, 0x43, 0xe0, 0xfc, 0xe1, 0xc5, 0x29, 0xa1, 0x1c, 0x65, 0xb0, 0x06, 0x2d, 0xb8, 0x61, 0x89, - 0x24, 0x9a, 0x9a, 0xf5, 0xd1, 0x70, 0xe9, 0x4c, 0x5a, 0xba, 0xb3, 0x73, 0x78, 0xe0, 0x60, 0x54, - 0xf2, 0xb8, 0x0a, 0xbb, 0x07, 0x68, 0xdf, 0xbf, 0x63, 0x0a, 0x15, 0xf9, 0x31, 0x44, 0x8b, 0xa7, - 0x6f, 0x29, 0xba, 0xd3, 0x42, 0xfc, 0x5e, 0xbe, 0x13, 0x97, 0xc3, 0x14, 0xa4, 0x0d, 0xc7, 0x97, - 0x41, 0x95, 0x54, 0xe1, 0xb9, 0xd3, 0xfe, 0x2b, 0x07, 0x25, 0x1f, 0xbb, 0x8b, 0xbd, 0xf2, 0x66, - 0x3c, 0x86, 0x61, 0xa1, 0x42, 0xa3, 0x3e, 0x11, 0xdd, 0xa2, 0x07, 0x74, 0x58, 0xd7, 0x5e, 0xa5, - 0xa9, 0x52, 0xe0, 0x2d, 0x7d, 0xe2, 0xc1, 0x15, 0x7b, 0x59, 0x8a, 0x11, 0x99, 0xb4, 0x96, 0x9e, - 0x2e, 0x6e, 0x0a, 0x49, 0x5d, 0x4b, 0x8f, 0x15, 0x47, 0x7d, 0x3b, 0x44, 0x04, 0x7b, 0x0c, 0x98, - 0xc0, 0x2f, 0xc3, 0x8f, 0x2b, 0xf6, 0x6e, 0x2e, 0x66, 0x78, 0xb9, 0x2e, 0xbb, 0xa8, 0x51, 0xd6, - 0x82, 0x12, 0x93, 0xd2, 0xbe, 0x30, 0x2b, 0x63, 0xfc, 0x8d, 0x0c, 0xb3, 0x72, 0x11, 0x27, 0xca, - 0x6b, 0x2e, 0x5c, 0x55, 0xe9, 0x52, 0x55, 0xc5, 0xd3, 0x6e, 0x14, 0xb6, 0x60, 0x5d, 0xc8, 0xc1, - 0x11, 0xc1, 0xca, 0x14, 0x49, 0xf2, 0x43, 0xad, 0x35, 0x26, 0x86, 0x78, 0x57, 0xb3, 0xeb, 0xd6, - 0x1c, 0xe3, 0x7c, 0x03, 0x72, 0x61, 0xbb, 0xc8, 0x41, 0xdb, 0x45, 0x64, 0xe9, 0xa2, 0xd7, 0x9f, - 0xaa, 0x33, 0xe4, 0x92, 0x31, 0x77, 0x58, 0x69, 0x07, 0x40, 0x83, 0x5e, 0x97, 0xd1, 0x8d, 0x29, - 0x06, 0x88, 0x4f, 0x20, 0x73, 0x1d, 0xf2, 0xf6, 0x50, 0xe1, 0x52, 0xd9, 0x93, 0x62, 0x94, 0x43, - 0x57, 0x47, 0x86, 0xcd, 0xf1, 0x27, 0x2b, 0xe3, 0x33, 0x59, 0xee, 0x87, 0x64, 0x6b, 0xcb, 0xef, - 0x3e, 0x49, 0x8d, 0x36, 0x4d, 0x21, 0x3d, 0x19, 0x02, 0x35, 0xa1, 0xe7, 0x67, 0x4f, 0x34, 0x74, - 0x86, 0x6f, 0x3e, 0x5e, 0xe5, 0xb3, 0x5d, 0xeb, 0x69, 0x58, 0x54, 0xc1, 0xf7, 0xee, 0xeb, 0x8f, - 0x03, 0xf7, 0xb1, 0x16, 0xfd, 0x63, 0xdc, 0x05, 0x41, 0x55, 0x36, 0x93, 0xfc, 0x7b, 0x38, 0x1c, - 0x0c, 0x63, 0xf9, 0x5b, 0x9f, 0x76, 0x60, 0xa2, 0x92, 0x4e, 0x7d, 0xd0, 0x18, 0x88, 0xfe, 0x76, - 0x6d, 0xf5, 0x1b, 0x5a, 0x15, 0x6b, 0xd4, 0xab, 0x56, 0x2d, 0x55, 0xab, 0x05, 0x8e, 0xe0, 0xa1, - 0xda, 0xa5, 0xac, 0x93, 0xb4, 0xf7, 0x79, 0x47, 0x75, 0xb9, 0x3c, 0x3c, 0x33, 0x06, 0x25, 0x78, - 0x24, 0xb0, 0xe9, 0x4b, 0x6b, 0x7f, 0x6e, 0xf3, 0xe8, 0xd9, 0x83, 0xc1, 0x03, 0xc3, 0x09, 0xba, - 0xff, 0xcd, 0x50, 0x1e, 0x99, 0xad, 0x10, 0x3f, 0x14, 0x7a, 0x53, 0x9b, 0x82, 0x64, 0xfc, 0x60, - 0x72, 0xf9, 0xcd, 0xdd, 0x6e, 0x5f, 0x1c, 0xa1, 0x57, 0xcc, 0xee, 0x6e, 0x90, 0x3b, 0x7e, 0x22, - 0xc9, 0x10, 0xad, 0x1c, 0x10, 0x44, 0xa4, 0x50, 0x8e, 0x9f, 0x88, 0x79, 0xd5, 0xad, 0xbd, 0x1a, - 0x99, 0x57, 0xbd, 0xb3, 0xd0, 0xd7, 0xa3, 0xe1, 0x61, 0xab, 0x41, 0x84, 0x49, 0xd8, 0x51, 0x76, - 0xda, 0x3b, 0x93, 0x16, 0x59, 0xa4, 0x24, 0xd9, 0x68, 0x4e, 0x20, 0xe2, 0xe4, 0x46, 0xad, 0xe9, - 0x4c, 0x90, 0x4b, 0x5a, 0x99, 0x6f, 0x21, 0xcd, 0xb8, 0x42, 0xd1, 0x0d, 0xfe, 0x90, 0xa0, 0x6c, - 0x9b, 0x6e, 0x5e, 0xea, 0x59, 0x48, 0xef, 0x83, 0xb0, 0x69, 0xd9, 0xd2, 0xef, 0xcb, 0xfb, 0xb0, - 0xdd, 0xae, 0xa5, 0x63, 0x4c, 0x9d, 0x18, 0x68, 0xd5, 0x32, 0x99, 0x77, 0x07, 0xf8, 0x4f, 0x1c, - 0x85, 0x35, 0xd3, 0x96, 0x89, 0xd1, 0xc3, 0x18, 0xbd, 0x5a, 0x8c, 0x7d, 0x3b, 0xc6, 0x3e, 0xc6, - 0xd8, 0x57, 0x31, 0x48, 0x51, 0xfb, 0xb9, 0x6b, 0x3b, 0x94, 0x6f, 0x0d, 0xf7, 0xf2, 0xae, 0xfd, - 0xb6, 0x57, 0x7f, 0xdb, 0xb3, 0xdf, 0xee, 0xd7, 0xdf, 0xee, 0xc3, 0x94, 0x27, 0x3e, 0x05, 0x02, - 0x39, 0x94, 0xbf, 0x79, 0x33, 0x9d, 0xa2, 0xc3, 0xa0, 0x72, 0xdd, 0x50, 0x2f, 0xd8, 0x87, 0x22, - 0x42, 0xf0, 0x18, 0x73, 0x22, 0x7c, 0x8d, 0x27, 0x39, 0x9d, 0x89, 0x46, 0x39, 0xda, 0x72, 0x90, - 0x66, 0xba, 0x04, 0x51, 0x83, 0x20, 0x68, 0x22, 0xcb, 0x6f, 0x2e, 0xaf, 0x3a, 0xe5, 0x0c, 0x04, - 0x09, 0x44, 0x99, 0x2e, 0x11, 0xdf, 0x9b, 0x21, 0x0e, 0x6b, 0x49, 0x08, 0xb9, 0x46, 0xe2, 0xdf, - 0xe3, 0x17, 0xe4, 0x11, 0xba, 0x13, 0x87, 0x10, 0x6c, 0x7e, 0x9a, 0x32, 0x79, 0xfd, 0xb4, 0x60, - 0x76, 0x1d, 0x37, 0xca, 0x0f, 0x18, 0xe5, 0xa5, 0x55, 0xb2, 0x0e, 0x55, 0xa3, 0x03, 0x03, 0xae, - 0x93, 0x8f, 0x61, 0x15, 0xc7, 0xd3, 0xfb, 0x85, 0x35, 0x37, 0x69, 0x6f, 0x27, 0x04, 0x25, 0x8a, - 0xb8, 0xe3, 0xc5, 0xf0, 0x30, 0xa5, 0x23, 0x68, 0x35, 0x91, 0x2d, 0x0a, 0xf3, 0x21, 0x48, 0x1a, - 0xa9, 0xcd, 0x70, 0x3e, 0xdc, 0x9b, 0xdc, 0x87, 0x35, 0xbf, 0x3c, 0x77, 0x76, 0x3b, 0xcc, 0xb9, - 0x0d, 0xa5, 0x55, 0x1e, 0x4c, 0x6e, 0xea, 0x1d, 0xec, 0x9e, 0x58, 0xd6, 0xbd, 0x84, 0xc3, 0xd2, - 0x04, 0x91, 0xeb, 0x09, 0x32, 0x53, 0x7c, 0x3a, 0x8b, 0x4b, 0xdb, 0x37, 0x98, 0xfc, 0x8b, 0x33, - 0x99, 0x82, 0x7d, 0x76, 0x11, 0xbb, 0x12, 0xe3, 0xe1, 0x5f, 0xcb, 0x37, 0x38, 0x1c, 0xea, 0x78, - 0x87, 0x3d, 0x15, 0xb3, 0x2b, 0x63, 0x76, 0x9d, 0x98, 0xb9, 0x89, 0xb9, 0xaf, 0x62, 0xf6, 0x64, - 0x4c, 0xd7, 0xdf, 0x58, 0xfb, 0x54, 0xc3, 0x7a, 0x03, 0xe2, 0x76, 0xbf, 0x44, 0xf7, 0x3b, 0x1e, - 0x7c, 0xd6, 0x9c, 0x20, 0xbd, 0xa4, 0xb0, 0x43, 0x88, 0x71, 0x14, 0x83, 0xfb, 0x5b, 0x15, 0x0a, - 0xe4, 0xbe, 0x00, 0xf1, 0xfa, 0xc5, 0xa0, 0x17, 0x1f, 0x80, 0xe6, 0x9c, 0xaa, 0x16, 0x2a, 0xd9, - 0x89, 0x39, 0x85, 0x15, 0x7e, 0xe0, 0x3b, 0xa1, 0x5b, 0x2a, 0xb8, 0xb0, 0xb4, 0x1c, 0x0c, 0x88, - 0xed, 0x80, 0x88, 0xb8, 0x3c, 0xb8, 0x97, 0x60, 0xd4, 0xe2, 0xe2, 0x10, 0x77, 0xed, 0xb0, 0xa9, - 0x5e, 0x12, 0x62, 0x8f, 0xbd, 0x3e, 0xbd, 0x1d, 0xbf, 0xdc, 0x85, 0x8d, 0x74, 0xba, 0x02, 0xdd, - 0x70, 0xba, 0xc4, 0xf4, 0x03, 0x5d, 0xaf, 0x0b, 0x79, 0x20, 0x15, 0xc3, 0xc9, 0x7d, 0x3e, 0xab, - 0x5a, 0x3d, 0xef, 0x51, 0x3a, 0xb1, 0x5a, 0x90, 0xfd, 0x18, 0xf7, 0xc9, 0x81, 0xf1, 0x60, 0x47, - 0x1c, 0xa6, 0xda, 0x43, 0x92, 0xfc, 0xb8, 0xd3, 0x4f, 0x10, 0x78, 0x26, 0x21, 0x1d, 0x31, 0xcb, - 0x1d, 0xb1, 0xee, 0x2a, 0x06, 0xc4, 0x92, 0xcd, 0x2c, 0x6c, 0x5d, 0x10, 0xda, 0x4e, 0x67, 0xe7, - 0xfd, 0x4c, 0xd7, 0x0a, 0xbc, 0x98, 0x9f, 0x41, 0x40, 0x1b, 0x95, 0x55, 0xe1, 0x47, 0x61, 0xf7, - 0x39, 0xd4, 0x73, 0xc9, 0xb7, 0xec, 0x2a, 0x9b, 0x7b, 0x03, 0xcd, 0x8a, 0x52, 0x3c, 0x55, 0x4b, - 0xad, 0x1a, 0x6a, 0x47, 0x57, 0x73, 0xce, 0x56, 0x77, 0xd1, 0xb0, 0xc6, 0x49, 0x89, 0x84, 0x08, - 0x8c, 0x0e, 0x18, 0xe0, 0xd5, 0x87, 0x83, 0xbe, 0xf2, 0x05, 0xae, 0x19, 0x3b, 0xbe, 0x61, 0x83, - 0x5a, 0xc7, 0x36, 0x74, 0xb1, 0x75, 0xcc, 0xb1, 0x64, 0x81, 0x46, 0x83, 0xfc, 0xc1, 0x0b, 0x28, - 0x06, 0xee, 0x46, 0xb8, 0x25, 0x14, 0xb0, 0x58, 0xfb, 0xe3, 0xf0, 0x6f, 0x07, 0xd2, 0x7a, 0x36, - 0x43, 0x27, 0xaf, 0x4b, 0xfc, 0xe7, 0x06, 0x04, 0xb9, 0x49, 0x82, 0x3b, 0x53, 0xad, 0x97, 0x5a, - 0xf6, 0x61, 0xc7, 0xff, 0x14, 0xb3, 0xb9, 0x35, 0xbb, 0x35, 0x34, 0x5f, 0x9f, 0xdc, 0xbb, 0xd4, - 0xc4, 0xe7, 0x3e, 0xc5, 0x79, 0x4d, 0xeb, 0xc5, 0xed, 0xf2, 0x8e, 0xbc, 0x75, 0xac, 0x8e, 0x50, - 0x1e, 0x1a, 0xf9, 0x9c, 0x12, 0xbf, 0x72, 0x45, 0xbf, 0x4d, 0xbf, 0xa1, 0x8f, 0x89, 0xd5, 0xd7, - 0x57, 0xe1, 0x15, 0xc7, 0xe6, 0x23, 0xc5, 0x4b, 0xbc, 0xd9, 0x9a, 0x78, 0x3b, 0xb4, 0xa2, 0x5c, - 0x32, 0xcd, 0x0c, 0x77, 0xe1, 0xad, 0x3b, 0x6b, 0xc4, 0xc0, 0x7b, 0x7b, 0x4f, 0x73, 0x05, 0x7e, - 0xfd, 0x78, 0x89, 0xb3, 0xa3, 0x24, 0x70, 0xa5, 0xc9, 0x16, 0xc2, 0x8e, 0x5b, 0xfb, 0x36, 0x1e, - 0x99, 0xa2, 0xa3, 0x15, 0x66, 0x52, 0x0d, 0x9c, 0xaa, 0xd8, 0x47, 0xbd, 0xb5, 0x7a, 0x74, 0x43, - 0xfb, 0x83, 0x1e, 0x4c, 0x3a, 0xb1, 0xd3, 0x45, 0xdc, 0xd0, 0xc7, 0x34, 0x45, 0x5b, 0x16, 0xe1, - 0xf2, 0x32, 0x06, 0x0b, 0xd5, 0x21, 0xef, 0x1a, 0xa0, 0xb8, 0x33, 0xea, 0xf9, 0x6b, 0x6d, 0x93, - 0x47, 0xe6, 0xa2, 0x91, 0xb1, 0xd0, 0xf3, 0x69, 0xa6, 0x3e, 0xb3, 0x42, 0xaa, 0xac, 0xbc, 0x76, - 0xd1, 0x22, 0xa7, 0x7b, 0x16, 0x7c, 0xe6, 0x8b, 0xbf, 0x03, 0x86, 0xb9, 0xbe, 0xae, 0xd9, 0x4f, - 0xbc, 0x69, 0x86, 0xce, 0x3f, 0xbb, 0x6c, 0x38, 0x09, 0xed, 0x5c, 0x30, 0x95, 0x31, 0x56, 0x25, - 0x5e, 0xa0, 0x0e, 0x4c, 0x8f, 0x93, 0xc6, 0xab, 0x3e, 0x1d, 0x38, 0x83, 0x1c, 0x0c, 0x5d, 0xce, - 0x3f, 0xa7, 0x19, 0xfa, 0x9a, 0x87, 0x7a, 0x52, 0x51, 0x12, 0x39, 0x2c, 0x8e, 0xb1, 0x6d, 0x83, - 0xa0, 0x8f, 0x61, 0x89, 0xf5, 0x02, 0xf6, 0x5d, 0x48, 0x7f, 0x1c, 0x2c, 0x9c, 0xca, 0x6c, 0xc9, - 0xda, 0x0c, 0x46, 0x56, 0x03, 0x61, 0x40, 0x6c, 0x07, 0x6c, 0x37, 0x80, 0xe4, 0x44, 0x2a, 0xef, - 0xdc, 0xda, 0x10, 0x19, 0xc4, 0x7a, 0x2d, 0x6f, 0x48, 0x8e, 0xf6, 0x19, 0x05, 0x9a, 0xf4, 0xd2, - 0x8e, 0x3c, 0xd0, 0xd8, 0xe6, 0xa1, 0xf3, 0xef, 0xc8, 0x9a, 0x04, 0x0f, 0xbc, 0xae, 0xb8, 0x1d, - 0x36, 0xba, 0x81, 0x0d, 0x56, 0xd8, 0xba, 0x96, 0x42, 0xdb, 0xed, 0x5b, 0xfa, 0xac, 0xb9, 0x36, - 0x66, 0x91, 0x5c, 0x3c, 0x4e, 0xe3, 0xfe, 0x02, 0x1a, 0xf7, 0x16, 0x0c, 0x45, 0xfd, 0x89, 0x60, - 0x6e, 0x59, 0x29, 0x1e, 0x1e, 0x6c, 0x03, 0x46, 0x83, 0x53, 0xeb, 0x0e, 0xb6, 0xb9, 0x3b, 0x34, - 0xd3, 0x83, 0xb6, 0xef, 0x6b, 0xb5, 0x3c, 0xac, 0x33, 0x04, 0x2c, 0xc2, 0x7d, 0xb1, 0x1f, 0x28, - 0x26, 0x77, 0x8e, 0x92, 0xc8, 0xb4, 0xa8, 0x94, 0x7f, 0x21, 0xad, 0x3c, 0x49, 0x12, 0x6d, 0xbb, - 0xd9, 0xfb, 0xf9, 0xe4, 0xcd, 0x7b, 0x90, 0x27, 0x60, 0x66, 0xcf, 0xf2, 0x12, 0xf1, 0x07, 0xd0, - 0x77, 0x90, 0x14, 0x74, 0x74, 0xe9, 0xba, 0x45, 0xa2, 0x4a, 0xc6, 0xf6, 0xb4, 0x89, 0xb3, 0xd0, - 0x7c, 0xb2, 0x97, 0xe5, 0x77, 0x7e, 0xf0, 0x14, 0xf1, 0x6e, 0xa5, 0x2e, 0xa9, 0x15, 0xe3, 0x3e, - 0xed, 0x7d, 0xb0, 0x92, 0x4e, 0x2f, 0x60, 0x37, 0xe5, 0x1f, 0x8a, 0x1c, 0xd2, 0x56, 0xa5, 0x15, - 0x5d, 0xad, 0x1e, 0xa7, 0xdd, 0xe8, 0x7b, 0xe5, 0x82, 0xd3, 0x1f, 0x6e, 0x81, 0x66, 0x4d, 0x57, - 0x14, 0x4c, 0x82, 0x64, 0x18, 0x2c, 0x7c, 0x69, 0x16, 0x32, 0x50, 0xdb, 0xc2, 0xa2, 0xc9, 0xd9, - 0x7f, 0x16, 0x91, 0x92, 0x8b, 0xac, 0x74, 0x95, 0xde, 0x7e, 0x91, 0x38, 0x4b, 0x69, 0x82, 0x30, - 0xa8, 0x0d, 0x5e, 0x89, 0x56, 0x0f, 0x11, 0xb6, 0x44, 0x25, 0x5c, 0x94, 0x83, 0xbb, 0x92, 0x4c, - 0x03, 0x3e, 0xf4, 0xcb, 0xf6, 0xdc, 0xbb, 0xf5, 0x62, 0x84, 0x76, 0x5d, 0x6c, 0x07, 0x71, 0x9b, - 0xfb, 0x65, 0x39, 0xb5, 0x7c, 0x2f, 0xb3, 0x10, 0x09, 0x60, 0x61, 0xaf, 0x8a, 0xe7, 0xc8, 0xd5, - 0x86, 0x1e, 0xd8, 0xbb, 0xe8, 0xa7, 0x0a, 0x2d, 0x8b, 0x44, 0x97, 0x53, 0xb6, 0xa9, 0x51, 0xc2, - 0x7e, 0x07, 0x2d, 0x95, 0x68, 0x73, 0xf8, 0xe7, 0x87, 0xb7, 0xbb, 0x2f, 0xbc, 0x45, 0x38, 0xca, - 0x2f, 0xbe, 0xc4, 0x95, 0xed, 0xb9, 0xf9, 0x08, 0x13, 0xd7, 0x23, 0x48, 0x23, 0xb0, 0x7f, 0x36, - 0x30, 0x88, 0xe1, 0x60, 0x7a, 0xa4, 0x4d, 0xec, 0x12, 0x46, 0xb9, 0x50, 0xe7, 0x2d, 0xca, 0xc2, - 0x43, 0x5c, 0x04, 0x64, 0x22, 0x33, 0x70, 0xa2, 0x64, 0xfc, 0x32, 0xd6, 0xb2, 0xaa, 0x85, 0x7b, - 0xa4, 0x5a, 0x62, 0x41, 0xab, 0x50, 0x7c, 0xd7, 0x16, 0x26, 0x69, 0x39, 0x93, 0x7f, 0x63, 0x21, - 0x21, 0xdb, 0xe9, 0x9a, 0x1b, 0x5f, 0x3a, 0x62, 0x28, 0x76, 0xdc, 0x04, 0x5a, 0xb8, 0x2b, 0x25, - 0x61, 0x82, 0x9c, 0x65, 0x6c, 0xd6, 0x5a, 0x3f, 0x63, 0x1e, 0x1e, 0x94, 0xf9, 0x15, 0xd9, 0x2d, - 0x7b, 0x07, 0x41, 0xcd, 0x14, 0xc6, 0x08, 0xee, 0x96, 0x26, 0x82, 0x05, 0x81, 0x75, 0xa4, 0x9c, - 0xc1, 0x12, 0x26, 0x3c, 0x49, 0xb3, 0xb1, 0xca, 0x43, 0xb6, 0xee, 0x68, 0x49, 0x0e, 0x29, 0xfa, - 0xd4, 0x70, 0x9e, 0x67, 0x31, 0x99, 0xcb, 0x90, 0x39, 0x21, 0x5b, 0x90, 0xe9, 0xcc, 0x1e, 0xe2, - 0xc6, 0xec, 0x41, 0x06, 0x7a, 0xe7, 0xb1, 0x14, 0x97, 0xac, 0x47, 0xb3, 0xf9, 0x1e, 0x4d, 0x50, - 0x64, 0xa4, 0x40, 0x2a, 0x10, 0x36, 0xa1, 0x44, 0x7c, 0x07, 0xb3, 0x9e, 0x2a, 0x9c, 0x14, 0x7f, - 0xc4, 0x5b, 0x5d, 0xe2, 0xf0, 0xb4, 0x16, 0xc4, 0x86, 0x4f, 0xe8, 0xfb, 0xd4, 0xa1, 0x2c, 0xf4, - 0xd1, 0x60, 0xb7, 0x05, 0xff, 0x04, 0x83, 0x73, 0x6c, 0x7f, 0x98, 0xf1, 0x0c, 0x75, 0xd0, 0xf9, - 0x88, 0xc4, 0x4e, 0x4c, 0x61, 0x48, 0xbc, 0x4a, 0xa4, 0x92, 0x7d, 0x3b, 0x27, 0x2b, 0xde, 0x51, - 0x64, 0x81, 0x24, 0x2f, 0x28, 0x14, 0xa9, 0x29, 0x21, 0x10, 0xcf, 0x56, 0x60, 0x32, 0x4d, 0x40, - 0x58, 0x93, 0x2f, 0x5e, 0xdf, 0x14, 0x0b, 0x04, 0xa3, 0x20, 0x67, 0xdc, 0xf3, 0xd8, 0xe3, 0xaf, - 0x5c, 0x30, 0xa4, 0x02, 0xc2, 0x02, 0xa3, 0xbb, 0xb6, 0x73, 0xf7, 0x37, 0x4b, 0x63, 0x6c, 0x42, - 0x28, 0xd4, 0x62, 0xd1, 0x28, 0x3f, 0xfa, 0xe8, 0xb8, 0x35, 0xd0, 0x66, 0xc3, 0x2d, 0xf5, 0x2b, - 0x00, 0x69, 0x99, 0x1c, 0xc3, 0xa9, 0xf8, 0x44, 0x0c, 0x8b, 0xdc, 0x4e, 0x99, 0xa8, 0xee, 0xf2, - 0xe2, 0x33, 0x57, 0x07, 0x16, 0xc8, 0x0e, 0xc6, 0x47, 0xe5, 0x98, 0xd8, 0xa2, 0x60, 0x9b, 0x41, - 0x8a, 0xb9, 0x0f, 0xf8, 0x9b, 0xab, 0x4d, 0xfc, 0x51, 0xeb, 0xf3, 0xe9, 0xa4, 0x79, 0x76, 0x09, - 0x91, 0x30, 0xb7, 0x3d, 0x4f, 0x39, 0xab, 0xcc, 0xd1, 0x84, 0x19, 0xcf, 0x71, 0xa1, 0x8a, 0x55, - 0xb9, 0x16, 0x8b, 0xbe, 0x85, 0x57, 0x4f, 0x9d, 0x4c, 0x86, 0xce, 0x02, 0xd1, 0xeb, 0x75, 0xe1, - 0xd7, 0x74, 0x20, 0xc2, 0xe5, 0xdf, 0x4e, 0xc5, 0x1d, 0x1e, 0x2e, 0xc8, 0x09, 0xc8, 0xc7, 0x97, - 0xf4, 0x96, 0xe6, 0x20, 0x4e, 0x4d, 0x72, 0xfa, 0x76, 0x5e, 0x49, 0x27, 0x75, 0x78, 0xf7, 0xee, - 0x16, 0x87, 0xe6, 0xbb, 0x5b, 0x79, 0x02, 0xb1, 0x7e, 0x46, 0x29, 0x82, 0x91, 0x54, 0x7e, 0x1b, - 0xf6, 0x20, 0xcc, 0x57, 0x30, 0x0d, 0x87, 0xd7, 0x7b, 0xed, 0x51, 0xae, 0xca, 0xfc, 0xf3, 0x39, - 0xbd, 0x25, 0xec, 0x77, 0xeb, 0x54, 0xeb, 0x70, 0x3a, 0x41, 0xd5, 0xbb, 0x63, 0x6e, 0xf7, 0x20, - 0x31, 0xb9, 0x37, 0x1c, 0xc1, 0x84, 0x8f, 0x47, 0xe9, 0x30, 0xa3, 0xab, 0x32, 0x1c, 0x09, 0xaf, - 0x04, 0xb0, 0x0f, 0xad, 0xcc, 0xa7, 0x7e, 0x54, 0x87, 0xdf, 0xda, 0xe8, 0x44, 0x8e, 0xf3, 0xa9, - 0xea, 0x47, 0x63, 0x9c, 0xbe, 0xe9, 0x61, 0x82, 0x11, 0xa1, 0x54, 0xf4, 0x5a, 0xef, 0x22, 0xde, - 0x4e, 0x05, 0x02, 0xeb, 0xc0, 0x83, 0x6d, 0x87, 0x70, 0x2f, 0x82, 0xd8, 0x29, 0xb5, 0xe3, 0x3f, - 0x56, 0x38, 0x5a, 0x95, 0xf2, 0x94, 0x56, 0xcc, 0xae, 0xe1, 0x16, 0x37, 0x92, 0xa0, 0xe6, 0xa6, - 0x2d, 0x0c, 0xf6, 0xaf, 0x14, 0x36, 0x30, 0xc2, 0x28, 0x87, 0x1d, 0x2c, 0x2c, 0xa7, 0x7f, 0x08, - 0xbf, 0xd1, 0xe5, 0xdc, 0xa9, 0x73, 0xdd, 0xa9, 0xcd, 0xfe, 0x74, 0x3a, 0xda, 0x8c, 0x10, 0x24, - 0x1a, 0xc0, 0xc4, 0xd8, 0xdd, 0xc7, 0x7c, 0xb3, 0xd3, 0x61, 0xff, 0xe5, 0xc2, 0xe3, 0x32, 0xd2, - 0xda, 0xca, 0x98, 0x64, 0xa3, 0x76, 0xb6, 0x1b, 0xe1, 0xb8, 0xe6, 0x98, 0xc7, 0x99, 0x18, 0xff, - 0xb9, 0xc6, 0x82, 0x24, 0xef, 0x4e, 0xa8, 0xc1, 0x5c, 0x1b, 0xc7, 0x2b, 0xeb, 0x46, 0x69, 0xb1, - 0x72, 0xf4, 0x23, 0x60, 0x32, 0x66, 0xd5, 0x2e, 0x4c, 0x6f, 0xe1, 0x10, 0x3c, 0xd8, 0x95, 0xa3, - 0x68, 0x8f, 0xae, 0x9d, 0x22, 0x8c, 0xb0, 0x6b, 0x28, 0x73, 0x6a, 0xab, 0x22, 0x6e, 0x44, 0x88, - 0x0b, 0xa2, 0xb6, 0x86, 0x28, 0xc4, 0x2d, 0xac, 0x74, 0x20, 0x3a, 0xc8, 0xbc, 0x8a, 0x1a, 0x04, - 0xce, 0xc6, 0xac, 0xcd, 0xa9, 0xb5, 0xdc, 0xed, 0x1a, 0x6f, 0xf7, 0xb0, 0x1b, 0xb5, 0xb8, 0x3b, - 0x2f, 0x49, 0x55, 0xda, 0xa9, 0xd0, 0x23, 0x1a, 0x11, 0x8b, 0x7d, 0x2e, 0xc7, 0xf5, 0x1d, 0x68, - 0x71, 0x99, 0x85, 0x1f, 0x81, 0xe2, 0x33, 0x7a, 0xb9, 0x68, 0x7f, 0xf2, 0xc6, 0x57, 0x04, 0x82, - 0x3b, 0x2d, 0x6c, 0xf4, 0x08, 0x6c, 0xda, 0x71, 0x91, 0xa7, 0x29, 0x7c, 0x3b, 0xff, 0x15, 0x3b, - 0x66, 0x3e, 0x12, 0x57, 0xc3, 0xdb, 0x69, 0x5e, 0xc4, 0x9a, 0x85, 0x98, 0x66, 0x1a, 0x3c, 0x12, - 0x3b, 0xf3, 0x42, 0xb9, 0xa7, 0xb4, 0x83, 0x7e, 0x6a, 0x44, 0xcf, 0xa3, 0x26, 0x56, 0xde, 0x51, - 0x0b, 0x32, 0x9e, 0xc6, 0xda, 0x29, 0xd7, 0x82, 0xe1, 0xed, 0xf7, 0x0c, 0x56, 0x84, 0x83, 0x73, - 0xf7, 0x1e, 0x64, 0xac, 0x52, 0x02, 0x56, 0x62, 0x46, 0x0d, 0xa8, 0x3b, 0x03, 0x72, 0x87, 0xc8, - 0x54, 0x44, 0x78, 0x90, 0x78, 0xfb, 0x2f, 0xbe, 0x63, 0xa6, 0xa0, 0x9f, 0x56, 0xe1, 0xdd, 0xb5, - 0x27, 0xf8, 0x4b, 0xa1, 0xef, 0x36, 0x05, 0x1f, 0xc3, 0xaa, 0xad, 0xc3, 0xb6, 0xc3, 0xc2, 0x5d, - 0xdf, 0xed, 0x76, 0x63, 0x73, 0xa9, 0xc0, 0x06, 0x90, 0x13, 0xcb, 0x50, 0xa3, 0xca, 0xe5, 0xa8, - 0x51, 0xe5, 0x62, 0x43, 0x0c, 0x32, 0x8c, 0xba, 0x0e, 0xda, 0xce, 0x81, 0xb3, 0xab, 0xbe, 0xbe, - 0x34, 0x8f, 0x01, 0xb0, 0x2a, 0x5b, 0xa9, 0x23, 0x46, 0xd5, 0x55, 0x03, 0xc1, 0xaa, 0xdc, 0x00, - 0xc1, 0xaa, 0x2a, 0x28, 0x01, 0x5f, 0x1b, 0x51, 0x59, 0x1a, 0x48, 0x71, 0x32, 0xa5, 0xe0, 0xbd, - 0xd2, 0x16, 0x88, 0xba, 0x6f, 0xe7, 0x6b, 0x11, 0xea, 0x16, 0xed, 0x5f, 0x7a, 0xbd, 0xe6, 0x4b, - 0xeb, 0x46, 0xcf, 0xc7, 0x35, 0xdd, 0x83, 0xc0, 0x75, 0xa6, 0x87, 0xa2, 0x7f, 0xf7, 0x60, 0x59, - 0x5f, 0x1c, 0x7b, 0xb8, 0xd0, 0xd8, 0xbe, 0x8a, 0xbb, 0x5f, 0x37, 0x6a, 0x6c, 0xf8, 0x31, 0x07, - 0x7b, 0xcc, 0x29, 0x1a, 0x01, 0x8f, 0x7d, 0x3b, 0xd7, 0xb3, 0x48, 0x2c, 0xe8, 0x22, 0x84, 0xbc, - 0xcb, 0x6b, 0x25, 0x1b, 0x7b, 0xcb, 0x06, 0xe1, 0xcc, 0x61, 0x3a, 0x51, 0xe8, 0x4d, 0xc8, 0x1c, - 0x8a, 0x4e, 0x19, 0x69, 0x2b, 0x7a, 0x94, 0xc2, 0x54, 0xae, 0x2d, 0xd1, 0x16, 0x6b, 0xa8, 0xed, - 0x08, 0xa0, 0xf2, 0x14, 0xe4, 0x06, 0xb0, 0x34, 0x4d, 0x63, 0xa1, 0x56, 0x25, 0x2c, 0x91, 0xc8, - 0x16, 0x4a, 0x09, 0xcb, 0x2c, 0x5f, 0xe0, 0xae, 0x71, 0xda, 0x46, 0xfd, 0x3f, 0x8f, 0xb8, 0x4c, - 0x70, 0x4e, 0x0d, 0xb4, 0x65, 0x3c, 0x91, 0x65, 0xf6, 0x80, 0xaf, 0x82, 0x56, 0xa6, 0x72, 0x7b, - 0x16, 0xb7, 0xae, 0xde, 0xa7, 0x91, 0x17, 0x77, 0x35, 0x7a, 0x57, 0xf7, 0xc5, 0x90, 0xe6, 0xf4, - 0xcb, 0x8b, 0x0b, 0xb5, 0x69, 0x34, 0x7b, 0x75, 0x29, 0xc8, 0x71, 0x1b, 0x9a, 0xf1, 0x32, 0x28, - 0xe3, 0xaf, 0x84, 0x2d, 0x7e, 0xb3, 0xff, 0xec, 0xc7, 0x06, 0xc1, 0xc3, 0x9f, 0x44, 0x21, 0x4e, - 0x11, 0xaa, 0xf7, 0x2b, 0x01, 0x88, 0x29, 0x8f, 0xaf, 0xc4, 0x1e, 0xa6, 0x3c, 0xbe, 0x12, 0x76, - 0x98, 0xf2, 0x58, 0x87, 0x38, 0x2c, 0x27, 0x92, 0x2b, 0xc9, 0x9d, 0xc0, 0x10, 0x4c, 0xb5, 0xdb, - 0x8c, 0xba, 0x6b, 0xde, 0x6f, 0x63, 0x63, 0xcc, 0x96, 0x10, 0x31, 0xda, 0x5c, 0x8b, 0x1d, 0x6d, - 0x63, 0x23, 0x5f, 0xe8, 0x6c, 0xc0, 0x7f, 0x62, 0xc3, 0x46, 0x8e, 0x6e, 0xd4, 0xfd, 0x9a, 0x57, - 0x1e, 0x81, 0xfa, 0xe0, 0x0b, 0xe2, 0x9f, 0x19, 0x82, 0x56, 0x51, 0x49, 0x67, 0x6a, 0xc3, 0x2e, - 0xee, 0x3c, 0xee, 0x21, 0xce, 0x95, 0x5f, 0xb6, 0xa1, 0x5c, 0x61, 0x36, 0x04, 0xb4, 0x80, 0x3f, - 0xe8, 0x04, 0xc1, 0xc5, 0x71, 0x47, 0x08, 0x09, 0x03, 0x68, 0x65, 0x0e, 0x66, 0xed, 0x45, 0x64, - 0x02, 0x85, 0xbd, 0x3a, 0x49, 0xdf, 0xd4, 0xdd, 0xe5, 0x53, 0xa1, 0x4e, 0x53, 0x2a, 0xc3, 0x25, - 0x5a, 0x26, 0x0e, 0x16, 0xf8, 0x64, 0x5a, 0x94, 0xb0, 0xbc, 0x7b, 0x47, 0x8a, 0x6c, 0xb1, 0x23, - 0x9b, 0x8b, 0x9b, 0x7f, 0x9b, 0x9c, 0x73, 0xb2, 0xc3, 0x59, 0xaa, 0xd9, 0xd2, 0x95, 0x37, 0x65, - 0x3f, 0xdb, 0xd9, 0x09, 0xa0, 0x4e, 0xdc, 0x2d, 0xb0, 0x4c, 0x17, 0x5f, 0xa0, 0x63, 0x90, 0xe9, - 0x72, 0xc7, 0xfd, 0xc4, 0x55, 0xf1, 0x87, 0x82, 0x74, 0xd9, 0x0e, 0x2b, 0xfb, 0x0a, 0xb7, 0x34, - 0x8c, 0x56, 0x4b, 0xaf, 0x2c, 0xd3, 0x22, 0x9c, 0xee, 0x22, 0x5c, 0x61, 0xd0, 0x4f, 0xf5, 0x81, - 0x2c, 0xde, 0xde, 0x24, 0xcb, 0x7c, 0x8b, 0x9d, 0x5a, 0xd1, 0x83, 0x19, 0x78, 0x3c, 0x9f, 0xd8, - 0x11, 0x83, 0x15, 0xbc, 0x3e, 0x94, 0xbd, 0xcd, 0xb0, 0xd3, 0x19, 0x06, 0x99, 0x3e, 0x41, 0x81, - 0xbe, 0xc2, 0x13, 0x21, 0xc3, 0x9a, 0x63, 0xbd, 0x08, 0x90, 0x9c, 0x4e, 0x7a, 0x30, 0x38, 0x2f, - 0x62, 0xbb, 0xc5, 0x3e, 0x99, 0x57, 0x74, 0x30, 0x71, 0x66, 0x73, 0xf0, 0xb0, 0x3d, 0xd7, 0xb6, - 0x68, 0xc1, 0xda, 0x7d, 0x92, 0xb2, 0x4b, 0xa8, 0xd3, 0xee, 0x78, 0x94, 0x3c, 0x16, 0xa0, 0xef, - 0x77, 0xc3, 0x28, 0xc4, 0xdb, 0xe5, 0xfa, 0xe5, 0xc5, 0x4d, 0xe1, 0xbe, 0x75, 0x5e, 0x7d, 0xaa, - 0xce, 0xec, 0xc8, 0xc6, 0x5e, 0xbc, 0x2c, 0x8d, 0x89, 0x41, 0x49, 0x9d, 0x11, 0x66, 0x79, 0xf7, - 0x8a, 0xb4, 0xb5, 0x98, 0xe6, 0xd8, 0xdc, 0x6f, 0x2d, 0x7e, 0xd8, 0x5d, 0x56, 0x74, 0xf7, 0x4d, - 0x4b, 0x39, 0x31, 0x82, 0x53, 0x1a, 0x1b, 0x63, 0x2d, 0x15, 0x27, 0x25, 0x03, 0xa0, 0xb8, 0x05, - 0x42, 0xcc, 0x34, 0xdd, 0xde, 0x65, 0xe3, 0xa2, 0x36, 0xa4, 0x7b, 0x7d, 0x53, 0xa8, 0x84, 0x65, - 0xc3, 0x2e, 0xef, 0xd7, 0xdb, 0xd2, 0xb6, 0xf4, 0x77, 0xa3, 0xef, 0x4b, 0xdd, 0x83, 0x76, 0x96, - 0x1f, 0x36, 0xcb, 0xd1, 0x69, 0xe9, 0x4d, 0x32, 0xfe, 0xc5, 0x4c, 0x72, 0x9d, 0x4b, 0x1f, 0xbd, - 0x95, 0xf8, 0xae, 0x5f, 0x8a, 0x0a, 0x42, 0x51, 0x5d, 0x5a, 0x70, 0x8d, 0xa1, 0xfd, 0x66, 0xe6, - 0xbc, 0x1a, 0xf8, 0x78, 0x70, 0x88, 0x90, 0x9e, 0xa0, 0x8e, 0xca, 0x35, 0x14, 0xd7, 0x4b, 0x27, - 0x4d, 0xde, 0x3d, 0x0f, 0xda, 0x67, 0x5a, 0x6c, 0x92, 0xbb, 0xca, 0xaf, 0xfa, 0x98, 0xb9, 0x29, - 0x4a, 0xb9, 0xb6, 0xc6, 0x82, 0xc9, 0xf7, 0x26, 0xbb, 0x30, 0x31, 0xd7, 0x7c, 0x99, 0x4d, 0x46, - 0x35, 0x3f, 0xdf, 0x13, 0x17, 0x7a, 0x44, 0xae, 0x78, 0x48, 0x33, 0x25, 0x4c, 0x6b, 0xa3, 0xd5, - 0x76, 0x56, 0xc6, 0xe8, 0xb2, 0x05, 0xfd, 0x08, 0x7f, 0x23, 0x04, 0xfa, 0xd2, 0xed, 0x1f, 0x7f, - 0xaa, 0x8a, 0xb3, 0x90, 0xab, 0x13, 0x47, 0x61, 0x11, 0xa3, 0xdd, 0x37, 0xbb, 0x88, 0x23, 0xad, - 0x2d, 0x9b, 0x5e, 0xe3, 0x58, 0x83, 0x7a, 0x00, 0x48, 0x5b, 0xa5, 0xc5, 0x01, 0x03, 0xa3, 0x80, - 0xd5, 0x35, 0xb9, 0x27, 0x82, 0x70, 0x04, 0x2a, 0xf4, 0x2e, 0x88, 0x1f, 0x71, 0x37, 0x9a, 0xdd, - 0xf7, 0x35, 0x63, 0xd2, 0x2a, 0x6e, 0x9b, 0xd3, 0xab, 0x9b, 0xc9, 0x04, 0xd4, 0x60, 0xa2, 0x99, - 0x5d, 0x2a, 0xac, 0x99, 0x7e, 0xb7, 0x05, 0x36, 0x1c, 0x2d, 0x4c, 0xa7, 0x0b, 0xfb, 0x8b, 0x55, - 0xd8, 0x87, 0x87, 0x0c, 0xfd, 0xe9, 0x1d, 0x9a, 0x0c, 0xfa, 0xc0, 0x6a, 0x79, 0x8e, 0x08, 0xd5, - 0x58, 0xa4, 0xc3, 0x9f, 0xab, 0x59, 0x83, 0x08, 0x27, 0x16, 0x17, 0xbb, 0xc9, 0x34, 0x9b, 0x56, - 0x22, 0xfd, 0xb2, 0x59, 0x0d, 0x66, 0xab, 0xaa, 0x90, 0x1d, 0x11, 0xe4, 0x89, 0x2e, 0xf8, 0x9f, - 0x29, 0xb5, 0xe9, 0x1c, 0x1e, 0x61, 0xba, 0x77, 0x14, 0xec, 0x96, 0xfc, 0x8c, 0xeb, 0xac, 0xef, - 0xe9, 0xb4, 0x46, 0x67, 0x90, 0x75, 0x74, 0xea, 0xa4, 0x94, 0x20, 0xbb, 0x46, 0x96, 0xaa, 0x63, - 0xd5, 0x86, 0x20, 0x05, 0x7a, 0x7f, 0x23, 0x75, 0x29, 0x92, 0xe2, 0x00, 0x7f, 0x3a, 0x03, 0xf5, - 0xe8, 0xa8, 0x83, 0x47, 0x8a, 0xa5, 0xa1, 0xf3, 0x75, 0x49, 0x68, 0xbc, 0x23, 0x98, 0x32, 0xd2, - 0x97, 0x2c, 0x46, 0x7c, 0x97, 0x66, 0x94, 0x65, 0x3c, 0x35, 0x20, 0x13, 0x58, 0xa5, 0xe3, 0xc9, - 0xb7, 0xa4, 0xc1, 0xe9, 0x0e, 0x09, 0x94, 0x2c, 0xb1, 0x07, 0x0f, 0x5e, 0xc5, 0x75, 0x9e, 0x60, - 0x82, 0xd0, 0xe7, 0xeb, 0xbc, 0x36, 0x47, 0xef, 0xa1, 0xe5, 0xb4, 0xe4, 0xd2, 0x8c, 0x81, 0x78, - 0xd8, 0xd0, 0x86, 0x20, 0x8d, 0x17, 0xca, 0x2d, 0xce, 0x8e, 0x0d, 0x9a, 0xa3, 0x16, 0xf7, 0x56, - 0x7e, 0x9d, 0xce, 0x83, 0xa8, 0x93, 0x5b, 0xa8, 0x6d, 0x34, 0x13, 0x72, 0xb3, 0xfb, 0x36, 0x50, - 0xf9, 0x2a, 0x28, 0x1d, 0xec, 0x70, 0xd8, 0x1c, 0x61, 0x47, 0xe9, 0x3c, 0x1b, 0xd1, 0x50, 0x7f, - 0x80, 0x94, 0x35, 0xed, 0xf0, 0x7c, 0xa1, 0x80, 0xd0, 0xca, 0x35, 0x74, 0x56, 0x8d, 0x41, 0x6d, - 0x30, 0xa9, 0xf0, 0xd5, 0x31, 0x43, 0x37, 0x76, 0x46, 0x84, 0x0f, 0x96, 0x89, 0xb2, 0x64, 0xb0, - 0x1c, 0x35, 0xda, 0x57, 0xce, 0x32, 0xac, 0xcb, 0x74, 0x44, 0x93, 0x4c, 0xce, 0xa2, 0xbf, 0x78, - 0xe6, 0xaf, 0x29, 0xfc, 0x29, 0x3a, 0x41, 0x2a, 0x43, 0xdf, 0x08, 0x0f, 0x48, 0x1f, 0x5b, 0xfa, - 0xf2, 0xff, 0xb1, 0xf4, 0xaf, 0xf8, 0xa3, 0xaa, 0x02, 0x78, 0x62, 0x25, 0x17, 0xb6, 0xc7, 0x94, - 0x1f, 0x3e, 0xe5, 0x3d, 0xb2, 0xdc, 0xe7, 0x35, 0xb8, 0x3f, 0x7d, 0xf4, 0x28, 0xe1, 0x7f, 0x9d, - 0x67, 0x0b, 0x05, 0xb8, 0xac, 0x11, 0x3b, 0x21, 0xcb, 0x15, 0x54, 0xd3, 0x45, 0x05, 0x7e, 0x0c, - 0xc1, 0x15, 0x55, 0x21, 0xbd, 0x9e, 0x35, 0xb1, 0x7f, 0x5b, 0x20, 0x7f, 0xcf, 0xeb, 0x90, 0xbf, - 0x4e, 0x31, 0xc9, 0x73, 0xbf, 0x5c, 0x8e, 0xfc, 0x0b, 0x5f, 0x42, 0x17, 0x9b, 0x99, 0x9a, 0xed, - 0x8c, 0xb5, 0xab, 0xb0, 0x80, 0x3b, 0x75, 0x35, 0xe9, 0x2b, 0x50, 0x82, 0xcb, 0x26, 0x4a, 0xb0, - 0x82, 0x77, 0x53, 0xe0, 0x79, 0x2b, 0xf9, 0x60, 0x98, 0xc2, 0x0b, 0x55, 0x37, 0x04, 0xe1, 0x31, - 0x94, 0x19, 0xd4, 0x58, 0xc4, 0x17, 0xb3, 0xa1, 0x51, 0x9c, 0xaa, 0x3c, 0x50, 0xe4, 0xf5, 0x1b, - 0xd0, 0xc1, 0x34, 0x97, 0xb5, 0xff, 0xc1, 0x1b, 0xd8, 0x8c, 0x5a, 0x45, 0x43, 0x27, 0xee, 0x2c, - 0x2f, 0x7b, 0x49, 0x65, 0x33, 0x05, 0xb1, 0xca, 0xf1, 0x7b, 0x2a, 0x0b, 0x61, 0x57, 0xe5, 0xf7, - 0xb4, 0xad, 0x26, 0x4f, 0x8f, 0x5a, 0x97, 0xd8, 0x2b, 0xef, 0xc8, 0x4f, 0x05, 0x4e, 0x77, 0x41, - 0x87, 0xfb, 0x30, 0x14, 0xf0, 0xa4, 0xd6, 0x2a, 0x1f, 0x2f, 0x8f, 0x81, 0x9b, 0x1a, 0x7a, 0x9f, - 0x2e, 0x00, 0x62, 0x6b, 0xd2, 0xc6, 0xbc, 0xad, 0x36, 0x66, 0xdc, 0x89, 0xb7, 0xb5, 0xb0, 0xf2, - 0x35, 0xd3, 0x18, 0xbe, 0x31, 0xf0, 0x4e, 0xa1, 0xaf, 0x3a, 0x33, 0xad, 0xf4, 0x82, 0x7c, 0x9e, - 0x17, 0x5e, 0x8c, 0xed, 0xef, 0xfd, 0x7c, 0x8b, 0x04, 0x9c, 0x20, 0xb5, 0x74, 0xee, 0xa6, 0xd5, - 0x55, 0x87, 0x1c, 0x27, 0xe0, 0xc3, 0xff, 0x84, 0xe5, 0x5b, 0xde, 0x85, 0x91, 0x61, 0x8b, 0x47, - 0xce, 0xfd, 0x71, 0x59, 0x13, 0x6f, 0xe0, 0xf1, 0x55, 0x69, 0x04, 0x1c, 0xac, 0xf7, 0xc3, 0x43, - 0xf5, 0xd5, 0xa2, 0x59, 0x5b, 0x7f, 0xcc, 0xf2, 0x9e, 0x55, 0x12, 0x78, 0x38, 0x7a, 0x79, 0x72, - 0xdc, 0x81, 0xae, 0xbc, 0x1e, 0x66, 0x17, 0xcc, 0x48, 0x0d, 0x63, 0x63, 0x58, 0x88, 0xa1, 0x4a, - 0x31, 0x9c, 0x4d, 0x35, 0xd9, 0x11, 0x25, 0x82, 0x00, 0x32, 0xc1, 0xca, 0x78, 0x47, 0x4b, 0xbe, - 0xd3, 0xb5, 0xbf, 0xd3, 0x55, 0x40, 0x8a, 0x6d, 0xbb, 0x70, 0xe7, 0xfa, 0x39, 0x48, 0xb8, 0x38, - 0x46, 0xaa, 0xbc, 0x73, 0xfc, 0xba, 0x63, 0x91, 0x9d, 0xf2, 0x4e, 0x75, 0x51, 0xb7, 0x2e, 0x6b, - 0x51, 0x8a, 0xda, 0x00, 0x7a, 0xe4, 0xc2, 0x16, 0xa8, 0x7a, 0x07, 0x11, 0x09, 0x54, 0x5d, 0x2d, - 0x50, 0x61, 0x7f, 0x8a, 0x18, 0x14, 0xff, 0x77, 0xd6, 0x71, 0xdf, 0x89, 0x1f, 0x2c, 0x8e, 0x96, - 0xc9, 0x05, 0xd4, 0xda, 0xeb, 0x65, 0x03, 0xf4, 0xe6, 0x3f, 0x61, 0xd1, 0x40, 0xc1, 0x5e, 0xae, - 0xb4, 0x59, 0xaa, 0x73, 0x08, 0xac, 0xad, 0x96, 0x0c, 0x78, 0x30, 0x62, 0x19, 0xb7, 0x97, 0x7f, - 0x92, 0x5a, 0x63, 0x7b, 0x47, 0xec, 0x6c, 0x5f, 0x88, 0xd4, 0xe5, 0x3b, 0x38, 0xf1, 0x29, 0x7c, - 0x63, 0xba, 0x03, 0xe6, 0x4c, 0xd8, 0x8e, 0xb7, 0x37, 0xb5, 0x77, 0x9f, 0xb8, 0x06, 0xef, 0xed, - 0x85, 0x5d, 0xf4, 0xf6, 0xee, 0xbf, 0x1b, 0x16, 0xaa, 0x52, 0xde, 0x68, 0xc6, 0x44, 0x81, 0x63, - 0x6b, 0x44, 0xe0, 0x7b, 0xcf, 0xb4, 0xbe, 0xaa, 0xbe, 0xbb, 0x72, 0xc0, 0x58, 0xc0, 0x9a, 0x29, - 0x94, 0x4d, 0xcc, 0xa4, 0x6e, 0x08, 0xe4, 0x92, 0x49, 0xf7, 0x68, 0xb6, 0x77, 0xb1, 0xd9, 0xbc, - 0xdf, 0x76, 0x37, 0x08, 0xe6, 0xeb, 0x97, 0x51, 0x5e, 0x31, 0x47, 0xbb, 0xeb, 0x74, 0xcb, 0x68, - 0x3f, 0xa1, 0x58, 0x66, 0x6e, 0xc7, 0xf7, 0xd3, 0xcc, 0x3a, 0x15, 0x95, 0xa2, 0xa9, 0x1f, 0x29, - 0x58, 0xc6, 0x73, 0x1b, 0x96, 0x6a, 0x16, 0xe1, 0xa4, 0x09, 0x40, 0x27, 0x9f, 0xe4, 0xe3, 0x1b, - 0x3c, 0x05, 0xaf, 0xd4, 0xf5, 0x3c, 0x02, 0xad, 0x06, 0x7d, 0xe8, 0x02, 0x7d, 0x9a, 0x04, 0xdd, - 0x4b, 0xb3, 0x2e, 0xee, 0x13, 0x02, 0x22, 0x44, 0xe6, 0x20, 0xf2, 0x4c, 0xdb, 0xf8, 0x5c, 0x77, - 0x2c, 0x32, 0x72, 0xf7, 0x91, 0x47, 0xee, 0xb3, 0xf2, 0x2d, 0x7b, 0xa3, 0xb5, 0x37, 0x44, 0x85, - 0xd7, 0xa9, 0xea, 0x76, 0x55, 0x65, 0xc0, 0xab, 0x5a, 0x11, 0x63, 0x61, 0xbd, 0xc7, 0xf7, 0x72, - 0x7a, 0xb4, 0x1b, 0xf7, 0xe8, 0x95, 0x01, 0x7c, 0xc4, 0xe3, 0x50, 0xfc, 0xdb, 0xb1, 0xce, 0x5f, - 0x5f, 0x44, 0xdf, 0x79, 0x16, 0xa9, 0x3d, 0xbd, 0xee, 0x6c, 0xaa, 0xb9, 0xb8, 0x6a, 0x0a, 0x1a, - 0x7b, 0xf4, 0xf4, 0x0b, 0x25, 0x9f, 0xb1, 0x56, 0x59, 0xbc, 0x6f, 0xeb, 0x26, 0x20, 0x0d, 0x6e, - 0x8b, 0x06, 0x38, 0x79, 0x74, 0x29, 0x0b, 0xd0, 0xae, 0x6b, 0x50, 0x2a, 0x46, 0xa4, 0xec, 0x74, - 0x6a, 0xe2, 0x01, 0xd7, 0x0b, 0x0f, 0xa3, 0x74, 0x0d, 0xd7, 0x6b, 0x16, 0xe9, 0x2e, 0x8c, 0x37, - 0x6b, 0x86, 0xb1, 0x99, 0x6f, 0xf3, 0x25, 0x44, 0x1d, 0x7b, 0xb8, 0x47, 0x99, 0x12, 0x29, 0xb3, - 0x68, 0xb6, 0xb9, 0x29, 0xd1, 0xeb, 0x9b, 0x82, 0xce, 0x3a, 0x96, 0x95, 0xf8, 0x83, 0xb6, 0x82, - 0x2c, 0x8b, 0xf1, 0x0d, 0x94, 0x70, 0xa7, 0xbb, 0xd8, 0xf4, 0x8b, 0xaa, 0xbf, 0x9f, 0x41, 0x7f, - 0xb7, 0x1e, 0x21, 0xba, 0xab, 0xba, 0x23, 0xe3, 0xa8, 0xc2, 0xf2, 0x92, 0xfe, 0xfc, 0xe0, 0x60, - 0x7f, 0x8f, 0x57, 0xf5, 0x68, 0xaf, 0x07, 0xfb, 0xae, 0x98, 0xc1, 0x8f, 0xae, 0xad, 0x53, 0x93, - 0xf9, 0xae, 0x31, 0x14, 0xb4, 0x28, 0x53, 0x37, 0xdf, 0x3d, 0xed, 0x46, 0xd0, 0xf3, 0xe5, 0x92, - 0x9a, 0x7e, 0x4d, 0xc9, 0x4d, 0x33, 0xaa, 0xb2, 0xeb, 0x92, 0x47, 0xed, 0x25, 0xff, 0xb0, 0x59, - 0xc1, 0x1d, 0x2b, 0xe1, 0xea, 0xf2, 0xaf, 0x18, 0x7d, 0xcd, 0xed, 0xe3, 0x31, 0xa3, 0x6f, 0x29, - 0x65, 0x4e, 0x63, 0x44, 0x3c, 0xd5, 0x6b, 0xc0, 0x92, 0x23, 0x1c, 0xb9, 0x74, 0xcf, 0xde, 0xdf, - 0x5c, 0xa3, 0x2d, 0xdf, 0xf2, 0x03, 0xfd, 0x98, 0xdf, 0x74, 0x32, 0x01, 0x1a, 0xd6, 0xb0, 0xea, - 0x80, 0xf0, 0x08, 0x82, 0x59, 0x4f, 0xda, 0x06, 0x4a, 0x14, 0x11, 0x30, 0x79, 0x67, 0xa8, 0xa5, - 0xb6, 0x2d, 0x09, 0x86, 0xf3, 0x9f, 0x5d, 0xff, 0xd5, 0x19, 0x73, 0x37, 0x8a, 0xac, 0xbd, 0x80, - 0xcc, 0x10, 0x2e, 0xdf, 0xb0, 0x65, 0xa8, 0x8e, 0xd4, 0xc2, 0xcc, 0x9b, 0x83, 0xde, 0x1a, 0xfe, - 0x53, 0x4b, 0xbc, 0xbd, 0x99, 0xcf, 0x97, 0xa5, 0x67, 0xa0, 0x19, 0x95, 0x78, 0x5d, 0xcb, 0xea, - 0xd8, 0xcd, 0x66, 0x55, 0x05, 0x69, 0x69, 0xd9, 0x65, 0x92, 0x87, 0xed, 0xed, 0x61, 0x6d, 0xef, - 0xda, 0x3a, 0x37, 0x01, 0x8d, 0xa1, 0x8a, 0x53, 0x31, 0xa9, 0xfa, 0x9b, 0x2e, 0x93, 0x27, 0xd2, - 0xa6, 0xa4, 0x86, 0xeb, 0x86, 0x1f, 0x4e, 0x5b, 0xbf, 0x4c, 0xb6, 0x96, 0xcd, 0x3f, 0x2d, 0x07, - 0xa8, 0xfe, 0xf8, 0xb6, 0xe5, 0x72, 0x47, 0x22, 0xbf, 0xa8, 0xc3, 0xfb, 0x49, 0xe5, 0xc0, 0xd8, - 0xe6, 0xfb, 0xe6, 0x4d, 0x8b, 0x1d, 0xbc, 0x6a, 0x73, 0x9e, 0x94, 0xb1, 0x7b, 0x6d, 0xb1, 0x29, - 0x56, 0x2c, 0x13, 0x39, 0xa5, 0x21, 0x62, 0x56, 0xa1, 0x3c, 0xcf, 0x31, 0x1f, 0x71, 0x08, 0x63, - 0x7b, 0xa0, 0xc1, 0x26, 0xcf, 0x63, 0xca, 0x78, 0x17, 0x02, 0x51, 0x9b, 0x35, 0x48, 0x4a, 0x4d, - 0x40, 0x5a, 0x02, 0xa1, 0x0d, 0x6d, 0xd1, 0x47, 0x49, 0x33, 0x30, 0x0c, 0x5a, 0x31, 0xde, 0xc3, - 0x25, 0x87, 0xf2, 0xf6, 0xd5, 0x51, 0xe5, 0x76, 0xa0, 0x1b, 0x6d, 0x0e, 0x45, 0x8b, 0x3f, 0x9d, - 0x2d, 0x6a, 0x37, 0x2a, 0x99, 0x1b, 0xab, 0x74, 0xb8, 0xb1, 0x4a, 0xc2, 0xdc, 0xc2, 0x3b, 0xe6, - 0x04, 0xcb, 0x8f, 0x7e, 0xdb, 0x25, 0x5e, 0x62, 0x45, 0x00, 0x1b, 0x6e, 0xe8, 0x45, 0xd0, 0xb7, - 0x3d, 0x32, 0xab, 0xc6, 0xa7, 0xdf, 0xdc, 0xff, 0xb5, 0x5f, 0x2e, 0x41, 0xcb, 0x5b, 0xa0, 0x80, - 0x28, 0x21, 0x09, 0x12, 0x11, 0xae, 0x2e, 0x00, 0x63, 0xb2, 0x5b, 0xee, 0xd9, 0x58, 0x08, 0x62, - 0x01, 0xa2, 0xfc, 0x1c, 0xe8, 0x4d, 0x9b, 0xa8, 0xcb, 0xf1, 0xdd, 0x96, 0xa7, 0xee, 0x78, 0x6d, - 0xe5, 0x51, 0x1d, 0xd1, 0xb7, 0x4e, 0x94, 0x57, 0xa0, 0x0e, 0x9d, 0x1b, 0xd4, 0x21, 0xe6, 0xfb, - 0x66, 0x18, 0xb3, 0xb0, 0x6c, 0x36, 0x19, 0x5f, 0x0b, 0xe4, 0x26, 0x13, 0x4e, 0x93, 0x09, 0x62, - 0xb2, 0x72, 0x9a, 0x8c, 0xab, 0x88, 0x5e, 0xf5, 0x7c, 0xaa, 0xe3, 0xa0, 0x8d, 0x55, 0x86, 0xcc, - 0xc3, 0x3a, 0x82, 0x42, 0xc9, 0xda, 0xba, 0xb4, 0x85, 0x17, 0x7e, 0x4a, 0x89, 0x97, 0x40, 0x7e, - 0xfb, 0x15, 0x67, 0x19, 0x2d, 0x16, 0xa5, 0x05, 0x3e, 0xe0, 0xfa, 0xe5, 0x96, 0xf6, 0xe2, 0xc9, - 0xcc, 0x76, 0xee, 0x94, 0xd5, 0x93, 0x43, 0x79, 0x77, 0x96, 0x6b, 0x41, 0x94, 0xb3, 0x75, 0x20, - 0xca, 0x58, 0xc3, 0x68, 0x2b, 0x91, 0x3e, 0x09, 0x2e, 0x62, 0x32, 0xf4, 0x96, 0x75, 0xa4, 0xe7, - 0x42, 0x27, 0x5f, 0x4f, 0xad, 0x57, 0x35, 0x08, 0x65, 0x7b, 0x3c, 0xd4, 0xc0, 0x94, 0x67, 0x77, - 0x85, 0x83, 0x33, 0x87, 0x5d, 0x8b, 0x23, 0x92, 0xdc, 0xa5, 0xf1, 0x46, 0x40, 0x62, 0x0d, 0xb4, - 0x0c, 0x9b, 0x0d, 0x9d, 0xe0, 0x70, 0x10, 0xe3, 0x11, 0x56, 0xc3, 0x33, 0xb5, 0x44, 0xb6, 0xb4, - 0x0c, 0x86, 0xf2, 0x6d, 0x9c, 0x86, 0xd7, 0xd3, 0x78, 0x18, 0xe2, 0x25, 0x8c, 0x70, 0x54, 0x4c, - 0xe3, 0xd6, 0x7a, 0x13, 0x17, 0x9d, 0x06, 0x98, 0x86, 0x1e, 0xc9, 0x17, 0x0b, 0xc5, 0x93, 0xd3, - 0x82, 0x47, 0x3b, 0xde, 0x00, 0x8f, 0xf6, 0x62, 0x3d, 0x1e, 0x6d, 0x38, 0x6b, 0x8f, 0x83, 0x38, - 0xd2, 0xaa, 0x1b, 0x0a, 0x1a, 0x16, 0x90, 0x73, 0x32, 0x0e, 0xf9, 0x37, 0xe4, 0x90, 0x5c, 0xc8, - 0xdf, 0xf9, 0x24, 0x99, 0x2d, 0xf8, 0x27, 0x8c, 0x0c, 0xba, 0x00, 0xc6, 0x34, 0xd4, 0xc2, 0xbd, - 0x37, 0x50, 0xd8, 0x47, 0xfa, 0xd2, 0x83, 0x71, 0xf5, 0x18, 0xea, 0xf3, 0x82, 0x52, 0x87, 0xc0, - 0xce, 0xea, 0x10, 0xd8, 0xd2, 0x6d, 0xa2, 0x71, 0xa6, 0x1d, 0x0e, 0x9b, 0x7e, 0x05, 0x4d, 0x3f, - 0xe5, 0x94, 0x9c, 0x90, 0x27, 0x7e, 0x7a, 0x58, 0xe2, 0x4d, 0xc8, 0x87, 0x87, 0xf4, 0x88, 0x20, - 0xda, 0x15, 0x78, 0x07, 0xc2, 0x2a, 0xa9, 0x2c, 0x13, 0x8a, 0xc2, 0x17, 0x72, 0x0f, 0x33, 0x8e, - 0x3e, 0xdc, 0x6d, 0xcd, 0xf2, 0x28, 0x6b, 0x64, 0x62, 0x1c, 0x23, 0xf0, 0x4d, 0xdf, 0x86, 0xb7, - 0xe2, 0x84, 0x64, 0xe9, 0x1b, 0x3e, 0x3c, 0x6c, 0x35, 0xc2, 0x91, 0xe8, 0xd0, 0xce, 0xab, 0x73, - 0xa1, 0x56, 0x42, 0x89, 0xee, 0xd1, 0x3a, 0x34, 0x53, 0x1e, 0x9a, 0x43, 0x1e, 0x48, 0xe4, 0xb3, - 0x9e, 0x1e, 0x32, 0xe2, 0xb7, 0xb5, 0xbd, 0x39, 0x20, 0xe4, 0x65, 0x03, 0x84, 0xbc, 0xcf, 0x37, - 0xf2, 0x0d, 0xb4, 0x96, 0x6a, 0xdd, 0xa2, 0xd9, 0xe2, 0xcd, 0xd6, 0x9d, 0xca, 0xd6, 0x9d, 0x1e, - 0x56, 0xdc, 0x5c, 0xd3, 0xa3, 0xaa, 0xd1, 0x30, 0xca, 0x22, 0x50, 0xe9, 0xd6, 0x2d, 0x54, 0x67, - 0x14, 0xab, 0x3b, 0x03, 0xdb, 0x31, 0xe7, 0x21, 0x49, 0xc8, 0x2f, 0xc9, 0x34, 0x54, 0x8f, 0xf9, - 0xec, 0x63, 0x52, 0x2c, 0xb0, 0x9a, 0xe3, 0x26, 0xa6, 0x33, 0x35, 0x7e, 0x63, 0xa5, 0x1c, 0xaf, - 0xc1, 0x77, 0x77, 0x26, 0x4e, 0xb6, 0x7e, 0xe2, 0xe4, 0x7a, 0xe2, 0x54, 0xaa, 0x58, 0x30, 0x71, - 0x4a, 0xf9, 0x1b, 0x26, 0x4e, 0x16, 0x52, 0xa7, 0xd4, 0xb1, 0xac, 0x11, 0x09, 0x85, 0xa2, 0x54, - 0x2e, 0x46, 0xb8, 0x8d, 0x73, 0x8d, 0xae, 0x4f, 0xda, 0x03, 0xd3, 0x99, 0x68, 0xb9, 0xeb, 0x93, - 0x22, 0xe7, 0x99, 0x61, 0x9d, 0x1c, 0xb4, 0x29, 0x18, 0xa0, 0x4d, 0x5c, 0xa1, 0xe1, 0xf1, 0x1a, - 0x36, 0xbf, 0x29, 0x68, 0x62, 0xe6, 0x4c, 0x07, 0xde, 0x48, 0x57, 0x08, 0x10, 0x99, 0xb6, 0xd0, - 0xcb, 0x41, 0x65, 0xb5, 0xbb, 0x1b, 0x2e, 0xdb, 0x86, 0x71, 0xd8, 0xc1, 0x86, 0x12, 0x38, 0x1b, - 0x6e, 0xf5, 0x8b, 0xb8, 0x5d, 0xb1, 0x77, 0xe3, 0x4a, 0xb9, 0x74, 0x89, 0xaf, 0xed, 0xdd, 0x98, - 0xd5, 0xc7, 0x55, 0x79, 0x7d, 0x74, 0xb3, 0xfa, 0xb8, 0x3c, 0xa7, 0x9f, 0xa6, 0x2b, 0xf2, 0x81, - 0x75, 0x7b, 0xd9, 0xce, 0xd2, 0xcc, 0x67, 0x55, 0x81, 0xae, 0xdd, 0x02, 0x5d, 0xaf, 0x2a, 0x50, - 0xb7, 0xb7, 0x2a, 0xa3, 0x6e, 0xcf, 0xcd, 0xa9, 0x4b, 0xf2, 0xad, 0xb4, 0x9b, 0x1d, 0xa3, 0x35, - 0xa7, 0x9e, 0xe1, 0xe9, 0xaa, 0x1a, 0x96, 0x6e, 0x0d, 0xcb, 0xe9, 0xba, 0xdc, 0x3e, 0xcc, 0x56, - 0xe4, 0x56, 0xcd, 0xe2, 0x65, 0x03, 0xb6, 0x9e, 0x0f, 0x49, 0x5f, 0x68, 0x5e, 0x7b, 0x9c, 0xfc, - 0xb5, 0x74, 0xc8, 0x89, 0x2a, 0xae, 0x1a, 0x35, 0x17, 0x97, 0x27, 0x77, 0xc5, 0x8a, 0xf2, 0xc2, - 0x96, 0xbc, 0xf5, 0x08, 0x19, 0xa0, 0x25, 0xff, 0x1f, 0x8b, 0x55, 0xad, 0xbb, 0xd1, 0x6e, 0xef, - 0xde, 0x29, 0xbc, 0x4c, 0xdf, 0x16, 0x42, 0xfc, 0x21, 0xf4, 0xd5, 0x71, 0x4b, 0xd2, 0x9e, 0xe3, - 0x95, 0x4a, 0xaf, 0xf2, 0x60, 0x41, 0x67, 0xb4, 0x23, 0x72, 0xe8, 0x61, 0xa9, 0x1a, 0xd4, 0x70, - 0x11, 0x9a, 0x2b, 0x9d, 0xc2, 0xba, 0xad, 0x2c, 0x2f, 0x64, 0x36, 0xee, 0x79, 0xfa, 0x90, 0x2d, - 0xfa, 0xa5, 0x82, 0x34, 0x19, 0xac, 0x90, 0xb9, 0xab, 0xb7, 0xff, 0xd2, 0x85, 0xe1, 0x5b, 0xd9, - 0x89, 0x18, 0x58, 0x18, 0x1c, 0x75, 0x3c, 0x8d, 0x6d, 0x85, 0xa7, 0x51, 0xc7, 0xa2, 0x8d, 0xe5, - 0x50, 0xd8, 0xd6, 0x84, 0xb2, 0xf5, 0xa4, 0xe7, 0xcb, 0x92, 0xd6, 0x91, 0x8b, 0x2d, 0xdc, 0xda, - 0x96, 0x11, 0x31, 0xb9, 0x37, 0xad, 0x0e, 0x4b, 0xf5, 0xe4, 0xfe, 0x42, 0x4c, 0xcc, 0x05, 0x16, - 0x7a, 0xac, 0x77, 0xa5, 0x86, 0x58, 0x7e, 0x54, 0x45, 0x15, 0xb7, 0x5e, 0x2b, 0xda, 0xf2, 0x26, - 0xd5, 0x5d, 0x99, 0xc1, 0xe3, 0x2a, 0x0d, 0xc9, 0x62, 0x51, 0xaf, 0x16, 0x0e, 0xcf, 0xda, 0xe8, - 0x6c, 0x8c, 0xc9, 0x26, 0x74, 0x5e, 0x50, 0x1f, 0xe7, 0x08, 0x7f, 0xe3, 0xb7, 0x8d, 0xf2, 0xf2, - 0xbe, 0x35, 0x2f, 0x07, 0x4c, 0xab, 0x31, 0x6d, 0x34, 0x98, 0x56, 0x6b, 0x96, 0xd3, 0xf6, 0x2c, - 0x1b, 0x08, 0x5c, 0x8d, 0x6c, 0x19, 0x84, 0x07, 0x7a, 0x50, 0x22, 0x08, 0xa2, 0xe2, 0xfe, 0xf0, - 0x20, 0x8e, 0xf6, 0x03, 0x77, 0x12, 0x2d, 0x16, 0xf5, 0x0d, 0x5e, 0xc1, 0x6f, 0x11, 0x58, 0x8d, - 0xdc, 0xbc, 0xf7, 0xa9, 0xf7, 0x79, 0x6e, 0x8d, 0xf7, 0x93, 0x32, 0xee, 0xd9, 0x01, 0x3d, 0x08, - 0x90, 0x3f, 0xbb, 0xb0, 0xa3, 0xd7, 0x26, 0x8f, 0x53, 0x2c, 0x86, 0xc9, 0xc1, 0x62, 0x59, 0xe8, - 0x86, 0xf5, 0xb2, 0x95, 0xba, 0x6c, 0xf2, 0xc3, 0xac, 0x8b, 0xe5, 0xfb, 0xc9, 0xd6, 0x56, 0x25, - 0x3f, 0x2d, 0x83, 0x7a, 0x14, 0x24, 0x1f, 0xba, 0xf8, 0x10, 0xd6, 0x54, 0x35, 0xe7, 0xf3, 0xef, - 0xf2, 0xe6, 0x02, 0x88, 0x93, 0x5e, 0xd4, 0x27, 0x00, 0xc9, 0x13, 0x96, 0x6a, 0x8f, 0x80, 0x86, - 0x0b, 0xa4, 0xa2, 0x50, 0xe6, 0x1b, 0x1f, 0xf5, 0x51, 0x79, 0x5b, 0x5e, 0xb9, 0x21, 0x20, 0xa2, - 0x89, 0x76, 0x49, 0xb8, 0x43, 0x89, 0xcd, 0x7e, 0x32, 0xb8, 0x23, 0x27, 0x68, 0xbe, 0x14, 0x5e, - 0x70, 0x98, 0x10, 0x91, 0x05, 0x66, 0x0f, 0xeb, 0xe3, 0x22, 0x94, 0x3e, 0xe9, 0x92, 0x08, 0xac, - 0x0a, 0x55, 0xea, 0xc0, 0xf8, 0x5f, 0xfe, 0x9e, 0x9a, 0xdf, 0x19, 0x5e, 0xc0, 0x74, 0xca, 0x03, - 0xab, 0x5f, 0x9e, 0x21, 0x7a, 0x75, 0x68, 0x89, 0x35, 0xef, 0xf2, 0x21, 0x5e, 0xf7, 0x90, 0xb6, - 0xd2, 0x8e, 0xb7, 0xa3, 0x7c, 0x08, 0x76, 0xbc, 0x8e, 0x4f, 0xd4, 0x9c, 0x81, 0xb7, 0x62, 0xc5, - 0xa3, 0xb3, 0x4c, 0x05, 0x41, 0x0b, 0x7d, 0x35, 0x3b, 0xae, 0xbb, 0x6a, 0xf2, 0x69, 0xac, 0x5e, - 0xc1, 0x03, 0xa8, 0xf6, 0xf1, 0x61, 0x97, 0x28, 0x3a, 0x8e, 0x93, 0x65, 0x87, 0xac, 0xa0, 0x6c, - 0x1d, 0x1f, 0xf5, 0x0e, 0xa2, 0x00, 0xe6, 0x77, 0x01, 0xa5, 0x94, 0x4e, 0xf5, 0xc7, 0xaf, 0x41, - 0xea, 0x82, 0x25, 0x60, 0x24, 0x3a, 0x78, 0x60, 0x9b, 0x83, 0x2e, 0x23, 0xca, 0x72, 0x4f, 0x51, - 0xd9, 0x20, 0x82, 0x96, 0x3f, 0x7b, 0x6f, 0x59, 0xc6, 0xc8, 0xe8, 0xa4, 0x59, 0xbf, 0xf1, 0x9d, - 0x5f, 0x0d, 0x3c, 0xed, 0xb0, 0xee, 0x19, 0x77, 0xfd, 0x60, 0x67, 0x76, 0xac, 0x70, 0x25, 0xe7, - 0x46, 0x9b, 0x6c, 0xb3, 0xaf, 0x05, 0xd5, 0x00, 0x04, 0x6b, 0x65, 0x4a, 0x36, 0xce, 0x9d, 0x61, - 0xc9, 0xed, 0x8b, 0x7f, 0xb1, 0x99, 0x63, 0xa4, 0x78, 0x19, 0x59, 0xa5, 0x21, 0xf7, 0x29, 0x4b, - 0xa9, 0x86, 0x81, 0x69, 0xbf, 0x2e, 0x9b, 0xaf, 0xc7, 0xce, 0xeb, 0xf1, 0xd5, 0xe7, 0x86, 0x73, - 0xae, 0xf4, 0xaa, 0x41, 0x49, 0x98, 0x00, 0xac, 0x6a, 0xa1, 0x5c, 0x77, 0x42, 0x22, 0x97, 0x5e, - 0x30, 0x2d, 0xdd, 0x63, 0xc5, 0x44, 0xfc, 0x1a, 0x6a, 0x4b, 0x43, 0x8f, 0x79, 0xae, 0x4e, 0xf0, - 0xb5, 0xb2, 0x59, 0x15, 0x5f, 0xe6, 0xa5, 0x0d, 0x21, 0x9b, 0x05, 0x0b, 0x46, 0x5c, 0xe0, 0x71, - 0x50, 0xe2, 0x80, 0x06, 0x89, 0xdd, 0xe0, 0xa0, 0x39, 0x4a, 0x89, 0xc9, 0x14, 0x4f, 0x74, 0x1d, - 0x92, 0x15, 0x62, 0x12, 0x7e, 0xf1, 0xa2, 0xdf, 0xe1, 0x49, 0xd0, 0x21, 0xcb, 0x75, 0xe7, 0x0b, - 0xa2, 0x19, 0x58, 0x3e, 0x07, 0x1d, 0xba, 0x7a, 0xc0, 0x68, 0x28, 0xd6, 0xc4, 0x99, 0x7b, 0xc1, - 0xd1, 0x6e, 0xf7, 0xd1, 0x9f, 0x3a, 0xfd, 0x02, 0x82, 0xcb, 0xbd, 0x44, 0xbc, 0x9b, 0x66, 0x9d, - 0x31, 0xd3, 0xd2, 0x62, 0xf5, 0xec, 0x8f, 0xf2, 0xe7, 0x08, 0x05, 0xae, 0x31, 0x55, 0xff, 0x6c, - 0xf5, 0xa4, 0x9d, 0x9f, 0x80, 0x09, 0x40, 0x6e, 0x13, 0x30, 0xb0, 0x27, 0xe8, 0xd4, 0x78, 0x9d, - 0x5f, 0x4c, 0x27, 0x5f, 0x70, 0x5a, 0x12, 0xba, 0x01, 0xcf, 0x4d, 0x90, 0xa9, 0x78, 0x60, 0xc1, - 0x9f, 0x19, 0x4e, 0xbc, 0x64, 0x76, 0x0c, 0x63, 0x04, 0x94, 0xab, 0xf7, 0x16, 0x4c, 0xcc, 0xb9, - 0xf4, 0xb7, 0x31, 0x96, 0x01, 0x9b, 0x3e, 0xba, 0x84, 0x25, 0x02, 0xd4, 0x63, 0x7b, 0x01, 0x38, - 0x1d, 0x12, 0x4e, 0x35, 0x4e, 0x7c, 0x9e, 0xf2, 0xb3, 0xe3, 0xe6, 0x9c, 0x2f, 0x11, 0xab, 0x3e, - 0x1f, 0xf0, 0x45, 0x98, 0x4f, 0xb3, 0xe3, 0x33, 0x58, 0xc8, 0x9d, 0x9b, 0x33, 0x10, 0xc4, 0x85, - 0x6a, 0x06, 0xe7, 0xcd, 0xa0, 0xdb, 0x66, 0x10, 0x7a, 0xad, 0xc2, 0x8c, 0x31, 0x1f, 0x98, 0x67, - 0xf1, 0xec, 0x7d, 0x08, 0x03, 0x29, 0xf6, 0x96, 0xb5, 0x16, 0x42, 0x23, 0x0a, 0xc1, 0x6d, 0x94, - 0x89, 0xbb, 0xf4, 0x0b, 0xad, 0x47, 0x17, 0xaa, 0xc7, 0xf6, 0x3c, 0xd8, 0xbd, 0x70, 0x28, 0xe2, - 0xcc, 0xd7, 0x1f, 0xc2, 0xa1, 0x49, 0xa1, 0x58, 0xa5, 0xdf, 0x53, 0xe7, 0x1d, 0x34, 0x0e, 0x86, - 0x05, 0x16, 0x64, 0x8a, 0x44, 0x31, 0xc1, 0xe6, 0x30, 0x07, 0x21, 0x61, 0x1d, 0xf0, 0x64, 0x76, - 0x5d, 0xbd, 0x83, 0xd6, 0x44, 0x26, 0x30, 0x0b, 0xfa, 0x24, 0x58, 0x10, 0xdc, 0x99, 0x8d, 0x38, - 0xc2, 0x7e, 0xa1, 0x6a, 0xc1, 0x24, 0x86, 0x5e, 0x75, 0xbf, 0x03, 0xdf, 0xa9, 0x81, 0xe4, 0x86, - 0xe2, 0x8d, 0x6f, 0x7b, 0x14, 0x6d, 0xee, 0x49, 0xea, 0x31, 0xd4, 0x35, 0xa9, 0x06, 0x50, 0xee, - 0x7e, 0x2d, 0xdf, 0xee, 0x63, 0xf2, 0xdd, 0x97, 0x6c, 0x96, 0x78, 0x49, 0xd5, 0x33, 0x2b, 0xe5, - 0xca, 0x95, 0x70, 0x99, 0x61, 0x54, 0x15, 0xc8, 0xdd, 0x6b, 0x55, 0x46, 0xb8, 0xb7, 0xd7, 0x6e, - 0x8b, 0xb4, 0x1d, 0x7b, 0x5c, 0xa0, 0x85, 0xb2, 0xef, 0xb4, 0xe1, 0x38, 0x9b, 0x0c, 0x7c, 0x37, - 0xcf, 0x0b, 0xb4, 0xd8, 0x2f, 0x02, 0x77, 0xc4, 0x41, 0x11, 0x9b, 0x3d, 0x6c, 0x1f, 0x58, 0x2d, - 0x3d, 0xae, 0xa2, 0xab, 0x0b, 0x7c, 0x46, 0x32, 0x6e, 0x02, 0x3f, 0x3f, 0xa2, 0x39, 0x5d, 0xcf, - 0x9a, 0x2d, 0x4c, 0x6b, 0xd5, 0x02, 0x04, 0x33, 0xc7, 0x00, 0x6f, 0x30, 0xd6, 0xe6, 0x08, 0x94, - 0x97, 0x08, 0xdb, 0xb8, 0xbe, 0x0c, 0xaa, 0x96, 0x0c, 0xdd, 0x41, 0xdb, 0xf9, 0xda, 0xfd, 0x3d, - 0xb1, 0xb1, 0xf4, 0x91, 0xb9, 0xb9, 0x5e, 0x55, 0xf9, 0x0e, 0xc7, 0xf8, 0x09, 0x91, 0xce, 0xf8, - 0xc5, 0xe5, 0xe8, 0xb4, 0x2a, 0xfc, 0xca, 0x82, 0x9c, 0x85, 0x29, 0x02, 0x4b, 0xe1, 0x18, 0x59, - 0x69, 0xb8, 0x1d, 0xd4, 0x46, 0x53, 0xe7, 0x69, 0x09, 0x5d, 0x3a, 0x20, 0x69, 0x8f, 0xb2, 0xd0, - 0xe7, 0x6d, 0x18, 0xdb, 0xa5, 0x7c, 0x43, 0x48, 0x24, 0x54, 0x07, 0x0c, 0x25, 0x58, 0x31, 0x54, - 0xd3, 0xae, 0x10, 0xe0, 0x59, 0xdf, 0x73, 0x79, 0x25, 0xd1, 0x41, 0xbd, 0x02, 0xd6, 0x6c, 0xa4, - 0xbb, 0x9c, 0x83, 0x3c, 0x39, 0xbf, 0x8a, 0x61, 0x5b, 0x86, 0xff, 0x6f, 0x63, 0x3c, 0x8b, 0x02, - 0xbd, 0xdd, 0xbe, 0x31, 0x73, 0x10, 0xb9, 0x6c, 0xd1, 0x3b, 0x88, 0x51, 0x78, 0x91, 0xcf, 0xc5, - 0xde, 0x95, 0x1d, 0x6d, 0xff, 0x79, 0x2d, 0x5e, 0xb0, 0xb8, 0x83, 0x36, 0x17, 0x3e, 0x05, 0x0e, - 0x47, 0xa5, 0x0f, 0x09, 0x76, 0xa9, 0x44, 0xc1, 0x21, 0x66, 0xc1, 0x85, 0x83, 0xc0, 0x85, 0x69, - 0x4b, 0xc1, 0xf0, 0xbc, 0xd8, 0x64, 0xe8, 0x06, 0x54, 0xe7, 0x14, 0xd3, 0xed, 0x26, 0x31, 0x27, - 0xec, 0x16, 0x86, 0x6e, 0xe8, 0xbb, 0xdc, 0x4d, 0x1a, 0xbd, 0xbb, 0x08, 0x5d, 0xe2, 0x26, 0xfd, - 0xe2, 0x32, 0x74, 0x59, 0x9b, 0x0c, 0xde, 0x37, 0x0f, 0x20, 0x10, 0xef, 0xed, 0x4f, 0x5c, 0x89, - 0xfb, 0x53, 0x3a, 0xfb, 0xb0, 0x4e, 0x41, 0xba, 0x0d, 0x03, 0x5e, 0x6d, 0xc0, 0x7d, 0xc2, 0x11, - 0x69, 0xf7, 0x62, 0x3f, 0xe3, 0xcd, 0x66, 0x07, 0xf6, 0xca, 0x2a, 0x3f, 0x95, 0xd9, 0x3c, 0x0f, - 0x02, 0x4d, 0xc9, 0x34, 0xd6, 0x25, 0x29, 0x4d, 0x58, 0x36, 0x59, 0x8f, 0x82, 0xb5, 0x1f, 0x78, - 0x92, 0xc5, 0xc6, 0x29, 0xf6, 0x8d, 0x08, 0x87, 0x4e, 0x48, 0x39, 0xac, 0xa4, 0x33, 0x48, 0x98, - 0x37, 0x87, 0xa9, 0xdd, 0x8c, 0x7f, 0xd7, 0x45, 0x49, 0x1d, 0x8c, 0x63, 0x03, 0x2f, 0x6c, 0x07, - 0xff, 0xaa, 0x83, 0x73, 0x50, 0x51, 0xa6, 0x45, 0xbe, 0xf7, 0x8a, 0x4b, 0x50, 0xde, 0x7e, 0xc8, - 0x7f, 0xb9, 0x1c, 0xf9, 0x30, 0xd2, 0x52, 0x18, 0x69, 0x30, 0xca, 0xd4, 0x58, 0xab, 0xe7, 0xda, - 0x72, 0x17, 0xb8, 0x9d, 0x6b, 0xd4, 0x5b, 0xc2, 0x5f, 0xfa, 0xcd, 0x70, 0x38, 0xec, 0xec, 0x76, - 0x0f, 0xbe, 0x0b, 0x3b, 0x48, 0x15, 0x8e, 0xde, 0xc9, 0xc5, 0x8e, 0x17, 0xe2, 0xdf, 0x4b, 0xf9, - 0x77, 0x04, 0x5b, 0x38, 0x2e, 0x47, 0x2b, 0x4a, 0x38, 0x6c, 0x2b, 0xdf, 0xaf, 0x7f, 0x49, 0xf9, - 0xa2, 0x28, 0xda, 0xac, 0x7c, 0xd6, 0x97, 0xff, 0xa1, 0x1b, 0xd6, 0xee, 0xad, 0xcf, 0x22, 0x05, - 0xe9, 0xc4, 0xcc, 0x12, 0x18, 0x26, 0x7c, 0xab, 0x3f, 0x98, 0x77, 0x41, 0xf9, 0x63, 0xc3, 0xd7, - 0x67, 0xf1, 0x05, 0x49, 0x29, 0x9e, 0x3c, 0x41, 0x72, 0x0e, 0x82, 0x2b, 0xb4, 0x97, 0x4e, 0x09, - 0x03, 0x20, 0x5a, 0x53, 0xe8, 0xc3, 0x15, 0x93, 0x42, 0x67, 0x62, 0xd3, 0x08, 0xd9, 0x43, 0xb6, - 0xdf, 0x38, 0x34, 0x34, 0x73, 0xe5, 0x79, 0x80, 0x90, 0xae, 0x2c, 0x20, 0xeb, 0x29, 0xef, 0x7d, - 0x83, 0x40, 0xc9, 0x36, 0x16, 0x24, 0x4c, 0x05, 0x29, 0x30, 0x93, 0xb9, 0xd9, 0x44, 0x9c, 0x4c, - 0x86, 0xc3, 0x28, 0xf2, 0x0c, 0x86, 0xe6, 0x8a, 0x69, 0x96, 0x30, 0x0a, 0x66, 0x15, 0x0c, 0x22, - 0x3a, 0x98, 0x94, 0x8b, 0x4a, 0xaf, 0xa6, 0xab, 0xaa, 0x65, 0x47, 0x6e, 0x9f, 0x08, 0x49, 0xa7, - 0x07, 0x05, 0x1e, 0x8a, 0xc8, 0x13, 0x05, 0x50, 0xc4, 0x9c, 0xf9, 0x03, 0xfa, 0x6d, 0x15, 0xc4, - 0xb5, 0xa0, 0x57, 0x57, 0x43, 0xd8, 0xde, 0x52, 0x68, 0x8f, 0xf2, 0x16, 0x3a, 0x12, 0xfe, 0x8f, - 0x96, 0x2e, 0xd9, 0x5f, 0x43, 0x2d, 0x57, 0xeb, 0x0d, 0xe8, 0x8b, 0xd5, 0x05, 0xb9, 0x72, 0x86, - 0xd2, 0xdf, 0x8d, 0xd9, 0xc3, 0xc9, 0xe7, 0x74, 0x6d, 0x3e, 0xa5, 0xd7, 0xba, 0x04, 0xd4, 0xf2, - 0xf9, 0x75, 0x6d, 0x3e, 0xb7, 0x5e, 0xeb, 0x9a, 0x51, 0xcb, 0xe7, 0x1f, 0xcd, 0x7c, 0xfc, 0x39, - 0x8f, 0xf8, 0xb8, 0x6d, 0x66, 0x2c, 0x6a, 0xe9, 0x71, 0x32, 0x3b, 0xa3, 0xb4, 0xb6, 0x2f, 0x84, - 0x55, 0xd2, 0xb6, 0x2b, 0xc8, 0x83, 0xab, 0xfa, 0x9e, 0xd0, 0x37, 0x83, 0xe5, 0x1c, 0xa7, 0xad, - 0xf1, 0x2e, 0x23, 0x04, 0x8d, 0x73, 0x76, 0xeb, 0xc9, 0x5a, 0x25, 0x8e, 0xfa, 0xd8, 0x2c, 0x12, - 0x11, 0xd6, 0xc3, 0x2e, 0x91, 0x3b, 0xa1, 0x16, 0x36, 0x4a, 0x4a, 0x05, 0x56, 0x2f, 0x5f, 0xd5, - 0xaa, 0xf8, 0x9b, 0xeb, 0xa7, 0xab, 0x85, 0x81, 0xb0, 0x5d, 0xf2, 0xa9, 0x1a, 0x73, 0x44, 0xa8, - 0x3a, 0xf3, 0x57, 0x64, 0x04, 0x77, 0xf0, 0x89, 0x9a, 0x6d, 0x4c, 0xce, 0x8c, 0xf9, 0x72, 0x09, - 0x8b, 0x4e, 0x5b, 0xc3, 0x2c, 0x4c, 0x61, 0xd7, 0xa9, 0x7d, 0x13, 0xb6, 0x9b, 0x08, 0x65, 0x52, - 0x81, 0xc8, 0x57, 0xd0, 0x43, 0x3f, 0xa6, 0xb0, 0x6a, 0xfa, 0x88, 0x4a, 0xbe, 0x76, 0xc8, 0x10, - 0xef, 0x77, 0x6f, 0x0b, 0x93, 0x1a, 0x66, 0x45, 0x3f, 0x6f, 0xb5, 0xbf, 0x69, 0x51, 0x2a, 0x50, - 0xa7, 0x8e, 0x75, 0x49, 0x61, 0x08, 0xbd, 0x50, 0x2a, 0x16, 0xc0, 0xe9, 0x5e, 0x11, 0xe7, 0xe1, - 0x10, 0x3a, 0x21, 0x33, 0x41, 0x97, 0x14, 0x34, 0x4a, 0x52, 0x13, 0x34, 0xa2, 0xa0, 0x3b, 0xd8, - 0xdc, 0x6a, 0x0d, 0x46, 0x1f, 0x51, 0xc7, 0xfa, 0xf0, 0x91, 0xf8, 0xd3, 0xa7, 0xb3, 0x90, 0xfe, - 0x3b, 0x5b, 0x2c, 0xe4, 0xb1, 0x37, 0x32, 0x10, 0x50, 0xec, 0xe4, 0x13, 0x37, 0x4e, 0x7e, 0x56, - 0x3f, 0xd6, 0x76, 0xec, 0xad, 0xc3, 0x14, 0x5d, 0xc3, 0xdb, 0x8f, 0x04, 0xc6, 0xe3, 0xca, 0xb6, - 0x4b, 0x3b, 0xfa, 0x40, 0x71, 0x5d, 0x7d, 0xb8, 0x4c, 0xff, 0xd4, 0x89, 0x08, 0x83, 0x8c, 0x9e, - 0x23, 0x37, 0x4f, 0xfc, 0xf4, 0x29, 0x0e, 0xf1, 0xa7, 0xa8, 0x1f, 0x7e, 0x48, 0x7a, 0xe7, 0x4b, - 0x39, 0xde, 0xc5, 0x1e, 0x5e, 0x5b, 0xf0, 0x6d, 0x94, 0x4f, 0x2f, 0x02, 0xc9, 0x32, 0xf1, 0x71, - 0x2d, 0x95, 0x94, 0x3e, 0xaf, 0x7f, 0xfe, 0xe9, 0x04, 0x8b, 0x5b, 0x04, 0x6c, 0x12, 0x79, 0x8b, - 0xeb, 0x0d, 0x2f, 0xf7, 0x22, 0xa4, 0xbb, 0x33, 0x4f, 0xef, 0xaf, 0x71, 0x0c, 0xb5, 0xb3, 0x75, - 0x0f, 0xc7, 0xcc, 0xd4, 0x8d, 0x11, 0x25, 0xb6, 0xe9, 0xa0, 0x41, 0xb2, 0x8a, 0x57, 0x66, 0x82, - 0xb8, 0x8d, 0x0e, 0x96, 0xde, 0x2c, 0x58, 0x22, 0xbe, 0x1a, 0xdb, 0xe2, 0x30, 0x12, 0xcb, 0xfc, - 0x37, 0x2e, 0xa0, 0xb2, 0x9c, 0xf8, 0xcc, 0xdc, 0x44, 0xc8, 0x48, 0x74, 0x39, 0xad, 0xae, 0x6e, - 0x46, 0x68, 0xea, 0x7f, 0xfa, 0x72, 0x5a, 0x8c, 0xf3, 0x3c, 0xff, 0x3c, 0x15, 0x4f, 0x91, 0xc4, - 0x0a, 0x9a, 0xe5, 0xf3, 0x14, 0x2d, 0x0e, 0x16, 0x78, 0x37, 0x69, 0xba, 0x0a, 0xe9, 0xcd, 0xf7, - 0xaf, 0xc6, 0x3b, 0x49, 0xf7, 0x45, 0x70, 0xb4, 0x8f, 0x18, 0xb0, 0x3e, 0x7e, 0x36, 0x08, 0xaf, - 0xc6, 0x47, 0x3d, 0xf5, 0xb8, 0x1f, 0xe1, 0x6e, 0xf8, 0xec, 0x59, 0x92, 0x5c, 0x8d, 0x29, 0x64, - 0x27, 0xd9, 0xc7, 0x90, 0xe8, 0x85, 0x15, 0x02, 0x19, 0x28, 0x01, 0x10, 0xf1, 0xc5, 0x02, 0x47, - 0xb5, 0x3a, 0xbf, 0x2a, 0xd1, 0xcf, 0xf4, 0x6a, 0xbc, 0x08, 0x3b, 0x88, 0xe6, 0x16, 0x76, 0x0e, - 0xa2, 0xef, 0x90, 0xc6, 0x37, 0xfc, 0xa1, 0x2b, 0x51, 0xc4, 0x41, 0x68, 0x2c, 0x1c, 0x44, 0x61, - 0x08, 0xf8, 0x85, 0xac, 0xb3, 0x6c, 0x3c, 0xc6, 0xf7, 0xce, 0x1a, 0x49, 0xda, 0x1e, 0x68, 0x62, - 0x5e, 0x20, 0x59, 0xa5, 0x3a, 0x62, 0xb9, 0x3a, 0x67, 0xfb, 0x1f, 0x22, 0xe6, 0xec, 0x64, 0x5a, - 0x5c, 0x77, 0x7e, 0x11, 0xa3, 0x3c, 0x97, 0x9a, 0xb5, 0xcf, 0xdf, 0x07, 0x41, 0xbe, 0x41, 0xed, - 0x54, 0x88, 0x49, 0xa2, 0x71, 0x08, 0x95, 0x01, 0x47, 0x15, 0xf9, 0xd4, 0x45, 0x41, 0x06, 0x19, - 0xa4, 0x74, 0x97, 0xf2, 0xa2, 0xe4, 0x32, 0xaa, 0x3a, 0x9c, 0x06, 0x7f, 0xb2, 0xb4, 0xfc, 0x65, - 0x53, 0xd8, 0x53, 0xa2, 0x2f, 0x54, 0x65, 0x08, 0x97, 0x64, 0x37, 0xa9, 0x67, 0x47, 0x6d, 0xaa, - 0x4f, 0xba, 0xbd, 0xbe, 0xed, 0xd3, 0x36, 0x67, 0x17, 0x8a, 0x88, 0xcf, 0xb2, 0x35, 0x63, 0xb9, - 0x74, 0x95, 0x3a, 0x5b, 0x30, 0xde, 0x24, 0x9f, 0xbd, 0x31, 0xf6, 0x46, 0x3e, 0x4b, 0xae, 0xef, - 0x42, 0x2b, 0x00, 0xd2, 0xff, 0x8b, 0xac, 0x1c, 0x56, 0x94, 0x8f, 0xc9, 0xf5, 0x95, 0x8d, 0xdb, - 0xdf, 0x75, 0x5c, 0xbf, 0x6c, 0xff, 0x38, 0x79, 0x86, 0xbe, 0xdc, 0x1b, 0x8f, 0x8c, 0x32, 0x12, - 0xfc, 0xf6, 0x35, 0xac, 0xd3, 0x36, 0xd6, 0xf4, 0xcc, 0x0a, 0x77, 0x89, 0x22, 0x88, 0x50, 0xec, - 0xb4, 0xca, 0x0b, 0x10, 0x76, 0x71, 0xde, 0x1e, 0x57, 0xe2, 0xda, 0xf7, 0xee, 0x52, 0x22, 0xb5, - 0xb8, 0xf7, 0xa4, 0x5b, 0x19, 0xca, 0x7a, 0x6c, 0x0c, 0xb1, 0x0c, 0xa2, 0x15, 0xf3, 0xae, 0xa0, - 0x4f, 0xda, 0x2d, 0x5d, 0xd6, 0xc0, 0x3f, 0x86, 0x89, 0xcd, 0x7c, 0x11, 0xde, 0xcc, 0xb8, 0x73, - 0x90, 0xeb, 0xd2, 0xb7, 0xe5, 0xc2, 0x85, 0x13, 0x6f, 0xbe, 0x08, 0x2f, 0xf5, 0xd1, 0x1b, 0x57, - 0x22, 0x0a, 0x25, 0xd8, 0xae, 0x55, 0xcc, 0xb2, 0x51, 0xcc, 0xb0, 0x86, 0xe9, 0x3c, 0x9f, 0xc5, - 0x76, 0xc6, 0xe1, 0xad, 0x8d, 0x47, 0x0b, 0x0f, 0x8b, 0xa6, 0xa6, 0x1d, 0x0a, 0x87, 0xc7, 0x4a, - 0x84, 0x3f, 0xfc, 0xe0, 0x1c, 0x7b, 0xd5, 0x0b, 0x46, 0x2b, 0x73, 0x1b, 0x9e, 0x33, 0x7c, 0xf8, - 0x7e, 0x30, 0x23, 0xdd, 0x61, 0x47, 0x04, 0xee, 0x8a, 0xfb, 0x15, 0xe8, 0xce, 0x36, 0xac, 0xf3, - 0x86, 0x58, 0xcd, 0x25, 0x1a, 0xed, 0xec, 0xf6, 0x75, 0x4f, 0x57, 0xa0, 0xb5, 0x9d, 0x46, 0x82, - 0x5e, 0x82, 0x3c, 0xd0, 0xd3, 0x66, 0x50, 0x37, 0xfd, 0x35, 0x2a, 0xbf, 0xd3, 0x85, 0xea, 0x2f, - 0xc2, 0x83, 0x08, 0xd6, 0x6a, 0x7f, 0x1d, 0x18, 0xb1, 0xb5, 0x21, 0x22, 0x50, 0x36, 0xe1, 0x13, - 0xe9, 0xa1, 0x29, 0x96, 0xeb, 0x62, 0x1a, 0xd5, 0x02, 0x6d, 0xfe, 0x52, 0xc8, 0xa9, 0x3b, 0xf6, - 0x6a, 0x5c, 0x0b, 0x09, 0x8f, 0xba, 0x9a, 0xd6, 0x73, 0x9a, 0x78, 0x33, 0x72, 0xd1, 0x84, 0x9d, - 0xad, 0x02, 0x95, 0xa2, 0xdb, 0x9f, 0x1e, 0x6a, 0xc0, 0x9e, 0xa9, 0xa2, 0x5b, 0xc8, 0x92, 0xf2, - 0xd3, 0xf4, 0x2c, 0x4c, 0x37, 0xa4, 0xc1, 0x04, 0xf5, 0xff, 0x9f, 0xb3, 0x99, 0x28, 0x5e, 0x0d, - 0x11, 0x5d, 0xbd, 0x9f, 0xd5, 0x4a, 0x9f, 0x6a, 0x53, 0xba, 0xac, 0x82, 0x1b, 0x1f, 0x59, 0x24, - 0x95, 0xfb, 0x31, 0xf4, 0xb2, 0x45, 0x7c, 0x98, 0x8a, 0x61, 0xc6, 0x38, 0xea, 0x6d, 0xf0, 0x22, - 0x72, 0xfe, 0x0a, 0x92, 0x0b, 0xa6, 0xf9, 0x4d, 0xe9, 0x36, 0xa1, 0x52, 0x79, 0x90, 0x7b, 0xc2, - 0x76, 0x38, 0xc6, 0x4c, 0xb0, 0xab, 0xde, 0xa0, 0xf0, 0x40, 0x9b, 0x3b, 0xff, 0xf2, 0xe8, 0xc4, - 0x19, 0x55, 0x16, 0x6f, 0x72, 0x4f, 0xbe, 0xef, 0x68, 0x72, 0x44, 0x96, 0xc1, 0x4c, 0x75, 0xce, - 0x1e, 0x82, 0xb3, 0x30, 0x3f, 0xc7, 0x34, 0xad, 0x88, 0x4f, 0xa3, 0xa5, 0xb9, 0xf9, 0xe4, 0x9a, - 0xae, 0x81, 0xa8, 0x2b, 0x9e, 0x67, 0x4d, 0x76, 0x2a, 0x75, 0x70, 0xdd, 0x75, 0xe4, 0x7d, 0xca, - 0xf6, 0xed, 0xbd, 0xa2, 0xbe, 0x11, 0x36, 0x0f, 0x61, 0xa5, 0xb8, 0x6e, 0x95, 0x63, 0xbe, 0xd2, - 0x18, 0xa3, 0x33, 0x5d, 0x57, 0x9d, 0xf1, 0xc0, 0xf0, 0xae, 0x4e, 0xd2, 0x2a, 0x7e, 0x74, 0x1b, - 0x7c, 0x6d, 0x2d, 0x2b, 0xa8, 0x65, 0xa5, 0xdd, 0x31, 0x4c, 0x6d, 0x9d, 0xd9, 0x80, 0x30, 0xaf, - 0xc3, 0xea, 0x65, 0x61, 0xe4, 0xf0, 0x10, 0xb9, 0x39, 0x0d, 0x82, 0x14, 0xb6, 0x81, 0x7b, 0xb1, - 0x5d, 0xe0, 0x25, 0x9d, 0x40, 0x8d, 0x52, 0x7a, 0xea, 0xb3, 0x86, 0x9c, 0xe1, 0xd1, 0x5f, 0x96, - 0x90, 0x78, 0x4d, 0xe1, 0xc9, 0xa7, 0xec, 0x0c, 0x59, 0x64, 0xfc, 0x8a, 0xe3, 0xc9, 0x4c, 0x83, - 0xc3, 0x32, 0xd0, 0x10, 0x5f, 0x20, 0x44, 0xa5, 0x87, 0xe5, 0x6e, 0xd5, 0x4f, 0x61, 0xe8, 0x73, - 0x2c, 0xda, 0x6c, 0x04, 0xdf, 0x05, 0xda, 0xed, 0x32, 0xb5, 0x56, 0xa3, 0x10, 0x16, 0x06, 0x7f, - 0x30, 0xcf, 0x1c, 0x50, 0x7e, 0xb7, 0x38, 0x55, 0x81, 0xa5, 0xb1, 0x10, 0xf8, 0xed, 0x42, 0x59, - 0x00, 0x46, 0x6e, 0xd9, 0xea, 0xe5, 0xb2, 0x22, 0xca, 0xe2, 0xd9, 0x37, 0x7e, 0xb0, 0x94, 0xa6, - 0x51, 0xb5, 0xb3, 0xa9, 0xa5, 0xd9, 0xb0, 0xc3, 0xbe, 0xf4, 0xa1, 0xb6, 0xbc, 0xf5, 0x73, 0xa9, - 0xef, 0x49, 0x5d, 0x71, 0x26, 0x9d, 0xb7, 0x6b, 0x7c, 0xd1, 0xd0, 0xa2, 0x8e, 0xb1, 0xb9, 0x6c, - 0x35, 0x36, 0x5b, 0x0c, 0xc4, 0x02, 0x3a, 0xbe, 0x72, 0x49, 0x9d, 0x55, 0x2c, 0x7d, 0x4b, 0x86, - 0xf7, 0xd5, 0x96, 0x3b, 0x02, 0x26, 0x46, 0x28, 0x8e, 0xa8, 0x3b, 0x55, 0x67, 0x63, 0x91, 0xeb, - 0xa9, 0x8c, 0x3f, 0x92, 0x49, 0x87, 0x40, 0xc5, 0xfa, 0x48, 0x3c, 0x0b, 0x06, 0xea, 0xce, 0x5e, - 0x76, 0x96, 0xcc, 0xe4, 0x0f, 0x7d, 0x9a, 0x11, 0x9a, 0x31, 0xa8, 0x63, 0x11, 0x36, 0x37, 0x74, - 0xa1, 0x0e, 0x90, 0xf0, 0x3d, 0x81, 0xc1, 0x0a, 0xd2, 0x61, 0x89, 0x81, 0xd9, 0xca, 0x08, 0x39, - 0xc7, 0x8e, 0x41, 0x2c, 0x0a, 0xf5, 0xbc, 0x10, 0x3f, 0xdc, 0xc9, 0x08, 0x81, 0x8f, 0xd4, 0xfd, - 0x1f, 0xe9, 0xe5, 0x6e, 0xc9, 0x64, 0x7c, 0x7d, 0x28, 0x23, 0x66, 0x3c, 0xeb, 0xc2, 0x50, 0xa6, - 0x18, 0x85, 0x96, 0xa6, 0x30, 0x5e, 0xc0, 0xb3, 0xe9, 0xaf, 0x20, 0xd7, 0x43, 0x80, 0x3a, 0x54, - 0xc9, 0xec, 0x73, 0xde, 0x24, 0x45, 0x1b, 0x7f, 0xda, 0x3c, 0xee, 0x94, 0x50, 0xe1, 0x9c, 0xa0, - 0x76, 0x38, 0xce, 0xfc, 0x10, 0x74, 0x53, 0x25, 0x5b, 0x7a, 0xa4, 0xd2, 0xb8, 0x5b, 0xc5, 0x08, - 0x27, 0x16, 0xdd, 0xfb, 0x92, 0x1a, 0x78, 0xde, 0x66, 0xa7, 0x34, 0x8b, 0xea, 0x4f, 0x5c, 0x8f, - 0xb2, 0x8c, 0xf1, 0x19, 0xad, 0x88, 0xc6, 0x18, 0xdf, 0xf0, 0xfd, 0x19, 0xa5, 0x37, 0x85, 0xdf, - 0x4a, 0x8a, 0xd7, 0x7c, 0x63, 0xbb, 0xe3, 0xf0, 0xdb, 0x05, 0x23, 0x5b, 0xfc, 0xef, 0xab, 0x26, - 0xc1, 0x92, 0x1a, 0xb7, 0x48, 0x73, 0x1c, 0xbe, 0x4f, 0x9e, 0xd1, 0x2c, 0x9c, 0x52, 0x49, 0x40, - 0x40, 0xbe, 0x8f, 0x24, 0xf7, 0x08, 0x55, 0xee, 0x94, 0xce, 0x1b, 0x65, 0xab, 0xf7, 0xad, 0xd2, - 0x33, 0x77, 0xc7, 0x5c, 0xab, 0x0c, 0x12, 0x5e, 0xe3, 0x43, 0x7e, 0x03, 0xbd, 0x54, 0x0e, 0xea, - 0x01, 0x48, 0x3a, 0x23, 0x2c, 0x23, 0xeb, 0xb0, 0x3c, 0x2e, 0x72, 0x42, 0xb9, 0xc3, 0x5c, 0xd4, - 0x92, 0xc8, 0xdc, 0x99, 0xc2, 0x66, 0xcc, 0x24, 0x21, 0x45, 0x93, 0x49, 0x96, 0xbf, 0x81, 0x3a, - 0xe9, 0x7b, 0x90, 0x56, 0x9f, 0x88, 0x83, 0xfc, 0xaf, 0xa8, 0x49, 0x6d, 0x69, 0x7c, 0xfc, 0x59, - 0xed, 0x63, 0xb3, 0x31, 0x72, 0x0f, 0xa8, 0x15, 0x09, 0x56, 0xaf, 0x61, 0x71, 0x29, 0xac, 0x69, - 0x4c, 0xcb, 0xbe, 0x0c, 0x74, 0x77, 0x5c, 0x1d, 0xa5, 0x6f, 0xcf, 0xf5, 0x2c, 0xa7, 0x36, 0xf7, - 0x82, 0x87, 0x07, 0xbb, 0x1a, 0x55, 0xed, 0xb9, 0x84, 0x67, 0x1f, 0x1a, 0x53, 0x35, 0x15, 0xe4, - 0x86, 0x16, 0xec, 0x7f, 0xe9, 0x86, 0xbd, 0x7c, 0xf3, 0xe3, 0xab, 0xf7, 0xbe, 0x57, 0x0d, 0x47, - 0x63, 0x16, 0x46, 0x41, 0x3f, 0xe7, 0x5e, 0x38, 0x93, 0x23, 0xeb, 0x43, 0x3e, 0x0b, 0xff, 0xf7, - 0x55, 0xdb, 0x55, 0x26, 0x39, 0xbc, 0xb6, 0x7c, 0xd5, 0x37, 0x51, 0xe0, 0x80, 0xf4, 0xd1, 0xd8, - 0xe7, 0xfa, 0x73, 0x8c, 0x27, 0x4f, 0x6a, 0xed, 0xd0, 0x2c, 0x56, 0x52, 0xed, 0xde, 0x23, 0xdd, - 0x29, 0x1d, 0x35, 0x91, 0x88, 0x5a, 0x22, 0x29, 0xdd, 0x8e, 0x9f, 0x7d, 0x5f, 0x3e, 0xbd, 0xfb, - 0x0d, 0xa4, 0xda, 0xfc, 0xed, 0xf4, 0x5e, 0x5c, 0x20, 0xbe, 0x66, 0xb4, 0x85, 0x6b, 0xac, 0xcf, - 0xc5, 0x3d, 0x8a, 0x08, 0xb3, 0x2b, 0xd0, 0x01, 0x87, 0xc4, 0xae, 0x8c, 0x01, 0xe9, 0xd1, 0x5e, - 0xb7, 0x07, 0x62, 0xcb, 0x26, 0x55, 0x05, 0x15, 0x86, 0x5b, 0x06, 0xf2, 0x81, 0x5a, 0xb3, 0xd4, - 0x45, 0xae, 0x50, 0x39, 0x88, 0x6b, 0xd5, 0x17, 0xdf, 0xdb, 0xdd, 0x9d, 0x7a, 0x21, 0xa7, 0xdb, - 0x45, 0x28, 0xf2, 0x34, 0xe9, 0xee, 0xa6, 0xca, 0xce, 0x36, 0x44, 0xc1, 0xeb, 0x73, 0x29, 0x8b, - 0x00, 0x52, 0xc4, 0xb2, 0x3c, 0x26, 0x5e, 0x98, 0x06, 0x9b, 0xb6, 0x6b, 0x17, 0x32, 0x92, 0x33, - 0xc2, 0x76, 0xe4, 0x22, 0x18, 0xfe, 0xf9, 0xdd, 0x6f, 0x89, 0xd4, 0xc9, 0x69, 0xf9, 0x20, 0x8c, - 0xee, 0xbe, 0xa5, 0x61, 0xd3, 0x55, 0x45, 0xd9, 0xc0, 0x0c, 0xc0, 0xdc, 0x2f, 0x5f, 0xe5, 0x29, - 0x16, 0x02, 0xbf, 0x22, 0x19, 0xcc, 0x42, 0x15, 0x36, 0x52, 0x36, 0x66, 0x5a, 0x5f, 0x9c, 0x74, - 0x32, 0xa6, 0x24, 0x51, 0x80, 0xda, 0x3f, 0x33, 0xc9, 0x90, 0x03, 0x5a, 0x65, 0x25, 0xe9, 0x46, - 0xa8, 0xa7, 0x61, 0x85, 0x40, 0xa5, 0xeb, 0x2e, 0xb9, 0xfb, 0xad, 0xc1, 0x47, 0xc2, 0x11, 0x18, - 0xd7, 0x97, 0x50, 0xe8, 0x28, 0xe0, 0x65, 0x22, 0x07, 0xc9, 0xcb, 0xb0, 0x5d, 0xe3, 0x9b, 0x8d, - 0xaf, 0xbd, 0x50, 0x46, 0x41, 0x0f, 0x06, 0xfa, 0x05, 0xf9, 0xe3, 0x56, 0xd9, 0x7b, 0xf6, 0xe4, - 0x89, 0x4a, 0x4d, 0x86, 0x42, 0x65, 0x8e, 0xc4, 0x1b, 0xaa, 0xd0, 0x5c, 0xfc, 0xee, 0xc9, 0x13, - 0x88, 0x0d, 0x91, 0xf7, 0xf1, 0xc7, 0x61, 0xb7, 0x77, 0x10, 0x0d, 0x7a, 0xfb, 0x51, 0xdc, 0x7b, - 0x0e, 0x3b, 0xcc, 0x16, 0x96, 0x04, 0x43, 0x29, 0x2f, 0x2a, 0x3b, 0xfd, 0x7e, 0x78, 0x30, 0x5f, - 0xa0, 0x50, 0x7e, 0xc0, 0xf9, 0x85, 0xb6, 0x26, 0xe8, 0x79, 0xbe, 0xe1, 0x5a, 0x1f, 0x09, 0x6a, - 0x5f, 0xe3, 0x6b, 0x8e, 0x27, 0x50, 0x76, 0x1b, 0x97, 0x9f, 0xcb, 0x63, 0xc3, 0xf2, 0xdb, 0x8d, - 0xcf, 0x23, 0x87, 0xe9, 0xea, 0x12, 0x55, 0x74, 0x73, 0x13, 0x04, 0x1a, 0x02, 0xe2, 0x0d, 0xbc, - 0x08, 0x75, 0xc7, 0x9b, 0x2a, 0xf7, 0x1e, 0xd1, 0x89, 0x7a, 0x5c, 0xf2, 0x65, 0x6e, 0x55, 0x0e, - 0xb4, 0x3e, 0x41, 0x6e, 0xcf, 0xf0, 0x8f, 0x8d, 0x7f, 0x20, 0x60, 0x61, 0x7a, 0x2d, 0x04, 0xba, - 0x67, 0xef, 0xed, 0xed, 0xb1, 0x53, 0xc7, 0x96, 0x62, 0x47, 0xd2, 0x26, 0x1b, 0xcd, 0xae, 0x0e, - 0xbb, 0xd2, 0xd5, 0x74, 0x02, 0x5a, 0x21, 0x5f, 0x32, 0x01, 0x9d, 0x93, 0x1c, 0xf3, 0xf8, 0x57, - 0x19, 0x04, 0x36, 0xe8, 0xd1, 0x14, 0x46, 0x72, 0x20, 0xdf, 0x20, 0x9c, 0xc0, 0x80, 0x56, 0xda, - 0x87, 0x07, 0x57, 0x51, 0x15, 0xe1, 0x1c, 0x42, 0xc9, 0x61, 0x22, 0xb4, 0x4a, 0x03, 0x61, 0x21, - 0xa5, 0x0a, 0xe2, 0xd6, 0xf8, 0x04, 0x6b, 0xa0, 0x2d, 0x60, 0x8d, 0x6a, 0x2c, 0x78, 0x0e, 0x2d, - 0x9d, 0xa5, 0x99, 0x17, 0xc2, 0x18, 0x97, 0xd3, 0x0b, 0x76, 0x5e, 0xd2, 0x11, 0x70, 0xb2, 0x8a, - 0x0c, 0x8f, 0xb1, 0x78, 0x50, 0x79, 0x44, 0x88, 0x41, 0x7d, 0x0f, 0xf9, 0x34, 0x63, 0x5d, 0xe7, - 0xe8, 0xec, 0x97, 0xdf, 0x41, 0x66, 0x38, 0xa1, 0x43, 0x14, 0x1c, 0x5a, 0x23, 0x56, 0xb8, 0x4b, - 0x31, 0xff, 0xc0, 0x9a, 0x98, 0x94, 0x25, 0x68, 0xe6, 0x5e, 0x88, 0x6b, 0xec, 0x9a, 0x78, 0x37, - 0xb3, 0x75, 0xd1, 0xe8, 0xc3, 0x20, 0x84, 0x99, 0x78, 0xff, 0x75, 0xf8, 0x14, 0xd6, 0xc1, 0xe9, - 0xac, 0x3a, 0xea, 0x1c, 0x3e, 0x45, 0x4e, 0x29, 0xfc, 0x7b, 0x55, 0x5d, 0xa7, 0x47, 0x9d, 0xff, - 0x03, 0x7f, 0x28, 0x02, 0xca, 0x46, 0x8e, 0x01, 0x00 + 0x4d, 0x34, 0xcb, 0xe7, 0x37, 0x98, 0xd5, 0xc7, 0x00, 0xe6, 0x74, 0xa8, 0xf4, 0xd5, 0x4e, 0x7a, + 0xe0, 0x0c, 0xdd, 0x93, 0x80, 0x25, 0x4b, 0x61, 0x2d, 0x74, 0xf8, 0xb2, 0xc0, 0xce, 0xfe, 0x31, + 0x80, 0x3a, 0x30, 0x9f, 0x9f, 0x84, 0x26, 0x5b, 0x56, 0xfe, 0x7d, 0x68, 0xdc, 0x85, 0x66, 0xef, + 0x02, 0xdf, 0xac, 0xeb, 0x23, 0x4d, 0x93, 0x10, 0x30, 0xad, 0xe5, 0xc0, 0x76, 0xd7, 0x27, 0x69, + 0x8b, 0x0c, 0x8d, 0x31, 0x39, 0x72, 0xc8, 0x30, 0x21, 0x4e, 0x34, 0x82, 0xa4, 0x31, 0x15, 0x93, + 0xb0, 0x1f, 0xbe, 0x8e, 0x88, 0xed, 0x1c, 0xdb, 0x86, 0x9e, 0x98, 0x59, 0x43, 0x04, 0xba, 0xfa, + 0x45, 0x0e, 0x7f, 0x86, 0x96, 0xa7, 0x24, 0x29, 0x57, 0x94, 0x25, 0x3e, 0x01, 0xbb, 0x83, 0x11, + 0xae, 0x2d, 0x1f, 0x22, 0xa5, 0xfa, 0xfc, 0xe6, 0x12, 0xde, 0x13, 0x59, 0x04, 0x7b, 0x20, 0x4c, + 0x76, 0x1c, 0x5b, 0xe4, 0x0f, 0x4c, 0x62, 0xec, 0xdb, 0xbd, 0xa9, 0x18, 0xb7, 0x09, 0x74, 0x19, + 0xeb, 0xc2, 0xaf, 0x8d, 0xb8, 0xdb, 0xc0, 0x1a, 0x6b, 0x50, 0x18, 0xba, 0x27, 0x2d, 0x4b, 0xf9, + 0x8c, 0xdb, 0x2c, 0x3a, 0x22, 0xfc, 0xfb, 0x3c, 0x87, 0x39, 0xc4, 0xae, 0x60, 0x39, 0x5e, 0x01, + 0xa9, 0x7f, 0xd4, 0x4d, 0x88, 0xcc, 0xfd, 0xd1, 0x16, 0x93, 0x69, 0x0a, 0x18, 0x7e, 0xcd, 0x18, + 0x3f, 0x6d, 0x0c, 0x19, 0x0a, 0x6c, 0x26, 0xac, 0x95, 0xb6, 0x36, 0xb2, 0xea, 0xdf, 0x96, 0x16, + 0x67, 0xd3, 0xeb, 0xd7, 0xf8, 0x9f, 0x65, 0x64, 0x1c, 0xd5, 0x2d, 0x05, 0x88, 0x1d, 0x91, 0x25, + 0x44, 0x74, 0x85, 0xc3, 0x21, 0xb8, 0xcc, 0x20, 0xec, 0xde, 0xee, 0x1d, 0x4c, 0x7a, 0x3c, 0x31, + 0x4a, 0x0d, 0x2e, 0x9a, 0xfe, 0x41, 0x8e, 0xb8, 0xff, 0xf0, 0xff, 0x0f, 0x2f, 0x0c, 0x94, 0x6e, + 0x3b, 0x44, 0xc7, 0x2e, 0xe6, 0x3c, 0x58, 0xd8, 0x7b, 0x30, 0xa0, 0xa0, 0xcc, 0x25, 0xaa, 0xfb, + 0x1e, 0x58, 0xcc, 0x07, 0x93, 0xde, 0xf6, 0xa0, 0xd3, 0x96, 0xdd, 0xcf, 0x37, 0xca, 0xb5, 0x30, + 0xd5, 0xd2, 0x32, 0x6d, 0x63, 0xea, 0x4a, 0x55, 0x3d, 0xcd, 0xa9, 0x8b, 0x7e, 0xd1, 0x90, 0xb5, + 0x83, 0x75, 0xb0, 0x3f, 0xf5, 0xe8, 0x20, 0x6c, 0x9b, 0xf0, 0x5d, 0x3b, 0x51, 0xa6, 0xf2, 0xc6, + 0xed, 0x0d, 0xd8, 0x45, 0x7b, 0x00, 0xa6, 0xbc, 0x2b, 0x0a, 0x8b, 0x40, 0x0a, 0x94, 0x0a, 0x00, + 0xd2, 0x72, 0x2e, 0x57, 0x5a, 0x0a, 0xe7, 0x1a, 0x0f, 0x28, 0x2d, 0xc9, 0xdb, 0x23, 0xfe, 0x32, + 0x94, 0x6b, 0x51, 0x30, 0x03, 0x28, 0xb3, 0xb1, 0x50, 0x52, 0x00, 0x2b, 0x85, 0x9c, 0x0f, 0xe0, + 0xda, 0x2a, 0x4c, 0xd2, 0x92, 0x9f, 0x00, 0x70, 0xed, 0xf3, 0x78, 0x0c, 0x00, 0xcc, 0x2d, 0x01, + 0x10, 0x96, 0x4b, 0xa5, 0x94, 0xff, 0xdc, 0x54, 0xd3, 0x82, 0x2b, 0x01, 0x5c, 0xfb, 0xab, 0x13, + 0x1d, 0x00, 0x78, 0x67, 0x2c, 0x27, 0xc7, 0x4a, 0x39, 0x5f, 0xf8, 0x1c, 0x84, 0x58, 0xf0, 0x7f, + 0x8c, 0x14, 0xf7, 0x61, 0x6f, 0x5b, 0x36, 0xcd, 0xd0, 0x73, 0xf1, 0x73, 0x74, 0x48, 0x4b, 0xfe, + 0x53, 0x74, 0x18, 0x3d, 0xeb, 0xa3, 0x0c, 0x41, 0x8e, 0x31, 0xe3, 0x45, 0x99, 0x2f, 0x7f, 0x38, + 0xbf, 0xc6, 0x0b, 0xde, 0x30, 0x48, 0xf2, 0x4e, 0x3c, 0xd9, 0x3b, 0x9f, 0x2b, 0x7e, 0x7c, 0xec, + 0xce, 0x6b, 0x32, 0xee, 0x71, 0xa1, 0x49, 0xd0, 0x0a, 0xb6, 0xd2, 0x16, 0x48, 0xcb, 0xb8, 0x66, + 0xd2, 0xcf, 0x9c, 0xc5, 0xff, 0xa3, 0xb6, 0xd2, 0x4f, 0x1e, 0xc5, 0x33, 0x1b, 0x62, 0x30, 0x2e, + 0x2a, 0x03, 0x04, 0xe8, 0x0d, 0x59, 0x15, 0x5d, 0xb9, 0xc0, 0xc6, 0x61, 0xc5, 0x19, 0x15, 0x39, + 0xf4, 0x64, 0xff, 0xe2, 0x04, 0xf1, 0xf3, 0x43, 0x41, 0xa0, 0xf2, 0x37, 0x93, 0x3b, 0xe5, 0xca, + 0xa7, 0x27, 0x28, 0x00, 0xe0, 0x08, 0x37, 0x27, 0x5b, 0x75, 0xde, 0x62, 0x0f, 0x89, 0xb8, 0x49, + 0xf2, 0x0b, 0xfe, 0x55, 0x9f, 0x09, 0xf9, 0x7f, 0xad, 0xcf, 0xc4, 0x32, 0xdb, 0x37, 0x8f, 0x75, + 0x8a, 0xb9, 0xac, 0x3f, 0x51, 0xaa, 0x37, 0xb4, 0xa5, 0x07, 0xe7, 0xb4, 0x56, 0x2e, 0x7a, 0x2a, + 0xe0, 0x6e, 0x3d, 0x2b, 0x67, 0x2c, 0xe4, 0x8f, 0xf5, 0x17, 0x8f, 0x61, 0x9a, 0xd9, 0xd8, 0x13, + 0x2a, 0xfe, 0x24, 0x61, 0x64, 0x3b, 0xc6, 0x90, 0xda, 0x5a, 0xfe, 0xda, 0xd2, 0x59, 0x8b, 0x77, + 0x78, 0xf8, 0x2b, 0x56, 0x75, 0x77, 0x3e, 0xd6, 0xfe, 0xf6, 0x84, 0x44, 0xe6, 0x23, 0x07, 0x74, + 0xcc, 0xc6, 0x23, 0x64, 0x57, 0x4f, 0x44, 0x3e, 0xb2, 0x68, 0xb8, 0x79, 0x58, 0x5b, 0x3d, 0x11, + 0x9e, 0xbb, 0xd6, 0x5f, 0x64, 0x62, 0xcd, 0xdc, 0x07, 0x1c, 0xcc, 0x9d, 0x87, 0xdc, 0x3f, 0xc3, + 0xc3, 0xe4, 0xff, 0x41, 0x0e, 0xf6, 0x89, 0x89, 0xc8, 0x8b, 0x5b, 0xee, 0x3c, 0xe4, 0x56, 0xcf, + 0x43, 0xe1, 0x6f, 0x2f, 0x08, 0x99, 0x94, 0xff, 0xd6, 0x82, 0xc8, 0x7f, 0x72, 0x41, 0xe4, 0x3f, + 0xb3, 0x20, 0xf2, 0xd9, 0xff, 0xad, 0xd7, 0x43, 0x21, 0x58, 0x0f, 0xf9, 0x65, 0xf3, 0xd0, 0x9b, + 0x1a, 0x66, 0xa0, 0xc3, 0xb8, 0x77, 0xb3, 0xa9, 0x9e, 0x16, 0x92, 0xab, 0x20, 0x43, 0x8e, 0x8a, + 0x54, 0x06, 0x27, 0x3c, 0xc7, 0x7b, 0x18, 0xba, 0xc6, 0xb6, 0x0f, 0x38, 0x2a, 0xb4, 0xed, 0xee, + 0x85, 0x5b, 0x4d, 0xda, 0x70, 0xb0, 0x78, 0x83, 0xe9, 0xa3, 0x5d, 0x5e, 0xf8, 0xde, 0x73, 0x9c, + 0x3c, 0x14, 0x9a, 0xbd, 0x0b, 0x3a, 0x80, 0x44, 0x56, 0xa2, 0xa7, 0x82, 0xee, 0x55, 0xac, 0xbf, + 0x24, 0xaa, 0xad, 0x85, 0x47, 0x9d, 0x8d, 0x08, 0x69, 0x1f, 0x0e, 0x3a, 0x5f, 0x68, 0x73, 0x83, + 0x5e, 0x5b, 0x36, 0x4d, 0xde, 0xa0, 0xb3, 0xde, 0xa0, 0x73, 0x4b, 0x07, 0x9d, 0x13, 0x17, 0x65, + 0xfd, 0xb8, 0x41, 0xe7, 0x3e, 0x3d, 0xe8, 0xb5, 0x55, 0x22, 0x34, 0x40, 0x96, 0xfb, 0x2b, 0x83, + 0x66, 0x4a, 0x72, 0xa1, 0xf3, 0xc1, 0x54, 0xf3, 0x83, 0xce, 0x79, 0x83, 0xce, 0x47, 0x06, 0xbd, + 0x16, 0x8c, 0x3a, 0xbf, 0x38, 0xd5, 0x71, 0x83, 0xce, 0x2f, 0x19, 0xf4, 0xa7, 0x14, 0x9b, 0xa5, + 0x1a, 0x31, 0x82, 0xd1, 0x22, 0xfd, 0x21, 0xc6, 0x3a, 0xff, 0xc8, 0xda, 0xe3, 0x5e, 0x08, 0x89, + 0x2a, 0xe1, 0x11, 0x76, 0xc7, 0xee, 0x88, 0x70, 0xec, 0x8e, 0x7d, 0x62, 0x49, 0xdc, 0x5a, 0x56, + 0x34, 0xb7, 0xdc, 0x0e, 0x69, 0xbb, 0x0b, 0xc7, 0xb2, 0xe1, 0x8d, 0x93, 0xe8, 0x2c, 0x00, 0x99, + 0x4a, 0x73, 0xd7, 0x04, 0x10, 0x24, 0xd8, 0xee, 0x00, 0x62, 0x4c, 0xea, 0x5b, 0x37, 0x41, 0xa0, + 0x80, 0x90, 0x6b, 0xa4, 0xe3, 0x78, 0x68, 0x67, 0x17, 0x27, 0x02, 0xfe, 0x46, 0x19, 0x5e, 0xa9, + 0x98, 0x2e, 0x7a, 0x1e, 0x60, 0x72, 0x3a, 0x1b, 0xf0, 0xb9, 0xf4, 0x26, 0x50, 0xbf, 0xde, 0xb6, + 0xcd, 0x1a, 0x73, 0x98, 0x46, 0xf3, 0x17, 0x75, 0x33, 0xa5, 0x5f, 0x28, 0xf3, 0xc9, 0xc9, 0x25, + 0x21, 0x5a, 0x20, 0x32, 0xec, 0x4b, 0x0b, 0x81, 0xfe, 0x10, 0xdb, 0xe6, 0xab, 0x26, 0xae, 0x38, + 0x68, 0xe9, 0x8a, 0x5b, 0x6e, 0x43, 0xdc, 0x11, 0xcb, 0x32, 0x83, 0x1a, 0xbb, 0xd9, 0x13, 0x6f, + 0x50, 0x8b, 0xb5, 0xa7, 0xad, 0x2d, 0x31, 0xe2, 0xd3, 0xf9, 0x4f, 0x7e, 0x13, 0x96, 0x58, 0xf0, + 0xdd, 0xec, 0x18, 0xf1, 0x82, 0xb7, 0x7b, 0x7d, 0xce, 0xec, 0xb5, 0xf6, 0x49, 0x03, 0xfe, 0x82, + 0xfd, 0x9e, 0x02, 0x11, 0xb1, 0x55, 0xb9, 0x2a, 0x76, 0xd8, 0x2a, 0xcf, 0xd0, 0x87, 0x64, 0x14, + 0xd0, 0xf3, 0xb2, 0xdd, 0xc3, 0xfc, 0x88, 0xa8, 0x97, 0xba, 0x0f, 0xd3, 0xaf, 0x33, 0x1a, 0x2e, + 0x5f, 0x70, 0xeb, 0x78, 0x8e, 0xed, 0x41, 0x11, 0x4d, 0xd5, 0x5f, 0xf8, 0xf3, 0x24, 0xc3, 0x24, + 0xfa, 0x8d, 0xd2, 0x4e, 0x2c, 0x77, 0x6a, 0xf7, 0xb6, 0x9c, 0x58, 0xa7, 0x76, 0xe6, 0x9d, 0x1c, + 0xef, 0x4e, 0xbf, 0xd0, 0xe9, 0xda, 0x42, 0xaf, 0xd9, 0x4f, 0xb8, 0xd2, 0x2f, 0x76, 0xea, 0x1a, + 0x8d, 0xd7, 0x3e, 0xd9, 0xed, 0x42, 0xaf, 0xb9, 0xa5, 0x57, 0x26, 0xfc, 0xad, 0x26, 0xf6, 0x6a, + 0x88, 0xbf, 0xfa, 0xff, 0xe6, 0x68, 0xf3, 0xcb, 0x46, 0xeb, 0xf3, 0xfa, 0xd8, 0x8b, 0x37, 0x8c, + 0x7c, 0xd6, 0x56, 0x5f, 0x1c, 0x70, 0xbf, 0xd8, 0x12, 0x3e, 0xb9, 0x67, 0x57, 0x8b, 0x14, 0x34, + 0xa9, 0x86, 0x6d, 0xc0, 0xd6, 0x1e, 0x7e, 0xa6, 0xe7, 0x06, 0xb3, 0xf0, 0x22, 0x59, 0x72, 0xb1, + 0x9a, 0x1f, 0xf6, 0x6c, 0xd5, 0xfd, 0x8f, 0x48, 0x1d, 0xbc, 0xe9, 0xe9, 0x2f, 0x08, 0x7a, 0x75, + 0x3d, 0x86, 0x06, 0x83, 0xb3, 0x0d, 0x81, 0x7e, 0x2e, 0xe7, 0xb3, 0x97, 0x5a, 0x04, 0x1a, 0x14, + 0x89, 0xe1, 0x0b, 0x0f, 0x50, 0xd6, 0x22, 0xe7, 0x0b, 0x3e, 0x10, 0xc3, 0x3e, 0x3b, 0xf9, 0x1d, + 0xf6, 0xbd, 0xfa, 0x13, 0x55, 0x14, 0x14, 0xcd, 0x71, 0xfd, 0xf2, 0xd1, 0xda, 0x53, 0xa5, 0x9f, + 0x1d, 0xc9, 0x98, 0x7a, 0xbf, 0xd6, 0x56, 0x6c, 0x52, 0x2a, 0x48, 0xea, 0xdd, 0xce, 0xc5, 0xf5, + 0x44, 0x3e, 0x39, 0xe8, 0x1b, 0x0d, 0xf8, 0xef, 0xbc, 0x75, 0x3b, 0xd8, 0xbb, 0xed, 0xc3, 0xd3, + 0x8e, 0x8c, 0xef, 0xfb, 0xcd, 0xc6, 0x23, 0xfc, 0x34, 0x8b, 0xfb, 0xa3, 0x5e, 0x11, 0x13, 0x1a, + 0x0f, 0xe7, 0xad, 0x6b, 0xf9, 0xa8, 0x61, 0xd9, 0x85, 0x4e, 0xe9, 0x0a, 0x13, 0xae, 0xf5, 0xab, + 0xdb, 0xec, 0x0e, 0x94, 0x99, 0x3e, 0x4f, 0xc6, 0xe5, 0xc7, 0xab, 0x5b, 0x4c, 0x3c, 0xee, 0xec, + 0x0d, 0x9e, 0x3a, 0x93, 0x46, 0x63, 0xd7, 0x3e, 0x83, 0xd7, 0xcd, 0xdd, 0x46, 0xa7, 0x3b, 0x7e, + 0x3d, 0xc0, 0x0a, 0x3b, 0xed, 0xd6, 0xed, 0xf5, 0xce, 0x5d, 0x73, 0x70, 0xa3, 0x3d, 0x56, 0xda, + 0xbb, 0x46, 0x63, 0xb2, 0x7b, 0x76, 0x7e, 0xbf, 0xa9, 0x57, 0xf4, 0x49, 0x53, 0x35, 0xdf, 0x9c, + 0xab, 0xf3, 0xc2, 0x53, 0xd9, 0x69, 0x5b, 0x37, 0x87, 0xc3, 0xdd, 0xe1, 0x7e, 0xc1, 0xb8, 0x7c, + 0x7f, 0xd3, 0xba, 0x93, 0xeb, 0x57, 0x33, 0xdb, 0x6a, 0x75, 0xf5, 0xbb, 0xcc, 0xf9, 0xe8, 0x69, + 0xf4, 0xfe, 0x4a, 0xac, 0xc6, 0xce, 0xdb, 0xf4, 0xe1, 0x5d, 0xdf, 0x99, 0xe4, 0xd5, 0xfe, 0x0b, + 0xd9, 0xdf, 0xeb, 0x3d, 0xbc, 0xdd, 0x8e, 0x06, 0x27, 0x99, 0xb7, 0xfd, 0x33, 0xb9, 0x39, 0x3d, + 0xee, 0xbd, 0xbd, 0x3e, 0x3c, 0xed, 0x5d, 0x74, 0x4a, 0x99, 0x96, 0x55, 0xc9, 0xb4, 0x7b, 0x9b, + 0xa3, 0xa3, 0x66, 0xf1, 0x7c, 0xd2, 0xdd, 0x34, 0xac, 0xb3, 0x71, 0xe3, 0x92, 0x8e, 0x65, 0x4f, + 0xdb, 0xbf, 0x79, 0x69, 0x8d, 0xae, 0x86, 0xcd, 0x26, 0x2c, 0x84, 0x45, 0x3f, 0xe1, 0x71, 0x98, + 0x73, 0x85, 0xdc, 0x66, 0x62, 0xf7, 0x54, 0xf7, 0x22, 0x30, 0x4f, 0x3b, 0xfc, 0xc9, 0x13, 0xdd, + 0x50, 0x7b, 0x40, 0xd7, 0x83, 0x95, 0x47, 0x60, 0x31, 0xad, 0x44, 0x28, 0xf0, 0x48, 0x07, 0x06, + 0xa9, 0x77, 0x88, 0x80, 0x67, 0x10, 0x7f, 0xb1, 0x2d, 0xef, 0x3c, 0x0f, 0x57, 0x67, 0xc2, 0x3f, + 0xc6, 0x63, 0x8a, 0x89, 0x98, 0x94, 0xc4, 0xff, 0xb2, 0x89, 0x86, 0x6e, 0x39, 0x5b, 0xb7, 0x34, + 0x89, 0x5e, 0xfe, 0x8b, 0xb9, 0x33, 0x14, 0xd7, 0x07, 0x15, 0x25, 0x70, 0xd5, 0x86, 0xa5, 0x89, + 0x8e, 0xde, 0xa3, 0x72, 0x04, 0x1b, 0x7f, 0xdb, 0x30, 0x9c, 0x48, 0xa3, 0xfe, 0x19, 0x31, 0x45, + 0x2e, 0x2f, 0xf8, 0x0d, 0xc4, 0xad, 0x33, 0x50, 0x21, 0x84, 0x89, 0xea, 0x0c, 0xdc, 0x1c, 0xe6, + 0x31, 0xa0, 0x58, 0x0e, 0xae, 0x09, 0x58, 0xc4, 0xe5, 0x42, 0x0d, 0xd6, 0xc6, 0xfe, 0x9e, 0xbc, + 0x57, 0x73, 0x37, 0x97, 0x35, 0xa1, 0xfd, 0x26, 0x34, 0x54, 0xab, 0x63, 0x18, 0xc6, 0x8b, 0x4a, + 0xe8, 0x2d, 0x5d, 0x67, 0x40, 0x84, 0xef, 0x8a, 0x40, 0xcf, 0x2e, 0xc5, 0x81, 0xe3, 0x98, 0x76, + 0x35, 0x93, 0xc1, 0x03, 0xc2, 0x34, 0xe8, 0x50, 0x1d, 0x63, 0x64, 0xd9, 0x24, 0x8d, 0x4e, 0x35, + 0x66, 0x06, 0xc4, 0x16, 0xc5, 0x02, 0xbc, 0xd4, 0xc5, 0xff, 0x72, 0x2f, 0xb1, 0xac, 0xd1, 0xfb, + 0x8f, 0x1d, 0x63, 0x38, 0x1c, 0xe9, 0xd4, 0x32, 0xa3, 0x6c, 0x2d, 0xdb, 0xc6, 0x74, 0x76, 0x75, + 0xec, 0xdf, 0xe5, 0x05, 0xcb, 0xae, 0x9a, 0x7d, 0x96, 0x19, 0xe0, 0x77, 0xb2, 0xc4, 0x2d, 0x0a, + 0xb6, 0xea, 0x92, 0x8a, 0xbd, 0x40, 0xdd, 0xfa, 0x22, 0x75, 0xbb, 0x8e, 0x49, 0xde, 0x89, 0xf6, + 0x92, 0x0f, 0x71, 0x8a, 0x9f, 0xa5, 0x5a, 0x94, 0x02, 0xfc, 0xa1, 0x2c, 0x52, 0x7e, 0xbc, 0xb4, + 0x3c, 0xd4, 0xc6, 0x78, 0x2c, 0xb0, 0x16, 0xc5, 0xa1, 0x0f, 0x37, 0xcb, 0x77, 0x61, 0x5c, 0x8c, + 0x48, 0x83, 0xcf, 0xb1, 0x23, 0x8b, 0xba, 0xb8, 0xe3, 0xc5, 0xd7, 0xc5, 0xa9, 0x8a, 0x20, 0x7c, + 0x30, 0xea, 0x93, 0xc8, 0x45, 0x46, 0x24, 0xd2, 0x35, 0xbf, 0x19, 0x0d, 0x98, 0xc4, 0x76, 0xc0, + 0x1b, 0xf0, 0xdf, 0x9a, 0x70, 0x63, 0x08, 0x23, 0x9b, 0x08, 0xed, 0x91, 0xaa, 0x61, 0x3c, 0x5e, + 0x81, 0xb0, 0x9d, 0x5b, 0xa2, 0xa9, 0x28, 0x27, 0x41, 0xd7, 0x16, 0x48, 0xb0, 0xee, 0xed, 0x76, + 0x01, 0xf6, 0x1b, 0x58, 0x91, 0xac, 0xee, 0xa3, 0x31, 0x12, 0x3a, 0x50, 0xc6, 0x22, 0xce, 0xc8, + 0xd2, 0x05, 0x74, 0xe9, 0x22, 0xc0, 0xc5, 0xd5, 0x21, 0xa1, 0xa7, 0xbb, 0x48, 0xdb, 0x18, 0xae, + 0xc3, 0xc6, 0x3b, 0xbe, 0x48, 0xd5, 0xf8, 0xc1, 0x1c, 0x40, 0x3e, 0x7d, 0x46, 0xa1, 0x14, 0xef, + 0x1f, 0x03, 0xb1, 0x5a, 0x3a, 0xb1, 0xd2, 0x6b, 0x6c, 0x45, 0x2d, 0x4c, 0x56, 0xc8, 0x61, 0xc9, + 0x39, 0x35, 0x2c, 0x2a, 0x91, 0x5c, 0x78, 0x50, 0x19, 0xd4, 0x03, 0x75, 0xc5, 0x92, 0x5f, 0xac, + 0x9f, 0xe3, 0xeb, 0x8f, 0x74, 0xbc, 0x76, 0x6e, 0xd1, 0xa5, 0xee, 0xb7, 0xc3, 0x2d, 0xee, 0xb5, + 0x60, 0x75, 0xaf, 0xed, 0x1b, 0x16, 0x0c, 0xdf, 0x76, 0x04, 0x93, 0x58, 0xe8, 0x8d, 0x86, 0xb4, + 0x2a, 0x09, 0x2a, 0x28, 0x11, 0xf8, 0x8d, 0x3d, 0x5c, 0x74, 0x84, 0x06, 0x1e, 0x01, 0x3c, 0x50, + 0x7c, 0x18, 0xbd, 0x9e, 0x3b, 0x6c, 0x40, 0xcb, 0x10, 0x91, 0x60, 0xc3, 0xea, 0x05, 0x56, 0x38, + 0x19, 0x10, 0x9d, 0x46, 0xc1, 0x00, 0x5c, 0x00, 0x9a, 0xd3, 0x0b, 0x2e, 0xc5, 0x6a, 0x30, 0xed, + 0x88, 0x33, 0x31, 0x66, 0xa2, 0x17, 0x86, 0x25, 0x27, 0x83, 0xc9, 0x5f, 0xf3, 0x67, 0xff, 0xbb, + 0x7b, 0xdb, 0x79, 0x6d, 0x0c, 0xa8, 0xd7, 0x8c, 0x8e, 0x6a, 0x4a, 0x93, 0x7b, 0x89, 0xf7, 0x4c, + 0x90, 0x26, 0xb6, 0xd4, 0xc1, 0xdb, 0x69, 0x12, 0x35, 0xe3, 0xd8, 0x92, 0xc3, 0x9c, 0x0c, 0xd0, + 0x59, 0xa1, 0xfe, 0x25, 0x8b, 0x3f, 0x26, 0xac, 0x65, 0x83, 0xf1, 0xa2, 0xaa, 0x28, 0xa9, 0xf6, + 0x85, 0x8e, 0x19, 0xba, 0xd6, 0xc0, 0x1f, 0xd5, 0x3e, 0x1d, 0xb3, 0x5f, 0x14, 0x0b, 0xd8, 0x13, + 0x5d, 0x49, 0xf8, 0x68, 0xbf, 0xe9, 0x9d, 0x16, 0x60, 0xc5, 0x7b, 0xbe, 0xe9, 0x6b, 0xd7, 0xa4, + 0x03, 0xe5, 0x65, 0x69, 0xa0, 0xd8, 0xd4, 0xcd, 0x14, 0xb3, 0xe0, 0xf9, 0xfa, 0x60, 0xc7, 0x7d, + 0x6a, 0x36, 0x6f, 0x58, 0xf3, 0xbb, 0x23, 0xab, 0x5e, 0x92, 0xe1, 0xe1, 0x46, 0xb1, 0xea, 0xf8, + 0x8b, 0x97, 0x33, 0x69, 0x4b, 0xa4, 0x7f, 0x8a, 0xaa, 0x99, 0x2c, 0x79, 0xb1, 0xab, 0xf6, 0xf9, + 0x97, 0x4b, 0x45, 0x83, 0xb7, 0x0e, 0xbc, 0xe2, 0xcf, 0xc8, 0xc2, 0xd8, 0x97, 0x4c, 0x44, 0xab, + 0x6f, 0xc0, 0x78, 0x40, 0xa2, 0x62, 0xfb, 0x05, 0xad, 0xd2, 0x6f, 0x1a, 0x40, 0x03, 0xf0, 0x08, + 0x0c, 0xd6, 0x7f, 0x34, 0x26, 0x30, 0xcd, 0xb7, 0x3a, 0xcc, 0x4d, 0x17, 0x5e, 0xa1, 0x2b, 0x10, + 0x2c, 0x31, 0x9d, 0xfd, 0x98, 0x1d, 0x0f, 0x10, 0xf6, 0x44, 0xd1, 0x80, 0xcd, 0x4e, 0x20, 0xd3, + 0xb1, 0xea, 0x9b, 0x52, 0xb7, 0xde, 0x05, 0x9d, 0x08, 0x45, 0x51, 0xa9, 0x37, 0x45, 0x69, 0xa6, + 0xfe, 0xe3, 0xa7, 0x64, 0xe2, 0xc6, 0x5a, 0x9f, 0xcd, 0x25, 0xe2, 0x3d, 0x68, 0xde, 0x83, 0x19, + 0x3c, 0x9d, 0xd7, 0x45, 0x51, 0x32, 0x8f, 0xb0, 0x9b, 0xf3, 0xd1, 0x10, 0x7f, 0x86, 0x4e, 0x3d, + 0x8b, 0x7f, 0x4f, 0x5b, 0xec, 0xed, 0x14, 0x7a, 0x42, 0x60, 0xe0, 0x07, 0x19, 0x19, 0xd6, 0x52, + 0xed, 0x33, 0x84, 0x61, 0x88, 0x00, 0x0c, 0x07, 0xf0, 0x67, 0x62, 0x5f, 0x9b, 0x58, 0xa8, 0xd3, + 0xeb, 0xd7, 0x67, 0x0e, 0xde, 0x17, 0xad, 0xce, 0x50, 0x8c, 0xaa, 0x82, 0x6c, 0x65, 0xbd, 0x88, + 0x52, 0xbb, 0x5f, 0x9d, 0x8d, 0x2c, 0xad, 0x2a, 0x8a, 0x73, 0x49, 0xd1, 0xcc, 0x81, 0x02, 0xd9, + 0xfd, 0x6a, 0xba, 0x24, 0x81, 0x54, 0x5b, 0x4d, 0x97, 0xe7, 0x12, 0x73, 0x06, 0xc5, 0x44, 0x28, + 0x82, 0xaf, 0x43, 0xb3, 0xca, 0x42, 0xdb, 0xd8, 0xd5, 0x19, 0xbb, 0xcb, 0x58, 0x85, 0x49, 0xb4, + 0xfa, 0xed, 0x2a, 0x74, 0xfc, 0x3a, 0x82, 0x14, 0x7c, 0x1f, 0x90, 0x29, 0xbc, 0xc3, 0xc8, 0xa8, + 0x6a, 0x8a, 0x29, 0x66, 0x67, 0x08, 0xcc, 0x18, 0x0b, 0x99, 0x6a, 0x17, 0x13, 0x00, 0xe5, 0x1a, + 0xd1, 0xab, 0x6c, 0x1a, 0xcd, 0x89, 0xe5, 0x3e, 0x91, 0xa9, 0x89, 0x4f, 0x1d, 0x9b, 0xd6, 0x1a, + 0x74, 0x95, 0x37, 0x1b, 0xdf, 0x01, 0x7d, 0xa4, 0x87, 0x29, 0x6a, 0xd7, 0x36, 0x2c, 0x6c, 0x07, + 0xa0, 0x01, 0x15, 0xb5, 0xfe, 0xe3, 0x87, 0x2c, 0x65, 0xb3, 0x52, 0xae, 0x20, 0x15, 0x24, 0x7f, + 0x97, 0x54, 0xfc, 0x9d, 0x34, 0xdd, 0x87, 0x6d, 0x78, 0xd4, 0x4e, 0xab, 0x46, 0x66, 0x3a, 0x54, + 0xec, 0x34, 0xc8, 0x91, 0xe2, 0x4f, 0x09, 0xea, 0xe4, 0xa4, 0xec, 0xa6, 0x94, 0x0d, 0xaa, 0x50, + 0x31, 0xd3, 0x4e, 0x53, 0x24, 0x74, 0x0c, 0xf4, 0x06, 0x48, 0xc3, 0x60, 0x33, 0x85, 0x4a, 0x16, + 0xff, 0x65, 0x73, 0xf9, 0xf4, 0xb3, 0x49, 0xab, 0xe6, 0xe4, 0x5c, 0x51, 0xca, 0x4b, 0x39, 0x6c, + 0x62, 0x75, 0x87, 0x04, 0x66, 0x06, 0x38, 0x9a, 0xdb, 0x25, 0xd4, 0x2b, 0x40, 0x95, 0x7c, 0xf6, + 0x2f, 0xd6, 0x93, 0xa5, 0x12, 0x0c, 0xed, 0x63, 0x48, 0x8b, 0xd9, 0x12, 0xfe, 0xdb, 0xac, 0xe4, + 0x3c, 0x48, 0xf1, 0xeb, 0x07, 0xd9, 0x4f, 0xd4, 0xcc, 0x66, 0x2b, 0xf8, 0xaf, 0x5c, 0x96, 0x65, + 0x56, 0xf5, 0x67, 0xad, 0x37, 0xd2, 0x69, 0x80, 0x70, 0x61, 0x00, 0x92, 0x88, 0x46, 0xee, 0xfc, + 0x50, 0x3b, 0x4d, 0x6a, 0x84, 0x4a, 0x24, 0x67, 0x5f, 0xba, 0x69, 0x16, 0xdc, 0x70, 0x7d, 0x5d, + 0x27, 0x13, 0x01, 0x38, 0x08, 0x7e, 0x8f, 0xd1, 0x5b, 0x52, 0x5b, 0x79, 0x92, 0x5f, 0x5f, 0x0f, + 0x09, 0x92, 0x73, 0xbf, 0x4d, 0x1b, 0x54, 0xd1, 0x04, 0x91, 0x9c, 0xe4, 0x0c, 0x44, 0x19, 0x77, + 0x7d, 0xec, 0x69, 0x04, 0x7f, 0xd2, 0x74, 0x8f, 0x4c, 0xc3, 0x12, 0xbd, 0xb4, 0x40, 0xda, 0xb3, + 0x9c, 0x37, 0x5a, 0x30, 0xa8, 0x8b, 0x5e, 0x2d, 0x24, 0x39, 0x73, 0x77, 0x9a, 0x6e, 0x1a, 0xa4, + 0x1e, 0xb7, 0xea, 0xce, 0x1b, 0xcd, 0xe2, 0x8a, 0xee, 0xed, 0x34, 0xcf, 0x97, 0x14, 0xb6, 0x77, + 0xde, 0x9a, 0xc8, 0x4a, 0xcf, 0x41, 0x77, 0x0a, 0x55, 0x52, 0xed, 0xbd, 0xa1, 0x89, 0xbd, 0xfa, + 0xd5, 0xe4, 0x7a, 0xbd, 0x7e, 0xd1, 0x7e, 0xc6, 0xef, 0x16, 0xbc, 0x90, 0x37, 0x1b, 0x72, 0xd2, + 0xcc, 0x2b, 0x94, 0xaf, 0x04, 0x05, 0xb8, 0x2a, 0x64, 0x7d, 0x5d, 0x34, 0x68, 0x15, 0xb1, 0x5e, + 0x47, 0xc3, 0x8a, 0xd1, 0xc3, 0xb4, 0x2f, 0x0d, 0xcb, 0x52, 0xde, 0xd2, 0xaa, 0x4d, 0x7f, 0x23, + 0xdd, 0xc2, 0x22, 0x27, 0x96, 0xda, 0x09, 0x5a, 0xf9, 0x02, 0x69, 0xca, 0x79, 0xc2, 0x54, 0x40, + 0xce, 0xdb, 0xc7, 0x48, 0x36, 0x90, 0x95, 0x5c, 0x5f, 0x57, 0xd1, 0x6e, 0x03, 0xec, 0x32, 0x52, + 0xfd, 0xba, 0xdf, 0xa6, 0xae, 0xf4, 0x61, 0xc0, 0x69, 0xe5, 0x23, 0x1d, 0xaa, 0xa6, 0x2d, 0xa8, + 0x1b, 0x4e, 0xe9, 0x2f, 0xa4, 0xb4, 0xb9, 0x26, 0x61, 0x45, 0xb7, 0x1c, 0x2b, 0x68, 0x0e, 0xef, + 0xd7, 0x25, 0xc4, 0x14, 0x34, 0x94, 0x12, 0x25, 0xfc, 0xed, 0xbb, 0xbf, 0xed, 0x94, 0x98, 0x14, + 0x43, 0xf5, 0xf0, 0x66, 0xb8, 0x5f, 0x2f, 0x9d, 0xcb, 0xe6, 0x4a, 0x7f, 0x86, 0x00, 0x49, 0xa5, + 0x37, 0xb3, 0xc5, 0xdc, 0x9f, 0x21, 0x50, 0x52, 0x69, 0x79, 0x33, 0x17, 0x4a, 0xe3, 0x81, 0xc1, + 0xa3, 0x89, 0xd6, 0x29, 0x36, 0x0a, 0xfb, 0x95, 0xe0, 0xd4, 0x49, 0x1a, 0x99, 0x29, 0xa4, 0xa6, + 0x27, 0xdb, 0x5c, 0x15, 0x3f, 0x31, 0x59, 0x05, 0xfe, 0x82, 0xa2, 0xad, 0x4e, 0xc4, 0x2f, 0x75, + 0xf4, 0xfc, 0x6b, 0x02, 0xe3, 0x1a, 0xc1, 0x9e, 0xd0, 0x42, 0xfa, 0xc2, 0x39, 0x44, 0x4b, 0x57, + 0x8b, 0x86, 0x66, 0xac, 0xb1, 0x5d, 0x07, 0xe6, 0x87, 0x47, 0xa3, 0xd7, 0x58, 0x72, 0x3b, 0x61, + 0xff, 0xfe, 0x0d, 0xef, 0x8c, 0x32, 0x29, 0xeb, 0xab, 0x7b, 0x83, 0xf4, 0x0b, 0x6d, 0x65, 0x73, + 0x9b, 0xdb, 0xf4, 0x82, 0x80, 0x58, 0xa5, 0xf7, 0x28, 0x40, 0x2d, 0xf1, 0xaa, 0x04, 0xce, 0xef, + 0x75, 0x6f, 0xaf, 0x5b, 0x5f, 0x77, 0xb6, 0xe4, 0xed, 0x5f, 0x51, 0x5f, 0xf8, 0x6c, 0x99, 0x5e, + 0xb5, 0x15, 0xfe, 0x98, 0x79, 0xd8, 0xf7, 0x3b, 0x98, 0x0b, 0x79, 0xf9, 0x5f, 0x12, 0x62, 0x37, + 0xf1, 0xc7, 0xcc, 0x99, 0x4b, 0xfe, 0x9f, 0x64, 0xf2, 0x57, 0x75, 0xa1, 0x74, 0xb2, 0x9a, 0xf0, + 0x07, 0x15, 0xf4, 0x99, 0x80, 0x9d, 0x20, 0x16, 0xac, 0x5f, 0x31, 0xcd, 0xfe, 0x92, 0x16, 0x87, + 0xed, 0xc4, 0x0c, 0x93, 0x9b, 0x25, 0xc5, 0x34, 0xb5, 0xb7, 0x66, 0xaf, 0x0f, 0xdc, 0xa1, 0xc3, + 0x62, 0x13, 0x88, 0x1a, 0xca, 0xb7, 0xb0, 0x08, 0xea, 0xb0, 0x01, 0xa5, 0xe9, 0xfe, 0x93, 0xc6, + 0xed, 0x27, 0x59, 0x43, 0x31, 0x84, 0x70, 0xa9, 0xb4, 0x83, 0x74, 0xbb, 0x5f, 0x03, 0x38, 0x29, + 0x7f, 0x10, 0x69, 0x7c, 0x4c, 0x51, 0x72, 0xcb, 0x3a, 0xb4, 0x2c, 0x6e, 0x3f, 0x69, 0xb6, 0xa7, + 0xd4, 0xbc, 0x52, 0x4e, 0xdb, 0x14, 0x25, 0x67, 0x5b, 0xcc, 0xd2, 0x8f, 0x98, 0xfb, 0x5f, 0x32, + 0xc7, 0x27, 0x00, 0x92, 0x3e, 0x63, 0x8c, 0x6c, 0x1a, 0x73, 0x09, 0x1f, 0x60, 0x66, 0xbc, 0xaa, + 0x6d, 0xb7, 0xaa, 0x1f, 0xb3, 0x52, 0x90, 0xbd, 0x2a, 0x6e, 0x8c, 0x26, 0xbe, 0xf0, 0xa0, 0x4b, + 0x0b, 0x53, 0x67, 0x59, 0x28, 0x46, 0x89, 0x8b, 0xcb, 0x1e, 0x3a, 0x34, 0x5b, 0xa6, 0xdd, 0x16, + 0x43, 0xfd, 0x38, 0x1b, 0x6d, 0x51, 0x0a, 0xc6, 0x4a, 0xf9, 0x6f, 0x1a, 0x76, 0xd8, 0xa0, 0x84, + 0xdd, 0x37, 0x59, 0x09, 0x3a, 0x42, 0xb6, 0x21, 0x6e, 0xb3, 0x2e, 0xaa, 0x6e, 0x8f, 0x50, 0x58, + 0x45, 0x7f, 0x92, 0xb0, 0xf3, 0x28, 0xba, 0x16, 0xf8, 0x9e, 0xa3, 0xb7, 0x2a, 0x60, 0x5f, 0x94, + 0x8e, 0x5b, 0x17, 0xe7, 0x30, 0x6f, 0xf8, 0x91, 0x56, 0xb5, 0xf7, 0x96, 0x80, 0x66, 0x93, 0x49, + 0x5f, 0x4c, 0x00, 0xe6, 0xd5, 0xb5, 0xd7, 0xd7, 0x99, 0xf2, 0x7c, 0x7b, 0xc4, 0xf3, 0x65, 0xcf, + 0x5b, 0x7c, 0xe6, 0x03, 0xc2, 0x36, 0xfa, 0x34, 0xec, 0xe6, 0xf5, 0x2f, 0x31, 0x89, 0x52, 0x30, + 0xe3, 0xa1, 0x56, 0xdc, 0xc0, 0x14, 0xb3, 0xf0, 0xa4, 0xd7, 0x97, 0x51, 0xc3, 0x36, 0x13, 0x46, + 0xaa, 0x6e, 0xfe, 0xb2, 0x56, 0x3d, 0x87, 0x8d, 0x59, 0x84, 0x12, 0x38, 0xd0, 0x58, 0xc2, 0xb2, + 0x06, 0xe8, 0xe5, 0x81, 0x85, 0xc1, 0x01, 0xed, 0x2f, 0x0e, 0x0e, 0x12, 0x63, 0x5b, 0x71, 0xe9, + 0x1a, 0x18, 0x11, 0x01, 0xbe, 0xc0, 0xd3, 0xa9, 0xf8, 0x95, 0x10, 0x9e, 0x1e, 0x3a, 0x1b, 0x3d, + 0x4c, 0xa4, 0xb7, 0x9d, 0xb8, 0xc4, 0x1c, 0x26, 0x76, 0xbb, 0xdd, 0x50, 0x62, 0x1e, 0x13, 0xdb, + 0xed, 0x76, 0x28, 0xb1, 0x80, 0x89, 0x8a, 0xa2, 0x84, 0x12, 0x8b, 0x98, 0x58, 0xa9, 0x54, 0x42, + 0x89, 0xa5, 0xb8, 0xc4, 0x32, 0x26, 0x96, 0xcb, 0xe5, 0x50, 0x62, 0x1b, 0x13, 0x0b, 0x85, 0x42, + 0x28, 0xb1, 0x83, 0x89, 0xf9, 0x7c, 0x3e, 0x94, 0x48, 0x30, 0x31, 0x9b, 0xcd, 0x86, 0x12, 0xbb, + 0x98, 0x98, 0xcb, 0xe5, 0x42, 0x89, 0x16, 0x85, 0x33, 0x17, 0x2e, 0xd9, 0xa7, 0x25, 0x95, 0x70, + 0xa2, 0x46, 0x13, 0x4b, 0x9d, 0x50, 0xa2, 0x01, 0x89, 0xf4, 0x73, 0x8f, 0x39, 0xb9, 0x20, 0x09, + 0xc1, 0x1f, 0x39, 0x5d, 0x49, 0x86, 0x0a, 0xda, 0x6d, 0x17, 0x9f, 0xf9, 0x48, 0xf2, 0xc0, 0x4d, + 0x2f, 0x85, 0xd2, 0x9d, 0xf6, 0x92, 0x86, 0xdd, 0x4f, 0x6b, 0x6f, 0xb4, 0x93, 0xc9, 0x48, 0x05, + 0xc5, 0xab, 0x91, 0xdd, 0x94, 0x25, 0x21, 0xf8, 0xb3, 0xbc, 0xc6, 0xe0, 0x53, 0x7d, 0x50, 0x4f, + 0x5c, 0x6a, 0xec, 0x4c, 0xba, 0xec, 0x94, 0x79, 0xaa, 0xa1, 0x19, 0x16, 0x83, 0x74, 0x27, 0xe4, + 0x74, 0x19, 0xca, 0x55, 0xa3, 0x04, 0x15, 0x45, 0x3f, 0x25, 0x28, 0xb6, 0xb7, 0x44, 0x08, 0x2a, + 0x3a, 0x27, 0xf9, 0xb8, 0x29, 0x2d, 0xc4, 0x4d, 0x3e, 0x25, 0xa8, 0x62, 0xb1, 0xb8, 0x48, 0x50, + 0xa5, 0x52, 0xe9, 0x93, 0x04, 0x15, 0xa5, 0x5c, 0x4a, 0x50, 0x9d, 0x4e, 0x67, 0x91, 0xa0, 0xa2, + 0x4b, 0xa4, 0x1b, 0xb7, 0x1a, 0x28, 0x41, 0x91, 0x42, 0x6e, 0x91, 0xa0, 0x0a, 0x24, 0xb7, 0x48, + 0x50, 0x85, 0xb2, 0x12, 0x4f, 0x50, 0x79, 0x98, 0x08, 0xef, 0xdf, 0x12, 0x6a, 0x02, 0x64, 0xc6, + 0x52, 0x13, 0xa4, 0x17, 0x97, 0x50, 0x13, 0xdf, 0xea, 0x67, 0x48, 0x49, 0xce, 0x01, 0x15, 0xf9, + 0x7f, 0x3e, 0x41, 0x4a, 0xc5, 0xac, 0x24, 0x78, 0xff, 0x3e, 0x4b, 0x47, 0x23, 0x1d, 0xf6, 0x01, + 0x91, 0xe3, 0x53, 0x68, 0xc6, 0xda, 0xe9, 0x07, 0x02, 0x13, 0xad, 0xda, 0xee, 0x63, 0x9f, 0xf5, + 0x6e, 0xba, 0x63, 0x11, 0x60, 0xfe, 0xae, 0x28, 0x4c, 0x9b, 0x14, 0x93, 0x35, 0xb5, 0x97, 0xb0, + 0xd3, 0x68, 0x76, 0x27, 0x92, 0x08, 0x3c, 0x9a, 0xfc, 0xfe, 0xed, 0xeb, 0x0d, 0xa0, 0xe7, 0xd9, + 0xa3, 0x61, 0xda, 0x1c, 0x80, 0xfa, 0x6f, 0x67, 0xb2, 0x95, 0x9c, 0x9c, 0xc9, 0xca, 0x65, 0x19, + 0x39, 0x39, 0xf4, 0x80, 0xdb, 0xb3, 0x5e, 0xf7, 0x94, 0x80, 0x5a, 0xcf, 0xb0, 0x12, 0xd4, 0xc8, + 0x20, 0x80, 0xd4, 0x0b, 0x1a, 0xda, 0xef, 0xdf, 0x3f, 0x7e, 0xb2, 0x42, 0x4a, 0x1d, 0xc4, 0x4d, + 0xed, 0x87, 0xfc, 0x73, 0x5b, 0x47, 0x51, 0x7c, 0x7f, 0xa4, 0x69, 0x8f, 0x20, 0xff, 0x24, 0x92, + 0x55, 0x4c, 0x94, 0x0c, 0xbf, 0x8d, 0x84, 0x22, 0x69, 0x3f, 0xb2, 0x3f, 0xe1, 0x4f, 0xee, 0x67, + 0x52, 0x52, 0x83, 0x74, 0x03, 0xc0, 0xc4, 0x2d, 0x8f, 0xbe, 0xa8, 0xd8, 0x08, 0x7d, 0x4a, 0xa6, + 0xb4, 0x1f, 0x79, 0x28, 0xa9, 0x6f, 0xd5, 0x0d, 0x50, 0x47, 0xbe, 0xd7, 0x55, 0x10, 0x76, 0xd8, + 0x60, 0xb4, 0x1f, 0x85, 0x9f, 0xc9, 0xf9, 0xdc, 0x46, 0xaf, 0xfd, 0x3d, 0xfc, 0x7c, 0x37, 0x9a, + 0x9f, 0x89, 0x4e, 0xac, 0x04, 0x35, 0xf5, 0x81, 0x7c, 0x51, 0xdf, 0x72, 0x47, 0xc0, 0x49, 0xda, + 0xd1, 0xbd, 0xba, 0xdd, 0x87, 0x8e, 0xa9, 0x38, 0xae, 0x83, 0xd0, 0x9c, 0xd0, 0xeb, 0xe9, 0x52, + 0x52, 0xf2, 0x94, 0x15, 0x37, 0xfa, 0x5c, 0x5d, 0xf7, 0x53, 0x02, 0xd1, 0xea, 0x08, 0x35, 0xae, + 0xfa, 0x2f, 0x50, 0xb1, 0x41, 0xbe, 0xa2, 0x10, 0x51, 0xc9, 0x8a, 0xdd, 0x02, 0xa1, 0x93, 0x12, + 0x9c, 0xde, 0x24, 0x43, 0xb2, 0x96, 0xe8, 0x45, 0x4c, 0xed, 0x80, 0x74, 0x1d, 0x99, 0xd9, 0xd6, + 0x8b, 0xaa, 0x37, 0x5b, 0x2d, 0x9c, 0x5e, 0x98, 0xb5, 0x2f, 0x4c, 0x27, 0x62, 0x38, 0x76, 0xea, + 0x11, 0x35, 0xe7, 0x46, 0xe9, 0x53, 0x25, 0x07, 0x0d, 0xd0, 0xb0, 0xce, 0x10, 0xd1, 0x31, 0x24, + 0x80, 0x07, 0x61, 0x40, 0x03, 0x76, 0x5a, 0xed, 0xc2, 0xfc, 0xc3, 0xfe, 0x47, 0x34, 0x3c, 0xd1, + 0x7c, 0xc3, 0x2f, 0x32, 0x13, 0x20, 0x2d, 0x48, 0x0a, 0x0e, 0x88, 0x33, 0xa0, 0x9c, 0x63, 0x4a, + 0x24, 0xda, 0x17, 0x00, 0x95, 0xc6, 0x1c, 0xa0, 0xb1, 0x34, 0x0d, 0x43, 0x59, 0x17, 0xf1, 0xa2, + 0x1a, 0x60, 0x05, 0xc3, 0xe7, 0xe9, 0xdd, 0xe6, 0x40, 0xd5, 0xba, 0x09, 0x1b, 0x66, 0x23, 0x50, + 0xd1, 0x58, 0x65, 0x5f, 0x59, 0x48, 0x80, 0x6c, 0xb3, 0xed, 0x19, 0x9a, 0x52, 0x62, 0x26, 0x23, + 0xa6, 0xa8, 0xad, 0xaa, 0x2a, 0x8a, 0xc9, 0x14, 0x09, 0xea, 0x19, 0x3a, 0x1a, 0x4b, 0x13, 0x8c, + 0xba, 0x49, 0x3d, 0x72, 0xdd, 0x07, 0x49, 0x19, 0x9d, 0x81, 0x49, 0x15, 0xe9, 0x38, 0x4d, 0x1b, + 0x03, 0xa4, 0x26, 0xa9, 0x21, 0x4b, 0x96, 0x12, 0xb4, 0xc9, 0x7a, 0x48, 0x8c, 0xea, 0x7b, 0x62, + 0x14, 0xa4, 0x1e, 0x99, 0x20, 0xd5, 0x82, 0xec, 0xcb, 0x8a, 0x41, 0x6d, 0x50, 0x08, 0x13, 0xe2, + 0x3e, 0xb4, 0x47, 0xa3, 0x82, 0xa5, 0x85, 0x4b, 0x0d, 0x3f, 0x71, 0x2d, 0xd0, 0x70, 0xc0, 0x2c, + 0xd0, 0xe0, 0xd1, 0xe5, 0x17, 0x71, 0x99, 0x60, 0xc6, 0x5a, 0x94, 0x68, 0x6b, 0xc9, 0x64, 0x0d, + 0xe4, 0x13, 0xe2, 0x6b, 0x31, 0xa6, 0xe2, 0x0c, 0xe8, 0x47, 0xb2, 0xec, 0x3a, 0x0d, 0x6e, 0xdb, + 0x21, 0xe8, 0xbd, 0x93, 0x06, 0x64, 0xd9, 0xf7, 0xaa, 0x33, 0x00, 0xac, 0x8a, 0xc9, 0xed, 0x8d, + 0x6c, 0x75, 0x6c, 0xa8, 0x5d, 0x41, 0x06, 0x02, 0x31, 0x41, 0x07, 0xa7, 0xa9, 0x35, 0xdf, 0x1c, + 0x17, 0x0c, 0x90, 0xf5, 0x01, 0x09, 0x03, 0xc3, 0x76, 0xb0, 0xd9, 0x14, 0xc8, 0xef, 0x18, 0x59, + 0x72, 0x1b, 0x64, 0xab, 0x14, 0x7b, 0x44, 0x4c, 0xc2, 0xfc, 0x30, 0x0d, 0x76, 0x4b, 0x06, 0x45, + 0x15, 0x15, 0x24, 0x1b, 0x17, 0x65, 0xc2, 0xc5, 0x0f, 0x6d, 0x25, 0x55, 0x87, 0x5e, 0x52, 0x98, + 0x9e, 0xac, 0xba, 0x7c, 0x00, 0x97, 0xbb, 0xd7, 0x2b, 0x2c, 0x02, 0x56, 0x3a, 0x39, 0x67, 0x54, + 0x17, 0x8f, 0xcd, 0x40, 0x28, 0x4d, 0xd6, 0x1c, 0xa8, 0x83, 0x66, 0x28, 0x50, 0x73, 0xfb, 0x64, + 0x97, 0x10, 0x13, 0xdf, 0x98, 0xac, 0x4a, 0x57, 0x5e, 0x02, 0xd4, 0x16, 0xbc, 0xf5, 0x84, 0xba, + 0xff, 0xad, 0xa3, 0x6a, 0x20, 0xe9, 0x26, 0x44, 0xc7, 0x1a, 0x11, 0xb1, 0xbe, 0xa4, 0x75, 0xb3, + 0x33, 0x14, 0x61, 0xa2, 0xbe, 0x64, 0xce, 0x8c, 0xb6, 0x9a, 0x01, 0x8d, 0xce, 0x76, 0x12, 0xba, + 0x32, 0x56, 0xfb, 0x0a, 0x94, 0x4c, 0x8f, 0x6c, 0x62, 0x35, 0xfa, 0x30, 0x49, 0xb0, 0x62, 0x71, + 0xb9, 0xad, 0x6c, 0x05, 0xca, 0x44, 0x23, 0xc7, 0x71, 0xf2, 0x5f, 0x20, 0x99, 0x53, 0x7b, 0xd4, + 0x76, 0x8f, 0x38, 0x9d, 0x41, 0x70, 0x10, 0x35, 0xc0, 0x60, 0xde, 0x90, 0x9e, 0x7e, 0xb6, 0x0d, + 0x1d, 0xd0, 0x3b, 0x1b, 0x12, 0x67, 0x60, 0x74, 0xab, 0x22, 0x94, 0x80, 0xb5, 0x8b, 0x9c, 0x44, + 0x4f, 0x00, 0x9f, 0x21, 0xb4, 0x40, 0x22, 0x19, 0xa4, 0xcc, 0xa2, 0x16, 0x01, 0xc0, 0x11, 0x1a, + 0xb5, 0x40, 0xb7, 0x4f, 0xa6, 0x81, 0x98, 0xa1, 0x17, 0x2c, 0x85, 0xd6, 0x5e, 0x03, 0x38, 0x83, + 0x66, 0xf4, 0x13, 0xe2, 0xb9, 0x21, 0x28, 0x58, 0xda, 0x65, 0xaf, 0xb4, 0x67, 0x34, 0x20, 0x87, + 0xa0, 0x48, 0x0b, 0xbb, 0xec, 0x93, 0x5e, 0x36, 0x65, 0x18, 0xa4, 0x9b, 0x16, 0xb1, 0xc9, 0x9e, + 0xaa, 0xc3, 0x92, 0x7c, 0x4b, 0x24, 0x92, 0xd0, 0xaa, 0xbb, 0x47, 0x70, 0xb2, 0x78, 0x3f, 0x0d, + 0x8c, 0x0a, 0xca, 0x55, 0x97, 0x65, 0x05, 0x88, 0x80, 0x85, 0xbe, 0xbe, 0xce, 0xf3, 0x22, 0x11, + 0xd7, 0x7f, 0x93, 0x2d, 0x7f, 0xfe, 0xee, 0xb4, 0xe4, 0xfa, 0x0f, 0xba, 0x87, 0xed, 0x98, 0xc2, + 0x6c, 0x98, 0xcb, 0x29, 0xe6, 0x72, 0xe8, 0xd0, 0xd5, 0xc4, 0xdd, 0x92, 0xf4, 0x01, 0xde, 0x7f, + 0x40, 0x5b, 0x35, 0xff, 0xce, 0x9e, 0xb9, 0x4b, 0x71, 0x7e, 0x1e, 0x7f, 0x11, 0x8e, 0xa5, 0x86, + 0x0d, 0x52, 0xc9, 0xb9, 0x84, 0x27, 0xea, 0x73, 0xfa, 0x3f, 0x46, 0x79, 0x2e, 0xe1, 0x75, 0x63, + 0xb6, 0x8b, 0x20, 0xdc, 0x34, 0x73, 0xc4, 0x12, 0xa5, 0x78, 0xdb, 0x98, 0xf4, 0x25, 0xeb, 0x6e, + 0xd5, 0x9d, 0xb1, 0xcf, 0xd3, 0xbd, 0x3d, 0x42, 0xa6, 0xca, 0x2c, 0x32, 0x60, 0x00, 0xc4, 0x7a, + 0x6b, 0x51, 0x4c, 0x19, 0x56, 0x43, 0xd3, 0x12, 0xdf, 0xb8, 0xc0, 0xad, 0xae, 0x27, 0xe5, 0xcf, + 0x6f, 0x49, 0x7f, 0x3b, 0xc5, 0x63, 0x04, 0xc1, 0x4e, 0xea, 0x31, 0x90, 0x39, 0xc6, 0xa8, 0x33, + 0xc0, 0xf3, 0x01, 0xd4, 0x40, 0x29, 0x15, 0xef, 0x50, 0xbf, 0x49, 0xd8, 0x10, 0x97, 0x95, 0x06, + 0x0e, 0x13, 0x29, 0x1b, 0x30, 0xd7, 0xc8, 0x84, 0x11, 0x6f, 0x63, 0x61, 0xf6, 0xb4, 0xc0, 0x6d, + 0x22, 0x80, 0xcd, 0x46, 0xd8, 0x9c, 0xa4, 0x1d, 0x73, 0xeb, 0x4d, 0xe9, 0x60, 0x90, 0x7d, 0x28, + 0xcb, 0x4c, 0xe4, 0xbf, 0x7f, 0x3b, 0x3f, 0xc8, 0xcf, 0xe8, 0x65, 0x3a, 0xaf, 0x10, 0xc7, 0xe0, + 0x5d, 0x3f, 0x0c, 0x22, 0x39, 0x75, 0xc0, 0xe7, 0x8c, 0xd5, 0x5e, 0x5f, 0xff, 0xe2, 0x00, 0x63, + 0x56, 0x5b, 0xe8, 0xe6, 0x09, 0xfb, 0xd4, 0x7f, 0x35, 0x63, 0xae, 0xb5, 0xd9, 0x43, 0xc3, 0x70, + 0x40, 0x9a, 0xc2, 0x69, 0x80, 0x02, 0x8b, 0x06, 0x45, 0x10, 0xb8, 0x54, 0x51, 0x62, 0x8d, 0x2c, + 0x90, 0x27, 0xe1, 0x0d, 0x1a, 0x18, 0xb3, 0x9e, 0x79, 0x5e, 0x78, 0x70, 0x20, 0x13, 0x63, 0x73, + 0xeb, 0x3a, 0x71, 0x24, 0x17, 0xb6, 0xfc, 0x66, 0x78, 0xfb, 0xb6, 0x40, 0x72, 0x73, 0x27, 0x9d, + 0x56, 0x64, 0x4e, 0x1e, 0x74, 0xab, 0xc5, 0x8f, 0x5e, 0x1e, 0xde, 0x9c, 0x9d, 0xd2, 0x1d, 0x37, + 0x8c, 0x12, 0x60, 0xd9, 0x04, 0xfd, 0x3e, 0x80, 0x71, 0x23, 0x10, 0xb0, 0x1c, 0xa8, 0x2f, 0x88, + 0x47, 0xe2, 0xee, 0xd1, 0x0c, 0x72, 0x72, 0xd6, 0xbd, 0xa2, 0xab, 0x43, 0xba, 0xf7, 0xb9, 0x46, + 0x2f, 0xef, 0xf0, 0xa6, 0x1e, 0x5d, 0x17, 0x71, 0x73, 0xc4, 0x7a, 0x98, 0x4b, 0xb9, 0x0a, 0xac, + 0x06, 0x09, 0x86, 0xc8, 0xf3, 0x1b, 0x12, 0xc1, 0x07, 0xe7, 0x8e, 0x92, 0x9c, 0x05, 0x08, 0x12, + 0x9b, 0x80, 0x10, 0xe2, 0xaa, 0xda, 0x86, 0x40, 0x35, 0x79, 0xa1, 0xa7, 0xc0, 0xa6, 0xd9, 0xfd, + 0x02, 0x73, 0x21, 0xf3, 0x3a, 0x74, 0xc4, 0xab, 0x85, 0xd4, 0x8b, 0x24, 0xef, 0x13, 0x19, 0x8f, + 0x23, 0x87, 0x03, 0xd6, 0x0d, 0xb9, 0x68, 0x27, 0x5c, 0xc4, 0x20, 0xab, 0x8c, 0xc7, 0xc9, 0xb2, + 0xa1, 0x3b, 0x4b, 0x87, 0x2e, 0xc5, 0x65, 0xb9, 0xdd, 0xcc, 0xa5, 0x10, 0x49, 0x00, 0xa3, 0xba, + 0xc6, 0xb3, 0xc2, 0x21, 0x71, 0xad, 0xa3, 0x0c, 0xec, 0xc0, 0x08, 0x89, 0x12, 0xf6, 0x19, 0x6c, + 0xeb, 0xe9, 0x9e, 0x66, 0xc0, 0xf2, 0x70, 0x32, 0xe5, 0x52, 0x01, 0xd1, 0xaa, 0xf3, 0xa9, 0x09, + 0x67, 0x83, 0x26, 0xff, 0x69, 0x27, 0x33, 0xf9, 0x12, 0x66, 0x6b, 0xf1, 0xd9, 0x1b, 0x98, 0xfb, + 0xa7, 0x9e, 0xcc, 0x94, 0x70, 0x43, 0xaa, 0xdb, 0xdb, 0x76, 0x4a, 0x14, 0xc4, 0x54, 0x22, 0x0b, + 0x7b, 0x36, 0x9a, 0x4d, 0xde, 0x44, 0x3c, 0xc9, 0x79, 0x83, 0xc5, 0x98, 0x12, 0x25, 0x41, 0xc4, + 0x2f, 0xa7, 0xb9, 0xd6, 0x5f, 0x25, 0x55, 0xd7, 0x7f, 0xff, 0xb6, 0xb7, 0x75, 0xbf, 0x82, 0xbe, + 0x2d, 0x0e, 0x8c, 0x11, 0x92, 0x14, 0xfe, 0x40, 0x15, 0x28, 0x2d, 0x7d, 0x01, 0x36, 0xae, 0x03, + 0x2a, 0xa1, 0x38, 0x36, 0x00, 0xa8, 0xd8, 0x2a, 0x56, 0x60, 0x9d, 0xd9, 0x2c, 0x4d, 0x4b, 0x51, + 0x27, 0x48, 0x4c, 0xff, 0x8e, 0xa0, 0xa0, 0xe1, 0x12, 0xf3, 0xb9, 0xf2, 0x6e, 0x3a, 0xa6, 0x38, + 0x1b, 0x25, 0xf9, 0x4f, 0xac, 0x62, 0x13, 0x54, 0xfe, 0x14, 0xce, 0x40, 0xad, 0x03, 0xaf, 0x30, + 0x26, 0xb8, 0x8e, 0xd0, 0x30, 0x2b, 0x7a, 0x02, 0xdf, 0xaf, 0xef, 0x8e, 0xb5, 0xf5, 0xdd, 0xe9, + 0x7a, 0x07, 0x9b, 0x2f, 0xe4, 0xcd, 0xe9, 0x8a, 0x5b, 0x7f, 0xcc, 0xc8, 0xfc, 0x7b, 0xc6, 0xe9, + 0xf2, 0x59, 0x63, 0x45, 0x63, 0x59, 0xce, 0x1c, 0x44, 0x69, 0x37, 0x3b, 0x03, 0xd5, 0x7f, 0x85, + 0x66, 0xe7, 0x94, 0x3b, 0xb3, 0xbb, 0x4c, 0xb0, 0xf9, 0x21, 0xf5, 0xac, 0xcf, 0xab, 0xe8, 0x61, + 0x2b, 0x3d, 0x82, 0x4b, 0x3a, 0x20, 0x1d, 0xae, 0xaf, 0x93, 0x54, 0xca, 0xc3, 0x19, 0xd9, 0xca, + 0x15, 0x71, 0x28, 0xa4, 0x0e, 0xbf, 0x49, 0x89, 0x13, 0x38, 0xa9, 0x27, 0xed, 0x2d, 0x34, 0xc9, + 0xb1, 0x43, 0xa0, 0xd4, 0x5f, 0x26, 0x42, 0xaa, 0x76, 0x7f, 0x25, 0x59, 0x10, 0xa6, 0xda, 0x17, + 0xda, 0xf2, 0x0f, 0xe7, 0xe7, 0xef, 0xdf, 0xf2, 0x17, 0x6c, 0x1d, 0xfb, 0xd8, 0x0e, 0x8a, 0xe2, + 0x97, 0x21, 0xa0, 0x70, 0xb0, 0xf4, 0x1d, 0xec, 0x72, 0x9b, 0xde, 0x66, 0x2b, 0x97, 0x6b, 0xc2, + 0xd1, 0xae, 0x30, 0x1c, 0xd9, 0x8e, 0xd0, 0x26, 0x02, 0xa4, 0x0b, 0x06, 0xa8, 0x51, 0xc4, 0xb6, + 0xd3, 0x38, 0xb1, 0xd5, 0x15, 0xad, 0xfc, 0xf2, 0xea, 0xe3, 0x79, 0xf6, 0xc4, 0x52, 0x31, 0xec, + 0xae, 0xf0, 0xc7, 0xcc, 0xa4, 0x92, 0xbf, 0x93, 0x9c, 0x7f, 0xe1, 0x70, 0x64, 0xba, 0x67, 0x1e, + 0xee, 0x30, 0x5c, 0xdf, 0x53, 0xa0, 0x11, 0xe2, 0xa1, 0x81, 0x8e, 0x61, 0x7d, 0x9d, 0x0d, 0x85, + 0xfc, 0x0c, 0x9e, 0xd2, 0x3a, 0x35, 0x22, 0x07, 0xaf, 0x30, 0xfd, 0xfc, 0x21, 0xc4, 0xa5, 0xa6, + 0xbc, 0xa1, 0x77, 0x25, 0x77, 0x08, 0xe1, 0x97, 0x35, 0xdd, 0x3c, 0xae, 0x35, 0x2f, 0x29, 0x6d, + 0xda, 0x1c, 0x78, 0x8a, 0xa9, 0xde, 0x29, 0x9a, 0xa7, 0xdb, 0xd0, 0xc2, 0x20, 0xe3, 0x79, 0x95, + 0x92, 0xee, 0x69, 0x84, 0xe8, 0x5a, 0x85, 0xdd, 0x93, 0x19, 0xa0, 0x10, 0xb5, 0xaf, 0x27, 0xf0, + 0xe0, 0xd4, 0x2b, 0xe8, 0x8d, 0xc6, 0x49, 0x83, 0x7a, 0xb0, 0x4d, 0xff, 0x56, 0x13, 0x5d, 0x82, + 0x01, 0x3e, 0x20, 0x4d, 0x97, 0xfc, 0x47, 0x33, 0x78, 0x7c, 0xd5, 0xa2, 0xc6, 0x53, 0x87, 0x5f, + 0xfc, 0xaf, 0x9a, 0x87, 0xbb, 0x0f, 0x31, 0xf5, 0xaa, 0x6d, 0x73, 0xcf, 0x78, 0x78, 0x1a, 0xd0, + 0x92, 0xb9, 0xd3, 0x79, 0xf1, 0x29, 0x93, 0x69, 0xe6, 0x68, 0xdf, 0xad, 0x11, 0xf7, 0x13, 0x4c, + 0x09, 0x6a, 0xa4, 0x27, 0x4e, 0xcb, 0xfb, 0x4c, 0xef, 0x35, 0x3d, 0x73, 0x93, 0xa5, 0x0a, 0xfd, + 0x0f, 0xc5, 0x13, 0x32, 0x25, 0x9d, 0xa6, 0x31, 0x1c, 0x82, 0x04, 0x82, 0x7b, 0x91, 0xf9, 0x86, + 0x62, 0x17, 0xcf, 0x8c, 0x4d, 0x95, 0x39, 0x20, 0x60, 0x58, 0xf6, 0xb6, 0xa1, 0x58, 0xc0, 0x85, + 0xb9, 0x81, 0x98, 0x74, 0xce, 0x29, 0x0f, 0x0e, 0x28, 0x01, 0xcf, 0x5f, 0x61, 0x69, 0xd6, 0x1c, + 0xeb, 0x6d, 0x96, 0xb0, 0x57, 0xc9, 0x67, 0x20, 0x2f, 0x7b, 0x1a, 0x44, 0x56, 0xa6, 0x24, 0x81, + 0x0c, 0xde, 0x95, 0x57, 0x93, 0xb3, 0x39, 0xd3, 0xa7, 0x7f, 0xf1, 0x2e, 0xaf, 0xf4, 0x13, 0x36, + 0x1d, 0x11, 0x88, 0x92, 0x6c, 0x7f, 0x0b, 0x87, 0x9f, 0xa8, 0x96, 0xc3, 0x1f, 0xf9, 0x14, 0xe8, + 0x47, 0x6c, 0xc5, 0x6f, 0xd5, 0x6f, 0xbe, 0x6b, 0x83, 0x7f, 0xda, 0x5d, 0x0b, 0x79, 0xe9, 0xd6, + 0xc4, 0xf8, 0xcb, 0xeb, 0xb5, 0x0f, 0x5a, 0x9e, 0x6f, 0xfd, 0xaa, 0xe9, 0x29, 0x58, 0x98, 0x22, + 0x7a, 0xae, 0x0c, 0x94, 0x31, 0x11, 0x74, 0xc3, 0x45, 0x8a, 0x2d, 0xbc, 0x11, 0xe7, 0x0b, 0x2c, + 0x38, 0x37, 0x90, 0x3c, 0xc8, 0xbf, 0x16, 0x11, 0x26, 0x8a, 0x8d, 0x4e, 0x30, 0xaa, 0x6d, 0x8f, + 0x08, 0x95, 0xa8, 0x71, 0x81, 0xbd, 0x01, 0x1b, 0xf5, 0x6a, 0xc1, 0x26, 0x87, 0xb2, 0x01, 0xb4, + 0x2a, 0x7a, 0x3e, 0x35, 0xa2, 0xc4, 0xfa, 0x38, 0x04, 0x8e, 0x84, 0x1f, 0x04, 0x72, 0x9b, 0x52, + 0x6d, 0x01, 0x85, 0x85, 0x91, 0xe9, 0x56, 0xa5, 0xf7, 0x6e, 0x51, 0x80, 0x52, 0x30, 0x61, 0xac, + 0x1a, 0x23, 0x9b, 0x79, 0x40, 0x69, 0x9a, 0xc2, 0x8e, 0x55, 0xc6, 0xb0, 0x8d, 0xe2, 0xc7, 0x3d, + 0xa8, 0xb7, 0xcd, 0x7f, 0xea, 0x82, 0x20, 0x24, 0x5a, 0xa0, 0xe6, 0x00, 0x04, 0x8a, 0xd7, 0xc6, + 0x44, 0xd5, 0xd8, 0x1d, 0x05, 0x01, 0x5d, 0xa7, 0xa9, 0xfb, 0x98, 0xe1, 0xb2, 0x02, 0x42, 0x7d, + 0x4f, 0x58, 0x97, 0x49, 0x18, 0xd7, 0xa1, 0x0b, 0x84, 0xe2, 0x81, 0x61, 0x30, 0xef, 0x14, 0x3c, + 0x20, 0x10, 0x5e, 0x74, 0x63, 0x02, 0x6c, 0xd4, 0x30, 0xba, 0xe8, 0xa4, 0xe3, 0x80, 0x06, 0x8d, + 0x83, 0xf8, 0xf6, 0xdd, 0xfb, 0xd8, 0x00, 0xf3, 0x58, 0xee, 0xd0, 0x0b, 0x05, 0x5e, 0xda, 0x96, + 0x0f, 0xd6, 0x4a, 0xaf, 0x1d, 0x97, 0xec, 0xd1, 0x11, 0xd9, 0x7c, 0x0b, 0x91, 0xa6, 0xef, 0x6f, + 0xf3, 0x2d, 0x29, 0x51, 0x04, 0x52, 0xef, 0x17, 0x91, 0x09, 0xd1, 0xae, 0x03, 0x39, 0xc7, 0xec, + 0x74, 0xc9, 0x97, 0xc2, 0xe8, 0xba, 0x61, 0xac, 0xb7, 0x6e, 0x47, 0x8c, 0x24, 0x1e, 0x95, 0x10, + 0x6a, 0x6c, 0xa1, 0xdc, 0x04, 0xf8, 0x31, 0x7a, 0x51, 0xd4, 0xa9, 0x02, 0x42, 0x9f, 0xb7, 0xe4, + 0xa4, 0xb7, 0x94, 0x0d, 0x73, 0x84, 0x01, 0xa5, 0xbc, 0x6a, 0x5f, 0x5c, 0x45, 0x05, 0xc9, 0x0e, + 0x7e, 0x25, 0xaa, 0x82, 0x13, 0xa0, 0xb9, 0x04, 0x08, 0xb1, 0x90, 0xf0, 0xa5, 0xee, 0xe6, 0x82, + 0x20, 0x12, 0xd1, 0x07, 0x5d, 0x92, 0xf8, 0x48, 0x1d, 0x14, 0x0b, 0x72, 0x81, 0x1a, 0x28, 0x10, + 0xd3, 0x23, 0x7b, 0x7b, 0x26, 0x57, 0x67, 0xf3, 0x79, 0x35, 0x46, 0x4b, 0x64, 0x1e, 0x23, 0x24, + 0x04, 0x4f, 0x14, 0xde, 0xb0, 0xc6, 0xc8, 0x2f, 0x70, 0x90, 0x5b, 0xa3, 0xaa, 0x1f, 0x1e, 0xb2, + 0x05, 0xd2, 0x11, 0x01, 0xc6, 0x92, 0x8c, 0xda, 0x98, 0x7c, 0xd5, 0xcb, 0xc3, 0x5f, 0x64, 0x90, + 0x08, 0x64, 0xc6, 0xf3, 0x1f, 0x5a, 0x31, 0xca, 0x04, 0x28, 0x3f, 0x2f, 0x20, 0x88, 0x44, 0x44, + 0x47, 0xc9, 0x1f, 0x26, 0x37, 0x4e, 0xe6, 0xfd, 0xe2, 0xb2, 0x74, 0x50, 0x5a, 0x2c, 0x15, 0xfb, + 0x4f, 0x06, 0x63, 0xf5, 0x83, 0xe6, 0x84, 0x06, 0xcb, 0x8b, 0xe9, 0xc8, 0x88, 0x16, 0x07, 0x8b, + 0xc7, 0x66, 0xdc, 0x89, 0x57, 0x64, 0xa8, 0xa0, 0x43, 0xae, 0x1a, 0xa4, 0xeb, 0x09, 0xf7, 0x0f, + 0x8d, 0x91, 0x7c, 0x30, 0x46, 0xd7, 0x63, 0xfe, 0x1f, 0x1f, 0x22, 0x55, 0x82, 0x57, 0x0d, 0x93, + 0x39, 0x28, 0xfd, 0x43, 0xa3, 0x4c, 0xb8, 0xee, 0x4e, 0x84, 0x5a, 0x7f, 0xd3, 0xf6, 0x40, 0xed, + 0x61, 0x51, 0x96, 0x9a, 0x1e, 0xe9, 0x2c, 0x41, 0xac, 0x7d, 0xa9, 0x89, 0xe1, 0x81, 0x06, 0x6e, + 0x52, 0x7f, 0x63, 0xc8, 0xb8, 0xef, 0x20, 0x2c, 0x2e, 0x61, 0x4b, 0xe6, 0xd5, 0x29, 0xb4, 0x14, + 0x38, 0xb3, 0x78, 0x38, 0xbe, 0x3a, 0xf5, 0xb7, 0x5f, 0xd8, 0xef, 0x80, 0x31, 0x40, 0x41, 0xdf, + 0x24, 0x06, 0xd8, 0xf1, 0x45, 0x45, 0x83, 0x65, 0xa1, 0xc7, 0x51, 0x82, 0x3a, 0xa2, 0xd4, 0xb7, + 0xc8, 0x0f, 0xf9, 0xe7, 0x96, 0x83, 0x76, 0x31, 0x89, 0x20, 0x67, 0x8c, 0xbb, 0x85, 0x73, 0x85, + 0x6e, 0x50, 0x14, 0xef, 0x78, 0x13, 0xe0, 0x1b, 0xc2, 0x41, 0xf1, 0x90, 0x84, 0x1a, 0xbf, 0x96, + 0x06, 0x10, 0x12, 0xf0, 0x5b, 0xe2, 0xee, 0xe5, 0x1c, 0x90, 0x72, 0xa1, 0x8b, 0xf9, 0xab, 0xd6, + 0x16, 0xbd, 0x98, 0x7a, 0x98, 0x94, 0xfb, 0xb9, 0x8d, 0x7f, 0x50, 0x9c, 0x08, 0x3b, 0xfe, 0x31, + 0x2e, 0x90, 0x70, 0xab, 0xc1, 0xc6, 0x88, 0x72, 0xf2, 0x8f, 0xec, 0xcf, 0xb9, 0xcf, 0x5b, 0x7f, + 0xd5, 0x18, 0x3b, 0x7d, 0xd5, 0x16, 0x83, 0xd9, 0x78, 0x9f, 0x4a, 0x83, 0xb9, 0x40, 0xf3, 0xa4, + 0x10, 0x5b, 0xd2, 0xd7, 0x8b, 0xfc, 0xc2, 0x7c, 0x8b, 0x9c, 0x32, 0x3b, 0x5f, 0xc0, 0xb7, 0xcf, + 0x8a, 0x3d, 0x2e, 0x9c, 0x60, 0x1c, 0x8d, 0x33, 0xfe, 0xad, 0x96, 0x36, 0x92, 0x4c, 0xac, 0x4b, + 0xce, 0x5c, 0x49, 0x8d, 0x89, 0x56, 0xf2, 0x4f, 0x57, 0x08, 0x04, 0x3d, 0xc6, 0x8e, 0x2e, 0x29, + 0x56, 0x01, 0xd4, 0x6c, 0x3a, 0x79, 0x9d, 0xa1, 0x79, 0x99, 0xf4, 0xe8, 0x81, 0x89, 0x27, 0x48, + 0x18, 0xdc, 0x51, 0x45, 0xc2, 0x75, 0xb3, 0xb3, 0xd9, 0x4c, 0x51, 0xe1, 0x93, 0xf9, 0xf5, 0xe0, + 0x51, 0x44, 0x32, 0x89, 0xdb, 0x90, 0xaa, 0x83, 0x88, 0xcf, 0x4c, 0xce, 0xbe, 0xc2, 0x87, 0x47, + 0x17, 0x4c, 0xc1, 0x57, 0xea, 0x58, 0x12, 0xa4, 0xbd, 0x9a, 0x02, 0x7b, 0x0b, 0x90, 0x8d, 0x39, + 0xb2, 0x07, 0x89, 0x1f, 0x44, 0x52, 0x24, 0x4f, 0xe6, 0xc6, 0x33, 0x0a, 0x96, 0x0c, 0x2b, 0xde, + 0x49, 0xc5, 0x88, 0x48, 0x34, 0x6a, 0x8e, 0x47, 0x03, 0x64, 0x6e, 0x88, 0x5b, 0xbf, 0x02, 0xa3, + 0x9b, 0xa9, 0x76, 0x51, 0xda, 0x8a, 0xd6, 0x53, 0x7d, 0x8d, 0x09, 0xf7, 0xcd, 0x5f, 0x31, 0x2d, + 0xa3, 0x31, 0x58, 0xf0, 0x23, 0xe6, 0xc4, 0x53, 0x0e, 0x99, 0x27, 0xb1, 0x99, 0x90, 0x14, 0xbf, + 0x2d, 0xfa, 0xce, 0xc5, 0xdf, 0xc2, 0xc1, 0xa3, 0xbe, 0x31, 0x87, 0xee, 0x3c, 0x0b, 0x9a, 0x80, + 0xfa, 0xc9, 0xdc, 0xd3, 0x37, 0x48, 0x72, 0x0e, 0xd2, 0x40, 0xd4, 0x2b, 0xd9, 0xff, 0xc4, 0xa0, + 0xd0, 0xd3, 0x2c, 0x6e, 0x84, 0x3a, 0x66, 0x84, 0x23, 0x33, 0xb4, 0x08, 0x88, 0xf2, 0x90, 0x97, + 0xca, 0xca, 0xf2, 0xdc, 0x8b, 0x5d, 0xd7, 0x71, 0xbf, 0x90, 0x42, 0xc7, 0x18, 0xd7, 0x7e, 0xa4, + 0x71, 0x74, 0x25, 0xf4, 0xda, 0xe0, 0x9a, 0x67, 0xd4, 0x1b, 0x6d, 0x3d, 0x5f, 0x61, 0xd1, 0x39, + 0xb0, 0xdd, 0xc8, 0x8c, 0x80, 0xb6, 0xc8, 0x87, 0x21, 0xf2, 0xda, 0xf6, 0x9b, 0xf6, 0x21, 0x72, + 0xb1, 0x4f, 0xdd, 0x35, 0x53, 0xa9, 0xf9, 0x12, 0xe1, 0xc5, 0xa1, 0xf9, 0x5b, 0xf2, 0x76, 0x82, + 0x0a, 0x21, 0x54, 0x8a, 0x58, 0x5f, 0x97, 0xdd, 0xdf, 0xc4, 0x72, 0xdf, 0x0e, 0x34, 0x8a, 0xa2, + 0x00, 0xe0, 0x2e, 0x05, 0xa0, 0x3a, 0xea, 0x39, 0xba, 0xbc, 0xfc, 0x82, 0x1f, 0x08, 0x5b, 0x11, + 0x49, 0xcf, 0xfe, 0x8a, 0x6d, 0x55, 0x43, 0x72, 0x82, 0x6f, 0xac, 0xbd, 0x6c, 0x24, 0x82, 0x0d, + 0x09, 0x99, 0x25, 0x63, 0x0b, 0xd1, 0xc0, 0x63, 0x6c, 0x55, 0x63, 0xdc, 0x31, 0x5e, 0xb5, 0x60, + 0x0b, 0xa3, 0x67, 0xd0, 0xc3, 0x47, 0xcf, 0x37, 0x95, 0xb8, 0x2b, 0x95, 0xa4, 0x91, 0x02, 0x6b, + 0x0b, 0x27, 0x5f, 0x3c, 0x82, 0x9c, 0xf4, 0x90, 0x6e, 0x07, 0x99, 0x1f, 0xff, 0x39, 0xca, 0xcb, + 0x05, 0x79, 0x03, 0x7f, 0x7a, 0x3d, 0xf8, 0x5b, 0x90, 0xf1, 0xa5, 0xd0, 0x6d, 0xc3, 0x4b, 0x81, + 0xd0, 0x97, 0x4a, 0x0f, 0x73, 0x7a, 0x15, 0xfa, 0xd2, 0x53, 0xe8, 0x4b, 0xaf, 0x54, 0xc2, 0x97, + 0x5e, 0x05, 0xeb, 0x64, 0xf3, 0x59, 0x78, 0xd9, 0xdd, 0xac, 0xec, 0xfe, 0xcc, 0xe0, 0xa9, 0xce, + 0x92, 0x43, 0x37, 0x3f, 0xb4, 0x28, 0xba, 0x5b, 0xd3, 0xcf, 0xb0, 0x33, 0x67, 0x2c, 0x3c, 0x77, + 0x15, 0x77, 0x29, 0x78, 0x6d, 0x34, 0xac, 0xd5, 0xeb, 0x75, 0x9c, 0xa7, 0x65, 0x8e, 0x8b, 0xab, + 0x9a, 0x21, 0x69, 0xfc, 0xee, 0x08, 0x55, 0x97, 0xc4, 0x04, 0x7e, 0x84, 0x28, 0x09, 0x4a, 0xb7, + 0x43, 0x8f, 0x8d, 0xbc, 0x44, 0x37, 0xa5, 0x9b, 0x66, 0x3c, 0xdf, 0x09, 0x9c, 0x91, 0x09, 0xf5, + 0xdb, 0x01, 0x36, 0x00, 0x2f, 0x21, 0x0f, 0x6a, 0x14, 0x1f, 0x2d, 0xcf, 0x35, 0xd9, 0x2d, 0x05, + 0x6f, 0x40, 0xa2, 0xd4, 0x63, 0x98, 0xa4, 0x7b, 0x76, 0x1a, 0xe5, 0x45, 0x26, 0x2f, 0x73, 0x1f, + 0x0b, 0xf2, 0x46, 0xee, 0x5e, 0xc8, 0xaf, 0xfb, 0x0e, 0x42, 0x7a, 0xb7, 0x03, 0xf4, 0x89, 0x82, + 0x82, 0xeb, 0xe4, 0x84, 0xfe, 0xc4, 0x7e, 0xcb, 0x40, 0x4e, 0xd3, 0xed, 0xd0, 0x5b, 0x7a, 0x52, + 0xa5, 0xde, 0xc6, 0xab, 0x8a, 0x0c, 0xa0, 0x48, 0x02, 0xbd, 0x93, 0x87, 0x13, 0x3c, 0x74, 0x1a, + 0xe2, 0x3e, 0xbb, 0x9d, 0xe0, 0x42, 0x94, 0xc9, 0xbb, 0x8b, 0xdb, 0x12, 0x0b, 0x50, 0xc6, 0x07, + 0x62, 0xcb, 0xee, 0xc6, 0x6e, 0x49, 0x8b, 0x05, 0x73, 0xab, 0x0a, 0x26, 0xab, 0x2b, 0x7a, 0x5e, + 0xde, 0x66, 0xf6, 0x53, 0x20, 0xe6, 0x96, 0x96, 0x4a, 0x2e, 0x6e, 0x8c, 0xde, 0x3a, 0xe1, 0xb6, + 0x31, 0x98, 0x30, 0x8b, 0x90, 0x01, 0x51, 0xcc, 0x0c, 0x7e, 0xb2, 0xaf, 0x66, 0xd7, 0xed, 0xb4, + 0x63, 0xec, 0xab, 0x53, 0xd2, 0x4d, 0x64, 0x93, 0xee, 0xde, 0xe5, 0x22, 0xd7, 0x9c, 0x58, 0x92, + 0x56, 0x17, 0xcf, 0x0d, 0x47, 0xc0, 0x0f, 0xcd, 0xd1, 0x26, 0xbb, 0x62, 0x4d, 0xdf, 0xca, 0x92, + 0xfc, 0xb6, 0x56, 0x4f, 0xe8, 0xf0, 0xff, 0x4c, 0x1d, 0x5e, 0x92, 0x7e, 0x13, 0x90, 0x27, 0x6f, + 0xcb, 0xd5, 0x6c, 0x32, 0x99, 0x12, 0x85, 0x86, 0x58, 0xd5, 0x71, 0x3a, 0x12, 0xb4, 0x6c, 0x51, + 0xfe, 0x93, 0xda, 0x20, 0xa9, 0x15, 0x1b, 0x2a, 0xc2, 0xb2, 0xc6, 0x42, 0xc3, 0x86, 0xe8, 0xed, + 0x6f, 0x4c, 0x58, 0x22, 0xe9, 0x51, 0x12, 0x77, 0x4e, 0x64, 0xbb, 0xce, 0x0f, 0xa0, 0xc6, 0x9f, + 0xa0, 0x41, 0x46, 0x05, 0x5a, 0x28, 0x93, 0xb4, 0x61, 0x3b, 0xdc, 0x56, 0x52, 0x75, 0xcf, 0xf8, + 0x07, 0x45, 0xe9, 0xf1, 0x33, 0xee, 0xa7, 0xd5, 0x70, 0x3a, 0xeb, 0xc1, 0xa8, 0x8b, 0x27, 0xa3, + 0xd1, 0x40, 0x79, 0x19, 0x89, 0x35, 0x92, 0x06, 0xfd, 0x35, 0x4d, 0x4f, 0x35, 0xdc, 0xb3, 0x50, + 0x39, 0x9d, 0x2d, 0xa4, 0xa9, 0xdd, 0x17, 0xca, 0x1d, 0x1a, 0x20, 0x42, 0xd2, 0x45, 0xd5, 0xd1, + 0x69, 0x0a, 0x3e, 0xb0, 0x9d, 0x6f, 0x0c, 0xbc, 0x19, 0x2b, 0xcf, 0x05, 0x90, 0x99, 0x8c, 0xb9, + 0xaf, 0x8f, 0x7f, 0xa7, 0x5f, 0xc8, 0x04, 0x4e, 0xfd, 0xc7, 0x4c, 0x99, 0xe3, 0x5f, 0x74, 0x73, + 0xa8, 0x2b, 0xdb, 0xa2, 0x58, 0xfd, 0xe6, 0x9b, 0x21, 0x0d, 0x0d, 0x6f, 0x60, 0xd4, 0x73, 0x5b, + 0xdf, 0x07, 0x96, 0x67, 0x5c, 0xf0, 0x6e, 0xe9, 0xf8, 0x9f, 0x5a, 0x77, 0x3f, 0x8d, 0x27, 0xd7, + 0x98, 0x77, 0x3c, 0x7e, 0x1c, 0x79, 0xf1, 0x4b, 0xae, 0x98, 0x4a, 0xd5, 0x64, 0xd7, 0x4c, 0xf9, + 0x8d, 0xf6, 0x99, 0x2b, 0x96, 0xd6, 0xf1, 0x38, 0xc8, 0xd9, 0xf6, 0x46, 0x2f, 0xee, 0x92, 0xf6, + 0xa8, 0x2f, 0x4a, 0xdf, 0x56, 0x44, 0x96, 0x8c, 0xbd, 0x9a, 0x37, 0xfb, 0xcf, 0x6f, 0x5d, 0xac, + 0xfa, 0x9f, 0xdf, 0xaa, 0xdf, 0x52, 0x89, 0x6c, 0xae, 0xec, 0x36, 0x2c, 0xf6, 0x14, 0xe0, 0xd6, + 0xb0, 0x25, 0xd3, 0xc3, 0xd3, 0x64, 0xea, 0x1b, 0x95, 0x07, 0xa3, 0xfb, 0x65, 0xb8, 0x0a, 0x28, + 0xaa, 0x55, 0x1a, 0xf5, 0x16, 0x8a, 0x07, 0x9f, 0x98, 0x0b, 0x5d, 0xd5, 0xfa, 0x86, 0xf6, 0x62, + 0x3a, 0x06, 0x1f, 0xf2, 0x9d, 0x91, 0xaa, 0xa1, 0x0b, 0x45, 0x7a, 0xac, 0x76, 0x93, 0xe1, 0xac, + 0x96, 0xda, 0x07, 0x61, 0x9d, 0x5e, 0x79, 0x41, 0xb1, 0x1a, 0x0b, 0x4d, 0xd4, 0x9e, 0x9a, 0xb6, + 0x69, 0x7a, 0x4a, 0xfc, 0x97, 0x40, 0x9d, 0x89, 0x69, 0x9a, 0x65, 0xdb, 0xaa, 0x24, 0x0a, 0xdd, + 0x9d, 0x21, 0xb0, 0xc9, 0x70, 0x33, 0xb7, 0x26, 0x9a, 0xda, 0x45, 0x29, 0x62, 0x76, 0x4f, 0x8f, + 0x68, 0x7a, 0x32, 0x52, 0xfa, 0x86, 0x96, 0x05, 0x2e, 0x8c, 0x79, 0xe1, 0x2c, 0x8c, 0xc6, 0x25, + 0xe0, 0xaa, 0x82, 0x35, 0x06, 0x7d, 0xbd, 0xec, 0xb8, 0x3d, 0x91, 0xb4, 0x69, 0x5b, 0xca, 0x70, + 0x3b, 0x5c, 0xf0, 0xb2, 0x75, 0xdd, 0x38, 0x13, 0xa5, 0x84, 0x9b, 0x4b, 0x57, 0x62, 0x92, 0x5b, + 0x86, 0x6e, 0x0b, 0x0b, 0x08, 0xd9, 0x03, 0x16, 0x3a, 0xc4, 0x45, 0x28, 0xb8, 0xd7, 0x4d, 0x44, + 0x49, 0x8b, 0x00, 0xd2, 0x00, 0xca, 0x84, 0xcd, 0x5a, 0xd8, 0xbf, 0x6c, 0x21, 0xa8, 0x74, 0x1d, + 0xf7, 0x4c, 0x3b, 0x52, 0xea, 0xac, 0xd1, 0x14, 0x80, 0x75, 0xe0, 0xa5, 0x29, 0x2c, 0x35, 0x54, + 0x3a, 0xd1, 0xf1, 0xa8, 0x1a, 0xb1, 0xdf, 0x6c, 0xd8, 0xee, 0x31, 0x1f, 0x78, 0xfc, 0x28, 0x85, + 0x07, 0xf6, 0xf4, 0xd1, 0x49, 0x21, 0x78, 0x88, 0x60, 0x6e, 0x3d, 0x83, 0xb0, 0xf2, 0x27, 0x2b, + 0x98, 0x61, 0x85, 0x60, 0x6d, 0xff, 0x6b, 0x01, 0xdf, 0x7b, 0xfa, 0x58, 0xb5, 0x0c, 0x7d, 0x48, + 0x41, 0x27, 0x69, 0xbc, 0x60, 0x4f, 0xcf, 0x0f, 0xd0, 0x2b, 0xc5, 0x22, 0xf0, 0x48, 0x67, 0x4d, + 0x9b, 0xa8, 0x26, 0x7a, 0x7d, 0x63, 0x65, 0xa0, 0x6d, 0xba, 0xac, 0x7e, 0x31, 0x26, 0xf8, 0x32, + 0x0e, 0xef, 0xe6, 0x54, 0xc6, 0xa6, 0x22, 0x33, 0xda, 0x99, 0x82, 0x22, 0x4b, 0x5c, 0xf1, 0x23, + 0x41, 0x62, 0x92, 0xcc, 0x79, 0xc8, 0x77, 0x97, 0x70, 0xed, 0x63, 0x6e, 0xcd, 0x96, 0x8a, 0x87, + 0x76, 0x7d, 0xf4, 0x2c, 0x08, 0x05, 0xa9, 0xe1, 0x3d, 0x4c, 0x3c, 0x4e, 0xeb, 0xdd, 0x08, 0xe7, + 0xb9, 0x2d, 0x15, 0xe5, 0x6d, 0x7f, 0xe3, 0x0b, 0x1d, 0xb5, 0x26, 0xf8, 0x1b, 0x41, 0xe1, 0x6b, + 0x40, 0xee, 0xed, 0x1f, 0x94, 0xb7, 0xfa, 0xae, 0xb2, 0xe0, 0x15, 0x4e, 0xa5, 0x68, 0x9b, 0x5a, + 0xa0, 0x1e, 0xe8, 0x69, 0x58, 0x1a, 0x35, 0xea, 0x7c, 0x10, 0x34, 0x42, 0x05, 0x3d, 0xae, 0x4d, + 0x2d, 0x05, 0x24, 0xa5, 0x6d, 0x61, 0xc3, 0x98, 0x85, 0xed, 0x6b, 0x49, 0xda, 0x92, 0xc2, 0xec, + 0xc3, 0xd0, 0xbe, 0x98, 0xd2, 0x92, 0x92, 0x51, 0xff, 0xf2, 0x45, 0xc1, 0xb3, 0x96, 0xf8, 0x83, + 0x31, 0x5f, 0x33, 0xfb, 0xfd, 0x1b, 0x6f, 0x2d, 0x68, 0xeb, 0xeb, 0xbc, 0x4b, 0x31, 0x64, 0xa3, + 0x33, 0x95, 0x18, 0x7c, 0x62, 0x5a, 0xac, 0xd9, 0x13, 0x15, 0x45, 0x2d, 0x1d, 0x65, 0xc7, 0xe4, + 0xac, 0x83, 0x6e, 0x2d, 0xd9, 0xaa, 0x1a, 0x3a, 0xbd, 0xac, 0xb5, 0x2d, 0xa2, 0xbc, 0xd4, 0x68, + 0x5e, 0x8e, 0xcf, 0xeb, 0x87, 0xf3, 0xf2, 0x7c, 0x9e, 0x96, 0x14, 0xe7, 0x08, 0xbf, 0xe5, 0x2a, + 0x22, 0xae, 0xd4, 0x0c, 0x12, 0xbf, 0x36, 0x0f, 0xc2, 0x4f, 0xb5, 0xed, 0x45, 0xbe, 0xc4, 0x05, + 0x3b, 0x12, 0x60, 0x7f, 0x13, 0xfe, 0x98, 0xe9, 0x69, 0x43, 0xdf, 0xc6, 0x33, 0x62, 0x91, 0xe9, + 0xbd, 0xbe, 0x04, 0xae, 0xcd, 0xa1, 0x40, 0x58, 0x9b, 0x01, 0xd4, 0x5d, 0x4e, 0xac, 0x04, 0xe6, + 0x25, 0x23, 0x7c, 0x6c, 0x79, 0xc8, 0x2a, 0x3e, 0x50, 0x12, 0x6b, 0x97, 0x7e, 0xc3, 0x64, 0x75, + 0xd0, 0x3d, 0xd2, 0xc7, 0x1b, 0x00, 0xac, 0xa3, 0xbf, 0x12, 0x3d, 0x6c, 0xc9, 0x37, 0xf0, 0x70, + 0x98, 0xd0, 0x2b, 0x0c, 0x2f, 0xb3, 0xf5, 0x61, 0xd0, 0x24, 0xfe, 0x2f, 0xa8, 0x87, 0x75, 0x9d, + 0xed, 0x93, 0x52, 0x97, 0x3e, 0x19, 0xa6, 0x64, 0x7a, 0x49, 0x8f, 0x52, 0xdf, 0x4d, 0x7b, 0x94, + 0x46, 0x75, 0x10, 0xc0, 0x80, 0x24, 0xbe, 0x0f, 0x27, 0x7f, 0x0e, 0x07, 0x52, 0x0f, 0xa6, 0x86, + 0x05, 0xbe, 0x09, 0x85, 0xbb, 0x71, 0xbf, 0x00, 0x8b, 0x97, 0x5f, 0xf1, 0xd6, 0x21, 0x11, 0x50, + 0x25, 0x3c, 0xc3, 0x4d, 0x52, 0xec, 0xaa, 0x16, 0x3b, 0x8f, 0x10, 0xe7, 0x4b, 0xe2, 0x33, 0x72, + 0x28, 0x84, 0x76, 0x22, 0x28, 0x83, 0x06, 0x3d, 0x7c, 0xe1, 0x68, 0xa1, 0xc0, 0xb6, 0xe8, 0x86, + 0xae, 0xa1, 0x33, 0xbb, 0xf5, 0x41, 0x98, 0x22, 0x2f, 0x74, 0xcd, 0x2f, 0x69, 0xfc, 0x01, 0xe8, + 0x67, 0x2a, 0x2a, 0x34, 0x1f, 0xc3, 0x38, 0x8c, 0x7e, 0xd3, 0xf0, 0x4c, 0xe5, 0x21, 0x1c, 0xaa, + 0x7f, 0x17, 0xc0, 0x01, 0xca, 0x72, 0x43, 0x64, 0x31, 0x23, 0xf4, 0xe8, 0xf9, 0x1c, 0xa6, 0x3f, + 0x81, 0xd3, 0xc7, 0x45, 0x94, 0x3e, 0x86, 0x70, 0xfa, 0xf8, 0x77, 0x21, 0x1e, 0xfe, 0x53, 0x28, + 0x7d, 0x5c, 0x40, 0x69, 0x08, 0xc2, 0xe1, 0xdf, 0x85, 0x90, 0xf1, 0xc3, 0x76, 0x98, 0x9f, 0xd0, + 0x1e, 0x15, 0x13, 0x2f, 0x57, 0xd3, 0xc8, 0xa0, 0xf0, 0x8c, 0xdf, 0x46, 0x34, 0xb9, 0x20, 0xad, + 0x5a, 0x5b, 0xdb, 0xb0, 0xdd, 0xf0, 0x4d, 0x7b, 0x94, 0x41, 0x0a, 0xd9, 0x5d, 0x61, 0xff, 0x81, + 0x4a, 0x84, 0xfc, 0x42, 0x23, 0xda, 0x86, 0x09, 0xdd, 0xb2, 0x23, 0xb8, 0x70, 0x6a, 0xa8, 0xbb, + 0x6c, 0x2e, 0x3a, 0xc2, 0x6c, 0xce, 0x63, 0x38, 0xdf, 0xdd, 0x18, 0x62, 0xc1, 0x27, 0x10, 0xff, + 0x98, 0x01, 0x27, 0x86, 0x61, 0x67, 0x73, 0xdb, 0x78, 0x2c, 0xce, 0x2e, 0xa6, 0xb2, 0x91, 0x5f, + 0x82, 0x30, 0xa1, 0xd9, 0xdf, 0x33, 0xac, 0x4e, 0xb4, 0x6e, 0x16, 0xeb, 0x66, 0x97, 0xd5, 0xdd, + 0x51, 0xac, 0x65, 0x15, 0x73, 0x58, 0x31, 0xb7, 0xac, 0x62, 0xc3, 0xea, 0x2c, 0xab, 0x98, 0xc7, + 0x8a, 0xf9, 0x65, 0x15, 0x9b, 0xf4, 0x7a, 0x76, 0x50, 0x37, 0xc3, 0xb2, 0x23, 0xbc, 0xe8, 0xc8, + 0x9d, 0x1f, 0x3a, 0x1b, 0xb6, 0x8e, 0x68, 0x53, 0x63, 0xa7, 0xa2, 0x85, 0x42, 0x89, 0x60, 0xab, + 0xc3, 0xbf, 0x3b, 0x0f, 0x76, 0x74, 0xed, 0xb6, 0xd4, 0x0f, 0x67, 0xc1, 0x56, 0x17, 0x11, 0x49, + 0x14, 0xa8, 0xa9, 0x7f, 0x3c, 0x0b, 0x31, 0x75, 0xef, 0xc9, 0xbd, 0xaa, 0x69, 0xd7, 0x46, 0xe7, + 0xe5, 0xd1, 0x18, 0x7d, 0x80, 0x99, 0x5a, 0xd4, 0x26, 0x07, 0xc3, 0xa0, 0x86, 0x25, 0x68, 0x5f, + 0xc3, 0x43, 0xa0, 0x21, 0xee, 0xe1, 0xa4, 0xbf, 0x2d, 0x86, 0xfa, 0x80, 0x5c, 0x63, 0x3b, 0xd8, + 0xd8, 0x17, 0x36, 0x40, 0x97, 0xee, 0xf1, 0x4a, 0x33, 0xdd, 0x42, 0xe0, 0x01, 0x4d, 0x52, 0x31, + 0x2b, 0x99, 0x85, 0xdc, 0xde, 0xfa, 0x78, 0x05, 0xdb, 0x44, 0x0b, 0x63, 0x16, 0xe5, 0x11, 0x7e, + 0x09, 0x43, 0xca, 0xdf, 0x5b, 0xc3, 0x91, 0xe1, 0x2f, 0xd8, 0x22, 0xb1, 0xa3, 0xbd, 0xa9, 0x3f, + 0x8b, 0x4b, 0x0c, 0x7c, 0xd6, 0x7b, 0x08, 0x5c, 0xfa, 0x1e, 0x98, 0xf7, 0xd0, 0xd7, 0x8d, 0xca, + 0x89, 0xf0, 0xeb, 0xba, 0x40, 0x26, 0x92, 0xb5, 0x20, 0x1c, 0x2f, 0x6d, 0xbc, 0x46, 0x45, 0x03, + 0x1c, 0x0b, 0xd4, 0xde, 0xb6, 0x5d, 0xbb, 0x0c, 0xfb, 0x85, 0x66, 0x41, 0x72, 0x02, 0x9c, 0x17, + 0xb2, 0x78, 0x19, 0x0c, 0x63, 0x2d, 0xe2, 0x4f, 0x3e, 0x57, 0x14, 0xe7, 0x54, 0x90, 0xf8, 0x95, + 0x02, 0x71, 0x49, 0xdf, 0x86, 0x7f, 0x55, 0x2f, 0x4c, 0x9a, 0x80, 0xc2, 0x59, 0xea, 0x5b, 0xc8, + 0x74, 0x68, 0x98, 0x8c, 0xd2, 0x3f, 0x84, 0x8d, 0x25, 0xe3, 0x16, 0x8d, 0x1e, 0x6a, 0x4a, 0x9f, + 0x1e, 0xfa, 0xe2, 0xd9, 0xf6, 0xd6, 0xb7, 0xd4, 0xaf, 0x28, 0x12, 0xfa, 0x6e, 0xbc, 0x2c, 0xef, + 0xd8, 0x9b, 0x6a, 0x9a, 0xb0, 0x43, 0xcf, 0x6b, 0xbc, 0x09, 0x15, 0x83, 0xc2, 0xe9, 0x64, 0xea, + 0x84, 0xe5, 0xe4, 0x45, 0x5f, 0xb9, 0x6f, 0x08, 0xe1, 0x37, 0x17, 0x1d, 0xb9, 0xcd, 0xf2, 0x1f, + 0xb3, 0x16, 0xb5, 0x16, 0xa6, 0xf1, 0xd3, 0x25, 0xcd, 0x81, 0x62, 0x35, 0xd1, 0x17, 0x96, 0x52, + 0x55, 0x4a, 0x6c, 0x88, 0xe9, 0x8e, 0x9b, 0xd4, 0x40, 0xc7, 0xce, 0xa4, 0x87, 0x8e, 0x5f, 0x91, + 0x71, 0x6f, 0x74, 0xbc, 0x68, 0x79, 0x94, 0x2c, 0x42, 0xa0, 0x7a, 0xf2, 0x61, 0x2f, 0x59, 0x0b, + 0x0b, 0x6e, 0x07, 0x96, 0x49, 0xe7, 0x46, 0x92, 0x03, 0x70, 0x1a, 0x5e, 0x74, 0x94, 0x15, 0x0d, + 0x59, 0x4b, 0x1b, 0xca, 0x06, 0x0d, 0xed, 0x7c, 0xa2, 0xa1, 0xfe, 0xd2, 0x86, 0x72, 0x41, 0x43, + 0xcd, 0x4f, 0x34, 0xa4, 0x2d, 0x6d, 0x28, 0x1f, 0x34, 0xb4, 0xeb, 0x37, 0xc4, 0x31, 0x09, 0xe1, + 0xd7, 0xe2, 0x84, 0xc7, 0x98, 0xcd, 0x5d, 0xc2, 0x5f, 0x6e, 0x38, 0xd7, 0xe2, 0x8d, 0xe6, 0x71, + 0xad, 0xc7, 0x1a, 0xcd, 0xb5, 0x58, 0x83, 0xb9, 0x16, 0x35, 0x96, 0xc3, 0x42, 0x88, 0xde, 0x58, + 0xb4, 0xa8, 0xa3, 0x7e, 0x94, 0xcf, 0xa9, 0x7a, 0x08, 0x70, 0x78, 0x65, 0xb0, 0x2c, 0xc4, 0x9d, + 0xf3, 0x68, 0xc8, 0x99, 0x3a, 0xa1, 0x1a, 0x4e, 0xec, 0xf7, 0x79, 0x82, 0xcf, 0xf3, 0xfc, 0x31, + 0x13, 0x89, 0x6d, 0x96, 0x73, 0xa5, 0x92, 0x58, 0xaf, 0xdb, 0x54, 0x6b, 0xdd, 0xce, 0xe7, 0xaa, + 0xa5, 0xc2, 0x9c, 0x17, 0xaf, 0xdd, 0x35, 0x8b, 0x1c, 0x34, 0x14, 0xe0, 0x8e, 0x7e, 0x03, 0x48, + 0x40, 0x6e, 0x94, 0x4e, 0xa7, 0xc5, 0x8c, 0x6b, 0x27, 0xf2, 0x11, 0x05, 0x9a, 0x21, 0x0d, 0x27, + 0xe8, 0x00, 0x41, 0xa3, 0xb7, 0x19, 0x40, 0xee, 0x74, 0xb7, 0xfe, 0x98, 0x8d, 0xb6, 0xc5, 0x16, + 0x8a, 0xd8, 0xc2, 0x03, 0x3a, 0x8b, 0xd0, 0xa7, 0xd3, 0xbd, 0x5d, 0x91, 0x39, 0xa6, 0x71, 0xa5, + 0x78, 0x1c, 0x01, 0xbc, 0xdb, 0xe2, 0x3d, 0x1a, 0x8f, 0x68, 0x1d, 0xe0, 0x11, 0x50, 0x79, 0xa1, + 0x00, 0x0b, 0xdd, 0x03, 0xea, 0xa4, 0x57, 0x28, 0xb6, 0x5d, 0x94, 0xc8, 0x2f, 0x7a, 0x3d, 0xbc, + 0xf8, 0xc3, 0x39, 0xc3, 0x71, 0x20, 0xba, 0x08, 0xe6, 0x18, 0x6e, 0x98, 0xcf, 0xaf, 0x0c, 0x76, + 0xf8, 0xc7, 0x2c, 0x31, 0xda, 0x1e, 0x4e, 0xaa, 0x9e, 0x01, 0x3a, 0xb9, 0x91, 0x9d, 0x73, 0xca, + 0x4a, 0x67, 0xbe, 0xa0, 0xf4, 0x9c, 0x12, 0x3d, 0x50, 0x87, 0xfc, 0x6f, 0x2c, 0x41, 0x67, 0xec, + 0x1b, 0x4b, 0x1e, 0x57, 0xe7, 0x46, 0xf1, 0x01, 0x80, 0xe4, 0x03, 0x00, 0x43, 0xf0, 0xf1, 0xc0, + 0x75, 0x37, 0x12, 0x51, 0x9c, 0x76, 0xaa, 0x72, 0xf2, 0x1f, 0x00, 0x59, 0xc0, 0x5e, 0x3d, 0xf7, + 0x25, 0xa4, 0xd9, 0x0d, 0x45, 0x53, 0xfb, 0x7a, 0x15, 0x79, 0xba, 0xe5, 0xa0, 0xcf, 0x12, 0x6e, + 0x87, 0x58, 0x68, 0x9c, 0xa2, 0x76, 0x47, 0x31, 0xd5, 0xc3, 0xa4, 0xc5, 0x91, 0xd2, 0x96, 0x98, + 0x61, 0x78, 0x61, 0x53, 0x37, 0x7a, 0xd1, 0x91, 0x73, 0x54, 0x6c, 0xf4, 0x56, 0x8d, 0x83, 0xb3, + 0x37, 0xc2, 0xf2, 0x84, 0x1e, 0x5c, 0xc3, 0xe6, 0x16, 0x23, 0xd0, 0x47, 0xcf, 0xa9, 0x12, 0x00, + 0x5b, 0xc0, 0x91, 0x78, 0x48, 0xed, 0x9c, 0x1e, 0xd1, 0x3d, 0xa2, 0x2d, 0xd0, 0x2f, 0x1f, 0xa6, + 0xb0, 0x0f, 0x66, 0xef, 0x5b, 0x4a, 0x4b, 0x7d, 0xb3, 0x1f, 0x57, 0xce, 0xdf, 0xb7, 0x54, 0x62, + 0x38, 0xd8, 0xc8, 0xa2, 0xbd, 0xd1, 0x1b, 0xde, 0xb7, 0x94, 0x89, 0x6f, 0x31, 0x83, 0xa3, 0x0d, + 0x2e, 0x99, 0x25, 0x37, 0x6f, 0x2b, 0x00, 0xf5, 0x03, 0xc0, 0xc8, 0x47, 0x80, 0x0d, 0x07, 0x21, + 0xa0, 0x12, 0xfd, 0x45, 0x7a, 0x32, 0xab, 0x68, 0x1c, 0xff, 0xf7, 0x81, 0x15, 0x96, 0xd3, 0x12, + 0x6c, 0xff, 0x43, 0x8f, 0x88, 0x06, 0xf0, 0xe0, 0x4f, 0x80, 0x48, 0x99, 0xac, 0x3f, 0xb3, 0x07, + 0x18, 0x02, 0x0c, 0x36, 0x6d, 0x7f, 0xfc, 0x2d, 0xbc, 0x6b, 0xc0, 0xbd, 0x87, 0xa7, 0x4e, 0xfc, + 0xd4, 0xda, 0xeb, 0x5b, 0x66, 0x2c, 0x92, 0xb2, 0xbc, 0x3d, 0x83, 0xa3, 0x4a, 0x28, 0xff, 0xbf, + 0x80, 0x23, 0xd8, 0x66, 0x67, 0xe5, 0xd4, 0x45, 0xa1, 0x82, 0xf2, 0xff, 0x08, 0x54, 0x9f, 0x30, + 0xcc, 0x33, 0x93, 0xd0, 0x32, 0xd9, 0xd5, 0xff, 0xe4, 0x92, 0x1f, 0x34, 0x97, 0xee, 0xb2, 0xf1, + 0x1f, 0x7f, 0xe3, 0xe6, 0xcb, 0xb3, 0xad, 0x86, 0xb7, 0xd4, 0x81, 0xd0, 0x0e, 0xeb, 0x42, 0x40, + 0x91, 0xa2, 0xbf, 0xc9, 0x27, 0xe8, 0xd6, 0xd0, 0x4b, 0xc2, 0xc3, 0xfa, 0x7a, 0x7f, 0xc3, 0xdc, + 0xca, 0xae, 0xaf, 0x77, 0x37, 0x3a, 0x5b, 0xd9, 0xed, 0x36, 0x25, 0x9c, 0x04, 0x49, 0x37, 0x46, + 0x5d, 0xd5, 0xb8, 0x26, 0xec, 0xda, 0xc5, 0xfa, 0x7a, 0x24, 0x81, 0x9a, 0xdb, 0xc4, 0xea, 0x11, + 0x6e, 0xe4, 0x2b, 0x4c, 0x02, 0x21, 0x00, 0xda, 0xce, 0x50, 0x74, 0xfb, 0xa6, 0xd1, 0x75, 0x4d, + 0x83, 0x9e, 0x40, 0x30, 0xab, 0x81, 0x1b, 0x4b, 0x0c, 0xfb, 0x06, 0xee, 0xb9, 0x5a, 0x03, 0xa1, + 0x0b, 0xc4, 0x31, 0x23, 0xba, 0xdd, 0x8d, 0xe9, 0xaf, 0xaa, 0x6f, 0x28, 0x7e, 0x3b, 0x66, 0x48, + 0x03, 0x81, 0xd5, 0xb0, 0x85, 0x87, 0x38, 0x1f, 0xb7, 0x1c, 0x67, 0xf1, 0x09, 0xb5, 0x1c, 0xb1, + 0xf9, 0xd0, 0x96, 0x91, 0x0f, 0xfe, 0x75, 0x0d, 0xa7, 0x8b, 0x11, 0x41, 0x51, 0x84, 0x5f, 0x4e, + 0x3d, 0x2e, 0x64, 0x16, 0x13, 0xb9, 0xfc, 0xcf, 0xde, 0x9a, 0xa0, 0x93, 0xba, 0x01, 0xc9, 0xf0, + 0x9e, 0x0e, 0x77, 0xfa, 0x63, 0x7e, 0x8a, 0xc8, 0x42, 0x9f, 0x47, 0x0c, 0x1c, 0x80, 0x3e, 0x03, + 0x4a, 0x37, 0x04, 0xca, 0x2e, 0xf5, 0xb6, 0xe1, 0x00, 0xe8, 0xf2, 0xca, 0xe0, 0xd6, 0x5f, 0xfc, + 0xb0, 0x58, 0x48, 0x1f, 0x5e, 0x94, 0x1d, 0x81, 0xe4, 0x2c, 0x64, 0x6b, 0xcc, 0xf9, 0x73, 0xee, + 0xd9, 0xbd, 0x17, 0x3d, 0x28, 0x5c, 0x13, 0x3b, 0x6f, 0x41, 0xdf, 0xaa, 0xb3, 0x03, 0xf0, 0x5a, + 0x70, 0x89, 0x4b, 0x73, 0x4f, 0x65, 0x41, 0xad, 0xc4, 0x5b, 0xa8, 0x49, 0xcf, 0x65, 0xd4, 0x7b, + 0xf7, 0xa2, 0x66, 0xa3, 0xeb, 0xb2, 0x6f, 0xf9, 0x57, 0xea, 0x72, 0x4d, 0xf9, 0x5e, 0xc7, 0x41, + 0xd6, 0x94, 0x54, 0x2a, 0x19, 0x18, 0xdf, 0x15, 0x6c, 0x20, 0x60, 0x20, 0x8a, 0x7f, 0x71, 0x88, + 0x9a, 0x7a, 0xa9, 0x6b, 0x3e, 0x5d, 0x0b, 0x0a, 0xda, 0x8e, 0x7f, 0x25, 0xfd, 0xce, 0xfb, 0x16, + 0xd6, 0x5d, 0x72, 0x6c, 0xfc, 0x85, 0xaf, 0x07, 0x80, 0xfd, 0xf2, 0xa1, 0x82, 0x95, 0xfb, 0x39, + 0xe0, 0xb3, 0x49, 0x76, 0x73, 0xdb, 0x3d, 0x80, 0xf8, 0x9e, 0x0b, 0x8a, 0xf6, 0xbb, 0xa0, 0x96, + 0xc2, 0x48, 0x96, 0xf5, 0x0e, 0xdc, 0x92, 0x8b, 0xfe, 0xe3, 0x55, 0x92, 0xd1, 0xf6, 0xed, 0xfa, + 0xd4, 0x26, 0x7d, 0x30, 0xfa, 0xa6, 0xbc, 0xf4, 0x58, 0x5b, 0xfa, 0x42, 0xed, 0xca, 0x5f, 0x34, + 0x76, 0xc1, 0x33, 0xbc, 0x4f, 0xf2, 0xcd, 0xb3, 0x91, 0x22, 0x48, 0x73, 0xdb, 0xbb, 0x31, 0x91, + 0x84, 0x8d, 0x34, 0xb5, 0xac, 0x14, 0xf1, 0x4b, 0x7d, 0xf7, 0xe4, 0xbc, 0x00, 0x22, 0x6b, 0x71, + 0x74, 0x71, 0x67, 0xf7, 0x7e, 0x44, 0xf1, 0xa8, 0xbf, 0x83, 0x87, 0x32, 0x60, 0x8c, 0xe1, 0x98, + 0x2e, 0x52, 0xf8, 0x52, 0x26, 0xfa, 0x30, 0x98, 0x76, 0x12, 0xed, 0x03, 0xf8, 0xe0, 0x3b, 0xbe, + 0x7b, 0x97, 0x8f, 0xbf, 0x9d, 0xd2, 0x30, 0xdf, 0x55, 0x16, 0x01, 0x3c, 0xc6, 0x6e, 0x65, 0xf7, + 0x79, 0x9e, 0x13, 0x39, 0xc1, 0x65, 0x31, 0xc2, 0xff, 0xf3, 0x5b, 0xd5, 0xc7, 0x01, 0xf7, 0x49, + 0xef, 0x18, 0x63, 0x56, 0x60, 0x83, 0xda, 0xba, 0x75, 0xdb, 0xec, 0xfa, 0xa6, 0xa7, 0x6f, 0x35, + 0xff, 0x5c, 0x9e, 0x3a, 0x2f, 0x0a, 0x0c, 0x64, 0x7a, 0x48, 0x45, 0x5d, 0x0f, 0xc3, 0x6d, 0xfd, + 0x31, 0x73, 0xd2, 0x6a, 0x77, 0x8e, 0x5e, 0x5f, 0x72, 0xbd, 0x8e, 0xcf, 0xdb, 0xa2, 0x6b, 0x97, + 0x10, 0xab, 0x4e, 0x1a, 0x2f, 0x4d, 0x80, 0xbe, 0xe4, 0x46, 0x31, 0x4f, 0x61, 0x7e, 0x4a, 0x64, + 0x4e, 0xcd, 0x73, 0xbf, 0xcb, 0x5f, 0x35, 0x42, 0x3d, 0xb6, 0x43, 0x36, 0x2f, 0xd7, 0x75, 0xdb, + 0xad, 0x19, 0x72, 0x0e, 0x8c, 0xe4, 0x2c, 0x9b, 0x3b, 0xce, 0x0f, 0x31, 0xa6, 0x2c, 0x47, 0x7d, + 0x8b, 0x27, 0x7d, 0xbe, 0x03, 0xad, 0xeb, 0xe1, 0x49, 0x1d, 0x6e, 0x25, 0x7a, 0xde, 0x47, 0x3c, + 0x47, 0x54, 0xff, 0x54, 0x12, 0xaf, 0x14, 0x38, 0xdf, 0x89, 0x3b, 0xa9, 0x35, 0x07, 0xd6, 0x3c, + 0xf9, 0xe1, 0xfc, 0xac, 0xcf, 0xd4, 0x6e, 0x15, 0x1f, 0xd0, 0x5f, 0x01, 0x35, 0x43, 0xf6, 0x92, + 0xfd, 0x39, 0xc7, 0x36, 0x78, 0x6f, 0x50, 0xea, 0xcf, 0x44, 0x6f, 0x9e, 0x6b, 0x04, 0x63, 0x4c, + 0x29, 0x16, 0x49, 0x38, 0x34, 0x31, 0x89, 0xce, 0x09, 0x9e, 0xa7, 0x2b, 0xb6, 0x27, 0xb3, 0x96, + 0xc4, 0x16, 0x5e, 0xd4, 0x15, 0xe7, 0x01, 0x10, 0xf4, 0xfa, 0x24, 0xf1, 0xe8, 0x09, 0xef, 0xe0, + 0x4b, 0x3a, 0xfc, 0xd0, 0x1b, 0xdc, 0xa2, 0x20, 0xa2, 0x8b, 0x87, 0xc8, 0xc2, 0x33, 0xd0, 0xce, + 0x54, 0xbd, 0x4b, 0xa6, 0x17, 0xbd, 0x84, 0x78, 0xdd, 0xba, 0xdb, 0x15, 0x93, 0xdf, 0x65, 0xea, + 0xac, 0x18, 0x26, 0x5a, 0xe6, 0x46, 0x8b, 0xae, 0xe9, 0xcc, 0xe3, 0xd6, 0x25, 0x5a, 0xec, 0x04, + 0x83, 0x30, 0xb0, 0x54, 0xbc, 0x9b, 0xc2, 0x32, 0xb6, 0xc5, 0x5a, 0xed, 0x4b, 0x2d, 0x0b, 0x62, + 0x83, 0x97, 0x51, 0x63, 0xd8, 0x41, 0x17, 0x09, 0x6d, 0xfb, 0xc7, 0xcf, 0xaa, 0xe6, 0xdd, 0x3d, + 0xaf, 0xd1, 0x18, 0x12, 0x8e, 0x5b, 0xf1, 0x7b, 0xfe, 0xf7, 0x6f, 0xea, 0x46, 0x41, 0x7d, 0x53, + 0xa1, 0x1c, 0xfe, 0x7a, 0x45, 0x25, 0x7a, 0xcd, 0x31, 0xe6, 0xaa, 0x39, 0x0d, 0xb5, 0xe5, 0x05, + 0x3c, 0xa3, 0x77, 0xcc, 0xdd, 0xab, 0x13, 0xc1, 0x57, 0xf0, 0xbc, 0xe3, 0x4f, 0xbf, 0xa3, 0x82, + 0xdf, 0x51, 0xfe, 0xe7, 0x36, 0xc8, 0xa1, 0x55, 0x7c, 0xa8, 0xc9, 0xf4, 0x0a, 0x57, 0x02, 0x5d, + 0x53, 0x92, 0x92, 0xc2, 0xf5, 0x95, 0x50, 0x00, 0x4f, 0x1d, 0x6d, 0x84, 0x91, 0x5c, 0x41, 0x3e, + 0x0c, 0x3a, 0x60, 0x5f, 0xaf, 0xc3, 0xc2, 0x41, 0x7e, 0x96, 0xcf, 0xa7, 0x1f, 0x8f, 0x0b, 0xe7, + 0xe7, 0xb8, 0x7c, 0xf6, 0xed, 0xb6, 0x70, 0xfe, 0x98, 0xcf, 0xa7, 0x5f, 0x4e, 0x0b, 0xe7, 0xf7, + 0xc2, 0xf9, 0xc5, 0x1a, 0x3a, 0x03, 0x39, 0xa9, 0x7a, 0x1f, 0x6f, 0xe4, 0xe2, 0x8e, 0x02, 0x44, + 0x8d, 0x9e, 0x7b, 0x87, 0xce, 0x50, 0x4b, 0xe0, 0x17, 0x34, 0x25, 0x22, 0xe9, 0x12, 0x8a, 0x27, + 0xfb, 0x0f, 0x80, 0x45, 0xea, 0x44, 0xc0, 0x36, 0x44, 0xef, 0xeb, 0x94, 0xfc, 0x7e, 0x18, 0xe3, + 0x6f, 0xeb, 0xfb, 0xc9, 0x33, 0x97, 0x7a, 0xdf, 0xff, 0xda, 0x7d, 0x0b, 0xb9, 0x33, 0x67, 0x7f, + 0x46, 0xc9, 0x97, 0xfa, 0xbb, 0xba, 0x65, 0x3d, 0x02, 0xfe, 0x21, 0x4b, 0x3e, 0x6f, 0xf8, 0xe9, + 0x85, 0xef, 0x02, 0xb2, 0xf4, 0x17, 0x12, 0xd2, 0x30, 0xad, 0x82, 0x9c, 0x26, 0x7e, 0x60, 0xee, + 0x6d, 0x01, 0x51, 0x42, 0x27, 0x65, 0x09, 0xbb, 0xa1, 0x63, 0xbc, 0xf4, 0x92, 0x7f, 0x45, 0xbf, + 0xfd, 0x68, 0xd2, 0x43, 0x44, 0x57, 0x45, 0xfa, 0x63, 0x06, 0xad, 0x42, 0xd9, 0x4b, 0x48, 0x6c, + 0xda, 0x76, 0x82, 0xfa, 0x60, 0xfb, 0xfe, 0x97, 0xbf, 0xe8, 0x36, 0xc8, 0x9c, 0x2e, 0xbd, 0x8f, + 0x06, 0x87, 0xf8, 0xce, 0x17, 0x2f, 0xb2, 0x9f, 0x77, 0xdc, 0x0f, 0x53, 0xe2, 0xbb, 0xbc, 0xe1, + 0xc7, 0x55, 0xa9, 0xc5, 0x25, 0xe9, 0xbb, 0x2b, 0x00, 0x63, 0x20, 0xdf, 0x17, 0x0b, 0x00, 0xbb, + 0x4b, 0x79, 0x0e, 0x09, 0x0b, 0x51, 0x34, 0x00, 0x10, 0xdf, 0x2b, 0xda, 0x07, 0x82, 0x0f, 0x7e, + 0x81, 0xb7, 0x53, 0xd3, 0xc6, 0xc8, 0x71, 0xa1, 0xfa, 0x00, 0x4d, 0xb0, 0x41, 0x6f, 0x10, 0x49, + 0xfc, 0x6f, 0xc1, 0xfd, 0x8e, 0x8d, 0x98, 0x42, 0x7e, 0xf0, 0xdf, 0xe2, 0xbf, 0x83, 0x35, 0xda, + 0x26, 0x8f, 0x36, 0xce, 0x57, 0xc2, 0x22, 0x5d, 0x4b, 0x99, 0xb8, 0x85, 0xfd, 0xe0, 0x1a, 0x31, + 0x57, 0xd5, 0xc5, 0xaf, 0xee, 0xf0, 0x84, 0x34, 0xf5, 0x57, 0x4e, 0xd6, 0x78, 0x97, 0x79, 0x81, + 0x04, 0x5e, 0x1b, 0x04, 0x16, 0x70, 0xa8, 0x7a, 0x42, 0x4c, 0xfb, 0x30, 0x26, 0x31, 0xd8, 0x9b, + 0x17, 0x62, 0xae, 0x1e, 0x99, 0x5d, 0x3f, 0x98, 0x9f, 0xda, 0x4d, 0x86, 0x43, 0x86, 0xf0, 0xc5, + 0x98, 0x47, 0x39, 0x1f, 0xc6, 0x36, 0xb8, 0x91, 0x1b, 0xa4, 0x21, 0x3f, 0x43, 0x7f, 0x6e, 0xc7, + 0xbb, 0x3d, 0xe8, 0xca, 0x00, 0x55, 0x81, 0x6e, 0xf8, 0xb5, 0x6c, 0xdd, 0xe7, 0x33, 0xe8, 0x89, + 0x09, 0x84, 0x59, 0xa7, 0x24, 0x1a, 0xe6, 0xa9, 0x74, 0x5d, 0xb8, 0xf9, 0xe8, 0x7e, 0x0b, 0xa8, + 0x4c, 0x7a, 0x57, 0xbb, 0x5d, 0x67, 0xf2, 0x80, 0x72, 0xbc, 0xf6, 0x18, 0xbd, 0xb0, 0x2d, 0x59, + 0xab, 0x3b, 0x1e, 0x67, 0x55, 0x24, 0x43, 0x52, 0x25, 0x0b, 0xe4, 0xb7, 0xda, 0x02, 0xef, 0xd6, + 0x92, 0x49, 0xab, 0xce, 0x79, 0xee, 0xa0, 0x87, 0x79, 0x06, 0x3a, 0xfb, 0x13, 0x83, 0x15, 0x48, + 0xcc, 0xcb, 0x5c, 0x32, 0xea, 0x18, 0x1c, 0x47, 0x52, 0xeb, 0x18, 0xf9, 0x86, 0x06, 0x15, 0x11, + 0x30, 0xea, 0x89, 0x85, 0xfc, 0x3a, 0xa9, 0x20, 0x6c, 0xae, 0x33, 0x9f, 0xa2, 0x77, 0x8d, 0x61, + 0x02, 0x5d, 0x47, 0x16, 0xd3, 0xd4, 0xc5, 0xb4, 0x20, 0x3e, 0x09, 0xa1, 0x1d, 0x6d, 0x64, 0x5d, + 0x97, 0x43, 0x76, 0xeb, 0xdd, 0x66, 0xc2, 0x27, 0xd0, 0xb2, 0x45, 0xf4, 0x9a, 0xc2, 0xf9, 0xb9, + 0xe0, 0x66, 0xe2, 0x4d, 0x99, 0x85, 0xdd, 0xc5, 0x67, 0xf5, 0xb1, 0xd7, 0xf8, 0xac, 0x76, 0x72, + 0xfe, 0x05, 0x66, 0xa2, 0x6e, 0x01, 0x8e, 0x43, 0x08, 0x20, 0x19, 0x0f, 0x9b, 0x14, 0x05, 0x78, + 0xeb, 0x9d, 0xfa, 0xd0, 0xbb, 0x61, 0x0b, 0x15, 0x8c, 0x58, 0x68, 0xe0, 0x1f, 0x75, 0x9e, 0xc4, + 0xd8, 0x89, 0xf3, 0x7f, 0x01, 0x59, 0xbb, 0xf7, 0x8b, 0x03, 0xb7, 0xbc, 0xaa, 0x10, 0x0d, 0xb7, + 0x88, 0x71, 0xaa, 0xd1, 0xe2, 0x25, 0x61, 0x68, 0x9e, 0x67, 0x43, 0xa5, 0x31, 0x21, 0x6a, 0xbf, + 0xc2, 0xc4, 0xb6, 0xb8, 0x40, 0xe9, 0x1d, 0x66, 0xe0, 0xcf, 0xb8, 0x2d, 0x4b, 0x4a, 0xe8, 0x32, + 0x73, 0x64, 0x25, 0x32, 0xb9, 0x0a, 0xcf, 0x0d, 0x69, 0x34, 0x72, 0xd7, 0xd2, 0x47, 0x4f, 0xec, + 0x50, 0xdf, 0xc2, 0xab, 0xcc, 0x78, 0xa4, 0xc6, 0xf9, 0x21, 0xd2, 0x3c, 0x10, 0xad, 0xd0, 0xee, + 0xa4, 0xa0, 0x7d, 0xe9, 0xdb, 0xdc, 0xd0, 0x9b, 0x4c, 0xf9, 0xfa, 0x63, 0xa6, 0xcf, 0x69, 0x94, + 0xc6, 0xa4, 0x7f, 0xc2, 0xe7, 0xea, 0x69, 0x6e, 0xaa, 0xb0, 0xf2, 0x1b, 0xbb, 0xf1, 0xc7, 0x4f, + 0x91, 0x13, 0x41, 0x5a, 0x95, 0xb3, 0xa1, 0x20, 0x80, 0x28, 0x74, 0xe0, 0x33, 0x99, 0x8b, 0xe1, + 0x93, 0x3e, 0x5a, 0x38, 0xac, 0x07, 0x2f, 0xfb, 0x78, 0x6f, 0xa8, 0x5e, 0xf0, 0xe9, 0x5e, 0x76, + 0x91, 0x3b, 0x74, 0x10, 0xe2, 0x1e, 0xf7, 0x83, 0x4e, 0xee, 0xab, 0x82, 0xfe, 0x74, 0xb4, 0x11, + 0x48, 0x5c, 0xf3, 0xae, 0xdb, 0x12, 0x71, 0x5d, 0x96, 0x72, 0x55, 0xe6, 0x81, 0x94, 0xab, 0xba, + 0xeb, 0x7b, 0xaf, 0x75, 0x99, 0xcf, 0x89, 0x9e, 0x5f, 0x12, 0xfb, 0xc9, 0x87, 0x32, 0x37, 0x5a, + 0x5e, 0x7e, 0xc1, 0xcd, 0x2f, 0x44, 0xf2, 0xf3, 0x6e, 0x7e, 0xd1, 0xcd, 0x2f, 0x86, 0xf3, 0x9b, + 0x5e, 0x7e, 0x96, 0xe5, 0x97, 0xf9, 0xce, 0xe9, 0x51, 0x84, 0x4b, 0x82, 0xe2, 0x36, 0x3f, 0x02, + 0x3d, 0x74, 0x81, 0x58, 0xc4, 0xe8, 0x41, 0xf4, 0x0e, 0x20, 0xa6, 0x6f, 0x83, 0x6c, 0x67, 0x56, + 0xd9, 0xf3, 0xe2, 0xa6, 0xce, 0xa2, 0xd2, 0xd3, 0xb0, 0xb9, 0x8c, 0xe7, 0x00, 0xf5, 0xe9, 0xf8, + 0x07, 0x24, 0x3a, 0x64, 0x22, 0x20, 0x75, 0x62, 0x89, 0xa4, 0xaf, 0xcb, 0xa2, 0x17, 0x9b, 0x9b, + 0xf8, 0x17, 0x64, 0x56, 0xaf, 0x99, 0x24, 0x34, 0xa9, 0xb0, 0x44, 0x97, 0x1d, 0xac, 0x38, 0x3d, + 0xc2, 0x43, 0xf2, 0x5c, 0x79, 0x5d, 0xa1, 0x41, 0xa2, 0xe8, 0xc9, 0x04, 0x9e, 0xd0, 0xf0, 0xc7, + 0x48, 0xd6, 0xd0, 0xb9, 0xe9, 0x6b, 0x89, 0x6f, 0xb0, 0x5c, 0x61, 0x90, 0xf3, 0x6f, 0xf4, 0xcb, + 0x55, 0xc9, 0x9a, 0x18, 0x76, 0xd2, 0xa2, 0xd7, 0x72, 0x3d, 0x67, 0xc4, 0x5f, 0x71, 0xd7, 0x49, + 0xfd, 0xcb, 0x50, 0xb4, 0x99, 0xd0, 0x27, 0xa4, 0xdd, 0x6f, 0xbd, 0xbb, 0x57, 0xc1, 0xbf, 0xd1, + 0x28, 0xe5, 0x99, 0x8c, 0xd7, 0x21, 0xf5, 0x81, 0xe5, 0xc8, 0xb3, 0xed, 0x51, 0x20, 0x9b, 0x12, + 0x25, 0xe9, 0x5e, 0x9f, 0xc1, 0xf2, 0x63, 0xb5, 0xfb, 0x3d, 0x97, 0x97, 0x37, 0xb3, 0x79, 0x19, + 0x07, 0x43, 0xb8, 0x5b, 0x54, 0xd2, 0x2f, 0xa8, 0x41, 0xc9, 0x30, 0x9b, 0xdb, 0x74, 0x07, 0x0c, + 0x55, 0xd1, 0x89, 0x41, 0x65, 0x7a, 0x14, 0xad, 0xbe, 0x2d, 0x9e, 0x67, 0x1a, 0x62, 0x95, 0x3e, + 0xcf, 0xe9, 0xc8, 0x40, 0xa0, 0x4a, 0xa5, 0xe6, 0x04, 0xbd, 0xeb, 0xbf, 0xcb, 0xdb, 0x36, 0xc8, + 0x82, 0xa1, 0x2f, 0x62, 0xe0, 0x8d, 0x5c, 0xd8, 0x95, 0xd0, 0xdc, 0xd7, 0x4d, 0x8b, 0x55, 0x99, + 0x7a, 0xef, 0x25, 0xb0, 0xd8, 0xb9, 0x21, 0x18, 0x78, 0x73, 0x38, 0xf8, 0x2c, 0x82, 0xd0, 0x43, + 0x7e, 0x96, 0x46, 0x41, 0x1c, 0x95, 0x3a, 0xcf, 0xad, 0x59, 0xe0, 0xfc, 0x38, 0x9b, 0xcc, 0xfd, + 0xd4, 0xaf, 0x53, 0x45, 0x6f, 0x4e, 0x3a, 0x99, 0x73, 0x5a, 0x50, 0xa7, 0x57, 0x74, 0x22, 0xae, + 0x9b, 0x7a, 0x48, 0x64, 0xb2, 0xc3, 0x37, 0x08, 0xdd, 0x8f, 0x22, 0xc4, 0x5e, 0x1d, 0x64, 0x9f, + 0x90, 0xf8, 0xd4, 0xcd, 0x41, 0xff, 0x82, 0xfa, 0x48, 0xeb, 0xd2, 0xd0, 0xf7, 0xd8, 0xb6, 0x80, + 0x8d, 0x0b, 0x28, 0x50, 0xb0, 0x58, 0x21, 0xb1, 0x57, 0x0a, 0xe3, 0x3e, 0x86, 0x23, 0x85, 0x17, + 0x87, 0x27, 0xae, 0x49, 0xe4, 0x83, 0xdb, 0x93, 0x0b, 0xc1, 0x6e, 0xa8, 0x15, 0x87, 0x09, 0x13, + 0x2c, 0x56, 0x14, 0x71, 0x45, 0x85, 0x5a, 0x10, 0xaa, 0x0b, 0x11, 0x8a, 0xfd, 0x7c, 0xd6, 0x77, + 0x15, 0xef, 0xa7, 0xe1, 0xba, 0x04, 0xc5, 0x67, 0x69, 0x20, 0x62, 0x87, 0x7a, 0xc2, 0x7a, 0xa1, + 0x69, 0xee, 0x90, 0xed, 0xd2, 0x60, 0xb2, 0x7d, 0x31, 0xe9, 0x2d, 0x73, 0x92, 0x1e, 0x28, 0x76, + 0xc3, 0x71, 0x2c, 0x15, 0x68, 0x10, 0x72, 0x87, 0xca, 0x54, 0x4c, 0x02, 0xab, 0x50, 0xbc, 0x24, + 0x7a, 0xdf, 0x83, 0x59, 0x0c, 0xaa, 0xb0, 0x9f, 0x7b, 0x91, 0x46, 0x78, 0xb7, 0x5f, 0x9a, 0x99, + 0x81, 0xf5, 0xa6, 0x7f, 0xa7, 0x01, 0x2e, 0x60, 0x95, 0xe5, 0x98, 0xe8, 0xa2, 0xd5, 0x17, 0xa2, + 0x10, 0x57, 0x58, 0x10, 0x62, 0x76, 0x9e, 0xdd, 0xee, 0x27, 0x29, 0xb9, 0xfc, 0xcb, 0x4b, 0xe8, + 0x6c, 0x94, 0xdc, 0x94, 0xe4, 0xaf, 0xda, 0xb2, 0x80, 0x77, 0xda, 0x9c, 0x59, 0xd3, 0x42, 0x68, + 0x5b, 0x86, 0x41, 0x3f, 0x42, 0x1d, 0xfb, 0xc6, 0x23, 0x43, 0x9c, 0xe2, 0xaa, 0x70, 0xbe, 0x00, + 0xef, 0x99, 0x44, 0x82, 0x80, 0xae, 0x5c, 0xf4, 0x22, 0x1a, 0x90, 0x35, 0xcd, 0x3e, 0x82, 0xf2, + 0x77, 0xbb, 0x8c, 0x0b, 0x25, 0xc4, 0x7d, 0x84, 0xd2, 0x0d, 0x92, 0x13, 0x21, 0x1c, 0xb4, 0x05, + 0x72, 0x51, 0xf8, 0x98, 0xed, 0x8a, 0xa0, 0x79, 0xcb, 0xa3, 0x9f, 0x48, 0x7c, 0x98, 0x48, 0x29, + 0x77, 0x54, 0xa0, 0x51, 0xc7, 0x96, 0x08, 0x0c, 0x60, 0x8b, 0xc7, 0x75, 0x4e, 0x95, 0x46, 0x95, + 0xb1, 0x37, 0x3c, 0x83, 0x68, 0xa8, 0xe9, 0xc7, 0x5f, 0x28, 0x2a, 0x86, 0xda, 0xc2, 0x24, 0xa3, + 0x1e, 0x01, 0x00, 0xa4, 0xb1, 0x48, 0x87, 0x20, 0x95, 0xf2, 0x29, 0x46, 0x0f, 0x92, 0x3a, 0x4c, + 0xfc, 0x1b, 0xbe, 0xb4, 0x1e, 0x0f, 0x61, 0x9d, 0x77, 0x83, 0xd7, 0x5d, 0x16, 0x39, 0x52, 0xb5, + 0xcf, 0x70, 0xc3, 0x70, 0xb6, 0xea, 0xd4, 0xb9, 0x34, 0x89, 0xcc, 0x4b, 0x73, 0x2f, 0xe6, 0xcb, + 0x92, 0x96, 0x66, 0x5f, 0x69, 0x80, 0x5f, 0x55, 0xc7, 0x18, 0xd4, 0x6c, 0x76, 0x59, 0x76, 0x16, + 0x54, 0x65, 0xcc, 0xa6, 0xbf, 0x2c, 0xdb, 0xa0, 0x0f, 0xcc, 0x4d, 0xd5, 0xa0, 0x99, 0x9e, 0xf9, + 0x6f, 0x23, 0x2b, 0xa9, 0x41, 0x66, 0x8a, 0xbe, 0x71, 0xd9, 0x52, 0x67, 0x7d, 0xbd, 0xb3, 0xc4, + 0xdc, 0xd9, 0x5d, 0x5f, 0xef, 0x2e, 0xc9, 0x02, 0x89, 0xd3, 0x5a, 0x6a, 0x8a, 0x62, 0x12, 0x71, + 0xa4, 0xe1, 0x88, 0xa5, 0x31, 0xd2, 0x76, 0x24, 0x37, 0xd2, 0x3c, 0xd7, 0x33, 0x9d, 0x39, 0x4e, + 0x54, 0xd6, 0x3c, 0x7a, 0xe0, 0x64, 0x64, 0x65, 0x29, 0x05, 0x18, 0x40, 0x01, 0x35, 0xfd, 0xcf, + 0xba, 0xba, 0x61, 0xd0, 0x86, 0xcc, 0xd0, 0xc4, 0xe1, 0x99, 0x0c, 0xd3, 0x8a, 0x81, 0x44, 0xf0, + 0x10, 0x08, 0x8a, 0xa1, 0xd5, 0xd2, 0x5c, 0x5f, 0x37, 0x97, 0x0f, 0x96, 0x0b, 0x17, 0xea, 0xdb, + 0x1a, 0xc2, 0x0a, 0xdc, 0x2f, 0xaa, 0xc0, 0xfd, 0xe0, 0xa4, 0xd8, 0xe0, 0xe3, 0x1b, 0x73, 0xf1, + 0x27, 0xeb, 0xd2, 0x61, 0x42, 0x83, 0xbd, 0x44, 0xfd, 0xa3, 0x7b, 0xaf, 0xbb, 0xd9, 0xdc, 0x90, + 0xa9, 0x23, 0xe9, 0x21, 0xd0, 0xa9, 0x07, 0x27, 0x34, 0xa4, 0xd3, 0x30, 0x9e, 0xbe, 0xbd, 0xeb, + 0xff, 0xfd, 0x3f, 0xfe, 0x2f, 0x34, 0x77, 0x6d, 0xeb, 0x4b, 0xc1, 0xaf, 0xea, 0xcb, 0xae, 0x5e, + 0x31, 0x83, 0xa2, 0x89, 0x57, 0x8f, 0x57, 0xdc, 0xe1, 0xf2, 0x00, 0x70, 0xcc, 0x5f, 0x21, 0x1b, + 0x3b, 0x65, 0x67, 0x26, 0x08, 0xda, 0xec, 0xfe, 0x71, 0x92, 0x9a, 0xe3, 0xf4, 0xad, 0xec, 0xb6, + 0x59, 0xc7, 0xf8, 0x4a, 0x20, 0xc4, 0xd9, 0x62, 0x15, 0xdd, 0x04, 0xb1, 0xfd, 0xba, 0x98, 0xa5, + 0x1e, 0x1b, 0xb0, 0x3a, 0x61, 0x2b, 0xf9, 0x12, 0x1a, 0x59, 0xdf, 0x82, 0x96, 0x19, 0x67, 0xef, + 0xc7, 0x2f, 0x79, 0x5a, 0xc2, 0x23, 0x83, 0xd1, 0x12, 0xc6, 0x61, 0x76, 0xfc, 0x32, 0x68, 0x02, + 0xc3, 0x5b, 0x03, 0xfd, 0xba, 0x7b, 0x3b, 0xac, 0xc7, 0xd8, 0x7f, 0x87, 0xc0, 0x8e, 0xa6, 0x67, + 0x12, 0xfd, 0xd4, 0x08, 0xb4, 0x54, 0xf7, 0x63, 0x02, 0x3d, 0x7a, 0x92, 0xb0, 0x95, 0xfd, 0xfd, + 0x7b, 0xb4, 0x25, 0xe3, 0xb3, 0x09, 0x02, 0x84, 0x00, 0xea, 0x43, 0x6f, 0x2e, 0x8c, 0x55, 0xcb, + 0x19, 0x29, 0x5a, 0x12, 0x74, 0x27, 0x6a, 0xcf, 0xf7, 0xd6, 0x32, 0x2b, 0x25, 0x0a, 0x3f, 0xf0, + 0x26, 0xa9, 0xf9, 0x33, 0x82, 0x29, 0xa0, 0xc3, 0x50, 0x24, 0x22, 0x33, 0xca, 0x21, 0xf1, 0x2e, + 0x29, 0x13, 0x20, 0x6b, 0xc4, 0x0b, 0x21, 0x87, 0xb7, 0x4e, 0xc5, 0x64, 0xd8, 0x76, 0x44, 0x92, + 0xb1, 0xf1, 0xb9, 0x3c, 0x67, 0xc8, 0x24, 0x57, 0x1b, 0xaf, 0xc9, 0xff, 0xd5, 0xda, 0x30, 0x5f, + 0xa1, 0x2f, 0xc0, 0xe0, 0x3d, 0xff, 0x70, 0x1c, 0xa7, 0x50, 0xf6, 0xdc, 0xf8, 0x45, 0x23, 0x56, + 0x3a, 0x51, 0x62, 0x09, 0x9a, 0x94, 0x12, 0xcb, 0xea, 0xbe, 0x6a, 0xed, 0x5f, 0x34, 0xa2, 0xe4, + 0xf2, 0xca, 0xf3, 0x28, 0x9a, 0x30, 0x82, 0xc1, 0x8c, 0xbb, 0xa3, 0x79, 0x69, 0x4c, 0x88, 0xe5, + 0x19, 0xbd, 0x71, 0xaf, 0xaa, 0xe3, 0xe7, 0x76, 0xb6, 0xbd, 0x50, 0x79, 0x18, 0xba, 0x8b, 0xbf, + 0xd1, 0xa9, 0x85, 0x8a, 0xea, 0x5a, 0x63, 0x59, 0xc9, 0xd6, 0x9b, 0xde, 0x09, 0x95, 0xf5, 0xbe, + 0xcc, 0x13, 0xaa, 0xc0, 0x60, 0x6a, 0xf9, 0xeb, 0xda, 0x0f, 0xb4, 0xd0, 0x0a, 0x3e, 0xad, 0x83, + 0xd6, 0x95, 0x04, 0xf7, 0xa9, 0x9d, 0xc5, 0x13, 0x31, 0x77, 0xff, 0xdc, 0xc1, 0x03, 0xa5, 0xa5, + 0xb9, 0x2d, 0x93, 0x20, 0x42, 0x96, 0xe6, 0x1f, 0x79, 0x5f, 0x97, 0x5f, 0x51, 0xa6, 0x99, 0x5d, + 0x95, 0x99, 0x5b, 0x95, 0x99, 0xc7, 0x4c, 0xef, 0x5b, 0x09, 0x89, 0x25, 0xa5, 0xae, 0x57, 0xb4, + 0x70, 0xb0, 0x22, 0x6f, 0x87, 0x86, 0x2e, 0x08, 0x3e, 0xc0, 0xb0, 0xa4, 0xd8, 0xbd, 0x98, 0xe4, + 0x3f, 0x88, 0xc0, 0xc5, 0xa1, 0x67, 0xb6, 0xc4, 0x09, 0xde, 0x54, 0x59, 0x38, 0xbf, 0xf2, 0x9a, + 0x8d, 0x9e, 0x5e, 0xb1, 0x2a, 0x6d, 0x45, 0x8b, 0xab, 0xd1, 0x6c, 0xde, 0xc4, 0x96, 0x1f, 0x90, + 0xe9, 0x64, 0xa1, 0x3c, 0xc1, 0x18, 0xff, 0xb1, 0xc5, 0xd9, 0x87, 0x7f, 0xe2, 0x3a, 0xa0, 0x78, + 0x04, 0xc1, 0x8b, 0x16, 0x88, 0xef, 0x6a, 0xd9, 0x68, 0xd8, 0xe7, 0x2a, 0x56, 0xd6, 0xb5, 0xff, + 0x8d, 0xba, 0xe3, 0x15, 0x75, 0x63, 0x2b, 0xbc, 0xac, 0xee, 0x6c, 0x05, 0x36, 0xad, 0x7e, 0x7b, + 0x65, 0x5d, 0x82, 0xdf, 0x17, 0x88, 0xad, 0xf9, 0xda, 0xb1, 0x37, 0x26, 0x2b, 0xea, 0xd1, 0x0f, + 0x2c, 0x45, 0x6b, 0x72, 0x97, 0xf6, 0xdd, 0x47, 0x1a, 0xdb, 0xd2, 0xb2, 0x13, 0x0b, 0xf2, 0x6a, + 0x74, 0xfd, 0x86, 0xa2, 0xd9, 0x05, 0x46, 0x6b, 0x69, 0x71, 0xe3, 0xfe, 0xc5, 0xc2, 0x93, 0x32, + 0x63, 0x90, 0x67, 0xa2, 0xfe, 0xf9, 0xc3, 0xb7, 0x14, 0x11, 0xb6, 0xf7, 0xdb, 0x74, 0xcb, 0xe6, + 0xcf, 0xd6, 0x99, 0xf9, 0x70, 0x51, 0x0e, 0xe0, 0x98, 0x33, 0x7e, 0x43, 0x68, 0x25, 0xfb, 0x96, + 0x9c, 0x0f, 0x85, 0x10, 0xd6, 0x3f, 0x32, 0xdd, 0xcf, 0x95, 0x5c, 0xce, 0x9b, 0x39, 0x71, 0x3a, + 0x6a, 0xf0, 0xc4, 0xdb, 0xcf, 0x28, 0x4a, 0x91, 0xef, 0xa5, 0xa4, 0x16, 0xd6, 0x19, 0x22, 0x32, + 0x1f, 0xd9, 0xca, 0xad, 0xaf, 0xd3, 0x78, 0x20, 0x2b, 0x8a, 0xe4, 0xb1, 0x48, 0xee, 0xe7, 0x6a, + 0xd1, 0x53, 0x08, 0xdd, 0xa0, 0xd4, 0x92, 0x62, 0x96, 0x1a, 0x8c, 0x3c, 0xab, 0x29, 0x96, 0x44, + 0xca, 0xf8, 0xe0, 0x74, 0x74, 0x91, 0xa3, 0xf3, 0xf1, 0xe7, 0x3c, 0x51, 0x4f, 0x42, 0x4d, 0x77, + 0xc5, 0xbc, 0xf7, 0xa6, 0xfc, 0x94, 0x47, 0xe5, 0x3e, 0x0c, 0x8c, 0xe0, 0x2c, 0xcc, 0xbd, 0xbd, + 0xd0, 0x64, 0x68, 0xee, 0x5d, 0x82, 0x59, 0x35, 0xf9, 0x5e, 0x90, 0x53, 0xf6, 0x31, 0x47, 0x16, + 0xc2, 0xc8, 0xbf, 0x08, 0xff, 0x37, 0x84, 0x53, 0x3d, 0x39, 0xd3, 0x57, 0xec, 0xeb, 0x18, 0x10, + 0x83, 0x9e, 0x21, 0x5f, 0x2a, 0x16, 0x8c, 0xda, 0xc1, 0x85, 0x14, 0xb4, 0xe2, 0xa9, 0xcd, 0x24, + 0xe6, 0x58, 0x84, 0x3f, 0x0d, 0x61, 0x26, 0x7f, 0x77, 0xce, 0xa8, 0x82, 0x18, 0xcc, 0x99, 0x61, + 0x3a, 0xe1, 0x78, 0x32, 0xfc, 0x17, 0x8d, 0x20, 0x93, 0x3b, 0x6b, 0xad, 0x11, 0xee, 0x20, 0x84, + 0x1e, 0x6c, 0x82, 0xbc, 0xe7, 0x7e, 0xf7, 0xee, 0xf7, 0xef, 0xac, 0xff, 0x0c, 0x19, 0x5f, 0xf0, + 0x44, 0x14, 0x03, 0xec, 0xe6, 0x7f, 0x06, 0x32, 0xb3, 0x4c, 0x0f, 0x88, 0x7f, 0xff, 0x66, 0x3e, + 0x1a, 0x98, 0xc7, 0xfe, 0x06, 0x25, 0x60, 0x7b, 0xdc, 0xaa, 0xcb, 0xd1, 0xd4, 0x2c, 0x95, 0xb4, + 0x97, 0x90, 0x55, 0x95, 0x2c, 0x8f, 0xad, 0xe0, 0x6a, 0xfe, 0xfa, 0xe7, 0xe4, 0x7e, 0xa3, 0x1e, + 0xf1, 0xf3, 0x59, 0x0c, 0x8b, 0xdc, 0x55, 0xc7, 0x3f, 0xa2, 0x97, 0xbb, 0xbc, 0xc0, 0xc8, 0x1c, + 0x92, 0x8d, 0xa4, 0xb2, 0xa8, 0x29, 0x2c, 0x87, 0xb3, 0xba, 0x64, 0x68, 0xb5, 0xbf, 0x02, 0x91, + 0x77, 0xc1, 0xe9, 0x43, 0x70, 0xfe, 0xcf, 0xff, 0x1b, 0xc1, 0x59, 0x5f, 0x0f, 0xa5, 0xfd, 0x3f, + 0x7f, 0x13, 0xc5, 0x9c, 0xec, 0xe8, 0x6e, 0x10, 0xd7, 0x18, 0xeb, 0x8f, 0x99, 0x86, 0xd9, 0xfe, + 0x43, 0xbf, 0x13, 0xba, 0x18, 0xda, 0xc4, 0x8b, 0x40, 0x42, 0xbd, 0x3d, 0x40, 0xbe, 0x40, 0xbe, + 0xe1, 0xde, 0x79, 0x01, 0x48, 0x44, 0x5a, 0x10, 0xad, 0x69, 0x8f, 0x09, 0xd9, 0x9c, 0x26, 0x69, + 0x04, 0x01, 0x3f, 0x05, 0xbf, 0x51, 0x9a, 0x14, 0xfd, 0x15, 0xad, 0xa9, 0xe6, 0x36, 0xfd, 0x8b, + 0x21, 0x67, 0xfd, 0x43, 0x7e, 0xf4, 0x3e, 0xb2, 0x41, 0x57, 0xc0, 0xfb, 0x23, 0x82, 0x98, 0xb2, + 0x5d, 0x1f, 0x1c, 0x2d, 0x7c, 0x29, 0xfc, 0x17, 0xfb, 0xca, 0x2b, 0xfd, 0x58, 0x26, 0x51, 0x69, + 0x20, 0x43, 0x84, 0x02, 0x63, 0x43, 0xa6, 0xb5, 0xe1, 0x9c, 0x1e, 0x90, 0xe0, 0xc9, 0x80, 0x6b, + 0x97, 0x0c, 0x3e, 0x7f, 0xb9, 0x28, 0xa0, 0xb0, 0xf1, 0xd0, 0xc1, 0x44, 0x76, 0x45, 0x2e, 0xde, + 0xe6, 0xd0, 0xbc, 0x64, 0xc8, 0x41, 0x25, 0xc0, 0x13, 0xb0, 0xd8, 0x77, 0x04, 0x61, 0x61, 0xd0, + 0x13, 0x70, 0xdd, 0x0f, 0x03, 0xe8, 0x07, 0xf2, 0xc5, 0xe3, 0xe5, 0xad, 0xc0, 0x2b, 0x87, 0x86, + 0x79, 0x62, 0x13, 0x0c, 0xe3, 0xa7, 0x75, 0xc2, 0xf7, 0x61, 0x40, 0x87, 0x75, 0x62, 0x92, 0x6b, + 0xd3, 0xba, 0xbd, 0x55, 0xd8, 0x5c, 0x5f, 0xb7, 0xbf, 0x17, 0xcb, 0xbf, 0x7f, 0xdb, 0x5b, 0xa5, + 0x02, 0x3e, 0x57, 0xb2, 0xf8, 0x5c, 0x29, 0xe1, 0x73, 0x36, 0x97, 0xc7, 0x97, 0x5c, 0xb1, 0xb8, + 0x2d, 0xd6, 0x01, 0xfe, 0x2d, 0x51, 0x7a, 0xab, 0xeb, 0xb4, 0x92, 0x4e, 0x2b, 0xe9, 0xb4, 0x92, + 0x4e, 0x2b, 0xe9, 0xb4, 0x92, 0xce, 0x2a, 0xe9, 0x7c, 0x25, 0x37, 0xe0, 0x69, 0x22, 0x41, 0xa1, + 0xf3, 0x82, 0xab, 0x6e, 0x8b, 0xdf, 0xc5, 0xea, 0x34, 0x99, 0x72, 0x87, 0x19, 0x31, 0xfb, 0xd3, + 0xc3, 0xcc, 0x70, 0xd9, 0xb7, 0x64, 0x8a, 0x8d, 0x83, 0x85, 0x3c, 0x94, 0xa5, 0x99, 0xce, 0x3c, + 0x35, 0xaa, 0x5f, 0x64, 0xde, 0x72, 0x3a, 0x54, 0x5e, 0xc8, 0x7d, 0x2b, 0x41, 0xb1, 0x3a, 0xb1, + 0x7f, 0xff, 0xf6, 0xcf, 0xca, 0x27, 0xf6, 0x77, 0x99, 0xb7, 0x9e, 0x12, 0x0c, 0x25, 0xba, 0xed, + 0x5b, 0x8c, 0x27, 0x18, 0x7a, 0xc6, 0x22, 0xf4, 0x0a, 0x46, 0x82, 0x7e, 0x5a, 0x40, 0x94, 0xc4, + 0xff, 0xaf, 0xb8, 0x2b, 0x7f, 0x6e, 0xdb, 0xc8, 0xd2, 0xbf, 0xef, 0x5f, 0x41, 0x21, 0x89, 0x05, + 0x44, 0x90, 0x0c, 0x52, 0x96, 0xc7, 0x01, 0x05, 0xb1, 0x1c, 0x1f, 0x3b, 0xaa, 0x71, 0x1c, 0x6d, + 0xe4, 0x49, 0xc6, 0xe5, 0x52, 0xad, 0x48, 0xaa, 0x29, 0xb1, 0x0c, 0x01, 0x0c, 0x00, 0x1d, 0x0e, + 0xc5, 0xff, 0x7d, 0xdf, 0xd1, 0x27, 0x00, 0x1e, 0x8a, 0x33, 0xb3, 0x55, 0x89, 0x45, 0x34, 0xba, + 0x1b, 0xaf, 0xef, 0x7e, 0xaf, 0x5f, 0x7f, 0xdf, 0x1d, 0xc1, 0x11, 0xdf, 0x21, 0xd9, 0x88, 0xb7, + 0x53, 0xa3, 0x73, 0x30, 0x74, 0x06, 0x94, 0xb8, 0x0f, 0x5f, 0x23, 0x46, 0x90, 0xdf, 0xc4, 0xe8, + 0x14, 0x1a, 0x5c, 0x10, 0x31, 0xde, 0xde, 0x68, 0x9a, 0x0d, 0x8b, 0x2f, 0x1f, 0xe8, 0x14, 0x8b, + 0xf0, 0xf1, 0x47, 0x37, 0x30, 0x89, 0x17, 0x5e, 0x78, 0x57, 0xee, 0x21, 0xa0, 0x41, 0x59, 0xa2, + 0x61, 0x12, 0xad, 0xc1, 0x38, 0x17, 0x4b, 0xae, 0x34, 0x6d, 0x1f, 0x87, 0x11, 0x4b, 0x87, 0xaf, + 0x3f, 0x52, 0x22, 0xcd, 0xa5, 0x61, 0x01, 0x94, 0x71, 0x82, 0x80, 0xcf, 0x91, 0x09, 0xb6, 0x21, + 0x98, 0x3b, 0xf8, 0xd3, 0x16, 0xf0, 0x5c, 0x10, 0x5a, 0x0f, 0xcc, 0xf5, 0x61, 0xb1, 0x8d, 0x2a, + 0x36, 0x93, 0xb0, 0x6e, 0xd1, 0x56, 0xb0, 0xf1, 0x9b, 0x42, 0x8b, 0xa7, 0x7a, 0x3c, 0xc2, 0xe2, + 0x80, 0x00, 0x06, 0xe5, 0xc0, 0x37, 0xf0, 0x4b, 0x30, 0xf8, 0x98, 0x8a, 0xf5, 0xc9, 0x13, 0x07, + 0x6e, 0xa6, 0x0c, 0x82, 0xd8, 0x02, 0x3d, 0x50, 0xdb, 0x33, 0x82, 0xfb, 0x1c, 0xc8, 0xbf, 0x71, + 0xd5, 0x77, 0xa6, 0x96, 0x32, 0xcc, 0x10, 0x20, 0x7f, 0x78, 0x71, 0x4a, 0x98, 0x46, 0x19, 0xcc, + 0x41, 0x0b, 0xae, 0x58, 0x22, 0x83, 0xa6, 0x6a, 0x7d, 0x34, 0x2c, 0x3a, 0x93, 0x93, 0xee, 0xec, + 0x1c, 0x1e, 0x38, 0x68, 0x94, 0xdc, 0xaf, 0xc2, 0xee, 0x01, 0xda, 0xf7, 0xef, 0x98, 0x2a, 0x45, + 0x7e, 0x0c, 0x51, 0xe1, 0xe9, 0x5b, 0x8a, 0xd6, 0xb4, 0x10, 0xbf, 0x97, 0xef, 0xc4, 0xe5, 0x30, + 0x85, 0xdd, 0x86, 0xe3, 0xc8, 0xa0, 0x24, 0x55, 0xb8, 0xed, 0xb4, 0xfe, 0xca, 0x4e, 0xc9, 0x67, + 0xee, 0x62, 0xaf, 0xbc, 0x19, 0x8f, 0xa1, 0x5b, 0xa8, 0xd0, 0xa8, 0x4f, 0x84, 0xb6, 0xe8, 0xfe, + 0x1c, 0xd6, 0xb5, 0x57, 0x69, 0xaa, 0x14, 0x78, 0x45, 0x9f, 0xf8, 0x6e, 0xc5, 0x5e, 0x96, 0x62, + 0x44, 0x26, 0xa7, 0xa5, 0xa7, 0x8b, 0x9b, 0x42, 0x52, 0xd4, 0xd2, 0x63, 0xc5, 0x51, 0xdf, 0x0e, + 0x11, 0xa9, 0x1e, 0x03, 0x26, 0xf0, 0xcb, 0xf0, 0xe0, 0x8a, 0xbd, 0x9b, 0x8b, 0x19, 0xde, 0xac, + 0xcb, 0x2e, 0x6a, 0xd4, 0xb4, 0xa0, 0xc4, 0xa4, 0xb4, 0x2e, 0xcc, 0xca, 0x18, 0x7f, 0x23, 0x93, + 0xac, 0x9c, 0xc4, 0x89, 0xda, 0x9a, 0x85, 0xab, 0x2a, 0x2d, 0x55, 0x55, 0x3c, 0xed, 0x46, 0x61, + 0x0b, 0xd0, 0x85, 0xec, 0x1c, 0x11, 0xcc, 0x4c, 0x91, 0x24, 0x39, 0xd4, 0x5a, 0x63, 0x62, 0x08, + 0x76, 0x35, 0x8b, 0x6e, 0xcd, 0x2b, 0xce, 0x37, 0x08, 0x17, 0xb6, 0x7f, 0x1c, 0xd4, 0x5d, 0x44, + 0x96, 0x2e, 0x7a, 0xfd, 0xa9, 0x3a, 0x43, 0xce, 0x18, 0x73, 0x81, 0x95, 0x56, 0x00, 0x34, 0xe8, + 0x75, 0x19, 0xc5, 0x98, 0x62, 0xc0, 0xf6, 0x09, 0xf6, 0x5c, 0x87, 0xbc, 0x3c, 0x54, 0x38, 0x55, + 0xf6, 0xe4, 0x36, 0xca, 0xa1, 0xa5, 0x23, 0xc3, 0xe6, 0xf8, 0x93, 0x95, 0xf1, 0x99, 0x94, 0xfb, + 0x21, 0xd9, 0xda, 0xf2, 0xbb, 0x4f, 0x52, 0xa3, 0x4d, 0x53, 0x48, 0x4f, 0x86, 0x40, 0x49, 0xe8, + 0xf9, 0xd9, 0x13, 0x8d, 0x9b, 0xe1, 0x9b, 0x8f, 0x57, 0xf9, 0x6c, 0xd7, 0x7a, 0x1a, 0x16, 0x55, + 0xf0, 0xbd, 0xfb, 0xfa, 0xe3, 0xc0, 0x7d, 0xac, 0x45, 0xff, 0x18, 0x77, 0x61, 0xa3, 0x2a, 0xab, + 0x49, 0xfe, 0x3d, 0x1c, 0x0e, 0x86, 0xb1, 0xfc, 0xad, 0x4f, 0x3b, 0x30, 0x51, 0x49, 0xa7, 0x3e, + 0x68, 0x0c, 0x44, 0x67, 0xbb, 0xb6, 0xf2, 0x0d, 0xad, 0x82, 0x35, 0xca, 0x55, 0x2b, 0x96, 0x2a, + 0xd5, 0x02, 0x7b, 0xf0, 0x50, 0xad, 0x52, 0xd6, 0x49, 0xda, 0xfb, 0xbc, 0xa3, 0x9a, 0x5c, 0x1e, + 0x9e, 0x19, 0x83, 0x12, 0x3c, 0x12, 0xa8, 0xf4, 0xa5, 0xb5, 0x3e, 0xb7, 0xb9, 0xf3, 0xec, 0x41, + 0xe7, 0x81, 0xee, 0x04, 0xcd, 0xff, 0x66, 0x28, 0x8f, 0xcc, 0x56, 0x6c, 0x3f, 0x14, 0x56, 0x53, + 0x9b, 0x82, 0x64, 0x9c, 0x60, 0x72, 0xf9, 0xcd, 0xdd, 0x6e, 0x5f, 0x1c, 0xa1, 0x4b, 0xcc, 0xee, + 0x6e, 0x90, 0x3b, 0x4e, 0x22, 0xc9, 0x10, 0xad, 0x1c, 0x10, 0x44, 0xe4, 0x4f, 0x8e, 0x93, 0x88, + 0x79, 0xd5, 0xad, 0xbd, 0x1a, 0x99, 0x57, 0xbd, 0xb3, 0xd0, 0xd7, 0xbd, 0xe1, 0x61, 0xab, 0x41, + 0x78, 0x49, 0x48, 0x51, 0x76, 0xda, 0x3b, 0x93, 0x16, 0xd9, 0xa2, 0x24, 0xa9, 0x68, 0x4e, 0x60, + 0xe1, 0xe4, 0x43, 0xad, 0x69, 0x4b, 0x90, 0x33, 0x5a, 0x99, 0x6f, 0x21, 0xcd, 0xb8, 0xc2, 0xad, + 0x1b, 0xfc, 0xa1, 0x8d, 0xb2, 0x6d, 0xba, 0x79, 0xa9, 0x47, 0x21, 0xbd, 0x0f, 0xc2, 0xa6, 0x65, + 0x4b, 0xbf, 0x2f, 0xef, 0xc3, 0x76, 0xbb, 0x96, 0x8e, 0x31, 0x75, 0x62, 0xa0, 0x55, 0xcb, 0x64, + 0xde, 0x1d, 0xe0, 0x3f, 0x71, 0x14, 0xd6, 0x4c, 0x5b, 0x26, 0x46, 0x0f, 0x63, 0xf4, 0x6a, 0x31, + 0xf6, 0xed, 0x18, 0xfb, 0x18, 0x63, 0x5f, 0xc5, 0x20, 0x45, 0xed, 0xe7, 0xae, 0xed, 0x4d, 0xbe, + 0x35, 0xdc, 0xcb, 0xbb, 0xf6, 0xdb, 0x5e, 0xfd, 0x6d, 0xcf, 0x7e, 0xbb, 0x5f, 0x7f, 0xbb, 0x0f, + 0x43, 0x9e, 0x78, 0x13, 0x08, 0xd2, 0x50, 0xfe, 0xe6, 0xc5, 0x74, 0x8a, 0xde, 0x82, 0xca, 0x6f, + 0x43, 0xbd, 0x60, 0x07, 0x8a, 0x08, 0x91, 0x63, 0xcc, 0x89, 0xf0, 0x35, 0x9e, 0xe4, 0x74, 0x26, + 0x1a, 0xe0, 0x68, 0xcb, 0x81, 0x99, 0xe9, 0x12, 0x3e, 0x0d, 0x42, 0x9e, 0x89, 0x2c, 0xbf, 0xb9, + 0xbc, 0xea, 0x94, 0x33, 0xd8, 0x48, 0x20, 0x9e, 0x74, 0x89, 0x38, 0xde, 0x0c, 0x68, 0x58, 0x4b, + 0x42, 0xb0, 0x35, 0x12, 0xe7, 0x1e, 0xbf, 0x20, 0xcf, 0xcf, 0x9d, 0x38, 0x04, 0x5f, 0xf3, 0xd3, + 0x94, 0x49, 0xea, 0xa7, 0x05, 0xb3, 0xe8, 0xb8, 0x51, 0x7e, 0xc0, 0x28, 0x2f, 0x2d, 0xc9, 0x3a, + 0x54, 0x8c, 0x0e, 0x74, 0xb8, 0x4e, 0x3e, 0x86, 0x59, 0x1c, 0x8f, 0xee, 0x17, 0xd6, 0xd8, 0xa4, + 0xb5, 0x9d, 0xd0, 0x93, 0x28, 0xe2, 0x8e, 0x17, 0xc3, 0xc3, 0x94, 0x8e, 0xa0, 0xd5, 0x40, 0xb6, + 0xa8, 0xca, 0x87, 0xb0, 0xd3, 0x48, 0x6d, 0x26, 0xf3, 0xe1, 0xde, 0xe4, 0x3e, 0xac, 0x39, 0xe5, + 0xb9, 0xa3, 0xdb, 0x61, 0xc8, 0x6d, 0x28, 0xad, 0xf2, 0x60, 0x72, 0x53, 0xd7, 0x60, 0xf7, 0xc4, + 0xb2, 0xee, 0x22, 0x1c, 0x96, 0x26, 0x88, 0xfc, 0x4e, 0x90, 0x81, 0xe2, 0xd3, 0x59, 0x5c, 0xda, + 0x8e, 0xc1, 0xe4, 0x5c, 0x9c, 0xc9, 0x14, 0xec, 0xb0, 0x8b, 0x48, 0x95, 0x18, 0x0f, 0xff, 0x5a, + 0x8e, 0xc1, 0xe1, 0x50, 0xc7, 0x3b, 0xec, 0xa9, 0x98, 0x5d, 0x19, 0xb3, 0xeb, 0xc4, 0xcc, 0x4d, + 0xcc, 0x7d, 0x15, 0xb3, 0x27, 0x63, 0xba, 0xce, 0xc6, 0xda, 0xa1, 0x1a, 0xe6, 0x1b, 0xd8, 0x6e, + 0xf7, 0x4b, 0xf4, 0xbd, 0xe3, 0xce, 0x67, 0x8d, 0x09, 0xd2, 0x4b, 0x0a, 0x3b, 0x84, 0x98, 0x45, + 0x31, 0xb8, 0xbf, 0x55, 0xe1, 0x86, 0xdc, 0x17, 0xb0, 0xbd, 0x7e, 0x31, 0xe8, 0xc5, 0x07, 0xa0, + 0x39, 0xa7, 0xaa, 0x86, 0x4a, 0xf6, 0x60, 0x4e, 0x61, 0x86, 0x1f, 0xf8, 0x4e, 0xe8, 0x96, 0x0a, + 0x2e, 0x2c, 0x2d, 0x07, 0x03, 0x62, 0x3b, 0x20, 0x22, 0xce, 0x0e, 0x6e, 0x25, 0xe8, 0xb5, 0x38, + 0x39, 0xc4, 0x5d, 0x3b, 0x6c, 0xaa, 0xa7, 0x84, 0xd8, 0x63, 0x97, 0x4f, 0x6f, 0xc7, 0x2f, 0x77, + 0x61, 0x21, 0x9d, 0xae, 0xc0, 0x32, 0x9c, 0x2e, 0x31, 0xfd, 0x40, 0xd3, 0x6b, 0x21, 0x0f, 0xa4, + 0x62, 0x38, 0xb9, 0xcf, 0x67, 0x55, 0xab, 0xdb, 0x3d, 0xee, 0x4e, 0xac, 0x1a, 0x64, 0x27, 0xc6, + 0x7d, 0xf2, 0x5e, 0x3c, 0xd8, 0x11, 0x87, 0xa9, 0x76, 0x8f, 0x24, 0x27, 0xee, 0xf4, 0x13, 0x04, + 0x9e, 0x49, 0x00, 0x47, 0xcc, 0x72, 0x47, 0xac, 0xbb, 0x87, 0x01, 0xb1, 0x64, 0x35, 0x0b, 0x5b, + 0x17, 0x84, 0xba, 0xd3, 0xd9, 0x79, 0x3f, 0xd3, 0x9d, 0x02, 0x2f, 0xe6, 0x67, 0xd8, 0xa0, 0x8d, + 0xca, 0xaa, 0xf0, 0xa3, 0xb0, 0xfb, 0x1c, 0xca, 0xb9, 0xe4, 0x5b, 0x76, 0x91, 0xcd, 0xa5, 0x81, + 0x66, 0x41, 0x29, 0x9e, 0x2a, 0xa5, 0x56, 0x0d, 0xb5, 0x97, 0xab, 0x39, 0x67, 0xab, 0xbb, 0x68, + 0x58, 0xfd, 0xa4, 0x44, 0xe2, 0x03, 0x46, 0x03, 0x0c, 0xf0, 0xde, 0xc3, 0x41, 0x5f, 0x39, 0x02, + 0xd7, 0x8c, 0x1d, 0xdf, 0xb0, 0x41, 0xad, 0x63, 0x1b, 0xba, 0xd8, 0x3a, 0xe6, 0x58, 0xb2, 0x40, + 0xa3, 0x41, 0x9e, 0xe0, 0x05, 0x88, 0x81, 0xab, 0x11, 0x2e, 0x09, 0x05, 0x4c, 0xd6, 0xfe, 0x38, + 0xfc, 0xdb, 0x81, 0xb4, 0x9e, 0xcd, 0xd0, 0xc3, 0xeb, 0x12, 0xff, 0xb9, 0x81, 0x8d, 0xdc, 0x24, + 0xc1, 0x95, 0xa9, 0xd6, 0x4a, 0x2d, 0xeb, 0xb0, 0xe3, 0x7c, 0x8a, 0xd9, 0xdc, 0x9a, 0xd5, 0x1a, + 0xaa, 0xaf, 0x4f, 0xbe, 0x5d, 0x6a, 0xe0, 0x73, 0x9b, 0xe2, 0xb8, 0xa6, 0xf9, 0xe2, 0x76, 0x79, + 0x43, 0xde, 0x3a, 0x56, 0x47, 0x90, 0x87, 0x7a, 0x3e, 0xa7, 0xc4, 0xaf, 0x5c, 0xd1, 0x6f, 0xd3, + 0x6e, 0xe8, 0x63, 0x62, 0xb5, 0xf5, 0x55, 0x78, 0xc5, 0xb1, 0xf9, 0x48, 0xf1, 0x12, 0xaf, 0xb5, + 0x26, 0xde, 0x0e, 0xcd, 0x28, 0x97, 0x4c, 0x27, 0xc3, 0x4d, 0x78, 0xeb, 0x8e, 0x1a, 0x31, 0xf0, + 0xde, 0xde, 0xd3, 0x58, 0x81, 0x5f, 0x3f, 0x5e, 0xe2, 0xe8, 0x28, 0x09, 0x59, 0x69, 0xb2, 0x85, + 0x00, 0xe3, 0xd6, 0xba, 0x8d, 0x47, 0xa6, 0xe8, 0x65, 0x85, 0x99, 0x54, 0x03, 0xa7, 0x28, 0xf6, + 0x51, 0x6f, 0xad, 0x1c, 0xdd, 0xd0, 0xfe, 0xa0, 0x07, 0x83, 0x4e, 0xec, 0x74, 0x11, 0x25, 0xf4, + 0x31, 0x55, 0xd1, 0x96, 0x45, 0xb8, 0x5c, 0xc6, 0x60, 0xa1, 0x1a, 0xe4, 0x5d, 0x03, 0x02, 0x77, + 0x46, 0x2d, 0x7f, 0xad, 0x6d, 0xf2, 0xc8, 0x50, 0x34, 0x32, 0x16, 0x7a, 0x3e, 0xcd, 0xd4, 0x67, + 0x56, 0x48, 0x89, 0x95, 0xd7, 0x6e, 0x59, 0xe4, 0x74, 0xc9, 0x82, 0xcf, 0x7c, 0xf1, 0x77, 0xc0, + 0x80, 0xd6, 0xd7, 0x35, 0xfb, 0x89, 0x37, 0xcd, 0xd0, 0xf9, 0x67, 0x97, 0x0d, 0x27, 0xa1, 0x9d, + 0x0b, 0xa6, 0x32, 0xc6, 0xaa, 0xc4, 0x0b, 0xd4, 0x81, 0xe9, 0x71, 0xd2, 0x78, 0xd5, 0xa7, 0x03, + 0x67, 0xd8, 0x07, 0x43, 0x93, 0xf3, 0xcf, 0x69, 0x86, 0x8e, 0xe6, 0xa1, 0x1e, 0x54, 0x94, 0x44, + 0x76, 0x8b, 0x63, 0xac, 0xdb, 0x20, 0xe8, 0x63, 0x58, 0x62, 0xbd, 0x80, 0x75, 0x17, 0xd2, 0x1f, + 0x07, 0x0b, 0xa7, 0x30, 0x5b, 0xb2, 0x34, 0x83, 0x91, 0x55, 0x41, 0x18, 0x10, 0xdb, 0x01, 0xdb, + 0x0d, 0x14, 0x39, 0x91, 0xca, 0x0b, 0xb7, 0x36, 0x3e, 0x06, 0xb1, 0x5b, 0xcb, 0xeb, 0x91, 0xa3, + 0x7d, 0xf2, 0x38, 0xec, 0x90, 0x5e, 0xda, 0x91, 0x07, 0x1a, 0xdb, 0xdc, 0x75, 0xfe, 0x1d, 0x59, + 0xd3, 0xc6, 0x03, 0xef, 0x2a, 0x6e, 0x87, 0x8d, 0x66, 0x60, 0x83, 0x15, 0xd6, 0xae, 0xa5, 0xd0, + 0x76, 0xfb, 0x96, 0x3e, 0x6b, 0xee, 0x8c, 0x59, 0x74, 0x16, 0x8f, 0xd3, 0xb8, 0xbf, 0x80, 0xc6, + 0xbd, 0x05, 0x5d, 0x51, 0x7f, 0x22, 0x98, 0x5b, 0x56, 0x8a, 0x87, 0x07, 0xdb, 0x80, 0xd1, 0xe0, + 0xce, 0xba, 0x83, 0x65, 0xee, 0x0e, 0xcd, 0xf4, 0xa0, 0xed, 0xfb, 0x5a, 0x2d, 0x0f, 0xeb, 0x5c, + 0x00, 0x8b, 0x70, 0x5f, 0xec, 0x07, 0x8a, 0xb1, 0x9d, 0xa3, 0x24, 0x32, 0x2d, 0x2a, 0xe5, 0x5f, + 0x48, 0x2b, 0x4f, 0x92, 0x44, 0xdb, 0x6e, 0xf6, 0x7e, 0x3e, 0x79, 0xf3, 0x1e, 0xf6, 0x13, 0x30, + 0xb2, 0x67, 0x79, 0x89, 0xe0, 0x03, 0xe8, 0x3b, 0x48, 0x0a, 0x3a, 0xba, 0x74, 0xdd, 0x22, 0x21, + 0x25, 0x03, 0x7a, 0xda, 0x04, 0x59, 0x68, 0x3e, 0xd9, 0xcb, 0xf2, 0x3b, 0x3f, 0x78, 0x8a, 0xe8, + 0xb6, 0x52, 0x97, 0xd4, 0x8a, 0x71, 0x9f, 0xd6, 0x3e, 0x98, 0x49, 0xa7, 0x17, 0xb0, 0x9a, 0xf2, + 0x0f, 0x45, 0x02, 0x69, 0xab, 0xd2, 0x8a, 0x96, 0x56, 0xf7, 0xd3, 0x6e, 0xf4, 0xbd, 0x72, 0xc1, + 0xe9, 0x0f, 0xb7, 0x40, 0xb3, 0xa6, 0xfb, 0x09, 0x26, 0x41, 0x32, 0x0c, 0x16, 0xbe, 0x34, 0x0b, + 0x19, 0x60, 0x6d, 0x61, 0xd1, 0xe1, 0xec, 0x3f, 0x8b, 0x48, 0xc9, 0x45, 0xf6, 0xb9, 0x4a, 0x2f, + 0xbf, 0x48, 0x90, 0xa5, 0x34, 0x41, 0xe8, 0xd4, 0x06, 0xac, 0x44, 0xab, 0x87, 0x88, 0x59, 0xa2, + 0x12, 0x2e, 0xca, 0xc1, 0x5d, 0x49, 0xa6, 0x01, 0x1f, 0xda, 0x65, 0x7b, 0xee, 0xdd, 0x7a, 0x31, + 0x62, 0xb9, 0x2e, 0xb6, 0x83, 0xb8, 0xcd, 0xfd, 0xb2, 0x9c, 0x5a, 0xbe, 0x97, 0x59, 0x88, 0x44, + 0xaf, 0xb0, 0x56, 0xc5, 0x73, 0xe4, 0x64, 0x43, 0xf7, 0xeb, 0x5d, 0x74, 0x52, 0x85, 0x9a, 0x45, + 0x42, 0xcb, 0x29, 0xdb, 0xd4, 0x28, 0x61, 0xbf, 0x83, 0x96, 0x4a, 0xb4, 0x39, 0xfc, 0xf3, 0xc3, + 0xdb, 0xdd, 0x17, 0xde, 0x22, 0x1c, 0xe5, 0x17, 0x5f, 0xe2, 0xca, 0xf6, 0xdc, 0x7c, 0x84, 0x89, + 0xeb, 0x11, 0xf4, 0x10, 0xd8, 0x3e, 0x1b, 0x18, 0xc4, 0xb0, 0x33, 0x3d, 0xd2, 0x26, 0x76, 0x09, + 0xbd, 0x5c, 0xa8, 0xf3, 0x16, 0x65, 0xe1, 0x21, 0xd6, 0x01, 0x32, 0x91, 0x19, 0x2c, 0x51, 0x32, + 0x7e, 0x19, 0x6b, 0x59, 0xd5, 0xc2, 0x32, 0x52, 0x2d, 0xb1, 0xa0, 0x55, 0xb8, 0x7d, 0xd7, 0x16, + 0x26, 0x69, 0x39, 0x93, 0x7f, 0x63, 0x21, 0x01, 0xda, 0xe9, 0x8e, 0x1b, 0xdf, 0x38, 0x62, 0xe0, + 0x75, 0x5c, 0x04, 0x5a, 0x38, 0x2a, 0x25, 0x35, 0x82, 0x1c, 0x65, 0x6c, 0xd6, 0x5a, 0x3f, 0x62, + 0x1e, 0x1e, 0x94, 0xf9, 0x15, 0x59, 0x2c, 0x7b, 0x07, 0x41, 0xcd, 0x14, 0xc6, 0x78, 0xed, 0x96, + 0x26, 0x82, 0x82, 0xc0, 0x3c, 0x52, 0xce, 0x60, 0x0a, 0x13, 0x9e, 0x24, 0xd4, 0x58, 0xe5, 0x21, + 0x5b, 0x77, 0xb4, 0x24, 0x87, 0x14, 0x7d, 0x6a, 0x38, 0xcf, 0xb3, 0x98, 0xcc, 0x65, 0xc8, 0x91, + 0x90, 0x2d, 0xc8, 0x74, 0x66, 0x77, 0x71, 0x63, 0xf6, 0x20, 0x03, 0xbd, 0xf3, 0x58, 0x8a, 0x4b, + 0xd6, 0xa3, 0xd9, 0x7c, 0x8f, 0x26, 0x28, 0x32, 0x52, 0x20, 0xe9, 0x07, 0x9b, 0x50, 0x22, 0xbe, + 0x80, 0x59, 0x4f, 0x15, 0x4e, 0x8a, 0x3f, 0xe2, 0xad, 0x2e, 0x71, 0x75, 0x5a, 0x13, 0x62, 0xc3, + 0x27, 0xf4, 0x7d, 0xea, 0x50, 0x13, 0xfa, 0x68, 0xb0, 0xdb, 0x82, 0x7f, 0x82, 0xc1, 0x39, 0xd6, + 0x3f, 0x8c, 0x78, 0xc6, 0x39, 0xe8, 0x7c, 0x44, 0x02, 0x27, 0xa6, 0x2a, 0x24, 0x02, 0x25, 0x52, + 0xc9, 0xbe, 0x9d, 0x93, 0x15, 0xef, 0x28, 0xb2, 0x50, 0x91, 0x17, 0x14, 0x8a, 0x14, 0x94, 0x10, + 0x88, 0x67, 0x2b, 0x30, 0x98, 0x26, 0xb0, 0x59, 0x93, 0x2f, 0x5e, 0xdf, 0x14, 0x0b, 0x44, 0xa2, + 0x20, 0x67, 0xdc, 0xf3, 0xd8, 0xe3, 0xaf, 0x5c, 0x30, 0x9e, 0x02, 0xc2, 0x01, 0xa3, 0xaf, 0xb6, + 0x73, 0xf1, 0x37, 0x4b, 0x63, 0xac, 0x42, 0x10, 0x6a, 0xb1, 0x68, 0xc8, 0x8f, 0x3e, 0x3a, 0x6e, + 0x09, 0xb4, 0xd9, 0x70, 0x4b, 0xfd, 0x0a, 0x60, 0xb7, 0x4c, 0x5e, 0xe1, 0x24, 0x3e, 0x11, 0xc0, + 0x22, 0x89, 0x53, 0x26, 0xaa, 0xbb, 0xbc, 0xf8, 0xcc, 0xc5, 0x81, 0x09, 0xb2, 0x83, 0xf1, 0x51, + 0x39, 0x26, 0x5a, 0x28, 0x58, 0x66, 0x90, 0x4a, 0xee, 0x03, 0xfe, 0xe6, 0x62, 0x13, 0x51, 0xd4, + 0xfa, 0x7c, 0x3a, 0x69, 0x9e, 0x5d, 0x42, 0x24, 0xcc, 0x6d, 0xcf, 0x53, 0xce, 0x2a, 0x73, 0x34, + 0x61, 0xc6, 0x73, 0x9c, 0xa8, 0x62, 0x25, 0xd7, 0x62, 0xd1, 0xb7, 0xd0, 0xe9, 0xa9, 0x91, 0xc9, + 0xd0, 0x59, 0x20, 0x56, 0xbd, 0x16, 0x7e, 0x4d, 0x03, 0x22, 0x38, 0xfe, 0xed, 0x54, 0xdc, 0xe1, + 0xe1, 0x82, 0x1c, 0x80, 0x7c, 0x7c, 0x49, 0x6f, 0x69, 0x0c, 0xe2, 0xd0, 0x24, 0xa7, 0x6f, 0xe7, + 0x95, 0x74, 0x52, 0x87, 0x77, 0xef, 0x6e, 0xb1, 0x6b, 0xbe, 0xbb, 0x95, 0x27, 0x10, 0xeb, 0x47, + 0x94, 0xa2, 0x12, 0x49, 0xe5, 0xb7, 0x61, 0x0d, 0xc2, 0x7c, 0x05, 0x13, 0x6e, 0x78, 0xbd, 0xd7, + 0x1e, 0xe5, 0xaa, 0xcc, 0x3f, 0x9f, 0xd3, 0x5b, 0x42, 0x7a, 0xb7, 0x4e, 0xb5, 0x0e, 0xa7, 0x13, + 0x54, 0xbd, 0x3b, 0xe6, 0x6a, 0x0f, 0x12, 0x90, 0x7b, 0xc3, 0x11, 0x0c, 0xf8, 0x78, 0x94, 0x0e, + 0x33, 0xba, 0x27, 0xc3, 0x91, 0xf0, 0x3e, 0x00, 0xfb, 0xd0, 0xca, 0x7c, 0xea, 0x47, 0x75, 0xf8, + 0xad, 0x8d, 0x4e, 0xe4, 0x38, 0x9f, 0xaa, 0x7e, 0x34, 0xc6, 0xe9, 0x9b, 0x1e, 0x26, 0x18, 0x11, + 0xa4, 0xa2, 0xd7, 0x7a, 0x15, 0xf1, 0x76, 0x2a, 0xd8, 0xb0, 0x0e, 0x3c, 0x58, 0x76, 0x08, 0xf4, + 0x22, 0x88, 0x1d, 0xa9, 0x1d, 0xff, 0xb1, 0xc2, 0xd1, 0xaa, 0x94, 0xa7, 0xb4, 0x62, 0x70, 0x0d, + 0xb7, 0xb8, 0x92, 0x04, 0x55, 0x37, 0x2d, 0x61, 0xb0, 0x7e, 0xa5, 0xb0, 0x80, 0x11, 0x28, 0x39, + 0xac, 0x60, 0x61, 0x39, 0xfd, 0x43, 0xf8, 0x8d, 0x26, 0xe7, 0x46, 0x9d, 0xeb, 0x46, 0x6d, 0xb6, + 0xa7, 0xd3, 0xd0, 0xa6, 0x87, 0x20, 0xad, 0x00, 0x26, 0xc6, 0xe6, 0x3e, 0xe6, 0x6b, 0x9d, 0x0e, + 0xcb, 0x2f, 0x0b, 0x8f, 0xd3, 0x48, 0x6b, 0x2d, 0x63, 0x92, 0x8d, 0xea, 0xd9, 0xae, 0x84, 0xe3, + 0x9a, 0x63, 0x1e, 0x67, 0x62, 0xfc, 0xe7, 0x1a, 0x13, 0x92, 0xbc, 0x3b, 0xa1, 0x3a, 0x73, 0xad, + 0x1f, 0xaf, 0x2c, 0x1b, 0xa5, 0xc5, 0xc2, 0xd1, 0x8f, 0x80, 0x49, 0x97, 0x55, 0xbd, 0x30, 0x99, + 0x85, 0x43, 0xe7, 0x60, 0x17, 0x8e, 0xa2, 0x3d, 0xba, 0x74, 0x8a, 0x1e, 0xc2, 0x2e, 0xa1, 0xcc, + 0xa9, 0xad, 0x88, 0xb8, 0x10, 0x21, 0x28, 0x88, 0x5a, 0x1a, 0xa2, 0x10, 0x97, 0xb0, 0xd2, 0xc1, + 0xe7, 0x20, 0xf3, 0x2a, 0x6a, 0x10, 0x38, 0x1a, 0xb3, 0x36, 0xa7, 0xd6, 0x72, 0xb7, 0x6b, 0xbc, + 0xdd, 0xc3, 0x6e, 0xd4, 0xe2, 0xee, 0xbc, 0x24, 0x55, 0x69, 0xa7, 0x42, 0x8f, 0x68, 0x84, 0x2b, + 0xf6, 0x59, 0x8e, 0xeb, 0x3b, 0xd0, 0xe2, 0x32, 0x0b, 0x3c, 0x02, 0xb7, 0xcf, 0xe8, 0xe5, 0xa2, + 0xfd, 0xc9, 0x1b, 0x5f, 0x11, 0x88, 0xec, 0xb4, 0xb0, 0xa1, 0x23, 0xb0, 0x6a, 0xc7, 0x45, 0x9e, + 0xa6, 0xf0, 0xed, 0xfc, 0x57, 0x6c, 0x98, 0xf9, 0x48, 0x5c, 0x0d, 0x6f, 0xa7, 0x79, 0x11, 0x6b, + 0xb6, 0x61, 0x1a, 0x69, 0xf0, 0x48, 0x2c, 0xcc, 0x0b, 0xe5, 0x9e, 0xd2, 0x8e, 0xf8, 0xa9, 0xe1, + 0x3c, 0x8f, 0x9a, 0x40, 0x79, 0x47, 0x2d, 0xb0, 0x78, 0x1a, 0x68, 0xa7, 0x5c, 0x8b, 0x84, 0xb7, + 0xdf, 0x33, 0x40, 0x11, 0x0e, 0xc8, 0xdd, 0x7b, 0xd8, 0x63, 0x95, 0x12, 0xad, 0x12, 0x33, 0x6a, + 0xe0, 0xdc, 0x19, 0x84, 0x3b, 0x84, 0xa5, 0x22, 0x82, 0x83, 0xc4, 0xdb, 0x7f, 0xf1, 0x1d, 0x73, + 0x02, 0xfd, 0xb4, 0x0a, 0xec, 0xae, 0x3d, 0xc1, 0x5f, 0x8a, 0x7b, 0xb7, 0x29, 0xf2, 0x18, 0x16, + 0x6d, 0x1d, 0xb0, 0x1d, 0x0a, 0x77, 0x7d, 0xb7, 0xdb, 0x8d, 0xcd, 0xa5, 0x02, 0x1b, 0x3d, 0x4e, + 0x2c, 0x83, 0x8c, 0x2a, 0x97, 0x43, 0x46, 0x95, 0x8b, 0x0d, 0x01, 0xc8, 0x30, 0xea, 0x3a, 0x5c, + 0x3b, 0x07, 0xcb, 0xae, 0xfa, 0x7a, 0x69, 0x1e, 0x83, 0x5e, 0x55, 0xb6, 0x72, 0x45, 0x8c, 0xaa, + 0xab, 0x06, 0x7c, 0x55, 0xb9, 0x01, 0x7c, 0x55, 0x55, 0x50, 0x02, 0xbe, 0x36, 0xa2, 0xb2, 0x34, + 0x78, 0xe2, 0x64, 0x4a, 0xc1, 0x4b, 0xa5, 0x2d, 0xf8, 0x74, 0xdf, 0xce, 0xd7, 0xc2, 0xd3, 0x2d, + 0xda, 0xbf, 0xf4, 0x7a, 0xcd, 0x97, 0xd6, 0xf5, 0x9e, 0x8f, 0x6b, 0x9a, 0x07, 0x51, 0xeb, 0x4c, + 0x0b, 0x45, 0xff, 0xee, 0xce, 0xb2, 0x5e, 0x1c, 0xbb, 0xbb, 0x50, 0xdf, 0xbe, 0x8a, 0xbb, 0x5f, + 0xd7, 0x6b, 0x6c, 0xec, 0x31, 0x07, 0x78, 0xcc, 0x11, 0x8d, 0x50, 0xc7, 0xbe, 0x9d, 0xeb, 0x51, + 0x24, 0x16, 0x74, 0x11, 0x42, 0x5e, 0xe4, 0xb5, 0x92, 0x8d, 0xbd, 0x65, 0x9d, 0x70, 0xe6, 0x50, + 0x9b, 0x28, 0xe8, 0x26, 0xe4, 0x08, 0x45, 0xa7, 0x8c, 0xb4, 0x15, 0x3a, 0x4a, 0x01, 0x2a, 0xd7, + 0xa6, 0x68, 0x8b, 0x1f, 0xd4, 0x76, 0x04, 0x50, 0x79, 0x0a, 0x72, 0x03, 0x58, 0x9a, 0xa6, 0x31, + 0x51, 0x2b, 0x09, 0x4b, 0x24, 0xac, 0x05, 0x29, 0x61, 0x9a, 0xe5, 0xdb, 0xdb, 0x35, 0xee, 0xda, + 0xa8, 0xff, 0xe7, 0xe1, 0x96, 0x09, 0xcb, 0xa9, 0x01, 0xb5, 0x8c, 0x27, 0xb2, 0x4c, 0x1d, 0xf0, + 0x55, 0xb8, 0xca, 0x24, 0xb7, 0x67, 0x71, 0xe8, 0xea, 0x75, 0x1a, 0xf9, 0x6f, 0x57, 0x43, 0x77, + 0x75, 0x5f, 0x0c, 0x69, 0x4c, 0xbf, 0xbc, 0xb8, 0x50, 0x8b, 0x46, 0xb3, 0x55, 0x97, 0x22, 0x1c, + 0xb7, 0x41, 0x19, 0x2f, 0xc3, 0x31, 0xfe, 0x4a, 0xcc, 0xe2, 0x37, 0xfb, 0xcf, 0x7e, 0x6c, 0xb0, + 0x3b, 0xfc, 0x49, 0x08, 0xe2, 0x14, 0x71, 0x7a, 0xbf, 0x12, 0x7d, 0x98, 0xf2, 0xf8, 0x4a, 0xe0, + 0x61, 0xca, 0xe3, 0x2b, 0x31, 0x87, 0x29, 0x8f, 0x75, 0x70, 0xc3, 0x72, 0x20, 0xb9, 0x3b, 0xb9, + 0x13, 0xe8, 0x82, 0xa9, 0x76, 0x9b, 0x51, 0x17, 0xcd, 0xfb, 0x6d, 0xbc, 0x8b, 0xd9, 0x12, 0xca, + 0x45, 0x9b, 0x55, 0xb1, 0xa3, 0x6d, 0x6c, 0xe4, 0x0b, 0x9d, 0x0d, 0xf8, 0x4f, 0x6c, 0x58, 0xc7, + 0xd1, 0x8d, 0xba, 0x5f, 0xf3, 0xca, 0x23, 0x44, 0x1f, 0x7c, 0x41, 0xdc, 0x33, 0x43, 0xd0, 0x2a, + 0x2a, 0xe9, 0x4c, 0x6d, 0x58, 0xc4, 0x9d, 0xc7, 0x3d, 0x04, 0xb9, 0xf2, 0xcb, 0x36, 0x88, 0x2b, + 0xcc, 0x86, 0x50, 0x16, 0xf0, 0x07, 0x9d, 0x20, 0xb8, 0x20, 0xee, 0x88, 0x1f, 0x61, 0xd0, 0xac, + 0xcc, 0xc1, 0xac, 0x3d, 0x89, 0x4c, 0x40, 0xd8, 0xab, 0x93, 0xf4, 0x4d, 0xdd, 0x5d, 0x3e, 0x15, + 0xea, 0x34, 0xa5, 0x32, 0xac, 0xa1, 0x65, 0xe2, 0x00, 0x81, 0x4f, 0xa6, 0x45, 0x09, 0xd3, 0xbb, + 0x77, 0xa4, 0x68, 0x15, 0x3b, 0xb2, 0xba, 0xb8, 0xfa, 0xb7, 0xc9, 0x39, 0x27, 0x3b, 0x9c, 0xa5, + 0x9a, 0x15, 0x5d, 0x79, 0x53, 0xf6, 0xb3, 0x9d, 0x9d, 0x00, 0xca, 0xc4, 0xcd, 0x02, 0xd3, 0x74, + 0xf1, 0x05, 0x1a, 0x06, 0x39, 0x2d, 0x77, 0xdc, 0x4f, 0x5c, 0x15, 0x7f, 0x28, 0x3c, 0x97, 0xed, + 0xb0, 0xb2, 0xaf, 0x70, 0x4b, 0xc3, 0x68, 0xb5, 0xf4, 0xca, 0x32, 0x4d, 0xc2, 0xe9, 0x2e, 0x62, + 0x15, 0x06, 0xfd, 0x54, 0x1f, 0xc8, 0xe2, 0xed, 0x4d, 0xb2, 0xcc, 0xb7, 0xd8, 0xa9, 0x15, 0x19, + 0x98, 0xc1, 0xc6, 0xf3, 0x89, 0x0a, 0x31, 0x58, 0x41, 0xea, 0x43, 0xd9, 0xdb, 0xf4, 0x3a, 0x9d, + 0x61, 0x90, 0xe9, 0x13, 0x14, 0x68, 0x2b, 0x3c, 0x11, 0x32, 0x94, 0x39, 0xd6, 0x8b, 0x00, 0xa9, + 0xe8, 0xa4, 0x07, 0x83, 0xf3, 0x22, 0xb6, 0x6b, 0xec, 0x93, 0x79, 0x45, 0x07, 0x13, 0x67, 0x36, + 0x01, 0x0f, 0xdb, 0x73, 0x6d, 0x8b, 0x16, 0xcc, 0xdd, 0x27, 0x29, 0xbb, 0x84, 0x3a, 0xf5, 0x8e, + 0x47, 0xc9, 0x63, 0x01, 0xfa, 0x7e, 0x37, 0x8c, 0x42, 0xbc, 0x5d, 0xae, 0x5f, 0x5e, 0xdc, 0x14, + 0xee, 0x5b, 0xe7, 0xd5, 0xa7, 0xea, 0xcc, 0x8e, 0x6c, 0xec, 0xc5, 0xcb, 0xd2, 0x98, 0x18, 0x94, + 0xd4, 0xe9, 0x61, 0x96, 0x77, 0xaf, 0x48, 0x5b, 0xc5, 0x34, 0xc7, 0xe6, 0x7e, 0xab, 0xf8, 0x61, + 0x77, 0x99, 0xe8, 0xee, 0x9b, 0x16, 0x39, 0x31, 0x82, 0x23, 0x8d, 0x0d, 0xb0, 0x96, 0x8a, 0x93, + 0x92, 0xd1, 0x4f, 0x5c, 0x81, 0x10, 0x30, 0x4d, 0xd7, 0x77, 0xd9, 0xb8, 0xa8, 0x0d, 0xe9, 0x5e, + 0xdf, 0x14, 0x2a, 0x61, 0xd9, 0xb0, 0xcb, 0xfb, 0xf5, 0xba, 0xb4, 0x2d, 0xfd, 0xdd, 0xe8, 0xfb, + 0x52, 0xb7, 0xa0, 0x9d, 0xe5, 0x87, 0xcd, 0x72, 0x74, 0x6a, 0x7a, 0x93, 0x8c, 0x7f, 0x31, 0x83, + 0x5c, 0xe7, 0xd2, 0x47, 0x6f, 0x25, 0xbe, 0xeb, 0x97, 0xa2, 0x82, 0x50, 0x54, 0x97, 0x16, 0x56, + 0x63, 0x68, 0xbf, 0x99, 0x39, 0xaf, 0x06, 0x3e, 0x1e, 0x1c, 0x22, 0x9e, 0x27, 0xa8, 0xa3, 0x72, + 0x0e, 0xc5, 0xf9, 0xd2, 0x49, 0x93, 0x77, 0xcf, 0x83, 0xf6, 0x91, 0x16, 0x9b, 0xe4, 0xae, 0xf2, + 0xab, 0x3e, 0x66, 0x6e, 0x8a, 0x52, 0xae, 0xad, 0xb1, 0x60, 0xf0, 0xbd, 0xc9, 0x2e, 0x4c, 0xcc, + 0x35, 0x5f, 0x66, 0x93, 0x51, 0xcd, 0xcf, 0xf7, 0xc4, 0xc5, 0x1d, 0x91, 0x33, 0x1e, 0x72, 0x4c, + 0x09, 0x53, 0xdb, 0x68, 0xb5, 0x9d, 0x95, 0x31, 0xba, 0x6c, 0x41, 0x3b, 0xc2, 0xdf, 0x08, 0x51, + 0xbe, 0x74, 0xfd, 0xc7, 0x9f, 0xaa, 0xe2, 0x2c, 0xe4, 0xe2, 0xc4, 0x51, 0x58, 0xc4, 0x68, 0xf7, + 0xcd, 0x2e, 0xe2, 0x48, 0x6b, 0xcb, 0xa6, 0xd5, 0x38, 0xd6, 0xa0, 0x1e, 0x00, 0xbb, 0xad, 0xd2, + 0x22, 0x80, 0x81, 0x5e, 0xc0, 0xea, 0x9a, 0x5c, 0x13, 0x61, 0x73, 0x04, 0x2a, 0xf4, 0x2e, 0x6c, + 0x3f, 0xe2, 0x6e, 0x34, 0xbb, 0xef, 0x6b, 0xba, 0xa4, 0x55, 0xc4, 0x36, 0xa7, 0x57, 0x37, 0x93, + 0x49, 0x2a, 0x88, 0x51, 0x76, 0xe9, 0x5e, 0xcd, 0x34, 0xbb, 0xbd, 0x5f, 0xc3, 0xce, 0xc2, 0xbc, + 0xb9, 0xb0, 0xbc, 0x58, 0xb2, 0x3e, 0x3c, 0x64, 0xe8, 0x4e, 0xef, 0x50, 0x64, 0x60, 0xfe, 0xab, + 0x77, 0x73, 0x48, 0xa5, 0xc6, 0xfb, 0x39, 0xf8, 0xb5, 0x9a, 0x2e, 0x88, 0x00, 0x62, 0x71, 0xa2, + 0x9b, 0x4c, 0xb3, 0x69, 0x25, 0xd2, 0x2f, 0x1b, 0x49, 0x3f, 0x5b, 0x25, 0x7e, 0x76, 0x44, 0x50, + 0x27, 0x5a, 0xe8, 0x47, 0x4b, 0x6c, 0x1a, 0x85, 0x7b, 0x96, 0x6e, 0x15, 0x85, 0xb5, 0x25, 0xbf, + 0xe1, 0x3a, 0xe9, 0x7b, 0x2a, 0xa9, 0x51, 0x15, 0x64, 0xf1, 0x9c, 0xf2, 0x28, 0xdd, 0xc7, 0x2e, + 0x8d, 0xa5, 0xe1, 0x58, 0x25, 0x21, 0x24, 0x81, 0xde, 0xdf, 0x48, 0x4b, 0x8a, 0xe4, 0x2e, 0x80, + 0xbf, 0x9c, 0x81, 0x56, 0x74, 0xd4, 0xc1, 0x93, 0xc4, 0x52, 0x53, 0xf6, 0xba, 0xbc, 0x33, 0xde, + 0x11, 0x0c, 0x14, 0xe9, 0x41, 0x16, 0x23, 0xa4, 0x4b, 0x23, 0xc6, 0x32, 0x66, 0x1a, 0xd8, 0x08, + 0x58, 0xb2, 0xf1, 0x88, 0x5b, 0x52, 0xd5, 0x74, 0x71, 0x04, 0xe4, 0x4a, 0xec, 0x2e, 0x83, 0xf7, + 0x6f, 0x9d, 0x27, 0x18, 0x15, 0xf8, 0xf5, 0x3a, 0x91, 0xcd, 0xd1, 0x7b, 0xa8, 0x35, 0xbd, 0x5b, + 0x69, 0x44, 0x40, 0xfc, 0x6b, 0xa8, 0x3f, 0xd8, 0x80, 0x17, 0xca, 0x13, 0xce, 0x8a, 0x0c, 0xba, + 0xa2, 0xde, 0xe0, 0xad, 0xfc, 0x34, 0x9d, 0x00, 0x61, 0xe3, 0xb6, 0x10, 0xd9, 0x28, 0x9e, 0xe3, + 0x46, 0xb3, 0x6d, 0xa0, 0xe1, 0x55, 0x20, 0x19, 0x2c, 0x68, 0x58, 0x11, 0x61, 0x47, 0xa9, 0x38, + 0x1b, 0xf1, 0x4b, 0x7f, 0x80, 0x94, 0x35, 0x65, 0xf0, 0x7c, 0xa1, 0x40, 0xcf, 0xca, 0x35, 0xd4, + 0x55, 0xf5, 0x8e, 0x6c, 0xe0, 0xa7, 0xe0, 0xcd, 0x31, 0x83, 0x34, 0x76, 0x46, 0x84, 0x04, 0x96, + 0x89, 0xb2, 0x24, 0x60, 0x1c, 0xd9, 0xbf, 0x57, 0x8e, 0x2a, 0x2c, 0xc7, 0x74, 0x44, 0x83, 0x4a, + 0x8e, 0x9a, 0xbf, 0x72, 0x94, 0xaf, 0x94, 0xfa, 0x14, 0x5d, 0x1d, 0x95, 0x39, 0x6f, 0x84, 0xc7, + 0xa0, 0x8f, 0x13, 0xbb, 0xfc, 0xff, 0x11, 0xfb, 0x15, 0x7f, 0x4f, 0x49, 0x8e, 0x07, 0x52, 0x3c, + 0x77, 0x3d, 0x42, 0x70, 0xf8, 0x8c, 0xf7, 0x28, 0x81, 0xcf, 0x6b, 0x30, 0x7e, 0xfa, 0x54, 0x51, + 0xc2, 0xfa, 0x3a, 0xcf, 0x16, 0xba, 0x6f, 0x59, 0x23, 0x6c, 0x42, 0xf6, 0x2a, 0x28, 0xa0, 0x8b, + 0xf6, 0xfb, 0x18, 0xe2, 0x2a, 0x2a, 0x40, 0x7a, 0x3d, 0x6b, 0x62, 0xfa, 0xb6, 0x40, 0xf9, 0x9e, + 0xd7, 0xa1, 0x7c, 0x1d, 0x31, 0xc9, 0x29, 0xbf, 0x5c, 0x8e, 0xe8, 0x0b, 0x5f, 0x42, 0xef, 0x99, + 0x99, 0x1a, 0xd6, 0x8c, 0xa1, 0xab, 0x30, 0x7e, 0x3b, 0x75, 0x0d, 0xe8, 0x2b, 0xd0, 0x7f, 0xcb, + 0x26, 0xfa, 0xaf, 0x82, 0x6d, 0x53, 0xa0, 0x78, 0x2b, 0x79, 0x5e, 0x98, 0x9a, 0x0b, 0xb5, 0x32, + 0xc4, 0xd7, 0x31, 0x54, 0x18, 0x54, 0x59, 0xc4, 0x03, 0xb3, 0xa1, 0xbd, 0x9b, 0x8a, 0x3c, 0x50, + 0x0c, 0xf4, 0x1b, 0xd0, 0xbc, 0x34, 0x66, 0xb0, 0xff, 0xc1, 0xbb, 0xd5, 0x8c, 0x47, 0x45, 0x3d, + 0x27, 0xee, 0x2c, 0x17, 0xbd, 0x24, 0xd1, 0x8c, 0x1c, 0x96, 0x18, 0xbf, 0xa7, 0x52, 0x06, 0xbb, + 0x24, 0xbf, 0xa7, 0x6d, 0x05, 0x79, 0x7a, 0xd4, 0x36, 0x99, 0x5e, 0x79, 0x47, 0x7e, 0x2a, 0x70, + 0x84, 0x0b, 0x3a, 0xb5, 0x87, 0x8e, 0x80, 0x47, 0xb0, 0x96, 0x78, 0x3c, 0x11, 0x06, 0x4e, 0x62, + 0x68, 0x7a, 0xba, 0xd8, 0x87, 0x55, 0x49, 0x0b, 0xef, 0xb6, 0x5a, 0x78, 0x71, 0xa5, 0xdd, 0x56, + 0xbb, 0x90, 0x3f, 0x3f, 0x78, 0xe1, 0x03, 0x03, 0xef, 0x14, 0x5a, 0xa9, 0x33, 0xd3, 0x9a, 0x2c, + 0x6c, 0xba, 0xf3, 0xc2, 0x8b, 0xb1, 0xe6, 0xbd, 0x9f, 0x6f, 0x91, 0x52, 0x13, 0xb6, 0x23, 0x9d, + 0xbb, 0x69, 0x75, 0xd5, 0x21, 0x6f, 0x08, 0xf8, 0xea, 0x3f, 0x61, 0x92, 0x96, 0x17, 0x5c, 0x64, + 0xd8, 0xe2, 0x51, 0x23, 0x7e, 0x5c, 0xd6, 0xb6, 0x2d, 0xf0, 0xf8, 0xaa, 0x34, 0x1b, 0x17, 0x2c, + 0xf2, 0xc3, 0x43, 0xf5, 0x75, 0xdb, 0xad, 0x96, 0x56, 0x98, 0xe5, 0x3d, 0x4b, 0x0a, 0x78, 0x38, + 0x7a, 0x79, 0x72, 0xdc, 0x81, 0xf6, 0xbb, 0x1e, 0x66, 0x17, 0x4c, 0x27, 0x0d, 0x1d, 0x62, 0x58, + 0x88, 0xa1, 0x4a, 0x31, 0x9c, 0x4d, 0x35, 0x71, 0x11, 0x25, 0x82, 0x00, 0xb2, 0xa8, 0xca, 0x78, + 0x47, 0xed, 0x9f, 0xe9, 0xda, 0x9f, 0xe9, 0x2a, 0x4c, 0xc4, 0x96, 0x35, 0xb6, 0x73, 0xfd, 0x1c, + 0xb6, 0xab, 0xd8, 0x2f, 0xaa, 0xbc, 0x73, 0xfc, 0xba, 0x63, 0xd1, 0x96, 0xf2, 0x5a, 0x74, 0x51, + 0x37, 0x15, 0xeb, 0x0d, 0x12, 0x15, 0x1f, 0x5a, 0xe2, 0xc2, 0xde, 0x26, 0xf5, 0x0e, 0x22, 0xda, + 0x26, 0x75, 0xf5, 0x36, 0x09, 0xdb, 0x51, 0xc4, 0xa0, 0xc5, 0xbf, 0xb3, 0xce, 0xee, 0x4e, 0xfc, + 0x60, 0x71, 0xb4, 0x64, 0xd1, 0xc7, 0x6a, 0x5e, 0xbf, 0xee, 0xa3, 0x63, 0xfe, 0x09, 0x2f, 0xfb, + 0x0a, 0xbe, 0x72, 0xa5, 0xf9, 0x51, 0x1d, 0x29, 0x60, 0x59, 0xf5, 0xaa, 0x4f, 0x3d, 0x10, 0x05, + 0xdc, 0x5e, 0xfe, 0x45, 0xaa, 0x8a, 0xed, 0x1d, 0xb1, 0xb3, 0x7d, 0x21, 0x52, 0x97, 0xb6, 0xe0, + 0xc4, 0xa7, 0xf0, 0x8d, 0x59, 0x0b, 0x98, 0xfa, 0x60, 0x3b, 0xde, 0xde, 0xd4, 0x72, 0x7d, 0xe2, + 0x9a, 0xae, 0xb7, 0x17, 0x96, 0xe4, 0xad, 0x0d, 0x7f, 0x37, 0x2c, 0x54, 0x91, 0xbc, 0xd1, 0x8c, + 0xd9, 0xfe, 0xc6, 0x56, 0x5f, 0xc0, 0xf7, 0x9e, 0xae, 0x78, 0x55, 0x76, 0x77, 0x9e, 0x80, 0x5e, + 0x80, 0xc5, 0x52, 0x48, 0x99, 0x98, 0x47, 0xdd, 0x9e, 0xc7, 0x62, 0x49, 0x2f, 0x67, 0x36, 0x5b, + 0xb1, 0xf5, 0xbb, 0xdf, 0x76, 0xc5, 0x07, 0x46, 0xe8, 0x97, 0x51, 0x5e, 0x31, 0xb1, 0xba, 0xeb, + 0x3b, 0xcb, 0xa0, 0x3d, 0xa1, 0x58, 0x66, 0x35, 0xc7, 0xf7, 0xd3, 0xcc, 0x3a, 0xdc, 0x94, 0xfb, + 0x4d, 0x3f, 0x52, 0xd0, 0x8a, 0xe7, 0x36, 0xba, 0xd4, 0x2c, 0xc2, 0xc1, 0x12, 0x80, 0x6a, 0x3d, + 0xc9, 0xc7, 0x37, 0x78, 0x98, 0x5d, 0xa9, 0x5b, 0x76, 0x04, 0x3c, 0x0d, 0xaa, 0xcd, 0x05, 0xba, + 0x26, 0x09, 0xba, 0x5e, 0x66, 0xdd, 0xbf, 0x27, 0x18, 0x43, 0x88, 0xcc, 0x41, 0xe4, 0x60, 0xb6, + 0xf1, 0xf1, 0xec, 0x58, 0x64, 0xe4, 0xb5, 0x23, 0x4f, 0xce, 0x67, 0xe5, 0x5b, 0x76, 0x2a, 0x6b, + 0xaf, 0x88, 0x0a, 0x6f, 0x45, 0xd5, 0xcd, 0xa3, 0xca, 0x0e, 0x57, 0xb5, 0xa2, 0xbe, 0xc2, 0xe4, + 0x8e, 0xef, 0x79, 0x64, 0xb4, 0x9b, 0xe8, 0xf0, 0x8d, 0x86, 0x6c, 0xc4, 0x23, 0x4d, 0xf8, 0xd3, + 0xb1, 0x8e, 0x50, 0x5f, 0x44, 0xdf, 0x79, 0x16, 0x0f, 0x3d, 0xbe, 0xed, 0x6c, 0xaa, 0x86, 0xb8, + 0x3a, 0x07, 0x9a, 0x6b, 0xf4, 0xa8, 0x0b, 0x25, 0x1d, 0xb1, 0xd6, 0x3f, 0xbc, 0x6f, 0xeb, 0x46, + 0x1c, 0x8d, 0x4d, 0x8b, 0x26, 0x34, 0x79, 0xf8, 0xc8, 0xdf, 0x6f, 0x57, 0x1d, 0x28, 0x11, 0x01, + 0x4a, 0x76, 0x3a, 0xb5, 0x3d, 0x00, 0x95, 0x09, 0xcf, 0x92, 0x54, 0xe1, 0xd6, 0x2b, 0x0a, 0xe9, + 0x2e, 0x74, 0x33, 0x6b, 0x54, 0xb1, 0x91, 0x6e, 0xf3, 0x59, 0x43, 0x1d, 0x5a, 0xb8, 0x07, 0x91, + 0x0c, 0x73, 0x59, 0xd4, 0xeb, 0xda, 0x88, 0xf3, 0xfa, 0xa6, 0xa0, 0x63, 0x8a, 0x25, 0xd2, 0x7e, + 0xd0, 0xf6, 0x8b, 0x25, 0x11, 0xbe, 0x01, 0xe1, 0x76, 0xba, 0x8b, 0x8d, 0x3e, 0xa6, 0x9a, 0xf8, + 0x19, 0x34, 0x71, 0xeb, 0xc1, 0x9f, 0x3b, 0x7d, 0x3b, 0xdb, 0x17, 0x25, 0x27, 0xcf, 0xdd, 0xcf, + 0x0f, 0x0e, 0xf6, 0xf7, 0x78, 0xfa, 0x8e, 0xf6, 0x7a, 0xb0, 0xb0, 0x8a, 0x19, 0xfc, 0xe8, 0xda, + 0x2a, 0x31, 0x19, 0xdd, 0x1a, 0xcd, 0xaf, 0xb7, 0x29, 0x75, 0xa3, 0xdb, 0xd3, 0x6e, 0x04, 0xad, + 0x5d, 0xb6, 0x97, 0xf2, 0x6b, 0x04, 0x37, 0x35, 0xa8, 0x44, 0xd7, 0x82, 0x47, 0xed, 0x82, 0x7f, + 0xd8, 0x4c, 0x6e, 0xc7, 0xb4, 0xb7, 0x52, 0xfc, 0x15, 0x7d, 0xae, 0xb9, 0x50, 0x3c, 0xa6, 0xcf, + 0x2d, 0xe5, 0xb8, 0xa9, 0x75, 0x86, 0xa7, 0x6a, 0xc0, 0x2f, 0x39, 0x73, 0x91, 0x93, 0xf4, 0xec, + 0xfd, 0xcd, 0x35, 0x1a, 0xdf, 0x2d, 0xc7, 0xcd, 0x8f, 0xf9, 0x4d, 0x27, 0x13, 0xa0, 0x33, 0x0d, + 0xab, 0x0e, 0x6c, 0x0a, 0x61, 0xd3, 0xd5, 0x93, 0x9a, 0x7d, 0x89, 0xdb, 0x00, 0x4c, 0xde, 0x19, + 0xea, 0x1d, 0xd9, 0x96, 0x44, 0xaf, 0xf9, 0xcf, 0xce, 0xf4, 0xea, 0x50, 0xb8, 0x1b, 0x45, 0xd6, + 0xac, 0x4f, 0x56, 0x04, 0x97, 0x1d, 0xd8, 0xb2, 0x2c, 0x47, 0x6a, 0x0a, 0xe6, 0x65, 0x40, 0x2f, + 0x02, 0xff, 0xa9, 0xc9, 0xdc, 0x5e, 0xb3, 0xe7, 0xcb, 0xd2, 0x33, 0x32, 0x8c, 0x4a, 0xbc, 0xae, + 0x66, 0x75, 0xec, 0x66, 0xb5, 0x2a, 0x41, 0x5a, 0x6a, 0x76, 0xd9, 0x06, 0xc3, 0x76, 0xcf, 0xb0, + 0x16, 0x72, 0x6d, 0x56, 0x9b, 0x80, 0x26, 0x50, 0xc5, 0xa9, 0x98, 0x54, 0xfd, 0x4d, 0x67, 0xc6, + 0x13, 0x69, 0x11, 0x52, 0x7d, 0x75, 0xc3, 0x0f, 0xa7, 0xad, 0x5f, 0x26, 0x7b, 0xc9, 0xe6, 0x9f, + 0x96, 0x1d, 0x54, 0x7f, 0x7c, 0xdb, 0xf2, 0x91, 0xa3, 0x0d, 0xbd, 0xa8, 0xe3, 0xf1, 0xc9, 0xad, + 0xbf, 0x31, 0xa6, 0xf7, 0xcd, 0x9b, 0x16, 0xc3, 0x75, 0xd5, 0xe6, 0xed, 0x28, 0x63, 0xf7, 0xda, + 0x62, 0x53, 0xac, 0x58, 0x26, 0x72, 0xa4, 0x21, 0x1a, 0x55, 0xa1, 0x5c, 0xc5, 0x31, 0x1f, 0x71, + 0x08, 0x7d, 0x7b, 0xa0, 0xd1, 0x21, 0xcf, 0x63, 0xca, 0x78, 0x17, 0x02, 0x51, 0x47, 0x35, 0xd0, + 0x47, 0x4d, 0x04, 0x59, 0x42, 0x8d, 0x0d, 0xed, 0x4d, 0x8e, 0xda, 0xb7, 0x40, 0x37, 0x68, 0x45, + 0x64, 0x0f, 0x97, 0x9c, 0xa2, 0xdb, 0x77, 0x3d, 0x95, 0x9f, 0x80, 0xae, 0xb4, 0x39, 0x88, 0x16, + 0x7f, 0x3a, 0x5b, 0xd4, 0xae, 0x40, 0x32, 0x93, 0x55, 0xe9, 0x30, 0x59, 0x95, 0x04, 0x92, 0x85, + 0x97, 0xc2, 0x09, 0x44, 0x1f, 0x1d, 0xad, 0x4b, 0xbc, 0x75, 0x8a, 0x88, 0x33, 0x5c, 0xd1, 0x8b, + 0xa0, 0x6f, 0xbb, 0x50, 0x56, 0x8d, 0x4f, 0xbf, 0xb9, 0xff, 0x6b, 0xbf, 0x5c, 0x82, 0x0e, 0xb7, + 0xc0, 0xad, 0xa0, 0xc4, 0x10, 0x48, 0x44, 0xb8, 0x5a, 0x00, 0x46, 0x50, 0xb7, 0xfc, 0xa9, 0x51, + 0x08, 0xe2, 0xec, 0xa1, 0xfc, 0x1c, 0xac, 0x4c, 0x9b, 0x56, 0xcb, 0x71, 0xb6, 0x96, 0xc7, 0xe4, + 0x78, 0xcf, 0xe4, 0x51, 0x0d, 0xd1, 0xb7, 0x8e, 0x80, 0x57, 0xc0, 0x04, 0x9d, 0x1b, 0x98, 0x20, + 0x66, 0xe7, 0x66, 0xdc, 0xb1, 0xb0, 0x6c, 0x56, 0x19, 0xdf, 0xe3, 0xe3, 0x2a, 0x13, 0x4e, 0x95, + 0x09, 0xe2, 0x9d, 0x72, 0xaa, 0x8c, 0x8b, 0x88, 0x6e, 0xf0, 0x7c, 0x0c, 0xe3, 0xc0, 0x83, 0x55, + 0x86, 0x7a, 0xc3, 0x3a, 0x33, 0xc2, 0x3d, 0xb4, 0x75, 0xcb, 0x0a, 0x6f, 0xe8, 0x94, 0x12, 0xe0, + 0x80, 0x1c, 0xed, 0x2b, 0xce, 0x32, 0x5a, 0x2c, 0x4a, 0x0b, 0x2d, 0xc0, 0x75, 0xa4, 0x2d, 0xed, + 0xc9, 0x93, 0x79, 0xe8, 0xdc, 0x21, 0xab, 0x07, 0x87, 0x72, 0xc7, 0x2c, 0xd7, 0xa2, 0x1e, 0x67, + 0xeb, 0x50, 0x8f, 0xb1, 0x84, 0xd1, 0x56, 0x22, 0x9d, 0x08, 0x5c, 0x88, 0x63, 0x68, 0x2d, 0xeb, + 0x0c, 0xce, 0xc5, 0x3a, 0xbe, 0x9e, 0x5a, 0xaf, 0x6a, 0x98, 0xc7, 0x76, 0x7f, 0xa8, 0xa1, 0x1f, + 0xcf, 0xee, 0x0a, 0x07, 0x18, 0x0e, 0x9b, 0x16, 0x7b, 0x24, 0xf9, 0x37, 0xa3, 0x0b, 0x7f, 0x62, + 0x75, 0xb4, 0x0c, 0xab, 0x0d, 0xbd, 0xd6, 0xb0, 0x13, 0xe3, 0x99, 0x53, 0xc3, 0x95, 0xb4, 0x44, + 0x6e, 0xb3, 0x0c, 0xba, 0xf2, 0x6d, 0x9c, 0x86, 0xd7, 0xd3, 0x78, 0x18, 0xe2, 0xad, 0x89, 0x70, + 0x54, 0x4c, 0xe3, 0xd6, 0x72, 0x13, 0x73, 0x9c, 0x46, 0x84, 0x86, 0x16, 0xc9, 0x17, 0x0b, 0xc5, + 0x6a, 0xd3, 0x02, 0x20, 0x3b, 0xde, 0x00, 0x40, 0xf6, 0x62, 0x3d, 0x80, 0x6c, 0x38, 0x6b, 0x8f, + 0x83, 0xc0, 0xcf, 0xaa, 0x19, 0x0a, 0xea, 0x16, 0x90, 0x73, 0x32, 0x0e, 0xf9, 0x37, 0xe4, 0x90, + 0x5c, 0xc8, 0xdf, 0xf9, 0x24, 0x99, 0x2d, 0xf8, 0x27, 0xf4, 0x0c, 0xba, 0xb1, 0xc5, 0xa4, 0xd1, + 0xc2, 0x75, 0xf4, 0x2f, 0xec, 0x33, 0x78, 0xe9, 0x72, 0xb8, 0xba, 0x0f, 0xf5, 0x79, 0x42, 0xa9, + 0x63, 0x56, 0x67, 0x75, 0xcc, 0x6a, 0xe9, 0xe7, 0xd0, 0x38, 0x84, 0x0e, 0x87, 0x4d, 0x47, 0x80, + 0xa6, 0x63, 0x71, 0x4a, 0x5e, 0xc3, 0x13, 0x3f, 0x3d, 0x2c, 0xf1, 0xea, 0xe2, 0xc3, 0x43, 0x7a, + 0x44, 0x98, 0xea, 0x0a, 0x6d, 0x03, 0x71, 0x90, 0x54, 0x96, 0x09, 0x45, 0xe1, 0x1b, 0xb4, 0x87, + 0x19, 0x47, 0x1f, 0xee, 0xb6, 0x66, 0x79, 0x94, 0x35, 0x32, 0x31, 0x9e, 0x0c, 0xf8, 0xa6, 0x6f, + 0xe3, 0x51, 0x71, 0x42, 0x32, 0xe1, 0x0d, 0x1f, 0x1e, 0xb6, 0x1a, 0xe1, 0x48, 0x4b, 0x68, 0xe7, + 0xd5, 0xb9, 0x50, 0x33, 0xa1, 0x84, 0xe3, 0x68, 0xed, 0x9a, 0x29, 0x77, 0xcd, 0x21, 0x77, 0x24, + 0x72, 0x32, 0x4f, 0x0f, 0x19, 0xa2, 0xdb, 0x5a, 0xde, 0x1c, 0xd4, 0xf0, 0xb2, 0x81, 0x1a, 0xde, + 0xe7, 0x2b, 0xf4, 0x06, 0x0b, 0x4b, 0xd5, 0x6e, 0xd1, 0xac, 0xf1, 0x66, 0xed, 0x4e, 0x65, 0xed, + 0x4e, 0x0f, 0x2b, 0xae, 0xae, 0xe9, 0x51, 0xd5, 0xa8, 0x18, 0xa5, 0xfb, 0x57, 0xba, 0x76, 0x0b, + 0xd5, 0x18, 0xc5, 0xea, 0xc6, 0xc0, 0x7a, 0xcc, 0xb9, 0x4b, 0x12, 0x54, 0x4b, 0x32, 0x0d, 0xd5, + 0x63, 0x3e, 0xfb, 0x98, 0x14, 0x0b, 0x2c, 0xe6, 0xb8, 0x09, 0xc2, 0x4c, 0x95, 0xdf, 0x98, 0x29, + 0xc7, 0x6b, 0x00, 0xd9, 0x9d, 0x81, 0x93, 0xad, 0x1f, 0x38, 0xb9, 0x1e, 0x38, 0x95, 0x12, 0x0b, + 0x06, 0x4e, 0x29, 0x7f, 0xc3, 0xc0, 0xc9, 0x42, 0x6a, 0x94, 0x3a, 0xf8, 0x34, 0x42, 0x97, 0x50, + 0x94, 0xca, 0x05, 0xf5, 0xb6, 0x81, 0xa9, 0xd1, 0x57, 0x49, 0xbb, 0x4c, 0x3a, 0x03, 0x2d, 0x77, + 0x9d, 0x48, 0xe4, 0x38, 0x33, 0x1c, 0x91, 0x83, 0x36, 0x05, 0x03, 0xb4, 0x89, 0x2b, 0x34, 0x2e, + 0x5e, 0xc3, 0xe2, 0x37, 0x05, 0x2d, 0xcc, 0x9c, 0xd2, 0xc0, 0x1b, 0xe9, 0xbb, 0x00, 0x5b, 0xa6, + 0x2d, 0x74, 0x4b, 0x50, 0x59, 0xed, 0xee, 0x86, 0xcb, 0x96, 0x61, 0xec, 0x76, 0xb0, 0xa0, 0x04, + 0xce, 0x82, 0x5b, 0xfd, 0x22, 0x6e, 0x57, 0xac, 0xdd, 0x38, 0x53, 0x2e, 0x9d, 0xe2, 0x6b, 0x6b, + 0x37, 0x66, 0xf5, 0x71, 0x55, 0x5e, 0x1f, 0xdd, 0xac, 0x3e, 0x2e, 0xcf, 0xe9, 0xa7, 0xe9, 0x8a, + 0x7c, 0x60, 0xde, 0x5e, 0xb6, 0xb2, 0x34, 0xf3, 0x59, 0x25, 0xd0, 0xb5, 0x2b, 0xd0, 0xf5, 0x2a, + 0x81, 0xba, 0xbd, 0x55, 0x19, 0x75, 0x7b, 0x6e, 0x4e, 0x5d, 0xda, 0xdf, 0x4a, 0x0b, 0xd9, 0x31, + 0x1a, 0x6f, 0xea, 0x19, 0x9e, 0xae, 0x2a, 0x61, 0xe9, 0x96, 0xb0, 0x9c, 0xae, 0xcb, 0xed, 0xc3, + 0x6c, 0x45, 0x6e, 0xd5, 0x2c, 0x5e, 0xd6, 0x61, 0xeb, 0xf9, 0xd0, 0xee, 0x0b, 0x0d, 0x69, 0x8f, + 0xdb, 0x7f, 0x2d, 0xed, 0x72, 0xa2, 0x8a, 0xab, 0x46, 0xc9, 0xc5, 0xe5, 0xc9, 0x5d, 0xb1, 0x42, + 0x5e, 0x58, 0x92, 0xb7, 0x1e, 0xb1, 0x07, 0x68, 0xc9, 0xff, 0xc7, 0x62, 0x55, 0xed, 0x6e, 0xb4, + 0xda, 0xbb, 0x97, 0x00, 0x2f, 0xd3, 0xb7, 0x85, 0x10, 0x7f, 0x08, 0x7d, 0xd7, 0xdb, 0xda, 0x69, + 0xcf, 0xf1, 0x0e, 0xa4, 0x57, 0x79, 0x30, 0xa1, 0x33, 0x3c, 0x11, 0x79, 0xe0, 0xf0, 0xae, 0x1a, + 0xd4, 0x70, 0x11, 0x9a, 0x3b, 0x98, 0xc2, 0xba, 0x5e, 0x2c, 0x6f, 0x50, 0x36, 0x2e, 0x66, 0xfa, + 0x90, 0x2d, 0x3a, 0x92, 0xc2, 0x6e, 0x32, 0x58, 0xb1, 0xe7, 0xae, 0xde, 0xfe, 0x4b, 0x0b, 0xc3, + 0xd7, 0xa8, 0x13, 0x31, 0xb0, 0x40, 0x33, 0xea, 0x00, 0x18, 0xdb, 0x0a, 0x00, 0xa3, 0x0e, 0x1e, + 0x1b, 0xcb, 0xae, 0xb0, 0xad, 0xe9, 0x5f, 0xeb, 0x49, 0xcf, 0x97, 0x25, 0xad, 0x43, 0x0d, 0x5b, + 0x40, 0xb3, 0x2d, 0x3d, 0x62, 0x72, 0x6f, 0x6a, 0x1d, 0xa6, 0xea, 0xc9, 0xfd, 0x85, 0x98, 0x98, + 0x1b, 0x27, 0xf4, 0x58, 0x6f, 0x4a, 0x8d, 0x89, 0xfc, 0xa8, 0x82, 0x2a, 0x26, 0xbc, 0x56, 0x78, + 0xe4, 0x4d, 0x8a, 0xbb, 0x32, 0x83, 0xc7, 0x15, 0x1a, 0x92, 0xc5, 0xa2, 0x5e, 0x2c, 0xec, 0x9e, + 0xb5, 0xde, 0xd9, 0xe8, 0x93, 0x4d, 0xac, 0xbb, 0xa0, 0xde, 0xcf, 0x11, 0xaf, 0xc6, 0x6f, 0xeb, + 0xe5, 0xe5, 0x7d, 0x6b, 0x5e, 0x0e, 0xfa, 0x55, 0x63, 0xd8, 0x68, 0xf4, 0xab, 0xd6, 0x2c, 0xa7, + 0xed, 0x59, 0x36, 0x20, 0xb3, 0x1a, 0xd9, 0x32, 0x6a, 0x0e, 0xb4, 0xa0, 0x84, 0xfc, 0x43, 0xc5, + 0xfd, 0xe1, 0x41, 0x1c, 0xed, 0x07, 0xee, 0x20, 0x5a, 0x2c, 0xea, 0x0b, 0xbc, 0xc2, 0xcb, 0x22, + 0x74, 0x19, 0xb9, 0x78, 0xef, 0x53, 0xeb, 0xf3, 0xd8, 0x1a, 0xef, 0x27, 0x65, 0xdc, 0xb3, 0x03, + 0x7a, 0x10, 0x20, 0x7f, 0x76, 0x61, 0x45, 0xaf, 0x0d, 0x1e, 0x47, 0x2c, 0xc6, 0xb5, 0x41, 0xb1, + 0x2c, 0x38, 0xc2, 0xba, 0x6c, 0xa5, 0x96, 0x4d, 0x7e, 0x98, 0x75, 0xb1, 0x7c, 0x3f, 0xd9, 0xda, + 0xaa, 0xe4, 0xa7, 0x65, 0x50, 0x8f, 0x82, 0xe4, 0x43, 0x17, 0x1f, 0xc2, 0x9a, 0xaa, 0xe6, 0x7c, + 0xfe, 0x5d, 0xde, 0x9c, 0x00, 0x71, 0xd0, 0x8b, 0xfa, 0x00, 0xa0, 0xfd, 0x84, 0xa5, 0xda, 0x23, + 0x02, 0xe1, 0x02, 0xb9, 0x23, 0x94, 0xf9, 0xc6, 0x47, 0x7d, 0x54, 0x5e, 0x6f, 0x57, 0xce, 0x05, + 0x08, 0x41, 0xa2, 0x1d, 0x0d, 0xee, 0x70, 0xc7, 0x66, 0x3f, 0x19, 0xa0, 0x90, 0x13, 0x34, 0x5f, + 0x0a, 0x2f, 0x38, 0x4c, 0x88, 0x79, 0x02, 0xb3, 0x87, 0xf9, 0x71, 0x11, 0x4a, 0x27, 0x72, 0x49, + 0xdb, 0x55, 0x85, 0x2a, 0x75, 0x60, 0x1c, 0x26, 0x7f, 0x4f, 0xcd, 0xef, 0x0c, 0x6f, 0x4c, 0x3a, + 0xf2, 0xc0, 0xec, 0x97, 0x67, 0x08, 0x37, 0x1d, 0x5a, 0xdb, 0x9a, 0x77, 0xf9, 0x10, 0xef, 0x67, + 0x48, 0x5b, 0x69, 0xc7, 0xdb, 0x51, 0x9e, 0x01, 0x3b, 0x5e, 0xc7, 0x27, 0x22, 0xcd, 0xc0, 0x5b, + 0x31, 0xe3, 0xd1, 0x89, 0xa5, 0xc2, 0x8c, 0x85, 0xb6, 0x9a, 0x1d, 0xd7, 0x7d, 0x2b, 0xf9, 0xc4, + 0x55, 0xcf, 0xe0, 0x01, 0x14, 0xfb, 0xf8, 0xb0, 0x4b, 0x9c, 0x1a, 0xc7, 0xc9, 0xb2, 0x83, 0x54, + 0x50, 0xb6, 0x8e, 0x8f, 0x7a, 0x07, 0x51, 0x00, 0xe3, 0xbb, 0x00, 0x29, 0xa5, 0x17, 0xfc, 0xf1, + 0x6b, 0xd8, 0x75, 0xc1, 0x14, 0x30, 0x12, 0x1d, 0x3c, 0x94, 0xcd, 0x41, 0x97, 0x11, 0x65, 0xb9, + 0xa7, 0xb8, 0x67, 0x10, 0xf2, 0xca, 0x9f, 0xbd, 0xb7, 0x2c, 0x63, 0x64, 0x74, 0xd2, 0x1c, 0xdd, + 0xf8, 0xce, 0xaf, 0x06, 0x9e, 0xf6, 0x30, 0xf7, 0x8c, 0x7f, 0x7d, 0xb0, 0x33, 0x3b, 0x56, 0x40, + 0x90, 0x73, 0xa3, 0x4d, 0xb6, 0xd9, 0xd7, 0x82, 0x6a, 0x00, 0x1b, 0x6b, 0x65, 0x4a, 0x36, 0xde, + 0x98, 0x61, 0xc9, 0xf5, 0x8b, 0x7f, 0xb1, 0x9a, 0x63, 0xe4, 0x64, 0x19, 0x59, 0xd2, 0x90, 0x13, + 0x94, 0xa5, 0x54, 0x43, 0xc7, 0xb4, 0x5f, 0x97, 0xcd, 0xd7, 0x63, 0xe7, 0xf5, 0xf8, 0xea, 0x73, + 0xc3, 0x9b, 0x56, 0xfa, 0xca, 0xe0, 0x4e, 0x98, 0x10, 0xa7, 0x6a, 0xa1, 0x5c, 0x76, 0x82, 0x0e, + 0x97, 0xbe, 0x2d, 0x2d, 0xcd, 0x63, 0xc5, 0x44, 0xc0, 0x19, 0xaa, 0x4b, 0x43, 0x66, 0x79, 0xae, + 0xce, 0xe8, 0xb5, 0xb2, 0x59, 0x15, 0x5f, 0xe6, 0xa5, 0x8d, 0xf9, 0x9a, 0x05, 0x0b, 0x86, 0x48, + 0xe0, 0x7e, 0x50, 0x62, 0x87, 0x86, 0x1d, 0xbb, 0x01, 0x2e, 0x73, 0x94, 0x12, 0x93, 0x29, 0x1e, + 0xdd, 0x3a, 0xac, 0x28, 0xc4, 0xfb, 0xfb, 0xe2, 0x45, 0xbf, 0xc3, 0x83, 0xa0, 0x43, 0x96, 0xeb, + 0xce, 0x17, 0x84, 0x1f, 0xb0, 0xbc, 0x0a, 0x3a, 0x74, 0x57, 0x80, 0xe1, 0x4b, 0xac, 0x81, 0x33, + 0xf7, 0x82, 0xa3, 0xdd, 0xee, 0xa3, 0x3f, 0x75, 0xfa, 0x05, 0x36, 0x2e, 0xf7, 0x12, 0xa2, 0x6e, + 0x9a, 0x75, 0xc6, 0x4c, 0x22, 0x8b, 0xc5, 0xb3, 0x3f, 0xca, 0x9f, 0x23, 0xd8, 0xb6, 0xc6, 0x50, + 0xfd, 0xb3, 0xc5, 0x93, 0x76, 0x7e, 0x42, 0x12, 0x80, 0x7d, 0x9b, 0x80, 0x8e, 0x3d, 0x41, 0x97, + 0xc4, 0xeb, 0xfc, 0x62, 0x3a, 0xf9, 0x82, 0xc3, 0x92, 0xe0, 0x08, 0x78, 0x6c, 0xc2, 0x9e, 0x8a, + 0x3b, 0x16, 0xfc, 0x99, 0xe1, 0xc0, 0x4b, 0x66, 0xc7, 0xd0, 0x47, 0x40, 0xb9, 0x7a, 0x6f, 0xe1, + 0xba, 0x9c, 0x4b, 0x37, 0x1a, 0x63, 0x19, 0xb0, 0xc9, 0x9e, 0x4b, 0x98, 0x22, 0x40, 0x3d, 0xb6, + 0x27, 0x80, 0xd3, 0x21, 0x01, 0x4b, 0xe3, 0xc0, 0xe7, 0x21, 0x3f, 0x3b, 0x6e, 0x8e, 0xf9, 0x12, + 0xc1, 0xe5, 0xf3, 0x01, 0xdf, 0x5c, 0xf9, 0x34, 0x3b, 0x3e, 0x83, 0x89, 0xdc, 0xb9, 0xea, 0x02, + 0x41, 0x2c, 0x54, 0x33, 0x38, 0x6f, 0x06, 0xdd, 0x36, 0x83, 0xd0, 0xdf, 0x14, 0x46, 0x8c, 0xf9, + 0xc0, 0x3c, 0x8b, 0x67, 0xef, 0x43, 0xe8, 0x48, 0xb1, 0xb7, 0xac, 0xb6, 0x10, 0xcb, 0x50, 0x08, + 0xae, 0xa3, 0x4c, 0xdc, 0xa5, 0x5f, 0x68, 0x3e, 0xba, 0x50, 0x2d, 0xb6, 0xe7, 0xc1, 0xea, 0x85, + 0x5d, 0x11, 0x47, 0xbe, 0xfe, 0x10, 0x76, 0x4d, 0x0a, 0xc5, 0x22, 0xfd, 0x9e, 0x3a, 0xef, 0xa0, + 0x72, 0x30, 0x2c, 0xb0, 0x30, 0x4e, 0x24, 0xec, 0x08, 0x56, 0x87, 0x39, 0x08, 0x09, 0xeb, 0x08, + 0x25, 0xb3, 0xeb, 0xea, 0x1d, 0xd4, 0x26, 0x52, 0x77, 0x59, 0x58, 0x25, 0xc1, 0x82, 0xf0, 0xc9, + 0x6c, 0x88, 0x10, 0xf6, 0xec, 0x54, 0x13, 0x26, 0xf1, 0xe9, 0xaa, 0x0b, 0x19, 0xf8, 0x4e, 0x75, + 0x24, 0x37, 0x14, 0xaf, 0x68, 0xdb, 0xbd, 0x68, 0x73, 0x5f, 0x50, 0x8f, 0xb1, 0xa9, 0x49, 0x35, + 0x00, 0xb9, 0xfb, 0xb5, 0x7c, 0xbb, 0x8f, 0xc9, 0x77, 0x5f, 0x72, 0x4f, 0xe2, 0xad, 0x52, 0xcf, + 0xcc, 0x94, 0x2b, 0x67, 0xc2, 0x65, 0x86, 0x51, 0x25, 0x90, 0xbb, 0xd6, 0xaa, 0x8c, 0x70, 0x6d, + 0xaf, 0x5d, 0xef, 0x68, 0x3b, 0xf6, 0xb8, 0x40, 0x0b, 0x65, 0xdf, 0xa9, 0xc3, 0x71, 0x36, 0x19, + 0xf8, 0x6e, 0x9e, 0x17, 0x68, 0xb1, 0x5f, 0x04, 0x6e, 0x8f, 0x03, 0x11, 0x9b, 0x2d, 0x6c, 0x1f, + 0x58, 0x2d, 0x3d, 0xae, 0xa2, 0xbb, 0x06, 0x7c, 0x46, 0x32, 0x6e, 0x22, 0x35, 0x3f, 0xa2, 0x3a, + 0x5d, 0x07, 0x9a, 0x2d, 0x4c, 0x6b, 0x95, 0x02, 0x36, 0x66, 0x8e, 0x01, 0xde, 0x80, 0xa2, 0xcd, + 0x11, 0xd9, 0x2e, 0x11, 0xb6, 0x71, 0x7d, 0x19, 0xb6, 0x2c, 0x19, 0xba, 0x83, 0xb6, 0xf3, 0xb5, + 0xfb, 0x7b, 0xa2, 0x4f, 0xe9, 0x23, 0xcf, 0x72, 0xbd, 0xa8, 0xf2, 0x1d, 0xf6, 0xf1, 0x13, 0x62, + 0x89, 0xf1, 0x8b, 0xcb, 0xd1, 0x69, 0x55, 0xf8, 0x95, 0x85, 0x11, 0x0b, 0x43, 0x04, 0xa6, 0xc2, + 0x31, 0xd2, 0xc8, 0x70, 0x3d, 0xa8, 0x85, 0xa6, 0x4e, 0xac, 0x12, 0xba, 0xfc, 0x3d, 0xd2, 0x1e, + 0x65, 0xc1, 0xc5, 0xdb, 0xb8, 0xb3, 0x4b, 0x09, 0x82, 0x90, 0xf9, 0xa7, 0x8e, 0xf0, 0x49, 0x38, + 0x60, 0xa8, 0xa6, 0x5d, 0x21, 0x22, 0xb3, 0xbe, 0x98, 0xf2, 0x4a, 0xc2, 0x79, 0x7a, 0x05, 0xcc, + 0xd9, 0xc8, 0x4f, 0x39, 0x87, 0xfd, 0xe4, 0xfc, 0x2a, 0x86, 0x65, 0x19, 0xfe, 0xbf, 0x8d, 0xf1, + 0x2c, 0x0a, 0xf4, 0x76, 0xfb, 0x8a, 0xcb, 0x41, 0xe4, 0x72, 0x3b, 0xef, 0x20, 0xa8, 0xe0, 0x45, + 0x3e, 0x17, 0x7b, 0x57, 0x76, 0xb4, 0xfd, 0xe7, 0xb5, 0x78, 0xc1, 0xe2, 0x0e, 0xea, 0x5c, 0xf8, + 0x14, 0x38, 0x1c, 0x95, 0x3e, 0x24, 0xd8, 0x25, 0x89, 0x82, 0x43, 0xcc, 0x82, 0x85, 0x83, 0xc0, + 0x85, 0xa9, 0x4b, 0xc1, 0x78, 0xba, 0x58, 0x65, 0xe8, 0xf0, 0x53, 0x27, 0x01, 0xd3, 0xf5, 0x26, + 0x41, 0x22, 0xec, 0x1a, 0x86, 0x66, 0xe8, 0xbb, 0x64, 0x4b, 0x1a, 0x6e, 0xbb, 0x08, 0x5d, 0xa6, + 0x25, 0xfd, 0xe2, 0x32, 0x74, 0x69, 0x96, 0x0c, 0x40, 0x37, 0x77, 0x20, 0xd8, 0xde, 0xdb, 0x9f, + 0xb8, 0x12, 0xf7, 0xa7, 0x74, 0xf6, 0x61, 0x9d, 0x82, 0x74, 0x1b, 0x06, 0xbc, 0x5a, 0x87, 0xfb, + 0x84, 0x3d, 0xd2, 0x6e, 0xc5, 0x7e, 0xc6, 0x8b, 0xcd, 0x0e, 0xac, 0x95, 0x55, 0x7e, 0x2a, 0xb3, + 0x79, 0x1e, 0x04, 0x9a, 0x43, 0x69, 0xac, 0x25, 0x29, 0x4d, 0x58, 0x36, 0x59, 0x0f, 0x5b, 0xb5, + 0x1f, 0x78, 0x92, 0x76, 0xc6, 0x11, 0xfb, 0x46, 0x84, 0x43, 0x27, 0xa4, 0x1c, 0x56, 0xd2, 0x0f, + 0x24, 0xcc, 0x9b, 0xdd, 0xd4, 0xae, 0xc6, 0xbf, 0x6b, 0x51, 0x52, 0x07, 0x94, 0xd8, 0xe0, 0x01, + 0xdb, 0xc1, 0xbf, 0xea, 0xe0, 0x1c, 0x54, 0x94, 0x69, 0x91, 0xef, 0xbd, 0x62, 0x09, 0xca, 0xdb, + 0x0f, 0xf9, 0x2f, 0x97, 0x23, 0x1f, 0x7a, 0x5a, 0x0a, 0x3d, 0x0d, 0x7a, 0x99, 0xea, 0x6b, 0xf5, + 0x5c, 0x5b, 0x2e, 0xef, 0xb6, 0x93, 0x83, 0x7a, 0x4b, 0x08, 0x47, 0xbf, 0x19, 0x0e, 0x87, 0x9d, + 0xdd, 0xee, 0xc1, 0x77, 0x61, 0x07, 0x89, 0xbd, 0xd1, 0xe7, 0xb8, 0xd8, 0xf1, 0x42, 0xfc, 0x7b, + 0x29, 0xff, 0x8e, 0x60, 0x09, 0xc7, 0xe9, 0x68, 0x85, 0x84, 0xc3, 0x36, 0xf9, 0x7e, 0xfd, 0x4b, + 0xe4, 0x8b, 0xa2, 0x68, 0x33, 0xf9, 0xac, 0x2f, 0xff, 0x43, 0x57, 0xac, 0xdd, 0x5a, 0x9f, 0x45, + 0x0a, 0xbb, 0x13, 0x33, 0x4a, 0xa0, 0x9b, 0xf0, 0x35, 0xfc, 0x60, 0xde, 0x05, 0xe5, 0x8f, 0x0d, + 0x5f, 0x9f, 0xc5, 0x17, 0x64, 0x91, 0x78, 0xf2, 0x04, 0xd9, 0x34, 0x08, 0x5f, 0xd0, 0x9e, 0x3a, + 0xe5, 0xbd, 0x7d, 0xd1, 0x9a, 0x42, 0x1f, 0xae, 0x98, 0x14, 0x3a, 0x13, 0x9b, 0xf7, 0xc7, 0xee, + 0xb2, 0xfd, 0xc6, 0xa1, 0xa1, 0x19, 0x2b, 0xcf, 0x03, 0xc4, 0x60, 0xe5, 0x0d, 0xb2, 0x1e, 0xf2, + 0xde, 0x37, 0x88, 0x6c, 0x6c, 0x83, 0x37, 0xc2, 0x50, 0x90, 0x1b, 0x66, 0x32, 0x37, 0x9b, 0x88, + 0x93, 0xc9, 0x70, 0x18, 0x45, 0x9e, 0x01, 0xbd, 0x5c, 0x31, 0xcc, 0x12, 0x86, 0xad, 0xac, 0x82, + 0x41, 0x44, 0x07, 0x93, 0x72, 0x52, 0xe9, 0xd5, 0x74, 0x55, 0x35, 0xed, 0xc8, 0xe5, 0x13, 0x31, + 0xe4, 0x74, 0xa7, 0xc0, 0x43, 0x11, 0x79, 0xa2, 0x00, 0x8a, 0x98, 0x33, 0x7e, 0x40, 0xbf, 0xad, + 0x82, 0xb8, 0x16, 0xf4, 0xea, 0x6a, 0x08, 0xcb, 0x5b, 0x0a, 0xf5, 0x51, 0xde, 0x42, 0x43, 0xc2, + 0xff, 0xd1, 0xd2, 0x29, 0xfb, 0x6b, 0xb8, 0xe0, 0x6a, 0xad, 0x01, 0x6d, 0xb1, 0x5a, 0x90, 0x2b, + 0xa7, 0x2b, 0xfd, 0xdd, 0x98, 0x3d, 0x9c, 0x7c, 0x4e, 0xd7, 0xe6, 0x53, 0x7a, 0xad, 0x53, 0x40, + 0x2d, 0x9f, 0x5f, 0xd7, 0xe6, 0x73, 0xeb, 0xb5, 0xce, 0x19, 0xb5, 0x7c, 0xfe, 0xd1, 0xcc, 0xc7, + 0x9f, 0x73, 0x8f, 0x8f, 0xdb, 0x46, 0xc6, 0xa2, 0x96, 0x1e, 0x07, 0xb3, 0xd3, 0x4b, 0x6b, 0xeb, + 0x42, 0x58, 0x25, 0x6d, 0xab, 0x82, 0x3c, 0xb8, 0xaa, 0xaf, 0x09, 0x7d, 0xd3, 0x59, 0xce, 0x71, + 0xd8, 0x1a, 0xcf, 0x32, 0x82, 0xbc, 0x38, 0x67, 0xb7, 0x9e, 0xac, 0x75, 0xc7, 0x51, 0xef, 0x9b, + 0x45, 0x22, 0xc2, 0x7a, 0xd8, 0x25, 0x92, 0x1d, 0xd4, 0xc2, 0x46, 0x49, 0xa9, 0xd0, 0xe5, 0xe5, + 0xab, 0x5a, 0x11, 0x7f, 0x73, 0x3d, 0x72, 0xf5, 0x66, 0x20, 0x6c, 0xdf, 0xf9, 0x54, 0x8d, 0x31, + 0x22, 0x54, 0x99, 0xf9, 0x2b, 0x32, 0x82, 0xdb, 0xf9, 0x44, 0xcd, 0x36, 0x26, 0x47, 0xc6, 0x7c, + 0xf9, 0x0e, 0x8b, 0x4e, 0x5b, 0xc3, 0x2c, 0x4c, 0x61, 0xd5, 0xa9, 0x7d, 0x13, 0x96, 0x9b, 0x08, + 0xf7, 0xa4, 0x02, 0xa1, 0xaa, 0xa0, 0x85, 0x7e, 0x4c, 0x61, 0xd6, 0xf4, 0x11, 0x46, 0x7c, 0x6d, + 0x97, 0x21, 0xa2, 0xee, 0xde, 0x16, 0x26, 0x35, 0x54, 0x88, 0x7e, 0xde, 0x6a, 0x7f, 0xd3, 0x5b, + 0xa9, 0x40, 0x9d, 0x3a, 0xd6, 0x77, 0x0a, 0x43, 0x68, 0x85, 0x52, 0xd1, 0xf6, 0x4d, 0xf7, 0x8a, + 0x38, 0x0f, 0x87, 0xd0, 0x08, 0x99, 0x09, 0xba, 0xa4, 0xa0, 0x51, 0x92, 0x9a, 0xa0, 0x11, 0x05, + 0xdd, 0xc1, 0xe2, 0x56, 0xab, 0x30, 0xfa, 0x88, 0x3a, 0xd6, 0x87, 0x8f, 0xc4, 0x9f, 0x3e, 0x9d, + 0x85, 0xf4, 0xdf, 0xd9, 0x62, 0x21, 0x8f, 0xbd, 0x91, 0x32, 0x80, 0x62, 0x27, 0x9f, 0xb8, 0x72, + 0xf2, 0xb3, 0xfa, 0xb1, 0xb6, 0x63, 0x6f, 0x1d, 0xa6, 0xe8, 0x01, 0xde, 0x7e, 0x24, 0x30, 0x1e, + 0x57, 0xb6, 0x5d, 0xda, 0xd1, 0x07, 0x8a, 0xeb, 0xea, 0xc3, 0x65, 0xfa, 0xa7, 0x4e, 0x44, 0x18, + 0x15, 0xf4, 0x5c, 0x53, 0xde, 0x8b, 0xc5, 0x53, 0xd4, 0x0f, 0x3f, 0x24, 0xbd, 0xf3, 0xa5, 0xa4, + 0xec, 0x62, 0x0f, 0x2f, 0x26, 0xf8, 0x36, 0x2c, 0xa7, 0x17, 0xc1, 0xce, 0x32, 0xf1, 0x71, 0x2e, + 0x95, 0x1c, 0x3c, 0xaf, 0x7f, 0xfe, 0xe9, 0x04, 0xc5, 0x2d, 0x02, 0x36, 0x89, 0xbc, 0xc5, 0xf9, + 0x86, 0xa7, 0x7b, 0x11, 0xd2, 0x95, 0x98, 0xa7, 0xf7, 0xd7, 0xd8, 0x87, 0xda, 0xe9, 0xb5, 0x87, + 0x63, 0xa6, 0xd6, 0xc6, 0x88, 0x12, 0x8c, 0x74, 0xd0, 0x60, 0x45, 0xc5, 0x9b, 0x30, 0x41, 0xdc, + 0xc6, 0xdf, 0x4a, 0x6f, 0x16, 0xbc, 0x23, 0xbe, 0x1a, 0xdb, 0xdb, 0x61, 0x64, 0x82, 0xf9, 0x6f, + 0x9c, 0x40, 0xa5, 0x9c, 0xf8, 0xcc, 0x64, 0x42, 0x48, 0x21, 0x74, 0x39, 0xad, 0xae, 0x6e, 0x46, + 0x68, 0xea, 0x7f, 0xfa, 0x72, 0x5a, 0x8c, 0xf3, 0x3c, 0xff, 0x3c, 0x15, 0x4f, 0x91, 0x75, 0x0a, + 0xaa, 0xe5, 0xf3, 0x14, 0x2d, 0x0e, 0x16, 0xda, 0x36, 0x69, 0xba, 0x0a, 0x9a, 0xcd, 0xf7, 0xaf, + 0xc6, 0x3b, 0x49, 0xf7, 0x45, 0x70, 0xb4, 0x8f, 0xa0, 0xad, 0x3e, 0x7e, 0x36, 0x08, 0xaf, 0xc6, + 0x47, 0x3d, 0xf5, 0xb8, 0x1f, 0xe1, 0x6a, 0xf8, 0xec, 0x59, 0x92, 0x5c, 0x8d, 0x29, 0x64, 0x27, + 0xd9, 0xc7, 0x90, 0xe8, 0x85, 0x15, 0x02, 0x19, 0xa8, 0x0d, 0x20, 0x02, 0x82, 0x05, 0x8e, 0x6a, + 0x75, 0x7e, 0x55, 0xa2, 0x93, 0xe9, 0xd5, 0x78, 0x11, 0x76, 0x10, 0x7e, 0x2d, 0xec, 0x1c, 0x44, + 0xdf, 0x21, 0xef, 0x6e, 0xf8, 0x43, 0x57, 0xc2, 0x7e, 0xc3, 0xa6, 0xb1, 0x70, 0x20, 0x80, 0x21, + 0xe0, 0x17, 0xb2, 0xce, 0xb2, 0xf1, 0x18, 0xdf, 0x3b, 0x73, 0x24, 0x69, 0x7b, 0xa0, 0x89, 0x79, + 0x81, 0xa4, 0x81, 0xea, 0x88, 0xe5, 0xea, 0x9c, 0xed, 0x7f, 0x88, 0x20, 0xb1, 0x93, 0x69, 0x71, + 0xdd, 0xf9, 0x45, 0x8c, 0xf2, 0x5c, 0x6a, 0xd6, 0x3e, 0x7f, 0x1f, 0x36, 0xf2, 0x0d, 0x2e, 0xa6, + 0x42, 0x4c, 0x12, 0x0d, 0x1c, 0xa8, 0x0c, 0x38, 0x4a, 0xe4, 0x53, 0x17, 0xb6, 0x18, 0xf6, 0x20, + 0xa5, 0x3b, 0x95, 0x17, 0x25, 0xcb, 0xa8, 0xca, 0x70, 0x1a, 0xfc, 0x49, 0x69, 0xf9, 0xcb, 0x46, + 0xd8, 0x53, 0xe2, 0x1b, 0x54, 0x32, 0x84, 0x4b, 0xb2, 0x9b, 0xd4, 0xb3, 0xa3, 0x3a, 0xd5, 0x27, + 0xdd, 0x5e, 0xdf, 0xf6, 0x69, 0x9b, 0xb3, 0x0b, 0x45, 0xc4, 0x67, 0xd9, 0x9a, 0x62, 0x5c, 0xba, + 0x4a, 0x9d, 0x2d, 0x18, 0x20, 0x92, 0xcf, 0xde, 0x18, 0x2c, 0x23, 0x9f, 0x25, 0xd7, 0x77, 0xa1, + 0x15, 0x00, 0xe9, 0xff, 0x45, 0x56, 0x0e, 0x2b, 0xca, 0xc7, 0xe4, 0xfa, 0xca, 0x06, 0xda, 0xef, + 0x3a, 0xae, 0x5f, 0xb6, 0x7f, 0x9c, 0x3c, 0x43, 0x5f, 0xee, 0x8d, 0x47, 0x46, 0x19, 0x89, 0x56, + 0xfb, 0x1a, 0xe6, 0x69, 0x1b, 0x1c, 0x7a, 0x66, 0x85, 0xbb, 0xcc, 0x0e, 0xc4, 0x00, 0x76, 0x5a, + 0xe5, 0x05, 0x6c, 0x76, 0x71, 0xdc, 0x1e, 0x57, 0xe2, 0xda, 0xf7, 0xee, 0x52, 0x62, 0xa1, 0xb8, + 0xf7, 0xa4, 0x5b, 0x19, 0xee, 0xf5, 0xd8, 0x18, 0x62, 0x19, 0x44, 0x2b, 0x26, 0x4a, 0x41, 0x9f, + 0xb4, 0x5b, 0xba, 0x96, 0x81, 0x7f, 0x0c, 0x75, 0x9a, 0xf9, 0x22, 0xbc, 0x99, 0x71, 0xe3, 0x20, + 0x39, 0xa5, 0x6f, 0xef, 0x0b, 0x17, 0x4e, 0xbc, 0xf9, 0x22, 0xbc, 0xd4, 0x47, 0x6f, 0x5c, 0x88, + 0x28, 0x94, 0xe8, 0xb8, 0x96, 0x98, 0x65, 0x43, 0xcc, 0xb0, 0x06, 0xc2, 0x3c, 0x9f, 0xc5, 0x76, + 0xc6, 0xe1, 0xad, 0x0d, 0x20, 0x0b, 0x0f, 0x8b, 0xa6, 0xa6, 0x1d, 0x0a, 0x87, 0x78, 0x4a, 0x84, + 0x3f, 0xfc, 0xe0, 0x1c, 0x7b, 0xd5, 0x05, 0xa3, 0x99, 0xb9, 0x0d, 0x80, 0x19, 0x3e, 0x7c, 0x3f, + 0x98, 0x91, 0xee, 0xb0, 0x23, 0x02, 0x77, 0xc6, 0xfd, 0x0a, 0x38, 0x66, 0x1b, 0x87, 0x79, 0x43, + 0x70, 0xe5, 0x12, 0x8d, 0x76, 0x76, 0xfd, 0xba, 0xa7, 0x2b, 0x50, 0xdb, 0x4e, 0x25, 0x41, 0x2b, + 0x41, 0x1e, 0xe8, 0x69, 0x33, 0xa8, 0x9b, 0xfe, 0x1a, 0x85, 0xdf, 0xe9, 0x42, 0xf1, 0x17, 0xe1, + 0x41, 0x04, 0x73, 0xb5, 0xbf, 0x0e, 0x3d, 0xd8, 0x5a, 0x10, 0x11, 0xd9, 0x9a, 0x00, 0x85, 0x74, + 0xd7, 0x14, 0xcb, 0x75, 0x31, 0x0d, 0x43, 0x81, 0x36, 0x7f, 0xb9, 0xc9, 0xa9, 0x3b, 0xf6, 0x6a, + 0x20, 0x0a, 0x89, 0x67, 0xba, 0x9a, 0x87, 0x73, 0x9a, 0x78, 0x33, 0x72, 0xd1, 0x84, 0x95, 0xad, + 0x02, 0x95, 0xa2, 0xdb, 0x9f, 0x1e, 0x6a, 0x84, 0x9d, 0xa9, 0xe2, 0x47, 0xc8, 0x92, 0xf2, 0xd3, + 0xf4, 0x2c, 0x4c, 0x37, 0xe4, 0xad, 0x04, 0xf5, 0xff, 0x9f, 0xb3, 0x99, 0x28, 0x5e, 0x0d, 0x11, + 0x0e, 0xbd, 0x9f, 0xd5, 0xa4, 0x4f, 0xb5, 0x29, 0x5d, 0x16, 0xc1, 0x8d, 0x8f, 0xb4, 0x8f, 0xca, + 0xfd, 0x18, 0x5a, 0xd9, 0x62, 0x2a, 0x4c, 0xc5, 0x30, 0x63, 0xe0, 0xf3, 0x36, 0x3c, 0x10, 0x39, + 0x7e, 0x05, 0xed, 0x0b, 0xa6, 0xf9, 0x4d, 0xe9, 0x56, 0xa1, 0x52, 0x79, 0x90, 0x2c, 0xc2, 0x76, + 0x38, 0xc6, 0x4c, 0xb0, 0xa9, 0xde, 0xe0, 0xe6, 0x81, 0x16, 0x77, 0xfe, 0xe5, 0xd1, 0x89, 0x33, + 0xaa, 0x2c, 0xde, 0xe4, 0x9e, 0x7c, 0xdf, 0xd1, 0xe4, 0x88, 0xb4, 0x80, 0x99, 0x6a, 0x9c, 0x3d, + 0x44, 0x53, 0x61, 0x42, 0x8d, 0x69, 0x5a, 0x11, 0x01, 0x46, 0x4b, 0x75, 0xf3, 0xc9, 0x35, 0x5d, + 0x01, 0x51, 0x17, 0x38, 0xcf, 0x9a, 0x74, 0x52, 0xea, 0xe0, 0xba, 0xeb, 0xec, 0xf7, 0x29, 0xdb, + 0xb7, 0xf7, 0x8a, 0xab, 0x46, 0xd8, 0xc4, 0x81, 0x95, 0x22, 0xa7, 0x55, 0x8e, 0xf9, 0x4a, 0x63, + 0x8c, 0xce, 0x74, 0x59, 0x75, 0xc6, 0x03, 0x43, 0x94, 0x3a, 0x49, 0xab, 0xf8, 0xd1, 0x75, 0xf0, + 0xb5, 0xa5, 0xac, 0xa0, 0x94, 0x95, 0x76, 0xc7, 0x30, 0xa5, 0x75, 0x46, 0x03, 0xe2, 0xb2, 0x0e, + 0xab, 0x97, 0x85, 0xd9, 0x87, 0x87, 0x48, 0xa6, 0x69, 0x20, 0x9f, 0xb0, 0x0e, 0xdc, 0xeb, 0xea, + 0x02, 0xef, 0xe7, 0x04, 0xaa, 0x97, 0xd2, 0x53, 0x9f, 0x35, 0xe4, 0x0c, 0x8f, 0xfe, 0xb2, 0x84, + 0xb6, 0xd7, 0x14, 0x9e, 0x7c, 0xca, 0xce, 0x90, 0xf6, 0xc5, 0xaf, 0x38, 0x9e, 0xcc, 0x34, 0x38, + 0x2c, 0x03, 0x8d, 0xc9, 0x05, 0x9b, 0xa8, 0xf4, 0xb0, 0xdc, 0xad, 0xfa, 0x29, 0x74, 0x7d, 0x8e, + 0x45, 0x8b, 0x8d, 0xe0, 0x6b, 0x40, 0xbb, 0x5d, 0xe6, 0xc2, 0x6a, 0x08, 0x61, 0x81, 0xe6, 0x07, + 0xf3, 0xcc, 0x41, 0xd1, 0x77, 0xc5, 0xa9, 0x0a, 0x94, 0xc6, 0x82, 0xcc, 0xb7, 0x85, 0xb2, 0x10, + 0x87, 0x5c, 0xd9, 0xea, 0x72, 0x59, 0x11, 0xa5, 0x78, 0xf6, 0x6d, 0x1f, 0x94, 0xd2, 0x54, 0xaa, + 0x76, 0x36, 0xb5, 0x34, 0x1b, 0x76, 0xd8, 0x97, 0x3e, 0xd4, 0x96, 0xb7, 0x7e, 0x2e, 0xf5, 0x3d, + 0xa9, 0x2b, 0xce, 0xa4, 0xf3, 0x76, 0x8d, 0xe0, 0x19, 0x6a, 0xd4, 0x31, 0x36, 0x97, 0xad, 0xc6, + 0x66, 0x8b, 0x32, 0x58, 0x40, 0xc3, 0x57, 0x2e, 0x0b, 0xb3, 0x8a, 0xa5, 0x6f, 0xc9, 0xf0, 0xba, + 0xda, 0x72, 0x47, 0xc0, 0xc4, 0x08, 0xc5, 0x11, 0x35, 0xa7, 0x6a, 0x6c, 0x14, 0xb9, 0x9e, 0xca, + 0xf8, 0x23, 0x99, 0x74, 0x88, 0x2c, 0xac, 0x8f, 0xc4, 0xb3, 0x60, 0xa0, 0xee, 0xe8, 0x65, 0x67, + 0xc9, 0x4c, 0xfe, 0xd0, 0xa7, 0x19, 0xa1, 0xe9, 0x83, 0x3a, 0x16, 0x81, 0x69, 0x43, 0x13, 0xea, + 0x00, 0x89, 0xb7, 0x13, 0x18, 0x70, 0x1f, 0x1d, 0x96, 0x18, 0x5c, 0xac, 0x8c, 0xb0, 0x6e, 0xec, + 0x18, 0x44, 0x7b, 0x50, 0xcf, 0x0b, 0x01, 0xbf, 0x9d, 0x8c, 0x10, 0xa9, 0x48, 0xdd, 0xff, 0x91, + 0x5e, 0xee, 0xd6, 0x9e, 0x8c, 0xaf, 0x0f, 0x65, 0x44, 0x65, 0x67, 0x5d, 0x18, 0xca, 0x14, 0x05, + 0xd0, 0xd2, 0x14, 0xc6, 0x0b, 0x78, 0x36, 0xfd, 0x15, 0xf6, 0xf5, 0x10, 0xa0, 0x0e, 0x55, 0x32, + 0xfb, 0x9c, 0x37, 0x49, 0xd1, 0xc6, 0x9f, 0x36, 0x8f, 0x3b, 0x25, 0xb6, 0x37, 0x27, 0xa8, 0x1d, + 0x8e, 0x33, 0xa1, 0x03, 0xdd, 0x54, 0xc9, 0x96, 0x1e, 0xa9, 0x34, 0xee, 0x56, 0x31, 0x46, 0x89, + 0xc5, 0xcf, 0xbe, 0xa4, 0x04, 0x9e, 0xb7, 0xd9, 0x29, 0xcd, 0xa2, 0xfa, 0x13, 0xd7, 0xa3, 0x2c, + 0x63, 0x7c, 0x46, 0x33, 0xa2, 0x31, 0xc6, 0x37, 0x7c, 0x7f, 0x46, 0xe9, 0x4d, 0xe1, 0xb7, 0xb2, + 0xd8, 0x35, 0xdf, 0xd8, 0xee, 0x38, 0xfc, 0x76, 0xc1, 0x78, 0x15, 0xff, 0xfb, 0xaa, 0xc9, 0x88, + 0xa4, 0xfa, 0x2d, 0xf2, 0x12, 0x87, 0xef, 0x93, 0x67, 0x34, 0x0a, 0xa7, 0x24, 0x09, 0x6c, 0x90, + 0xef, 0x23, 0x49, 0x16, 0x42, 0x85, 0x3b, 0xa5, 0xf3, 0x46, 0x59, 0xeb, 0x7d, 0x4b, 0x7a, 0x26, + 0xdb, 0x98, 0x6b, 0x95, 0x41, 0x82, 0x66, 0x7c, 0xc8, 0x6f, 0xa0, 0x95, 0xca, 0x41, 0x3d, 0x00, + 0x59, 0x62, 0x84, 0x65, 0x64, 0x1d, 0x96, 0xc7, 0x45, 0x4e, 0xb0, 0x74, 0x98, 0x8b, 0x9a, 0x12, + 0x99, 0xec, 0x52, 0xd8, 0x14, 0x97, 0xb4, 0x49, 0xd1, 0xec, 0x8f, 0xe5, 0x6f, 0xa0, 0x4e, 0xfa, + 0x1e, 0xa4, 0xd5, 0x27, 0xe2, 0xb0, 0xff, 0x57, 0x5c, 0xa2, 0xf6, 0x6e, 0x7c, 0xfc, 0x59, 0xad, + 0x63, 0xb3, 0x31, 0x92, 0x05, 0xa8, 0x19, 0x09, 0x66, 0xaf, 0x61, 0x71, 0x29, 0xac, 0x61, 0x4c, + 0xd3, 0xbe, 0x0c, 0x74, 0x57, 0x5c, 0x1d, 0xa5, 0x6f, 0x8f, 0xf5, 0x2c, 0xa7, 0x3a, 0xf7, 0x82, + 0x87, 0x07, 0xbb, 0x18, 0x55, 0xed, 0xb9, 0x84, 0x67, 0x1f, 0x2a, 0x53, 0x55, 0x15, 0xe4, 0x86, + 0x16, 0xec, 0x7f, 0xe9, 0x8a, 0xbd, 0x7c, 0xf3, 0xe3, 0xab, 0xf7, 0xbe, 0x57, 0x0d, 0x47, 0x63, + 0xde, 0x8c, 0x82, 0x7e, 0xce, 0xad, 0x70, 0x26, 0x7b, 0xd6, 0x87, 0x7c, 0x16, 0xfe, 0xef, 0xab, + 0xb6, 0xab, 0x4c, 0xb2, 0x7b, 0x6d, 0xf9, 0xaa, 0x6d, 0xa2, 0xc0, 0x41, 0xd5, 0xa3, 0xbe, 0xcf, + 0xe5, 0xe7, 0x18, 0x4f, 0x9e, 0xd4, 0xea, 0xa1, 0x29, 0x56, 0x52, 0xed, 0xde, 0x23, 0x3f, 0x29, + 0x1d, 0x35, 0xd1, 0x16, 0xb5, 0x44, 0x16, 0xb9, 0x1d, 0x3f, 0xfb, 0xbe, 0x7c, 0x7a, 0xf7, 0x1b, + 0xec, 0x6a, 0xf3, 0xb7, 0xd3, 0x7b, 0x71, 0x81, 0x80, 0x98, 0xd1, 0x16, 0xce, 0xb1, 0x3e, 0x8b, + 0x7b, 0x14, 0x11, 0xca, 0x56, 0xa0, 0x03, 0x0e, 0x89, 0x0e, 0x19, 0x03, 0xd2, 0xa3, 0xbd, 0x6e, + 0x0f, 0xb6, 0x2d, 0x9b, 0x14, 0x15, 0x54, 0x18, 0xae, 0x19, 0xc8, 0x07, 0x4a, 0xcd, 0xbb, 0x2e, + 0x72, 0x85, 0xca, 0x61, 0xbb, 0x56, 0x7d, 0xf1, 0xbd, 0xdd, 0xdd, 0xa9, 0x17, 0x72, 0xba, 0x5d, + 0xc4, 0x0e, 0x4f, 0x93, 0xee, 0x6e, 0xaa, 0xec, 0x6c, 0x43, 0xdc, 0x78, 0x7d, 0x2e, 0xa5, 0x08, + 0xb0, 0x8b, 0x58, 0x96, 0xc7, 0xc4, 0x0b, 0xd3, 0x60, 0xd3, 0x7a, 0xed, 0x42, 0x46, 0x72, 0x44, + 0xd8, 0x8e, 0x5c, 0x84, 0x9b, 0x3f, 0xbf, 0xfb, 0x2d, 0x91, 0x3a, 0x39, 0x4d, 0x1f, 0x04, 0xaa, + 0xdd, 0xb7, 0x34, 0x6c, 0xba, 0xaa, 0x28, 0x2b, 0x98, 0x11, 0x93, 0xfb, 0xe5, 0xab, 0x3c, 0x45, + 0x21, 0xf0, 0x2b, 0x92, 0x72, 0x2c, 0x54, 0x61, 0x23, 0x65, 0x63, 0xa6, 0xf9, 0xc5, 0x49, 0x27, + 0x63, 0x4a, 0xd6, 0x03, 0x28, 0xfd, 0x33, 0x93, 0x0c, 0x49, 0x9b, 0x55, 0x56, 0x92, 0x1f, 0x84, + 0x5a, 0x1a, 0x66, 0x08, 0x54, 0xba, 0xee, 0x92, 0xbb, 0xdf, 0x1a, 0x04, 0x22, 0x1c, 0x81, 0x81, + 0x78, 0x09, 0x36, 0x8e, 0x02, 0x5e, 0x26, 0xb2, 0x93, 0xbc, 0x0c, 0xdb, 0x35, 0xbe, 0xd9, 0xf8, + 0xda, 0x0b, 0x65, 0x14, 0xf4, 0x60, 0xa0, 0x5f, 0x90, 0x3f, 0x2e, 0x95, 0xbd, 0x67, 0x4f, 0x9e, + 0xa8, 0xd4, 0x64, 0x28, 0x54, 0xe6, 0x48, 0xbc, 0xa1, 0x0a, 0xd5, 0xc5, 0xef, 0x9e, 0x3c, 0x81, + 0xd8, 0x10, 0x79, 0x1f, 0x7f, 0x1c, 0x76, 0x7b, 0x07, 0xd1, 0xa0, 0xb7, 0x1f, 0xc5, 0xbd, 0xe7, + 0xb0, 0xc2, 0x6c, 0xa1, 0x24, 0x18, 0x4a, 0x79, 0x91, 0xec, 0xf4, 0xfb, 0xe1, 0xc1, 0x7c, 0x81, + 0x42, 0xf9, 0x01, 0xc7, 0x17, 0xda, 0x9a, 0xa0, 0xe5, 0xf9, 0x86, 0x6b, 0xbd, 0x27, 0xa8, 0x75, + 0x8d, 0xaf, 0x39, 0x9e, 0x80, 0xec, 0x36, 0x90, 0x3e, 0xcb, 0x63, 0xe3, 0xe8, 0xdb, 0x95, 0xcf, + 0x3d, 0x87, 0xf9, 0xe5, 0x12, 0x25, 0xba, 0xb9, 0x09, 0x02, 0x15, 0x01, 0xf1, 0x06, 0x5e, 0x84, + 0xba, 0xe3, 0x4d, 0x95, 0x7b, 0x8f, 0x68, 0x44, 0xdd, 0x2f, 0xf9, 0x22, 0xb7, 0x92, 0x03, 0xad, + 0x4f, 0x90, 0xdb, 0x33, 0xfc, 0x63, 0x23, 0x1d, 0x08, 0x98, 0x98, 0x5e, 0x0b, 0x81, 0xee, 0xd9, + 0x7b, 0x7b, 0x7b, 0xec, 0xd4, 0xb1, 0xa5, 0xe8, 0x8c, 0xb4, 0xc9, 0x46, 0xd3, 0xa1, 0xc3, 0xaa, + 0x74, 0x35, 0x9d, 0x80, 0x56, 0xc8, 0x97, 0x4c, 0x40, 0xe7, 0x24, 0xc7, 0x3c, 0xfe, 0x55, 0x06, + 0x81, 0x0d, 0x65, 0x34, 0x85, 0x9e, 0x1c, 0xc8, 0x37, 0x08, 0x1f, 0x30, 0xa0, 0x99, 0xf6, 0xe1, + 0xc1, 0x55, 0x54, 0x45, 0x38, 0x87, 0x50, 0x72, 0x98, 0x08, 0x2d, 0x69, 0x20, 0x2c, 0xa4, 0x54, + 0x41, 0xdc, 0x1a, 0x9f, 0x50, 0x0c, 0xb4, 0x05, 0xac, 0x51, 0x8c, 0x05, 0x8f, 0xa1, 0xa5, 0xa3, + 0x34, 0xf3, 0x42, 0xe8, 0xe3, 0x72, 0x78, 0xc1, 0xca, 0x4b, 0x3a, 0x02, 0x0e, 0x56, 0x91, 0xe1, + 0x31, 0x16, 0x77, 0x2a, 0x8f, 0x18, 0x2c, 0xa8, 0xed, 0x21, 0x9f, 0x66, 0xac, 0xeb, 0x1c, 0x9d, + 0xfd, 0xf2, 0x3b, 0xc8, 0x0c, 0x07, 0x74, 0x88, 0x1b, 0x87, 0xd6, 0x88, 0x15, 0xae, 0x52, 0x4c, + 0x18, 0xb0, 0x26, 0x26, 0x65, 0x09, 0x9a, 0xb9, 0x17, 0xe2, 0x1c, 0xbb, 0x26, 0xde, 0xcd, 0x6c, + 0x5d, 0x34, 0xfa, 0x30, 0x6c, 0xc2, 0x4c, 0xbc, 0xff, 0x3a, 0x7c, 0x0a, 0xf3, 0xe0, 0x74, 0x56, + 0x1d, 0x75, 0x0e, 0x9f, 0x22, 0x09, 0x14, 0xfe, 0xbd, 0xaa, 0xae, 0xd3, 0xa3, 0xce, 0xff, 0x01, + 0x95, 0x74, 0x9f, 0xea, 0xf4, 0x8d, 0x01, 0x00 }; From 1900686bc43b2275ca9432e7c73548934ea47c55 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 22 Dec 2023 15:49:51 +0100 Subject: [PATCH 017/694] Bump --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- wled00/improv.cpp | 2 +- wled00/wled.h | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d050caef8..7e6fc3c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## WLED changelog +#### Build 2312220 +- Version bump: 0.14.1-b2 +- Fix for Pixel Magic button + +#### Build 2311280 +- Bugfixes (#3593, #3490, #3573, #3517, #3561, #3555, #3541, #3536, #3515, #3522, #3533, #3508) + #### Build 2311160 - Version bump: 0.14.1-b1 - Bugfixes (#3526, #3502, #3496, #3484, #3487, #3445, #3466, #3296, #3382, #3312) diff --git a/package-lock.json b/package-lock.json index a421b467f..71f73fefe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b1", + "version": "0.14.1-b2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 169812124..c4faddc7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b1", + "version": "0.14.1-b2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/improv.cpp b/wled00/improv.cpp index bc260e073..0f4081f2d 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.14.1-b1/%i"), VERSION); + sprintf_P(vString, PSTR("0.14.1-b2/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index 0f8052e89..0cd91585f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2311280 +#define VERSION 2312220 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 5ab012163e5878e5bd67719762fe7e72d8030435 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 22 Dec 2023 23:28:58 +0100 Subject: [PATCH 018/694] workaround for #3601 if case of invalid or impossible sunset/sunrise results, retry with the previous day. max 3 days back, to prevent infinite loops and far-away results. --- wled00/ntp.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 3d15c8c25..4e90766cc 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -448,7 +448,7 @@ void checkTimers() #define ZENITH -0.83 // get sunrise (or sunset) time (in minutes) for a given day at a given geo location -int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunset=false) { +static int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunset=false) { //1. first calculate the day of the year float N1 = 275 * month / 9; float N2 = (month + 9) / 12; @@ -509,7 +509,18 @@ void calculateSunriseAndSunset() { tim_0.tm_sec = 0; tim_0.tm_isdst = 0; - int minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude); + // Due to math instability, its possible to get a bad sunrise/sunset = 00:00 (see issue #3601) + // So in case we get 00:00, try to use the sunset/sunrise of previous day. Max 3 days back, this worked well in all cases I tried. + // When latitude = 66,6 (N or S), the functions sometimes returns 2147483647, so "unexpected large" is another condition for retry + int minUTC = 0; + int retryCount = 0; + do { + time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds + minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, false); + DEBUG_PRINT(F("* sunrise (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + retryCount ++; + } while ((minUTC == 0 || abs(minUTC) > 2*24*60) && (retryCount <= 3)); + if (minUTC) { // there is a sunrise if (minUTC < 0) minUTC += 24*60; // add a day if negative @@ -521,7 +532,14 @@ void calculateSunriseAndSunset() { sunrise = 0; } - minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude, true); + retryCount = 0; + do { + time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds + minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, true); + DEBUG_PRINT(F("* sunset (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + retryCount ++; + } while ((minUTC == 0 || abs(minUTC) > 2*24*60) && (retryCount <= 3)); + if (minUTC) { // there is a sunset if (minUTC < 0) minUTC += 24*60; // add a day if negative From 72e864b013170825c3a1aadd7d5b90dc01ebb477 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 23 Dec 2023 13:13:10 +0100 Subject: [PATCH 019/694] sunrise/sunset: fix for ambiguous error value sunset = 0 is a valid result, as the function result is in UTC and not local time . Example: local time is UTC-8, local sunrise = 08:00am => getSunriseUTC() = 0. So we cannot use "0" for "invalid". Using UINT16_MAX resolves the problem, and even allows to simplify calculateSunriseAndSunset() a bit. --- wled00/ntp.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 4e90766cc..2e95eae23 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -447,7 +447,7 @@ void checkTimers() } #define ZENITH -0.83 -// get sunrise (or sunset) time (in minutes) for a given day at a given geo location +// get sunrise (or sunset) time (in minutes) for a given day at a given geo location. Returns >= INT16_MAX in case of "no sunset" static int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunset=false) { //1. first calculate the day of the year float N1 = 275 * month / 9; @@ -482,8 +482,8 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo //7a. calculate the Sun's local hour angle float cosH = (sinf(DEG_TO_RAD*ZENITH) - (sinDec * sinf(DEG_TO_RAD*lat))) / (cosDec * cosf(DEG_TO_RAD*lat)); - if ((cosH > 1.0f) && !sunset) return 0; // the sun never rises on this location (on the specified date) - if ((cosH < -1.0f) && sunset) return 0; // the sun never sets on this location (on the specified date) + if ((cosH > 1.0f) && !sunset) return INT16_MAX; // the sun never rises on this location (on the specified date) + if ((cosH < -1.0f) && sunset) return INT16_MAX; // the sun never sets on this location (on the specified date) //7b. finish calculating H and convert into hours float H = sunset ? RAD_TO_DEG*acosf(cosH) : 360 - RAD_TO_DEG*acosf(cosH); @@ -499,6 +499,7 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo return UT*60; } +#define SUNSET_MAX (24*60) // 1day = max expected absolute value for sun offset in minutes // calculate sunrise and sunset (if longitude and latitude are set) void calculateSunriseAndSunset() { if ((int)(longitude*10.) || (int)(latitude*10.)) { @@ -509,9 +510,9 @@ void calculateSunriseAndSunset() { tim_0.tm_sec = 0; tim_0.tm_isdst = 0; - // Due to math instability, its possible to get a bad sunrise/sunset = 00:00 (see issue #3601) - // So in case we get 00:00, try to use the sunset/sunrise of previous day. Max 3 days back, this worked well in all cases I tried. - // When latitude = 66,6 (N or S), the functions sometimes returns 2147483647, so "unexpected large" is another condition for retry + // Due to limited accuracy, its possible to get a bad sunrise/sunset displayed as "00:00" (see issue #3601) + // So in case of invalid result, we try to use the sunset/sunrise of previous day. Max 3 days back, this worked well in all cases I tried. + // When latitude = 66,6 (N or S), the functions sometimes returns 2147483647, so this "unexpected large" is another condition for retry int minUTC = 0; int retryCount = 0; do { @@ -519,9 +520,9 @@ void calculateSunriseAndSunset() { minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, false); DEBUG_PRINT(F("* sunrise (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); retryCount ++; - } while ((minUTC == 0 || abs(minUTC) > 2*24*60) && (retryCount <= 3)); + } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); - if (minUTC) { + if (abs(minUTC) <= SUNSET_MAX) { // there is a sunrise if (minUTC < 0) minUTC += 24*60; // add a day if negative tim_0.tm_hour = minUTC / 60; @@ -538,9 +539,9 @@ void calculateSunriseAndSunset() { minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, true); DEBUG_PRINT(F("* sunset (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); retryCount ++; - } while ((minUTC == 0 || abs(minUTC) > 2*24*60) && (retryCount <= 3)); + } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); - if (minUTC) { + if (abs(minUTC) <= SUNSET_MAX) { // there is a sunset if (minUTC < 0) minUTC += 24*60; // add a day if negative tim_0.tm_hour = minUTC / 60; From 5f62b4a89dac944417b268a8b9fd6a90b1efb7b9 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 23 Dec 2023 20:58:55 +0100 Subject: [PATCH 020/694] Fix for #2922 --- wled00/cfg.cpp | 4 +- wled00/data/settings_wifi.htm | 1 + wled00/html_settings.h | 324 +++++++++++++++++----------------- wled00/set.cpp | 1 + wled00/wled.cpp | 2 +- wled00/wled.h | 1 + wled00/xml.cpp | 1 + 7 files changed, 170 insertions(+), 164 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index becc601bb..ad02ef198 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -74,7 +74,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted noWifiSleep = !noWifiSleep; - //int wifi_phy = doc[F("wifi")][F("phy")]; //force phy mode n? + force802_3g = doc[F("wifi")][F("phy")] | force802_3g; //force phy mode g? JsonObject hw = doc[F("hw")]; @@ -682,7 +682,7 @@ void serializeConfig() { JsonObject wifi = doc.createNestedObject("wifi"); wifi[F("sleep")] = !noWifiSleep; - //wifi[F("phy")] = 1; + wifi[F("phy")] = (int)force802_3g; #ifdef WLED_USE_ETHERNET JsonObject ethernet = doc.createNestedObject("eth"); diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index b8ab6aff7..bfc530352 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -189,6 +189,7 @@
AP IP: Not active

Experimental

+ Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
Can help with connectivity issues.
Do not enable if WiFi is working correctly, increases power consumption.
diff --git a/wled00/html_settings.h b/wled00/html_settings.h index e9a8d9340..50678ca3e 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -8,7 +8,7 @@ // Autogenerated from wled00/data/style.css, do not edit!! const uint16_t PAGE_settingsCss_length = 888; const uint8_t PAGE_settingsCss[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x56, 0x51, 0x8b, 0xab, 0x38, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xad, 0x56, 0x51, 0x8b, 0xab, 0x38, 0x14, 0xfe, 0x2b, 0x2e, 0x65, 0x60, 0x2e, 0x54, 0x51, 0xab, 0x9d, 0xde, 0xc8, 0xc2, 0xb2, 0xef, 0xf7, 0x6d, 0x58, 0x16, 0x96, 0x79, 0x88, 0xe6, 0x58, 0x43, 0x63, 0x22, 0x49, 0xbc, 0xb5, 0x23, 0xfe, 0xf7, 0x4d, 0xa2, 0x8e, 0xb6, 0x23, 0x73, 0x5f, 0x2e, 0xa5, 0x25, 0xe6, 0xc4, 0xe4, 0x3b, @@ -70,7 +70,7 @@ const uint8_t PAGE_settingsCss[] PROGMEM = { // Autogenerated from wled00/data/settings.htm, do not edit!! const uint16_t PAGE_settings_length = 1115; const uint8_t PAGE_settings[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x56, 0xdb, 0x52, 0xe3, 0x46, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xad, 0x56, 0xdb, 0x52, 0xe3, 0x46, 0x10, 0x7d, 0xf7, 0x57, 0x0c, 0xb3, 0x15, 0x56, 0x2a, 0x64, 0xf9, 0x42, 0x2a, 0x95, 0xc8, 0x96, 0xa9, 0x2c, 0x97, 0x0d, 0x29, 0xa8, 0xa5, 0x02, 0x2c, 0x49, 0x25, 0x79, 0x18, 0x6b, 0x5a, 0xd6, 0x2c, 0xd2, 0x8c, 0x6a, 0xa6, 0x65, 0x70, 0xbc, 0xfc, 0x7b, 0x7a, 0x64, 0x63, 0x60, 0xe1, 0x21, @@ -144,164 +144,166 @@ const uint8_t PAGE_settings[] PROGMEM = { // Autogenerated from wled00/data/settings_wifi.htm, do not edit!! -const uint16_t PAGE_settings_wifi_length = 2378; +const uint16_t PAGE_settings_wifi_length = 2410; const uint8_t PAGE_settings_wifi[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xb5, 0x58, 0xff, 0x53, 0xdb, 0x3a, - 0x12, 0xff, 0x3d, 0x7f, 0x85, 0xd0, 0xdd, 0x30, 0xf6, 0xe0, 0x38, 0x09, 0x69, 0xfb, 0x7a, 0x21, - 0x4e, 0x0f, 0x42, 0x5e, 0xe1, 0x1e, 0x85, 0x5c, 0xc3, 0x95, 0xb9, 0xe1, 0x3a, 0xef, 0x19, 0x5b, - 0x49, 0x54, 0x1c, 0xc9, 0xcf, 0x92, 0x13, 0x18, 0xc8, 0xff, 0x7e, 0xbb, 0x92, 0x9d, 0xc4, 0x01, - 0xda, 0x9b, 0x0e, 0x37, 0x9d, 0x29, 0xb6, 0xbc, 0xda, 0xfd, 0xec, 0xb7, 0x8f, 0x56, 0xe9, 0xee, - 0x1c, 0x5f, 0xf4, 0x2f, 0xff, 0x3d, 0x1c, 0x90, 0xa9, 0x9e, 0x25, 0xbd, 0x2e, 0xfe, 0x4f, 0x92, - 0x50, 0x4c, 0x02, 0xca, 0x04, 0x85, 0x77, 0x16, 0xc6, 0xbd, 0xee, 0x8c, 0xe9, 0x90, 0x44, 0xd3, - 0x30, 0x53, 0x4c, 0x07, 0x34, 0xd7, 0xe3, 0xfa, 0x7b, 0x5a, 0xac, 0xd6, 0x22, 0x29, 0x34, 0x13, - 0xb0, 0xbc, 0xe0, 0xb1, 0x9e, 0x06, 0x31, 0x9b, 0xf3, 0x88, 0xd5, 0xcd, 0x8b, 0xc7, 0x05, 0xd7, - 0x3c, 0x4c, 0xea, 0x2a, 0x0a, 0x13, 0x16, 0xb4, 0xbc, 0x59, 0x78, 0xc7, 0x67, 0xf9, 0x6c, 0xf5, - 0x9e, 0x2b, 0x96, 0x99, 0x97, 0xf0, 0x06, 0xde, 0x85, 0xa4, 0xa4, 0x26, 0xc2, 0x19, 0x0b, 0xe8, - 0x9c, 0xb3, 0x45, 0x2a, 0x33, 0x0d, 0x56, 0x34, 0xd7, 0x09, 0xeb, 0x5d, 0xf1, 0x5f, 0x39, 0x19, - 0x31, 0xad, 0xb9, 0x98, 0xa8, 0x6e, 0xc3, 0x2e, 0x76, 0x55, 0x94, 0xf1, 0x54, 0xf7, 0x6a, 0xf3, - 0x30, 0x23, 0x89, 0x8c, 0x78, 0xea, 0xc5, 0x41, 0x2c, 0xa3, 0x7c, 0x06, 0x80, 0x3c, 0x58, 0x08, - 0x76, 0x5a, 0xf8, 0x27, 0xcd, 0xa4, 0x96, 0x01, 0x9d, 0x6a, 0x9d, 0x76, 0xa8, 0x07, 0xf6, 0xc4, - 0x99, 0x94, 0xa9, 0x0a, 0x9a, 0x5e, 0x9a, 0xb1, 0x11, 0xbc, 0x8e, 0x46, 0xa7, 0xc7, 0x01, 0xa5, - 0x07, 0xe3, 0x5c, 0x44, 0x9a, 0x4b, 0x41, 0x26, 0xa7, 0xb1, 0xa3, 0xdd, 0x87, 0x8c, 0xe9, 0x3c, - 0x13, 0x24, 0xf6, 0x27, 0x4c, 0x0f, 0x12, 0x86, 0x6a, 0x8f, 0xee, 0xcd, 0xa7, 0xe5, 0x4a, 0x34, - 0x1a, 0x54, 0x24, 0xa3, 0x8c, 0x85, 0x9a, 0x15, 0xc2, 0x15, 0xc1, 0x13, 0xc7, 0x7d, 0x58, 0x70, - 0x11, 0xcb, 0x85, 0x2f, 0x53, 0x26, 0x1c, 0x83, 0x47, 0x75, 0x1a, 0x8d, 0x5b, 0x21, 0xfd, 0x45, - 0xc2, 0xd0, 0x4a, 0x63, 0x0c, 0xbb, 0xf3, 0x8c, 0xa9, 0x86, 0x2a, 0x7c, 0x6d, 0xfc, 0x65, 0xc1, - 0xc7, 0xbc, 0x5e, 0xbe, 0xd2, 0x0d, 0x85, 0x47, 0x5b, 0x0a, 0x01, 0xe4, 0xbf, 0x3e, 0x9f, 0x39, - 0xb4, 0xb1, 0x16, 0xf6, 0xe8, 0xef, 0x8a, 0x25, 0xe3, 0xcd, 0x5d, 0xe7, 0xb0, 0x0b, 0x72, 0xa6, - 0x34, 0xd1, 0x01, 0xba, 0x49, 0x31, 0x1e, 0xd4, 0x3d, 0xd0, 0x7e, 0xcc, 0x15, 0xe6, 0x21, 0x0e, - 0x76, 0x9a, 0x9e, 0xf6, 0x35, 0xbb, 0xd3, 0xfd, 0x32, 0xb7, 0x18, 0x24, 0x01, 0x2a, 0x7d, 0xdf, - 0xa7, 0xde, 0x98, 0xe9, 0x68, 0xba, 0xb6, 0xf6, 0x4d, 0x49, 0xd1, 0x10, 0x4c, 0x53, 0xd7, 0xf5, - 0xf5, 0x14, 0x70, 0xe8, 0xa0, 0xa7, 0x7d, 0x5c, 0x75, 0x36, 0x56, 0x2e, 0x6e, 0xbe, 0xb1, 0x48, - 0xfb, 0xa1, 0x52, 0x7c, 0x22, 0x9c, 0x87, 0xa5, 0xf7, 0x00, 0x5b, 0x16, 0x32, 0xbb, 0x55, 0x9d, - 0xeb, 0xaf, 0x4b, 0x4f, 0xbb, 0x7e, 0xf9, 0xee, 0x2b, 0x48, 0xbc, 0xe3, 0x68, 0x8f, 0xb9, 0x41, - 0x8f, 0xf9, 0x19, 0xec, 0xa8, 0x6b, 0xf3, 0xc7, 0xf5, 0x33, 0x16, 0xe7, 0x11, 0x2b, 0x3f, 0x3a, - 0x1a, 0x64, 0x67, 0xcc, 0x1a, 0x84, 0xef, 0x71, 0x10, 0x04, 0xcc, 0x3c, 0xb8, 0x8f, 0x8f, 0xda, - 0x4f, 0x73, 0x35, 0x75, 0x98, 0x0b, 0xba, 0xbd, 0xeb, 0xaf, 0x25, 0x14, 0x16, 0xf4, 0x1e, 0xf8, - 0xd8, 0x69, 0x1a, 0xd1, 0x84, 0x89, 0x89, 0x9e, 0xee, 0xee, 0xae, 0x4a, 0xa2, 0xdb, 0x6a, 0xba, - 0x45, 0x2a, 0x57, 0x6b, 0x7b, 0x7b, 0xde, 0x5c, 0xf2, 0x98, 0x40, 0x54, 0x2f, 0xf9, 0x8c, 0xc9, - 0x5c, 0x3b, 0xe7, 0x5e, 0x8b, 0xb5, 0xdd, 0x83, 0x8d, 0x4a, 0x3a, 0x48, 0x98, 0x26, 0xc2, 0x06, - 0xb4, 0x3f, 0x82, 0x70, 0x82, 0x0d, 0xe1, 0x3e, 0xe0, 0xaa, 0x0e, 0xa0, 0x44, 0x28, 0xa4, 0x01, - 0xfc, 0x37, 0x71, 0x06, 0x45, 0x87, 0x5a, 0x67, 0xfc, 0x26, 0xd7, 0xcc, 0xa1, 0x3c, 0xa6, 0x9e, - 0xd9, 0xe2, 0x6d, 0x7f, 0xc1, 0x56, 0x78, 0xe9, 0x9b, 0x14, 0xd0, 0x8e, 0x62, 0x82, 0xdf, 0x2f, - 0x1d, 0x17, 0x04, 0x36, 0x2b, 0x59, 0xf8, 0xf3, 0x30, 0xc9, 0xd9, 0xc1, 0x58, 0x66, 0x0e, 0x22, - 0x60, 0x80, 0x8f, 0x75, 0xb5, 0x1f, 0x4d, 0x79, 0x12, 0x67, 0x4c, 0x14, 0x7e, 0x1f, 0xb0, 0xbd, - 0x3d, 0x17, 0x22, 0xcb, 0x66, 0x72, 0xce, 0xfa, 0xf8, 0xcd, 0x59, 0xcb, 0x5c, 0xb3, 0xaf, 0xee, - 0x4a, 0x81, 0x04, 0x05, 0xb2, 0x5b, 0xc6, 0xeb, 0x40, 0xc2, 0xbe, 0xa2, 0x86, 0xb8, 0x71, 0x4e, - 0xa6, 0x58, 0x5a, 0xe8, 0xf5, 0x16, 0x4c, 0x83, 0x83, 0x7a, 0xec, 0x5a, 0x7e, 0xb5, 0x79, 0xf1, - 0x78, 0xa5, 0xb0, 0xfe, 0xf8, 0xeb, 0xc3, 0xea, 0xdb, 0x92, 0x38, 0xc5, 0x1b, 0xe6, 0x7a, 0x49, - 0xe2, 0xa3, 0x99, 0xfb, 0xc7, 0x7a, 0x2b, 0x24, 0xac, 0xf0, 0x6b, 0x77, 0x77, 0xdb, 0x8c, 0x0d, - 0x2e, 0xc3, 0x48, 0xae, 0x1e, 0x31, 0x66, 0x61, 0x0a, 0x7d, 0x11, 0x5b, 0xd7, 0xb8, 0xbb, 0xb4, - 0x90, 0x65, 0x15, 0xb2, 0x7c, 0x1e, 0x32, 0xdd, 0xe9, 0x63, 0xff, 0xc8, 0x6a, 0x1f, 0x5c, 0x40, - 0x0d, 0x65, 0xa4, 0xa8, 0x54, 0xd3, 0x0c, 0x55, 0x23, 0xd2, 0xf5, 0x04, 0x04, 0x34, 0x4d, 0xc2, - 0x88, 0x5d, 0x71, 0x3d, 0x45, 0x02, 0xd8, 0x6c, 0xac, 0xd6, 0x73, 0x8d, 0x45, 0x97, 0x1b, 0xed, - 0x09, 0xd9, 0x2c, 0xaa, 0xa6, 0x52, 0x4b, 0x3b, 0xfa, 0xf1, 0xd1, 0x40, 0xda, 0x09, 0xb4, 0x8d, - 0x42, 0x51, 0xa7, 0x07, 0x36, 0xc1, 0xe8, 0x12, 0x17, 0x69, 0x8e, 0x15, 0xc6, 0x7c, 0x7d, 0x9f, - 0x02, 0x89, 0xa2, 0x21, 0x88, 0xbd, 0x0f, 0xb1, 0x43, 0x3d, 0xf0, 0x64, 0xc9, 0xd5, 0x3e, 0x57, - 0xbd, 0x06, 0x66, 0xb6, 0xc9, 0xa5, 0x5e, 0x7b, 0xdf, 0x85, 0xcf, 0xc6, 0x46, 0xb0, 0x51, 0x55, - 0x9e, 0xae, 0x38, 0xc6, 0x36, 0x30, 0x27, 0x32, 0x8c, 0xff, 0x31, 0xc2, 0xa6, 0x04, 0xea, 0xb0, - 0xf0, 0x85, 0x2d, 0x7a, 0xc3, 0xcf, 0x00, 0x49, 0x6c, 0x27, 0x2c, 0x8b, 0x28, 0xf6, 0xe5, 0xf6, - 0x3a, 0x02, 0x87, 0xd8, 0x23, 0xf2, 0xc6, 0xb7, 0x70, 0x1e, 0x96, 0x0a, 0x9e, 0x08, 0x86, 0xea, - 0x5e, 0x80, 0x0a, 0xe8, 0xee, 0xd8, 0xbf, 0x91, 0xf1, 0x7d, 0x25, 0x0b, 0x02, 0xe5, 0xc3, 0x38, - 0x1e, 0xcc, 0x21, 0xc6, 0x67, 0x5c, 0x41, 0xa8, 0x59, 0xe6, 0x50, 0x84, 0x49, 0x3d, 0x07, 0x88, - 0xe3, 0xe1, 0x23, 0xd3, 0x5f, 0x1c, 0x77, 0xf9, 0xbc, 0x1c, 0xcb, 0x32, 0x99, 0x01, 0x3c, 0x90, - 0xc3, 0x8a, 0x91, 0x09, 0x14, 0xbe, 0x9c, 0x38, 0x74, 0x80, 0xeb, 0xa4, 0xf0, 0x17, 0xc8, 0x90, - 0x8c, 0x79, 0xc2, 0x8c, 0x1b, 0x70, 0x9a, 0x01, 0x65, 0xd1, 0xb3, 0x62, 0x5d, 0x8e, 0x09, 0x6c, - 0x1c, 0xf3, 0x49, 0x9e, 0x85, 0x26, 0x40, 0xd6, 0x0d, 0x32, 0x0e, 0x39, 0xd2, 0xfc, 0x7f, 0xc4, - 0xa9, 0x88, 0xe4, 0x2c, 0x85, 0x38, 0x31, 0x92, 0x86, 0x13, 0x46, 0xe2, 0x50, 0x87, 0x3b, 0x40, - 0xd2, 0x1b, 0x31, 0x1d, 0xad, 0xea, 0xa0, 0xa0, 0x78, 0x38, 0xc7, 0x8c, 0x32, 0xac, 0x06, 0x8a, - 0x96, 0x3b, 0x34, 0x80, 0x5a, 0x30, 0x67, 0x5b, 0x24, 0x13, 0xd7, 0x1c, 0x77, 0x4d, 0xcf, 0x31, - 0xe7, 0x60, 0x80, 0xd2, 0xc9, 0x48, 0xcb, 0x0c, 0xd4, 0xe3, 0xe9, 0x75, 0xaa, 0xd9, 0x0c, 0x23, - 0x10, 0x9d, 0xa6, 0xc0, 0xd3, 0x8f, 0x8f, 0x85, 0x18, 0xec, 0x9e, 0xa5, 0x80, 0xfc, 0x57, 0xd0, - 0x47, 0x3e, 0xc9, 0x98, 0xf9, 0x64, 0x98, 0xb0, 0x50, 0x31, 0x02, 0x11, 0x81, 0x4a, 0xbf, 0x3a, - 0x1b, 0x1c, 0x93, 0xd3, 0x21, 0x60, 0xf3, 0x2a, 0x1a, 0x55, 0x55, 0xa3, 0x67, 0xb4, 0xb9, 0x50, - 0x7a, 0x89, 0x62, 0x0f, 0xb6, 0x22, 0x01, 0x5a, 0xa8, 0xa7, 0x58, 0x71, 0x9e, 0x40, 0x56, 0x4e, - 0x60, 0x2c, 0x70, 0x5a, 0x50, 0x59, 0x90, 0x26, 0x65, 0x4a, 0x88, 0x36, 0xa8, 0xfb, 0xa1, 0xde, - 0xea, 0x18, 0x76, 0x6d, 0xba, 0xbe, 0x4a, 0x13, 0xae, 0xcd, 0xea, 0x41, 0x49, 0x51, 0xbd, 0xfd, - 0xdd, 0x5d, 0x67, 0x75, 0x82, 0xaf, 0xbd, 0xf5, 0x0a, 0x6f, 0xad, 0x17, 0xda, 0x9f, 0x4a, 0xa5, - 0xd1, 0xd4, 0x1e, 0x30, 0x18, 0xce, 0x0d, 0x1f, 0x68, 0x87, 0xee, 0xd9, 0xc7, 0x0e, 0xa5, 0xee, - 0x1e, 0x28, 0xdd, 0x13, 0xd7, 0xcd, 0xaf, 0xee, 0xb2, 0xa8, 0xd5, 0x27, 0x67, 0x65, 0x43, 0xc1, - 0x91, 0xf5, 0x21, 0x0d, 0x5a, 0xe0, 0xea, 0x4e, 0xcb, 0xb8, 0x0b, 0xa6, 0x63, 0x7f, 0x34, 0xf6, - 0x43, 0x93, 0x91, 0xe0, 0xe9, 0x16, 0x3c, 0x99, 0x21, 0x9a, 0xeb, 0xa4, 0x15, 0x22, 0xab, 0x79, - 0x00, 0xa1, 0x7f, 0x28, 0xe1, 0x03, 0x08, 0x40, 0x61, 0x10, 0x1b, 0x4c, 0x7a, 0x59, 0xeb, 0x36, - 0x8a, 0x09, 0xa6, 0xab, 0xf4, 0x3d, 0x0c, 0x34, 0x7f, 0xe7, 0x33, 0x84, 0x4c, 0xf2, 0x2c, 0x81, - 0x1e, 0xc1, 0x25, 0x3f, 0x52, 0x40, 0x43, 0x07, 0x20, 0x68, 0x04, 0xba, 0x0d, 0x3b, 0x90, 0x61, - 0xc9, 0x43, 0x25, 0xa2, 0x33, 0xc0, 0x21, 0x70, 0x00, 0xf4, 0xba, 0xc0, 0xd5, 0xb3, 0x1a, 0xc1, - 0x5e, 0xc7, 0xa7, 0xdf, 0x15, 0x25, 0xb6, 0xdb, 0x47, 0x63, 0x4a, 0x60, 0x52, 0x9b, 0x4a, 0xf8, - 0x92, 0x42, 0x98, 0x40, 0x34, 0xe6, 0x73, 0x12, 0x25, 0x70, 0x14, 0x03, 0x47, 0x48, 0xc0, 0xb6, - 0xa8, 0xae, 0x4d, 0x59, 0x92, 0x1e, 0xd1, 0x5e, 0xad, 0x0b, 0xad, 0xa6, 0xc1, 0x2b, 0xcb, 0x26, - 0xf6, 0x85, 0x82, 0xd5, 0x08, 0x52, 0x79, 0x1b, 0xd0, 0x13, 0x34, 0xfb, 0xa1, 0xdb, 0xb0, 0x1f, - 0x00, 0x1a, 0xa8, 0xe8, 0x3d, 0xbf, 0xa7, 0xb6, 0xda, 0x74, 0x84, 0x9b, 0x8e, 0xc2, 0xe8, 0x76, - 0xbd, 0xaf, 0xb2, 0x43, 0xe5, 0x37, 0x33, 0x0e, 0x18, 0x47, 0xe1, 0x9c, 0x91, 0x5d, 0x02, 0x3c, - 0x29, 0x80, 0xce, 0xd7, 0xc2, 0xd3, 0x0c, 0x70, 0x59, 0x4b, 0xd3, 0x7d, 0x3b, 0x15, 0x42, 0x3a, - 0xf2, 0x14, 0x02, 0xb3, 0x0f, 0x4b, 0xed, 0x5e, 0xb1, 0x83, 0x68, 0x49, 0xd8, 0x1d, 0xb4, 0x34, - 0xf6, 0x63, 0xc1, 0xd7, 0x20, 0xd3, 0x5e, 0x99, 0xab, 0x55, 0x11, 0x62, 0xdc, 0xcc, 0x20, 0xb4, - 0xf6, 0xef, 0x1c, 0xa1, 0x22, 0xfb, 0x6d, 0x40, 0x05, 0xeb, 0xe7, 0x56, 0x99, 0x89, 0x2e, 0x71, - 0x0c, 0x33, 0x12, 0x06, 0x4d, 0x74, 0x8f, 0x26, 0x85, 0xd4, 0xd8, 0xfa, 0x88, 0xc0, 0xed, 0xa0, - 0x78, 0xd7, 0x90, 0x32, 0xd9, 0x20, 0x64, 0x52, 0xd0, 0x71, 0x39, 0xea, 0xe2, 0xe3, 0x8a, 0x7d, - 0x03, 0xda, 0xde, 0xa7, 0xc6, 0x4e, 0x69, 0x26, 0x85, 0x94, 0xc0, 0x43, 0xfc, 0x54, 0x5b, 0xf9, - 0x65, 0xad, 0x69, 0x58, 0xd1, 0xf4, 0xae, 0x6d, 0x35, 0x8d, 0x34, 0xf0, 0x46, 0x04, 0x5d, 0x4c, - 0xe0, 0x2c, 0xc7, 0xb0, 0x86, 0x9a, 0x34, 0x7d, 0xf3, 0x8f, 0x40, 0xa9, 0x90, 0xe3, 0x93, 0xfe, - 0xb0, 0x02, 0xb6, 0x50, 0x77, 0xda, 0xa4, 0x85, 0x25, 0x91, 0xcf, 0x6e, 0x58, 0x46, 0xcb, 0xfa, - 0x80, 0xd2, 0x9a, 0x71, 0x11, 0xd0, 0xa6, 0x31, 0x17, 0xd0, 0xfd, 0xb7, 0x6f, 0x29, 0xc9, 0xd8, - 0x9f, 0x39, 0x87, 0xf1, 0xac, 0x47, 0x7c, 0xb2, 0xa5, 0xa7, 0xf5, 0x4a, 0x7a, 0xf6, 0x5f, 0x49, - 0x4f, 0xfb, 0xa7, 0xf4, 0x6c, 0x84, 0x72, 0x02, 0x13, 0xfe, 0x22, 0xbc, 0xef, 0xd4, 0x36, 0x82, - 0x66, 0x75, 0x7f, 0xfc, 0xe9, 0x98, 0xd5, 0xaa, 0x7a, 0x5e, 0x29, 0x66, 0x1f, 0x5f, 0x29, 0x66, - 0x1f, 0x7f, 0x3e, 0x66, 0xb5, 0x22, 0x68, 0xd0, 0xd9, 0xd0, 0x87, 0x20, 0xa5, 0x6e, 0x3b, 0x4f, - 0xe2, 0x36, 0xfa, 0x1f, 0xe2, 0x56, 0xfb, 0x2e, 0xd0, 0x42, 0x4f, 0xeb, 0x95, 0xf4, 0xec, 0xbf, - 0x92, 0x9e, 0xf6, 0xcf, 0xe9, 0xc1, 0x00, 0xcd, 0x8e, 0xcf, 0x47, 0x04, 0xa6, 0x13, 0xb8, 0x0d, - 0xaa, 0xb2, 0x75, 0x2d, 0xd1, 0x60, 0xd7, 0x0a, 0x49, 0x50, 0xc0, 0x36, 0xae, 0xb9, 0xd9, 0x36, - 0x1a, 0x85, 0xed, 0x5a, 0x85, 0x6e, 0x0a, 0x6e, 0xf8, 0xf4, 0x84, 0x65, 0x88, 0x19, 0x29, 0x12, - 0xdc, 0xdf, 0x4f, 0x38, 0x9c, 0xf7, 0x40, 0x11, 0x1d, 0xd2, 0x55, 0x69, 0x28, 0x56, 0x28, 0x79, - 0x0a, 0xe7, 0xc0, 0xf9, 0x9a, 0xd4, 0x58, 0x0c, 0x27, 0x11, 0x08, 0x18, 0x80, 0x05, 0xdd, 0x9a, - 0x39, 0x87, 0x91, 0xc3, 0x28, 0x42, 0xa0, 0x43, 0xc9, 0x85, 0x36, 0x4c, 0x5b, 0x3b, 0x1c, 0x12, - 0xa4, 0xc7, 0x67, 0xb1, 0x1f, 0x0e, 0x5f, 0xe4, 0x47, 0x0b, 0xf8, 0xf0, 0x29, 0x2d, 0x9a, 0x76, - 0x3b, 0xe1, 0x31, 0xd8, 0x1a, 0x1a, 0xa9, 0x0e, 0xa9, 0x6c, 0x8f, 0xa6, 0x2c, 0xba, 0xbd, 0x91, - 0x77, 0x2b, 0x15, 0x27, 0x96, 0x00, 0x11, 0x48, 0x49, 0x96, 0x4f, 0xc1, 0xe0, 0xd5, 0xd9, 0xfd, - 0x1e, 0xbb, 0x16, 0xca, 0x86, 0x36, 0x53, 0x1b, 0xec, 0x0a, 0x4a, 0x35, 0x0c, 0x49, 0x90, 0x45, - 0xc7, 0x7f, 0x78, 0xef, 0xbd, 0x6b, 0x2f, 0xdd, 0x47, 0x38, 0x33, 0x88, 0xf9, 0x71, 0x22, 0xa0, - 0x03, 0x63, 0x02, 0x2c, 0x40, 0xa2, 0x7d, 0xf2, 0xde, 0xfc, 0x7e, 0x02, 0xa3, 0x04, 0xcb, 0x54, - 0x09, 0x6b, 0x23, 0x62, 0xc4, 0x1c, 0x65, 0x78, 0xa7, 0x13, 0x2c, 0xe9, 0x54, 0x8b, 0xe8, 0xb0, - 0xff, 0x42, 0x11, 0xdd, 0x95, 0x55, 0xd4, 0x2a, 0xab, 0xa8, 0xd5, 0xde, 0x2a, 0x22, 0x70, 0x1d, - 0x1d, 0x54, 0x98, 0x58, 0x73, 0x33, 0x2a, 0x75, 0xc2, 0x01, 0xdf, 0xb5, 0x77, 0x20, 0x62, 0x47, - 0x7d, 0x28, 0x45, 0x4c, 0x75, 0x99, 0x69, 0xfc, 0x10, 0x8e, 0x71, 0x06, 0xbc, 0x91, 0x12, 0x12, - 0x6a, 0x65, 0xb7, 0xf7, 0xb4, 0x68, 0xef, 0x98, 0xab, 0x8d, 0xe2, 0xd8, 0x12, 0xab, 0x15, 0x72, - 0x90, 0xbd, 0xc3, 0x04, 0x28, 0x53, 0xbd, 0xa4, 0x08, 0xce, 0xaa, 0x73, 0x36, 0x07, 0x6b, 0x0e, - 0x1e, 0xa1, 0x19, 0x83, 0xf9, 0x78, 0x06, 0x83, 0x22, 0x8b, 0xdd, 0xd5, 0x0e, 0x9c, 0x96, 0x8c, - 0x0b, 0xa5, 0x63, 0xcf, 0x97, 0x2b, 0x56, 0x2b, 0x4e, 0x6c, 0x73, 0xb6, 0x55, 0xaa, 0x83, 0xbb, - 0x94, 0x65, 0x1c, 0x7f, 0x96, 0x81, 0xa2, 0x37, 0x05, 0x7a, 0x6c, 0xef, 0x65, 0x36, 0xf6, 0x2a, - 0x61, 0x2c, 0xfd, 0x41, 0x45, 0x5d, 0x8d, 0x6c, 0xea, 0xba, 0xbc, 0x57, 0xeb, 0x83, 0x5d, 0x9c, - 0x94, 0xc8, 0x02, 0x66, 0xd9, 0x55, 0xd4, 0xe6, 0x1c, 0x72, 0xce, 0x95, 0xca, 0x99, 0xf2, 0x4d, - 0x92, 0x8f, 0xed, 0x50, 0xc0, 0x84, 0xb1, 0xc4, 0xc7, 0xd6, 0x18, 0x57, 0x04, 0x4f, 0x77, 0x1c, - 0x4e, 0x22, 0x99, 0x81, 0xbb, 0x3a, 0xb9, 0xf7, 0x08, 0x17, 0xf8, 0xeb, 0x91, 0x62, 0x8a, 0xa4, - 0x72, 0x01, 0xb1, 0xc0, 0xdb, 0x47, 0x3e, 0x33, 0xde, 0xfb, 0xdd, 0x06, 0x37, 0x43, 0x9a, 0x1d, - 0xf1, 0xe0, 0xce, 0x1e, 0x53, 0xe3, 0xd5, 0x15, 0xa4, 0x3a, 0xc1, 0x32, 0xfa, 0x0c, 0xd7, 0x78, - 0xcd, 0xec, 0x8c, 0xc3, 0x7b, 0xf6, 0x36, 0x63, 0x0a, 0x9c, 0xe1, 0xed, 0x46, 0x11, 0x89, 0xd1, - 0x1d, 0x8c, 0x86, 0xf5, 0xf3, 0x8b, 0x2b, 0x83, 0xec, 0x37, 0xf0, 0x97, 0x94, 0x57, 0x53, 0x44, - 0x86, 0x38, 0x73, 0x85, 0x98, 0x42, 0x92, 0x19, 0x6d, 0x3f, 0x80, 0x84, 0x91, 0x00, 0x58, 0xb5, - 0x81, 0x75, 0xce, 0x22, 0xf8, 0x41, 0x08, 0x3f, 0x0f, 0x6c, 0x08, 0x4f, 0xc2, 0x2c, 0x5e, 0x84, - 0xc0, 0x1b, 0x9f, 0x0e, 0xfb, 0xab, 0x2d, 0xb5, 0xa7, 0x3c, 0xf0, 0x19, 0xbe, 0xdb, 0x1d, 0x67, - 0x21, 0x5c, 0xde, 0x47, 0x8c, 0x89, 0xad, 0xac, 0x67, 0x09, 0x8f, 0x31, 0xed, 0xa2, 0x92, 0x70, - 0x33, 0x23, 0xd6, 0xcc, 0x58, 0x8b, 0x01, 0x83, 0x01, 0xb8, 0x08, 0xd8, 0x00, 0xaf, 0xf0, 0x78, - 0x12, 0x5d, 0x82, 0x2d, 0x1b, 0xae, 0x4a, 0x67, 0x0c, 0x2e, 0x4f, 0x9e, 0x6b, 0x0d, 0xd4, 0x5f, - 0x7b, 0xa9, 0x7a, 0xff, 0x06, 0xe5, 0x7d, 0xd4, 0xdf, 0xb1, 0xd7, 0xa5, 0x2f, 0x6f, 0xda, 0x30, - 0xb9, 0xe2, 0x05, 0x0f, 0xce, 0x3d, 0x88, 0xcb, 0x4b, 0x9b, 0x90, 0xd1, 0x20, 0x1f, 0xed, 0xfd, - 0xfa, 0xf0, 0x62, 0xf0, 0x92, 0xd0, 0x3b, 0xda, 0x33, 0x32, 0xc7, 0x2c, 0xbf, 0x7b, 0x49, 0xe6, - 0x17, 0xda, 0xfb, 0xed, 0xf4, 0xb2, 0xfe, 0x65, 0xf0, 0x22, 0xbe, 0xf7, 0xb4, 0xf7, 0xcf, 0x9c, - 0x0b, 0x40, 0x57, 0x3f, 0xe6, 0x93, 0xfa, 0x45, 0xa4, 0x43, 0x80, 0x78, 0x59, 0x07, 0x5f, 0x9f, - 0x35, 0x5e, 0xb6, 0xed, 0x9b, 0xf5, 0x3e, 0x83, 0xe2, 0x45, 0x1a, 0x80, 0x00, 0x8d, 0x58, 0x36, - 0xf9, 0xe5, 0x0d, 0xaa, 0x7c, 0x46, 0xae, 0x54, 0xf8, 0x96, 0xf6, 0x2e, 0x17, 0x3c, 0xe1, 0x93, - 0xa9, 0x3e, 0x03, 0x72, 0xfd, 0xbe, 0x56, 0xe0, 0x84, 0xab, 0xe7, 0x05, 0x6a, 0x6b, 0xfa, 0xb9, - 0xba, 0x84, 0x00, 0x82, 0xd1, 0x66, 0x6b, 0x2d, 0xb5, 0xc9, 0x13, 0xeb, 0x62, 0xc0, 0xbb, 0xc3, - 0xff, 0xfb, 0x7a, 0xd2, 0xc0, 0xbb, 0x17, 0x32, 0x15, 0x5e, 0xd0, 0xf0, 0xb6, 0x86, 0x3f, 0xaa, - 0xff, 0x17, 0x39, 0xb0, 0x85, 0xbd, 0x64, 0x17, 0x00, 0x00 + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xb5, 0x58, 0x6d, 0x53, 0xdb, 0x3a, + 0x16, 0xfe, 0x9e, 0x5f, 0x21, 0xb4, 0x3b, 0x8c, 0x3d, 0x38, 0x4e, 0x42, 0xda, 0x5e, 0x36, 0xc4, + 0xe9, 0xf2, 0x92, 0x16, 0xf6, 0x52, 0xc8, 0x36, 0xec, 0x65, 0x76, 0xba, 0x9d, 0x7b, 0x8d, 0xad, + 0x24, 0x2a, 0xb6, 0xe4, 0x6b, 0xc9, 0x09, 0x0c, 0xf0, 0xdf, 0xf7, 0x1c, 0xc9, 0x4e, 0xe2, 0x00, + 0xed, 0x4e, 0x87, 0x9d, 0xce, 0x14, 0x5b, 0x3e, 0x3a, 0xe7, 0x39, 0x6f, 0x8f, 0x8e, 0xd2, 0xdf, + 0x3a, 0xbe, 0x38, 0xba, 0xfc, 0xf7, 0x68, 0x48, 0x66, 0x3a, 0x4d, 0x06, 0x7d, 0xfc, 0x9f, 0x24, + 0xa1, 0x98, 0x06, 0x94, 0x09, 0x0a, 0xef, 0x2c, 0x8c, 0x07, 0xfd, 0x94, 0xe9, 0x90, 0x44, 0xb3, + 0x30, 0x57, 0x4c, 0x07, 0xb4, 0xd0, 0x93, 0xe6, 0x1e, 0x2d, 0x57, 0x1b, 0x91, 0x14, 0x9a, 0x09, + 0x58, 0x5e, 0xf0, 0x58, 0xcf, 0x82, 0x98, 0xcd, 0x79, 0xc4, 0x9a, 0xe6, 0xc5, 0xe3, 0x82, 0x6b, + 0x1e, 0x26, 0x4d, 0x15, 0x85, 0x09, 0x0b, 0x3a, 0x5e, 0x1a, 0xde, 0xf2, 0xb4, 0x48, 0x97, 0xef, + 0x85, 0x62, 0xb9, 0x79, 0x09, 0xaf, 0xe1, 0x5d, 0x48, 0x4a, 0x1a, 0x22, 0x4c, 0x59, 0x40, 0xe7, + 0x9c, 0x2d, 0x32, 0x99, 0x6b, 0xb0, 0xa2, 0xb9, 0x4e, 0xd8, 0xe0, 0x8a, 0x7f, 0xe0, 0x64, 0xcc, + 0xb4, 0xe6, 0x62, 0xaa, 0xfa, 0x2d, 0xbb, 0xd8, 0x57, 0x51, 0xce, 0x33, 0x3d, 0x68, 0xcc, 0xc3, + 0x9c, 0x24, 0x32, 0xe2, 0x99, 0x17, 0x07, 0xb1, 0x8c, 0x8a, 0x14, 0x00, 0x79, 0xb0, 0x10, 0x6c, + 0x75, 0xf0, 0x4f, 0x96, 0x4b, 0x2d, 0x03, 0x3a, 0xd3, 0x3a, 0xeb, 0x51, 0x0f, 0xec, 0x89, 0x33, + 0x29, 0x33, 0x15, 0xb4, 0xbd, 0x2c, 0x67, 0x63, 0x78, 0x1d, 0x8f, 0x4f, 0x8f, 0x03, 0x4a, 0xf7, + 0x27, 0x85, 0x88, 0x34, 0x97, 0x82, 0x4c, 0x4f, 0x63, 0x47, 0xbb, 0xf7, 0x39, 0xd3, 0x45, 0x2e, + 0x48, 0xec, 0x4f, 0x99, 0x1e, 0x26, 0x0c, 0xd5, 0x1e, 0xde, 0x99, 0x4f, 0x8f, 0x4b, 0xd1, 0x68, + 0x58, 0x93, 0x8c, 0x72, 0x16, 0x6a, 0x56, 0x0a, 0xd7, 0x04, 0x4f, 0x1c, 0xf7, 0x7e, 0xc1, 0x45, + 0x2c, 0x17, 0xbe, 0xcc, 0x98, 0x70, 0x0c, 0x1e, 0xd5, 0x6b, 0xb5, 0x6e, 0x84, 0xf4, 0x17, 0x09, + 0x43, 0x2b, 0xad, 0x09, 0xec, 0x2e, 0x72, 0xa6, 0x5a, 0xaa, 0xf4, 0xb5, 0xf5, 0x97, 0x05, 0x9f, + 0xf0, 0x66, 0xf5, 0x4a, 0xd7, 0x14, 0x1e, 0x6e, 0x28, 0x04, 0x90, 0xff, 0xfa, 0x7c, 0xe6, 0xd0, + 0xd6, 0x4a, 0xd8, 0xa3, 0xbf, 0x2b, 0x96, 0x4c, 0xd6, 0x77, 0x9d, 0xc3, 0x2e, 0xc8, 0x99, 0xd2, + 0x44, 0x07, 0xe8, 0x26, 0xc5, 0x78, 0x50, 0x77, 0x5f, 0xfb, 0x31, 0x57, 0x98, 0x87, 0x38, 0xd8, + 0x6a, 0x7b, 0xda, 0xd7, 0xec, 0x56, 0x1f, 0x55, 0xb9, 0xc5, 0x20, 0x09, 0x50, 0xe9, 0xfb, 0x3e, + 0xf5, 0x26, 0x4c, 0x47, 0xb3, 0x95, 0xb5, 0x6f, 0x4a, 0x8a, 0x96, 0x60, 0x9a, 0xba, 0xae, 0xaf, + 0x67, 0x80, 0x43, 0x07, 0x03, 0xed, 0xe3, 0xaa, 0xb3, 0xb6, 0x72, 0x71, 0xfd, 0x8d, 0x45, 0xda, + 0x0f, 0x95, 0xe2, 0x53, 0xe1, 0xdc, 0x3f, 0x7a, 0xf7, 0xb0, 0x65, 0x21, 0xf3, 0x1b, 0xd5, 0xfb, + 0xf2, 0xf5, 0xd1, 0xd3, 0xae, 0x5f, 0xbd, 0xfb, 0x0a, 0x12, 0xef, 0x38, 0xda, 0x63, 0x6e, 0x30, + 0x60, 0x7e, 0x0e, 0x3b, 0x9a, 0xda, 0xfc, 0x71, 0xfd, 0x9c, 0xc5, 0x45, 0xc4, 0xaa, 0x8f, 0x8e, + 0x06, 0xd9, 0x94, 0x59, 0x83, 0xf0, 0x3d, 0x0e, 0x82, 0x80, 0x99, 0x07, 0xf7, 0xe1, 0x41, 0xfb, + 0x59, 0xa1, 0x66, 0x0e, 0x73, 0x41, 0xb7, 0xf7, 0xe5, 0x6b, 0x05, 0x85, 0x05, 0x83, 0x7b, 0x3e, + 0x71, 0xda, 0x46, 0x34, 0x61, 0x62, 0xaa, 0x67, 0xdb, 0xdb, 0xcb, 0x92, 0xe8, 0x77, 0xda, 0x6e, + 0x99, 0xca, 0xe5, 0xda, 0xce, 0x8e, 0x37, 0x97, 0x3c, 0x26, 0x10, 0xd5, 0x4b, 0x9e, 0x32, 0x59, + 0x68, 0xe7, 0xdc, 0xeb, 0xb0, 0xae, 0xbb, 0xbf, 0x56, 0x49, 0xfb, 0x09, 0xd3, 0x44, 0xd8, 0x80, + 0x1e, 0x8d, 0x21, 0x9c, 0x60, 0x43, 0xb8, 0xf7, 0xb8, 0xaa, 0x03, 0x28, 0x11, 0x0a, 0x69, 0x00, + 0xff, 0x4d, 0x9c, 0x41, 0xd1, 0x81, 0xd6, 0x39, 0xbf, 0x2e, 0x34, 0x73, 0x28, 0x8f, 0xa9, 0x67, + 0xb6, 0x78, 0x9b, 0x5f, 0xb0, 0x15, 0x5e, 0xfa, 0x26, 0x05, 0xb4, 0xa3, 0x98, 0xe2, 0xf7, 0x4b, + 0xc7, 0x05, 0x81, 0xf5, 0x4a, 0x16, 0xfe, 0x3c, 0x4c, 0x0a, 0xb6, 0x3f, 0x91, 0xb9, 0x83, 0x08, + 0x18, 0xe0, 0x63, 0x7d, 0xed, 0x47, 0x33, 0x9e, 0xc4, 0x39, 0x13, 0xa5, 0xdf, 0xfb, 0x6c, 0x67, + 0xc7, 0x85, 0xc8, 0xb2, 0x54, 0xce, 0xd9, 0x11, 0x7e, 0x73, 0x56, 0x32, 0x5f, 0xd8, 0x57, 0x77, + 0xa9, 0x40, 0x82, 0x02, 0xd9, 0xaf, 0xe2, 0xb5, 0x2f, 0x61, 0x5f, 0x59, 0x43, 0xdc, 0x38, 0x27, + 0x33, 0x2c, 0x2d, 0xf4, 0x7a, 0x03, 0xa6, 0xc1, 0x41, 0x3d, 0xf6, 0x45, 0x7e, 0xb5, 0x79, 0xf1, + 0x78, 0xad, 0xb0, 0xfe, 0xf8, 0xeb, 0xfd, 0xf2, 0xdb, 0x23, 0x71, 0xca, 0x37, 0xcc, 0xf5, 0x23, + 0x89, 0x0f, 0x53, 0xf7, 0x8f, 0xd5, 0x56, 0x48, 0x58, 0xe9, 0xd7, 0xf6, 0xf6, 0xa6, 0x19, 0x1b, + 0x5c, 0x86, 0x91, 0x5c, 0x3e, 0x62, 0xcc, 0xc2, 0x0c, 0xfa, 0x22, 0xb6, 0xae, 0x71, 0xf7, 0xd1, + 0x42, 0x96, 0x75, 0xc8, 0xf2, 0x79, 0xc8, 0x74, 0xeb, 0x08, 0xfb, 0x47, 0xd6, 0xfb, 0xe0, 0x02, + 0x6a, 0x28, 0x27, 0x65, 0xa5, 0x9a, 0x66, 0xa8, 0x1b, 0x91, 0xae, 0x27, 0x20, 0xa0, 0x59, 0x12, + 0x46, 0xec, 0x8a, 0xeb, 0x19, 0x12, 0xc0, 0x7a, 0x63, 0x75, 0x9e, 0x6b, 0x2c, 0xfa, 0xb8, 0xd6, + 0x9e, 0x90, 0xcd, 0xb2, 0x6a, 0x6a, 0xb5, 0xb4, 0xa5, 0x1f, 0x1e, 0x0c, 0xa4, 0xad, 0x40, 0xdb, + 0x28, 0x94, 0x75, 0xba, 0x6f, 0x13, 0x8c, 0x2e, 0x71, 0x91, 0x15, 0x58, 0x61, 0xcc, 0xd7, 0x77, + 0x19, 0x90, 0x28, 0x1a, 0x82, 0xd8, 0xfb, 0x10, 0x3b, 0xd4, 0x03, 0x4f, 0x96, 0x5c, 0xed, 0x73, + 0xdd, 0x6b, 0x60, 0x66, 0x9b, 0x5c, 0xea, 0x75, 0x77, 0x5d, 0xf8, 0x6c, 0x6c, 0x04, 0x6b, 0x55, + 0xe5, 0xe9, 0x9a, 0x63, 0x6c, 0x0d, 0x73, 0x22, 0xc3, 0xf8, 0x1f, 0x63, 0x6c, 0x4a, 0xa0, 0x0e, + 0x0b, 0x5f, 0xd8, 0xa2, 0x37, 0xfc, 0x0c, 0x90, 0xc4, 0x66, 0xc2, 0xf2, 0x88, 0x62, 0x5f, 0x6e, + 0xae, 0x23, 0x70, 0x88, 0x3d, 0x22, 0x6f, 0x7d, 0x0b, 0xe7, 0x61, 0xa5, 0xe0, 0x89, 0x60, 0xa8, + 0xee, 0x04, 0xa8, 0x80, 0xee, 0x8e, 0xfd, 0x6b, 0x19, 0xdf, 0xd5, 0xb2, 0x20, 0x50, 0x3e, 0x8c, + 0xe3, 0xe1, 0x1c, 0x62, 0x7c, 0xc6, 0x15, 0x84, 0x9a, 0xe5, 0x0e, 0x45, 0x98, 0xd4, 0x73, 0x80, + 0x38, 0xee, 0x3f, 0x32, 0xfd, 0x9b, 0xe3, 0x3e, 0x3e, 0x2f, 0xc7, 0xf2, 0x5c, 0xe6, 0x00, 0x0f, + 0xe4, 0xb0, 0x62, 0x64, 0x02, 0x85, 0x2f, 0xa7, 0x0e, 0x1d, 0xe2, 0x3a, 0x29, 0xfd, 0x05, 0x32, + 0x24, 0x13, 0x9e, 0x30, 0xe3, 0x06, 0x9c, 0x66, 0x40, 0x59, 0xf4, 0xac, 0x5c, 0x97, 0x13, 0x02, + 0x1b, 0x27, 0x7c, 0x5a, 0xe4, 0xa1, 0x09, 0x90, 0x75, 0x83, 0x4c, 0x42, 0x8e, 0x34, 0xff, 0x1f, + 0x71, 0x2a, 0x22, 0x99, 0x66, 0x10, 0x27, 0x46, 0xb2, 0x70, 0xca, 0x48, 0x1c, 0xea, 0x70, 0x0b, + 0x48, 0x7a, 0x2d, 0xa6, 0xe3, 0x65, 0x1d, 0x94, 0x14, 0x0f, 0xe7, 0x98, 0x51, 0x86, 0xd5, 0x40, + 0xd1, 0x72, 0x8f, 0x06, 0x50, 0x0b, 0xe6, 0x6c, 0x8b, 0x64, 0xe2, 0x9a, 0xe3, 0xae, 0xed, 0x39, + 0xe6, 0x1c, 0x0c, 0x50, 0x3a, 0x19, 0x6b, 0x99, 0x83, 0x7a, 0x3c, 0xbd, 0x4e, 0x35, 0x4b, 0x31, + 0x02, 0xd1, 0x69, 0x06, 0x3c, 0xfd, 0xf0, 0x50, 0x8a, 0xc1, 0xee, 0x34, 0x03, 0xe4, 0x1f, 0x40, + 0x1f, 0xf9, 0x24, 0x63, 0xe6, 0x93, 0x51, 0xc2, 0x42, 0xc5, 0x08, 0x44, 0x04, 0x2a, 0xfd, 0xea, + 0x6c, 0x78, 0x4c, 0x4e, 0x47, 0x80, 0xcd, 0xab, 0x69, 0x54, 0x75, 0x8d, 0x9e, 0xd1, 0xe6, 0x42, + 0xe9, 0x25, 0x8a, 0xdd, 0xdb, 0x8a, 0x04, 0x68, 0xa1, 0x9e, 0x61, 0xc5, 0x79, 0x02, 0x59, 0x39, + 0x81, 0xb1, 0xc0, 0xe9, 0x40, 0x65, 0x41, 0x9a, 0x94, 0x29, 0x21, 0xda, 0xa2, 0xee, 0xfb, 0x66, + 0xa7, 0x67, 0xd8, 0xb5, 0xed, 0xfa, 0x2a, 0x4b, 0xb8, 0x36, 0xab, 0xfb, 0x15, 0x45, 0x0d, 0x76, + 0xb7, 0xb7, 0x9d, 0xe5, 0x09, 0xbe, 0xf2, 0xd6, 0x2b, 0xbd, 0xb5, 0x5e, 0x68, 0x7f, 0x26, 0x95, + 0x46, 0x53, 0x3b, 0xc0, 0x60, 0x38, 0x37, 0xbc, 0xa7, 0x3d, 0xba, 0x63, 0x1f, 0x7b, 0x94, 0xba, + 0x3b, 0xa0, 0x74, 0x47, 0x7c, 0x69, 0x7f, 0x75, 0x1f, 0xcb, 0x5a, 0x7d, 0x72, 0x56, 0xb6, 0x14, + 0x1c, 0x59, 0xef, 0xb3, 0xa0, 0x03, 0xae, 0x6e, 0x75, 0x8c, 0xbb, 0x60, 0x3a, 0xf6, 0xc7, 0x13, + 0x3f, 0x34, 0x19, 0x09, 0x9e, 0x6e, 0xc1, 0x93, 0x19, 0xa2, 0xb9, 0x4a, 0x5a, 0x29, 0xb2, 0x9c, + 0x07, 0x10, 0xfa, 0xfb, 0x0a, 0x3e, 0x80, 0x00, 0x14, 0x06, 0xb1, 0xc1, 0xa4, 0x1f, 0x1b, 0xfd, + 0x56, 0x39, 0xc1, 0xf4, 0x95, 0xbe, 0x83, 0x81, 0xe6, 0xef, 0x3c, 0x45, 0xc8, 0xa4, 0xc8, 0x13, + 0xe8, 0x11, 0x5c, 0xf2, 0x23, 0x05, 0x34, 0xb4, 0x0f, 0x82, 0x46, 0xa0, 0xdf, 0xb2, 0x03, 0x19, + 0x96, 0x3c, 0x54, 0x22, 0x3a, 0x03, 0x1c, 0x02, 0x07, 0xc0, 0xa0, 0x0f, 0x5c, 0x9d, 0x36, 0x08, + 0xf6, 0x3a, 0x3e, 0xfd, 0xae, 0x28, 0xb1, 0xdd, 0x3e, 0x9e, 0x50, 0x02, 0x93, 0xda, 0x4c, 0xc2, + 0x97, 0x0c, 0xc2, 0x04, 0xa2, 0x31, 0x9f, 0x93, 0x28, 0x81, 0xa3, 0x18, 0x38, 0x42, 0x02, 0xb6, + 0x45, 0x7d, 0x6d, 0xc6, 0x92, 0xec, 0x90, 0x0e, 0x1a, 0x7d, 0x68, 0x35, 0x0d, 0x5e, 0x59, 0x36, + 0xb1, 0x2f, 0x14, 0xac, 0x46, 0x90, 0xca, 0x9b, 0x80, 0x9e, 0xa0, 0xd9, 0xf7, 0xfd, 0x96, 0xfd, + 0x00, 0xd0, 0x40, 0xc5, 0xe0, 0xf9, 0x3d, 0x8d, 0xe5, 0xa6, 0x43, 0xdc, 0x74, 0x18, 0x46, 0x37, + 0xab, 0x7d, 0xb5, 0x1d, 0xaa, 0xb8, 0x4e, 0x39, 0x60, 0x1c, 0x87, 0x73, 0x46, 0xb6, 0x09, 0xf0, + 0xa4, 0x00, 0x3a, 0x5f, 0x09, 0xcf, 0x72, 0xc0, 0x65, 0x2d, 0xcd, 0x76, 0xed, 0x54, 0x08, 0xe9, + 0x28, 0x32, 0x08, 0xcc, 0x2e, 0x2c, 0x75, 0x07, 0xe5, 0x0e, 0xa2, 0x25, 0x61, 0xb7, 0xd0, 0xd2, + 0xd8, 0x8f, 0x25, 0x5f, 0x83, 0x4c, 0x77, 0x69, 0xae, 0x51, 0x47, 0x88, 0x71, 0x33, 0x83, 0xd0, + 0xca, 0xbf, 0x73, 0x84, 0x8a, 0xec, 0xb7, 0x06, 0x15, 0xac, 0x9f, 0x5b, 0x65, 0x26, 0xba, 0xc4, + 0x31, 0xcc, 0x48, 0x18, 0x34, 0xd1, 0x1d, 0x9a, 0x14, 0x52, 0x63, 0xeb, 0x23, 0x02, 0xb7, 0x87, + 0xe2, 0x7d, 0x43, 0xca, 0x64, 0x8d, 0x90, 0x49, 0x49, 0xc7, 0xd5, 0xa8, 0x8b, 0x8f, 0x4b, 0xf6, + 0x0d, 0x68, 0x77, 0x97, 0x1a, 0x3b, 0x95, 0x99, 0x0c, 0x52, 0x02, 0x0f, 0xf1, 0x53, 0x6d, 0xd5, + 0x97, 0x95, 0xa6, 0x51, 0x4d, 0xd3, 0xbb, 0xae, 0xd5, 0x34, 0xd6, 0xc0, 0x1b, 0x11, 0x74, 0x31, + 0x81, 0xb3, 0x1c, 0xc3, 0x1a, 0x6a, 0xd2, 0xf6, 0xcd, 0x3f, 0x02, 0xa5, 0x42, 0x8e, 0x4f, 0x8e, + 0x46, 0x35, 0xb0, 0xa5, 0xba, 0xd3, 0x36, 0x2d, 0x2d, 0x89, 0x22, 0xbd, 0x66, 0x39, 0xad, 0xea, + 0x03, 0x4a, 0x2b, 0xe5, 0x22, 0xa0, 0x6d, 0x63, 0x2e, 0xa0, 0xbb, 0x6f, 0xdf, 0x52, 0x92, 0xb3, + 0x3f, 0x0b, 0x0e, 0xe3, 0xd9, 0x80, 0xf8, 0x64, 0x43, 0x4f, 0xe7, 0x95, 0xf4, 0xec, 0xbe, 0x92, + 0x9e, 0xee, 0x4f, 0xe9, 0x59, 0x0b, 0xe5, 0x14, 0x26, 0xfc, 0x45, 0x78, 0xd7, 0x6b, 0xac, 0x05, + 0xcd, 0xea, 0xfe, 0xf8, 0xd3, 0x31, 0x6b, 0xd4, 0xf5, 0xbc, 0x52, 0xcc, 0x3e, 0xbe, 0x52, 0xcc, + 0x3e, 0xfe, 0x7c, 0xcc, 0x1a, 0x65, 0xd0, 0xa0, 0xb3, 0xa1, 0x0f, 0x41, 0x4a, 0xdd, 0xf4, 0x9e, + 0xc4, 0x6d, 0xfc, 0x3f, 0xc4, 0xad, 0xf1, 0x5d, 0xa0, 0xa5, 0x9e, 0xce, 0x2b, 0xe9, 0xd9, 0x7d, + 0x25, 0x3d, 0xdd, 0x9f, 0xd3, 0x83, 0x01, 0x4a, 0x8f, 0xcf, 0xc7, 0x04, 0xa6, 0x13, 0xb8, 0x0d, + 0xaa, 0xaa, 0x75, 0x2d, 0xd1, 0x60, 0xd7, 0x0a, 0x49, 0x50, 0xc0, 0x36, 0xae, 0xb9, 0xd9, 0xb6, + 0x5a, 0xa5, 0xed, 0x46, 0x8d, 0x6e, 0x4a, 0x6e, 0xf8, 0xf4, 0x84, 0x65, 0x88, 0x19, 0x29, 0x12, + 0xdc, 0x7f, 0x94, 0x70, 0x38, 0xef, 0x81, 0x22, 0x7a, 0xa4, 0xaf, 0xb2, 0x50, 0x2c, 0x51, 0xf2, + 0x0c, 0xce, 0x81, 0xf3, 0x15, 0xa9, 0xb1, 0x18, 0x4e, 0x22, 0x10, 0x30, 0x00, 0x4b, 0xba, 0x35, + 0x73, 0x0e, 0x23, 0x07, 0x51, 0x84, 0x40, 0x47, 0x92, 0x0b, 0x6d, 0x98, 0xb6, 0x71, 0x30, 0x22, + 0x48, 0x8f, 0xcf, 0x62, 0x3f, 0x18, 0xbd, 0xc8, 0x8f, 0x16, 0xf0, 0xc1, 0x53, 0x5a, 0x34, 0xed, + 0x76, 0xc2, 0x63, 0xb0, 0x35, 0x32, 0x52, 0x3d, 0x52, 0xdb, 0x1e, 0xcd, 0x58, 0x74, 0x73, 0x2d, + 0x6f, 0x97, 0x2a, 0x4e, 0x2c, 0x01, 0x22, 0x90, 0x8a, 0x2c, 0x9f, 0x82, 0xc1, 0xab, 0xb3, 0xfb, + 0x3d, 0x76, 0x2d, 0x95, 0x8d, 0x6c, 0xa6, 0xd6, 0xd8, 0x15, 0x94, 0x6a, 0x18, 0x92, 0x20, 0x8b, + 0x8e, 0x7f, 0xbf, 0xe7, 0xbd, 0xeb, 0x3e, 0xba, 0x0f, 0x70, 0x66, 0x10, 0xf3, 0xe3, 0x44, 0x40, + 0x87, 0xc6, 0x04, 0x58, 0x80, 0x44, 0xfb, 0x64, 0xcf, 0xfc, 0x7e, 0x02, 0xa3, 0x04, 0xcb, 0x55, + 0x05, 0x6b, 0x2d, 0x62, 0xc4, 0x1c, 0x65, 0x78, 0xa7, 0x13, 0x2c, 0xe9, 0xd5, 0x8b, 0xe8, 0xe0, + 0xe8, 0x85, 0x22, 0xba, 0xad, 0xaa, 0xa8, 0x53, 0x55, 0x51, 0xa7, 0xbb, 0x51, 0x44, 0xe0, 0x3a, + 0x3a, 0xa8, 0x30, 0xb1, 0xe6, 0x66, 0x54, 0xe9, 0x84, 0x03, 0xbe, 0x6f, 0xef, 0x40, 0xc4, 0x8e, + 0xfa, 0x50, 0x8a, 0x98, 0xea, 0x2a, 0xd3, 0xf8, 0x21, 0x9c, 0xe0, 0x0c, 0x78, 0x2d, 0x25, 0x24, + 0xd4, 0xca, 0x6e, 0xee, 0xe9, 0xd0, 0xc1, 0x31, 0x57, 0x6b, 0xc5, 0xb1, 0x21, 0xd6, 0x28, 0xe5, + 0x20, 0x7b, 0x07, 0x09, 0x50, 0xa6, 0x7a, 0x49, 0x11, 0x9c, 0x55, 0xe7, 0x6c, 0x0e, 0xd6, 0x1c, + 0x3c, 0x42, 0x73, 0x06, 0xf3, 0x71, 0x0a, 0x83, 0x22, 0x8b, 0xdd, 0xe5, 0x0e, 0x9c, 0x96, 0x8c, + 0x0b, 0x95, 0x63, 0xcf, 0x97, 0x2b, 0x56, 0x2b, 0x4e, 0x6c, 0x73, 0xb6, 0x51, 0xaa, 0xc3, 0xdb, + 0x8c, 0xe5, 0x1c, 0x7f, 0x96, 0x81, 0xa2, 0x37, 0x05, 0xfa, 0x41, 0xe6, 0x11, 0x23, 0x7b, 0xed, + 0x5d, 0xbf, 0xd3, 0x99, 0x92, 0x14, 0xc6, 0x5f, 0xe2, 0x0c, 0xc7, 0xa3, 0xbd, 0xdd, 0x77, 0xef, + 0x70, 0xb8, 0xba, 0x73, 0x7f, 0x50, 0x60, 0x1f, 0x3e, 0x96, 0x99, 0x3c, 0xb6, 0x17, 0x3c, 0x9b, + 0x44, 0x95, 0x30, 0x96, 0xfd, 0x60, 0xe7, 0xd5, 0xd8, 0xee, 0xec, 0xf3, 0x41, 0xe3, 0x08, 0x1c, + 0xc0, 0x91, 0x8b, 0x2c, 0x60, 0x28, 0x5e, 0x86, 0x7f, 0xce, 0xa1, 0x78, 0xb8, 0x52, 0x05, 0x53, + 0xbe, 0xb5, 0x61, 0xa7, 0x0b, 0x26, 0x8c, 0x25, 0x3e, 0xb1, 0xc6, 0xb8, 0x22, 0x38, 0x26, 0xe0, + 0x94, 0x13, 0xc9, 0x1c, 0xe2, 0xa6, 0x93, 0x3b, 0x8f, 0x70, 0x81, 0x3f, 0x43, 0x29, 0xa6, 0x48, + 0x26, 0x17, 0x10, 0x54, 0xbc, 0xc6, 0x14, 0xa9, 0x09, 0xa3, 0xdf, 0x6f, 0x71, 0x33, 0xed, 0xd9, + 0x59, 0x11, 0x2e, 0xff, 0x31, 0x35, 0xe1, 0xb9, 0x82, 0x9a, 0x49, 0xb0, 0x1e, 0x3f, 0xb3, 0x54, + 0x6a, 0x66, 0x87, 0x25, 0x3e, 0xb0, 0xd7, 0x22, 0xd3, 0x29, 0x0c, 0xaf, 0x49, 0x8a, 0x48, 0x4c, + 0x13, 0x84, 0xa9, 0x79, 0x7e, 0x71, 0x65, 0x90, 0xfd, 0x0a, 0xfe, 0x92, 0xea, 0x8e, 0x8b, 0xc8, + 0x10, 0x67, 0xa1, 0x10, 0x53, 0x48, 0x72, 0xa3, 0xed, 0x07, 0x90, 0x30, 0x12, 0x00, 0xab, 0x31, + 0xb4, 0xce, 0x59, 0x04, 0x3f, 0x08, 0xe1, 0xe7, 0xa1, 0x0d, 0xe1, 0x49, 0x98, 0xc7, 0x8b, 0x10, + 0x08, 0xe8, 0xd3, 0xc1, 0xd1, 0x72, 0x4b, 0xe3, 0x29, 0xa1, 0x7c, 0x86, 0xef, 0x76, 0xc7, 0x59, + 0xa8, 0x34, 0x19, 0x33, 0x26, 0x36, 0xca, 0x27, 0x4f, 0x78, 0x8c, 0xf5, 0x23, 0x6a, 0x95, 0x63, + 0x86, 0xcd, 0x86, 0x99, 0x8f, 0x31, 0x60, 0x30, 0x49, 0x97, 0x01, 0x1b, 0xe2, 0x6f, 0x01, 0x78, + 0xa4, 0x5d, 0x82, 0x2d, 0x1b, 0xae, 0x5a, 0x8b, 0x0d, 0x2f, 0x4f, 0x9e, 0xeb, 0x31, 0xd4, 0xdf, + 0x78, 0xa9, 0x0d, 0xfe, 0x06, 0x7d, 0x72, 0x78, 0xb4, 0x65, 0xef, 0x5d, 0xbf, 0xbd, 0xe9, 0xc2, + 0x08, 0x8c, 0x37, 0x45, 0x38, 0x40, 0x21, 0x2e, 0x2f, 0x6d, 0x42, 0x6a, 0x84, 0x7c, 0x74, 0x77, + 0x9b, 0xa3, 0x8b, 0xe1, 0x4b, 0x42, 0xef, 0xe8, 0xc0, 0xc8, 0x1c, 0xb3, 0xe2, 0xf6, 0x25, 0x99, + 0x5f, 0xe8, 0xe0, 0xd7, 0xd3, 0xcb, 0xe6, 0x6f, 0xc3, 0x17, 0xf1, 0xed, 0xd1, 0xc1, 0x3f, 0x0b, + 0x2e, 0x00, 0x5d, 0xf3, 0x98, 0x4f, 0x9b, 0x17, 0x91, 0x0e, 0x01, 0xe2, 0x65, 0x13, 0x7c, 0x7d, + 0xd6, 0x78, 0xd5, 0xff, 0x6f, 0x56, 0xfb, 0x0c, 0x8a, 0x17, 0xf9, 0x04, 0x02, 0x34, 0x66, 0xf9, + 0xf4, 0x97, 0x37, 0xa8, 0xf2, 0x19, 0xb9, 0x4a, 0xe1, 0x5b, 0x3a, 0xb8, 0x5c, 0xf0, 0x84, 0x4f, + 0x67, 0xfa, 0x0c, 0x58, 0xfa, 0xfb, 0x5a, 0x81, 0x5c, 0xae, 0x9e, 0x17, 0x68, 0xac, 0x78, 0xec, + 0xea, 0x12, 0x02, 0x08, 0x46, 0xdb, 0x9d, 0x95, 0xd4, 0x3a, 0xe1, 0xac, 0x8a, 0x01, 0x2f, 0x21, + 0xff, 0xef, 0x7b, 0x4e, 0x0b, 0x2f, 0x71, 0x48, 0x79, 0x78, 0xd3, 0xc3, 0x6b, 0x1f, 0xfe, 0x3a, + 0xff, 0x5f, 0xa1, 0xa9, 0x22, 0x71, 0xad, 0x17, 0x00, 0x00 }; // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const uint16_t PAGE_settings_leds_length = 8389; const uint8_t PAGE_settings_leds[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xdd, 0x7d, 0xeb, 0x76, 0xda, 0xc8, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xdd, 0x7d, 0xeb, 0x76, 0xda, 0xc8, 0x96, 0xf0, 0x7f, 0x9e, 0x42, 0xae, 0xee, 0x76, 0xa4, 0x46, 0x80, 0x84, 0xc1, 0x4d, 0x00, 0xe1, 0x31, 0x76, 0x92, 0xf6, 0x1c, 0x3b, 0xf6, 0xb2, 0x9d, 0xce, 0x99, 0x95, 0xce, 0x74, 0x84, 0x28, 0x40, 0xb1, 0x90, 0x38, 0x92, 0xf0, 0x65, 0x6c, 0xcf, 0x33, 0xcd, 0x33, 0xcc, 0x93, 0x7d, 0x7b, @@ -832,7 +834,7 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { // Autogenerated from wled00/data/settings_dmx.htm, do not edit!! const uint16_t PAGE_settings_dmx_length = 1740; const uint8_t PAGE_settings_dmx[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x95, 0x57, 0x5b, 0x73, 0xdb, 0x36, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x57, 0x5b, 0x73, 0xdb, 0x36, 0x16, 0x7e, 0xd7, 0xaf, 0x40, 0xf0, 0x50, 0x93, 0x23, 0x86, 0x94, 0x9c, 0x75, 0xb7, 0x91, 0x45, 0x7a, 0x63, 0xc5, 0x6b, 0x7b, 0xc7, 0x76, 0x3d, 0x51, 0xd2, 0x74, 0xa7, 0xe9, 0x74, 0x20, 0x12, 0x12, 0x51, 0x93, 0x04, 0x17, 0x00, 0x25, 0xbb, 0x69, 0xfe, 0xfb, 0x7e, 0x00, 0xa9, 0x8b, 0x6f, @@ -947,7 +949,7 @@ const uint8_t PAGE_settings_dmx[] PROGMEM = { // Autogenerated from wled00/data/settings_ui.htm, do not edit!! const uint16_t PAGE_settings_ui_length = 3345; const uint8_t PAGE_settings_ui[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x5a, 0x6b, 0x73, 0xd3, 0x48, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xad, 0x5a, 0x6b, 0x73, 0xd3, 0x48, 0x16, 0xfd, 0xee, 0x5f, 0xd1, 0x34, 0x54, 0xc6, 0xaa, 0x08, 0xc5, 0x81, 0xa9, 0x5a, 0xb0, 0x2d, 0x67, 0x49, 0x60, 0x20, 0x53, 0x61, 0x60, 0x71, 0x58, 0x66, 0x8a, 0xa5, 0x32, 0xb2, 0xd4, 0xb6, 0x9b, 0xc8, 0x6a, 0x8d, 0xba, 0x15, 0x27, 0x6b, 0xfc, 0xdf, 0xf7, 0xdc, 0x6e, 0xc9, 0x96, 0x9d, @@ -1163,7 +1165,7 @@ const uint8_t PAGE_settings_ui[] PROGMEM = { // Autogenerated from wled00/data/settings_sync.htm, do not edit!! const uint16_t PAGE_settings_sync_length = 3465; const uint8_t PAGE_settings_sync[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x9d, 0x5a, 0xeb, 0x53, 0xdb, 0x48, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x9d, 0x5a, 0xeb, 0x53, 0xdb, 0x48, 0x12, 0xff, 0xae, 0xbf, 0x62, 0xa2, 0xab, 0xca, 0xda, 0x8b, 0xf1, 0x03, 0x30, 0x21, 0x60, 0x29, 0x07, 0x98, 0x80, 0xef, 0x20, 0x71, 0x6c, 0xd8, 0x64, 0xeb, 0xee, 0x6a, 0x6b, 0x2c, 0x8d, 0xed, 0x01, 0x49, 0xa3, 0xd5, 0x8c, 0x78, 0x54, 0x36, 0xff, 0xfb, 0x75, 0xcf, 0x48, 0xb2, 0x2d, 0xfc, @@ -1386,7 +1388,7 @@ const uint8_t PAGE_settings_sync[] PROGMEM = { // Autogenerated from wled00/data/settings_time.htm, do not edit!! const uint16_t PAGE_settings_time_length = 3437; const uint8_t PAGE_settings_time[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xd5, 0x1a, 0x6b, 0x77, 0xda, 0x38, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xd5, 0x1a, 0x6b, 0x77, 0xda, 0x38, 0xf6, 0x3b, 0xbf, 0x42, 0x51, 0x7b, 0x32, 0x78, 0x62, 0x9e, 0x09, 0x6d, 0x02, 0x98, 0x2c, 0x21, 0xb4, 0x49, 0x0b, 0x24, 0xa7, 0xd0, 0xc9, 0x6e, 0x1f, 0x67, 0x2a, 0xb0, 0x00, 0x25, 0x46, 0x62, 0x6d, 0x39, 0x24, 0x9b, 0xe6, 0xbf, 0xef, 0x95, 0x64, 0xcc, 0xd3, 0x49, 0xdb, 0x99, 0xfd, 0xb0, @@ -1607,7 +1609,7 @@ const uint8_t PAGE_settings_time[] PROGMEM = { // Autogenerated from wled00/data/settings_sec.htm, do not edit!! const uint16_t PAGE_settings_sec_length = 2551; const uint8_t PAGE_settings_sec[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x9d, 0x58, 0x6d, 0x53, 0xdb, 0x48, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x9d, 0x58, 0x6d, 0x53, 0xdb, 0x48, 0x12, 0xfe, 0xee, 0x5f, 0x31, 0x9e, 0xad, 0xca, 0x4a, 0x17, 0x21, 0x03, 0x49, 0x6d, 0x25, 0x60, 0x99, 0x83, 0x40, 0x36, 0x5c, 0x41, 0xa0, 0xb0, 0xd9, 0xdc, 0x55, 0x2e, 0x95, 0x92, 0xa5, 0xb1, 0x35, 0xb1, 0xac, 0xd1, 0xce, 0x8c, 0x70, 0x7c, 0xd9, 0xfc, 0xf7, 0x7b, 0x7a, 0x24, 0xf9, 0x85, @@ -1773,7 +1775,7 @@ const uint8_t PAGE_settings_sec[] PROGMEM = { // Autogenerated from wled00/data/settings_um.htm, do not edit!! const uint16_t PAGE_settings_um_length = 3298; const uint8_t PAGE_settings_um[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x59, 0x6d, 0x73, 0xdb, 0x36, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xad, 0x59, 0x6d, 0x73, 0xdb, 0x36, 0x12, 0xfe, 0xae, 0x5f, 0x41, 0xa3, 0x19, 0x99, 0x1c, 0xd1, 0x94, 0x9c, 0xb6, 0x33, 0x39, 0x49, 0x94, 0x2e, 0x76, 0xdc, 0xc6, 0x97, 0x17, 0x7b, 0xe2, 0xa4, 0x99, 0x1b, 0xc7, 0x17, 0x53, 0x22, 0x24, 0x21, 0xa6, 0x08, 0x96, 0x00, 0xed, 0xf8, 0x64, 0xfd, 0xf7, 0x7b, 0x16, 0x20, 0x29, 0xca, @@ -1986,7 +1988,7 @@ const uint8_t PAGE_settings_um[] PROGMEM = { // Autogenerated from wled00/data/settings_2D.htm, do not edit!! const uint16_t PAGE_settings_2D_length = 3288; const uint8_t PAGE_settings_2D[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xc5, 0x5a, 0x5b, 0x77, 0xdb, 0x36, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xc5, 0x5a, 0x5b, 0x77, 0xdb, 0x36, 0x12, 0x7e, 0xd7, 0xaf, 0x80, 0xb1, 0x5d, 0x97, 0xb4, 0xa8, 0x9b, 0x9b, 0xf6, 0xb4, 0x92, 0x28, 0x6d, 0x1c, 0xa7, 0xb1, 0xf7, 0xd8, 0x89, 0x8e, 0xe5, 0xc6, 0xc9, 0x69, 0x7b, 0x36, 0x34, 0x09, 0x49, 0x48, 0x28, 0x80, 0x25, 0x40, 0xd9, 0xae, 0xe3, 0xff, 0xbe, 0x33, 0x00, 0x6f, 0xba, 0xd8, @@ -2198,7 +2200,7 @@ const uint8_t PAGE_settings_2D[] PROGMEM = { // Autogenerated from wled00/data/settings_pin.htm, do not edit!! const uint16_t PAGE_settings_pin_length = 461; const uint8_t PAGE_settings_pin[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x5d, 0x92, 0x4b, 0x6f, 0x13, 0x31, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x5d, 0x92, 0x4b, 0x6f, 0x13, 0x31, 0x14, 0x85, 0xf7, 0xf3, 0x2b, 0xcc, 0xdd, 0x34, 0x41, 0xc9, 0x4c, 0xa9, 0x58, 0x80, 0x6a, 0x8f, 0x50, 0xa0, 0x0b, 0x36, 0xa5, 0x52, 0xd9, 0xa0, 0xaa, 0xaa, 0x1c, 0xfb, 0x4e, 0x62, 0xd5, 0x8f, 0xc1, 0x8f, 0x84, 0x80, 0xfa, 0xdf, 0xb9, 0x9e, 0x29, 0x05, 0xb1, 0x19, 0xfb, 0x5c, 0xdb, 0xe7, diff --git a/wled00/set.cpp b/wled00/set.cpp index e61da0902..85a3b4ecd 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -32,6 +32,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (passlen == 0 || (passlen > 7 && !isAsterisksOnly(request->arg(F("AP")).c_str(), 65))) strlcpy(apPass, request->arg(F("AP")).c_str(), 65); int t = request->arg(F("AC")).toInt(); if (t > 0 && t < 14) apChannel = t; + force802_3g = request->hasArg(F("FG")); noWifiSleep = request->hasArg(F("WS")); #ifndef WLED_DISABLE_ESPNOW diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 505160215..8ba6b1a56 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -683,7 +683,7 @@ void WLED::initConnection() WiFi.disconnect(true); // close old connections #ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); + WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); #endif if (staticIP[0] != 0 && staticGateway[0] != 0) { diff --git a/wled00/wled.h b/wled00/wled.h index 20ceb2d38..03e197071 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -315,6 +315,7 @@ WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling #else WLED_GLOBAL bool noWifiSleep _INIT(false); #endif +WLED_GLOBAL bool force802_3g _INIT(false); #ifdef WLED_USE_ETHERNET #ifdef WLED_ETH_DEFAULT // default ethernet board type if specified diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 38413383b..c7a03cf90 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -277,6 +277,7 @@ void getSettingsJS(byte subPage, char* dest) sappends('s',SET_F("AP"),fapass); sappend('v',SET_F("AC"),apChannel); + sappend('c',SET_F("FG"),force802_3g); sappend('c',SET_F("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW From a3a5bffce41c39653adf405557c82726a3c8043b Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sun, 24 Dec 2023 12:10:51 -0500 Subject: [PATCH 021/694] Update readme.md Fixed typo --- usermods/quinled-an-penta/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/quinled-an-penta/readme.md b/usermods/quinled-an-penta/readme.md index 2338747d6..c1260d913 100644 --- a/usermods/quinled-an-penta/readme.md +++ b/usermods/quinled-an-penta/readme.md @@ -2,7 +2,7 @@ The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), e.g. using the OLED and the SHT30 temperature/humidity sensor. ## Requirements -* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 +* "u8g2" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 * "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85 ## Usermod installation From 5ebc345e95e2a68d0799d23acf7acc27d94b06a9 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 25 Dec 2023 17:47:39 +0100 Subject: [PATCH 022/694] Possible bugfix for #3609 #3616 --- wled00/FX_fcn.cpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 08024fa9c..2a3c51de9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -90,18 +90,21 @@ Segment::Segment(const Segment &orig) { //DEBUG_PRINTF("-- Copy segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); _t = nullptr; // copied segment cannot be in transition - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } else { name = nullptr; } - if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } else { data = nullptr; _dataLen = 0; } + name = nullptr; + data = nullptr; + _dataLen = 0; + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } // move constructor Segment::Segment(Segment &&orig) noexcept { //DEBUG_PRINTF("-- Move segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); + orig._t = nullptr; // old segment cannot be in transition any more orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; // old segment cannot be in transition any more } // copy assignment @@ -110,14 +113,7 @@ Segment& Segment::operator= (const Segment &orig) { if (this != &orig) { // clean destination if (name) { delete[] name; name = nullptr; } - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } - if (_t) { - #ifndef WLED_DISABLE_MODE_BLEND - if (_t->_segT._dataT) free(_t->_segT._dataT); - #endif - delete _t; - _t = nullptr; // copied segment cannot be in transition - } + stopTransition(); deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -125,6 +121,7 @@ Segment& Segment::operator= (const Segment &orig) { data = nullptr; _dataLen = 0; // copy source data + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } } return *this; @@ -135,13 +132,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { //DEBUG_PRINTF("-- Moving segment: %p -> %p\n", &orig, this); if (this != &orig) { if (name) { delete[] name; name = nullptr; } // free old name - if (_t) { - #ifndef WLED_DISABLE_MODE_BLEND - if (_t->_segT._dataT) free(_t->_segT._dataT); - #endif - delete _t; - _t = nullptr; - } + stopTransition(); deallocateData(); // free old runtime data memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; @@ -153,10 +144,13 @@ Segment& Segment::operator= (Segment &&orig) noexcept { } bool Segment::allocateData(size_t len) { - if (data && _dataLen == len) return true; //already allocated + if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) + if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation + return true; + } //DEBUG_PRINTF("-- Allocating data (%d): %p\n", len, this); deallocateData(); - if (len == 0) return(false); // nothing to do + if (len == 0) return false; // nothing to do if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { // not enough memory DEBUG_PRINT(F("!!! Effect RAM depleted: ")); From 010c3494fdc7c17d638c735460b43beefa389d74 Mon Sep 17 00:00:00 2001 From: Istvan Meszaros Date: Tue, 26 Dec 2023 11:09:20 +0100 Subject: [PATCH 023/694] Make palette editor mobile friendly. --- wled00/data/cpal/cpal.htm | 32 +- wled00/html_cpal.h | 604 +++++++++++++++++++------------------- 2 files changed, 333 insertions(+), 303 deletions(-) diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index e9a3799c8..d91b8591f 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -1,6 +1,7 @@ + @@ -45,6 +46,7 @@ width: 7px; top: 50%; transform: translateY(-50%); + touch-action: none; } .color-picker-marker { height: 7px; @@ -94,9 +96,14 @@ line-height: 1; } .wrap { - width: 800px; + width: 100%; margin: 0 auto; } + @media (min-width: 800px) { + .wrap { + width: 800px; + } + } .palette { height: 20px; } @@ -136,6 +143,9 @@ .sendSpan, .editSpan{ cursor: pointer; } + h1 { + font-size: 1.6rem; + } @@ -349,24 +359,31 @@ var gradientLength = maxX - minX + 1; elmnt.onmousedown = dragMouseDown; + elmnt.ontouchstart = dragMouseDown; function dragMouseDown(e) { removeTrashcan(event) e = e || window.event; - e.preventDefault(); + var isTouch = e.type.startsWith('touch'); + if (!isTouch) e.preventDefault(); // get the mouse cursor position at startup: - mousePos = e.clientX; + mousePos = isTouch ? e.touches[0].clientX : e.clientX; d.onmouseup = closeDragElement; + d.ontouchcancel = closeDragElement; + d.ontouchend = closeDragElement; // call a function whenever the cursor moves: d.onmousemove = elementDrag; + d.ontouchmove = elementDrag; } function elementDrag(e) { e = e || window.event; - e.preventDefault(); + var isTouch = e.type.startsWith('touch'); + if (!isTouch) e.preventDefault(); // calculate the new cursor position: - posNew = mousePos - e.clientX; - mousePos = e.clientX; + var clientX = isTouch ? e.touches[0].clientX : e.clientX; + posNew = mousePos - clientX; + mousePos = clientX; mousePosInGradient = mousePos - (minX + 1) truePos = Math.round((mousePosInGradient/gradientLength)*256); @@ -393,7 +410,10 @@ function closeDragElement() { /* stop moving when mouse button is released:*/ d.onmouseup = null; + d.ontouchcancel = null; + d.ontouchend = null; d.onmousemove = null; + d.ontouchmove = null; } } diff --git a/wled00/html_cpal.h b/wled00/html_cpal.h index a20096397..b4ac5684c 100644 --- a/wled00/html_cpal.h +++ b/wled00/html_cpal.h @@ -7,302 +7,312 @@ */ // Autogenerated from wled00/data/cpal/cpal.htm, do not edit!! -const uint16_t PAGE_cpal_L = 4721; +const uint16_t PAGE_cpal_L = 4891; const uint8_t PAGE_cpal[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xbd, 0x3b, 0x7f, 0x73, 0xdb, 0xb6, - 0x92, 0xff, 0xe7, 0x53, 0x20, 0x4c, 0x5f, 0x42, 0xd6, 0x14, 0x45, 0xd2, 0xb6, 0x64, 0x4b, 0xa2, - 0x3b, 0xa9, 0x93, 0x77, 0xce, 0x8d, 0xdd, 0x64, 0x5e, 0x7c, 0x6e, 0x7b, 0x3e, 0xbf, 0x31, 0x4d, - 0x42, 0x12, 0x1b, 0x8a, 0xe0, 0x03, 0x21, 0xd9, 0xae, 0xac, 0xef, 0x7e, 0xbb, 0x00, 0x48, 0x91, - 0x94, 0xe4, 0x24, 0xd7, 0x37, 0xd7, 0xf1, 0x44, 0x20, 0xb0, 0x58, 0xec, 0x2e, 0xf6, 0x17, 0x16, - 0xe8, 0xe8, 0xe5, 0xbb, 0x8f, 0xa7, 0x97, 0xbf, 0x7f, 0x7a, 0x4f, 0xa6, 0x62, 0x96, 0x9e, 0x90, - 0x51, 0xf9, 0x43, 0xc3, 0x18, 0x7e, 0x66, 0x54, 0x84, 0x30, 0x22, 0xf2, 0x0e, 0xfd, 0xd7, 0x3c, - 0x59, 0x04, 0xc6, 0x69, 0x18, 0x4d, 0x69, 0xe7, 0x94, 0x65, 0x82, 0xb3, 0xd4, 0x20, 0x2f, 0x22, - 0x68, 0xd1, 0x4c, 0x04, 0x46, 0xc6, 0x3a, 0x11, 0x8e, 0xd9, 0x04, 0x5a, 0x85, 0x60, 0x1c, 0x5a, - 0xb3, 0x79, 0x21, 0x3a, 0x9c, 0x2e, 0xc2, 0x34, 0x89, 0x43, 0x41, 0x8d, 0x6d, 0x08, 0x3f, 0xf1, - 0x70, 0x32, 0x0b, 0xb7, 0x61, 0xda, 0x0a, 0xfe, 0xfe, 0x21, 0x4f, 0x38, 0x2d, 0x0c, 0x52, 0x81, - 0xbb, 0x08, 0x27, 0x12, 0x91, 0xd2, 0x93, 0x17, 0xbf, 0x9e, 0xbf, 0x7f, 0x47, 0x4e, 0x61, 0x55, - 0x36, 0x23, 0x9f, 0xc2, 0x94, 0x0a, 0x41, 0xc9, 0xfb, 0x38, 0x01, 0x6a, 0x46, 0x5d, 0x05, 0x42, - 0x46, 0x45, 0xc4, 0x93, 0x5c, 0x10, 0xf1, 0x98, 0xd3, 0xc0, 0x10, 0xf4, 0x41, 0x74, 0xff, 0x08, - 0x17, 0xa1, 0xea, 0x35, 0x4e, 0x5e, 0x8c, 0xe7, 0x59, 0x24, 0x12, 0x96, 0x91, 0xc9, 0x87, 0xd8, - 0xa4, 0xd6, 0x92, 0x53, 0x31, 0xe7, 0x19, 0x89, 0x9d, 0x09, 0x15, 0xef, 0x53, 0x3a, 0x83, 0x35, - 0x7f, 0x7e, 0x94, 0x43, 0xab, 0x0a, 0x34, 0x7a, 0xdf, 0x80, 0x8c, 0x38, 0x05, 0x6e, 0x35, 0x30, - 0x02, 0x2e, 0x42, 0x4e, 0xe2, 0x20, 0x66, 0xd1, 0x1c, 0x7b, 0x5e, 0x8c, 0xba, 0x6a, 0x35, 0x24, - 0x46, 0x3c, 0x22, 0xdd, 0x77, 0x2c, 0x7e, 0x5c, 0x8e, 0x81, 0xa3, 0xce, 0x38, 0x9c, 0x25, 0xe9, - 0xe3, 0xe0, 0x2d, 0x4f, 0xc2, 0xd4, 0x2e, 0xc2, 0xac, 0xe8, 0x14, 0x94, 0x27, 0xe3, 0xe1, 0x5d, - 0x18, 0x7d, 0x99, 0x70, 0x36, 0xcf, 0xe2, 0x4e, 0xc4, 0x52, 0xc6, 0x07, 0xaf, 0x3c, 0xcf, 0x1b, - 0xca, 0x29, 0x45, 0xf2, 0x27, 0x1d, 0x78, 0xbd, 0xfc, 0x61, 0xa8, 0x47, 0xe2, 0x38, 0x1e, 0xce, - 0x42, 0x3e, 0x49, 0xb2, 0x81, 0x4b, 0x3c, 0x17, 0x06, 0xd2, 0x24, 0xa3, 0x9d, 0x29, 0x4d, 0x26, - 0x53, 0x31, 0x70, 0x0e, 0x57, 0xaf, 0xf2, 0x90, 0x03, 0x21, 0x1d, 0x94, 0x61, 0x08, 0x43, 0x7c, - 0x99, 0xb3, 0x22, 0x41, 0x56, 0x06, 0x9c, 0xa6, 0xa1, 0x48, 0x16, 0x74, 0x78, 0x9f, 0xc4, 0x62, - 0x3a, 0xf0, 0x5c, 0xf7, 0x6f, 0x43, 0x3d, 0xd1, 0x07, 0x4c, 0xab, 0x57, 0x77, 0x4c, 0x80, 0x74, - 0x4f, 0x37, 0x67, 0x86, 0x77, 0x05, 0x4b, 0xe7, 0x82, 0xea, 0xa5, 0x3b, 0x82, 0xe5, 0x83, 0x43, - 0x39, 0x65, 0xc2, 0xc3, 0x38, 0xc1, 0xf5, 0xee, 0xd8, 0xc3, 0x72, 0x13, 0x2f, 0xb6, 0x57, 0x8e, - 0xa4, 0xbd, 0x03, 0x73, 0xbf, 0x50, 0x6e, 0xeb, 0xaf, 0x3c, 0x89, 0xe0, 0x4b, 0x77, 0x6e, 0x59, - 0xe9, 0x8e, 0xf1, 0x18, 0xc6, 0x11, 0xfd, 0xbc, 0x18, 0xec, 0x03, 0xa3, 0x1b, 0x62, 0x2a, 0x92, - 0x74, 0x41, 0xb9, 0x86, 0x1c, 0xf8, 0xf9, 0x03, 0x81, 0xb9, 0x49, 0x4c, 0xf8, 0xe4, 0x2e, 0x34, - 0x7b, 0x47, 0xb6, 0xfa, 0x73, 0x0e, 0xad, 0xe1, 0x9f, 0x9d, 0x24, 0x8b, 0xe9, 0xc3, 0xc0, 0x6f, - 0xd2, 0xb2, 0xd4, 0x54, 0xee, 0xa3, 0x1c, 0x15, 0xf1, 0x7d, 0x68, 0x29, 0xee, 0xfe, 0x36, 0x14, - 0x1c, 0xf6, 0x68, 0xcc, 0xf8, 0x6c, 0x20, 0x5b, 0x20, 0x3c, 0xfa, 0xbb, 0xd9, 0x81, 0x11, 0x6b, - 0xb5, 0x95, 0x09, 0x8d, 0xad, 0xbf, 0x81, 0xcc, 0x3b, 0x44, 0x29, 0xc4, 0x14, 0x94, 0x96, 0xee, - 0xe6, 0x58, 0x4f, 0x3f, 0xac, 0xa6, 0x63, 0xeb, 0x1b, 0xc4, 0xf0, 0x6a, 0x3c, 0x1e, 0x97, 0x42, - 0xd8, 0xaf, 0x84, 0xf0, 0xea, 0xf8, 0xce, 0x3f, 0xf2, 0x8f, 0xe4, 0xfa, 0xbe, 0x0f, 0xdc, 0x6c, - 0xc8, 0x40, 0x11, 0xbf, 0x9b, 0x10, 0xaf, 0x22, 0xc4, 0xab, 0x08, 0x91, 0xcd, 0x92, 0xa5, 0x0a, - 0xa5, 0x57, 0x92, 0x59, 0x53, 0xdf, 0xad, 0x4a, 0xbd, 0x72, 0xee, 0xe6, 0xa0, 0x62, 0x59, 0x94, - 0x86, 0x45, 0xb1, 0xcc, 0xc3, 0x38, 0x4e, 0xb2, 0xc9, 0xc0, 0xad, 0x34, 0x7a, 0x08, 0xfb, 0x29, - 0x92, 0x28, 0x4c, 0x3b, 0xe0, 0x56, 0x26, 0xd9, 0x40, 0x29, 0xe4, 0x0e, 0x5c, 0x6d, 0x75, 0x25, - 0x45, 0x1e, 0x66, 0xcb, 0x38, 0x29, 0xf2, 0x34, 0x7c, 0x1c, 0x24, 0x99, 0x34, 0x8c, 0x71, 0x4a, - 0x1f, 0x86, 0x12, 0x59, 0x27, 0x11, 0x74, 0x56, 0x0c, 0x22, 0x50, 0x56, 0x50, 0x9a, 0x9a, 0xe8, - 0x6a, 0x86, 0x06, 0x3a, 0xd4, 0x26, 0x61, 0x96, 0xc4, 0x71, 0x4a, 0x57, 0xaf, 0x92, 0x6c, 0xcc, - 0x2a, 0xe4, 0x86, 0x31, 0x44, 0xef, 0xa2, 0x41, 0xbe, 0x8a, 0x72, 0xd3, 0x02, 0x6b, 0x76, 0xb4, - 0x61, 0xc4, 0x20, 0xa5, 0x7b, 0x1e, 0xe6, 0xda, 0x9a, 0x8e, 0x5c, 0x1c, 0xaf, 0x4c, 0x3e, 0x9c, - 0x0b, 0xb6, 0x72, 0x72, 0xe5, 0xff, 0x96, 0x75, 0xeb, 0x2d, 0x3b, 0xff, 0x43, 0x5b, 0x64, 0xb1, - 0x44, 0xde, 0x61, 0x6f, 0x6a, 0x40, 0x9b, 0xea, 0x54, 0x4d, 0x2b, 0x2e, 0x40, 0x88, 0xcb, 0x96, - 0x7d, 0xd7, 0x3c, 0x85, 0x04, 0xbc, 0x64, 0x79, 0xb9, 0xe6, 0x38, 0x51, 0x3e, 0x06, 0x56, 0xfa, - 0x8b, 0xb2, 0x68, 0xf1, 0x0e, 0xcb, 0x94, 0x2c, 0x7c, 0x92, 0xae, 0xac, 0x92, 0xfa, 0xae, 0xbd, - 0xdc, 0x42, 0x51, 0x5b, 0xbe, 0xff, 0x56, 0x0a, 0x95, 0x0e, 0x17, 0xef, 0x92, 0xc5, 0x56, 0x6d, - 0xd3, 0x6b, 0xa7, 0x74, 0xdc, 0x30, 0x66, 0xb9, 0x47, 0x14, 0x02, 0xd6, 0x67, 0x50, 0x53, 0xdb, - 0x29, 0x68, 0x16, 0x63, 0x6b, 0x19, 0xcd, 0x79, 0x01, 0x94, 0xe4, 0x2c, 0x41, 0xba, 0x56, 0x18, - 0x41, 0x64, 0xe0, 0x20, 0xa3, 0xae, 0x0e, 0xd4, 0x18, 0x41, 0xe0, 0x27, 0x4e, 0x16, 0x24, 0x89, - 0x03, 0x03, 0x95, 0x03, 0x62, 0x24, 0x9a, 0x90, 0xfe, 0xd0, 0x83, 0x2f, 0xe4, 0xc4, 0xc0, 0x68, - 0xc8, 0xeb, 0x0f, 0x88, 0x98, 0xc9, 0xf8, 0xb1, 0x94, 0x8c, 0x66, 0x1f, 0xa7, 0x4c, 0xbd, 0xed, - 0x33, 0x36, 0x25, 0x8c, 0xd0, 0xc5, 0x62, 0x52, 0x81, 0x2b, 0x8e, 0xf6, 0x31, 0x2c, 0x95, 0x1e, - 0xb4, 0x57, 0x29, 0x69, 0x87, 0xcb, 0x1e, 0xe8, 0x30, 0xc8, 0x22, 0xa1, 0xf7, 0x3f, 0xb3, 0x07, - 0x08, 0xe4, 0xc4, 0x25, 0xfb, 0x3e, 0xfc, 0x19, 0x27, 0xa3, 0x3c, 0x14, 0x53, 0xf2, 0x62, 0x9c, - 0xa4, 0x69, 0x60, 0xbc, 0x72, 0xdd, 0x7d, 0xd8, 0x02, 0x03, 0x42, 0xa8, 0x71, 0xd1, 0x23, 0xbe, - 0x3f, 0x3d, 0x5a, 0x1c, 0x9c, 0xf5, 0xfe, 0xbc, 0xf0, 0x0e, 0x88, 0x77, 0x30, 0x3d, 0x58, 0x1c, - 0x4d, 0x3b, 0x07, 0xf0, 0x75, 0x04, 0xb1, 0xae, 0xfa, 0xf2, 0x7d, 0xd2, 0x43, 0xb8, 0x69, 0xe7, - 0xe8, 0x4f, 0xa3, 0x7b, 0x02, 0x02, 0x5b, 0x4c, 0x4e, 0x5e, 0x00, 0x89, 0x20, 0x4e, 0x29, 0x21, - 0x94, 0x9b, 0x71, 0xf2, 0x5c, 0xc2, 0x80, 0xa0, 0x52, 0xc2, 0x1e, 0xfe, 0x0b, 0xc2, 0x2b, 0x45, - 0x88, 0xd3, 0xdb, 0x11, 0xd4, 0xa8, 0x09, 0xbf, 0x1e, 0xef, 0x80, 0x17, 0x3d, 0xb5, 0x8e, 0xe1, - 0xfb, 0x36, 0xa1, 0xc4, 0x5b, 0x5a, 0x21, 0x26, 0x4b, 0x6a, 0x67, 0xeb, 0x76, 0xd9, 0x82, 0x04, - 0x33, 0xac, 0x14, 0x40, 0x7f, 0x02, 0xff, 0xa7, 0x73, 0x8e, 0x74, 0xa7, 0x8f, 0x24, 0xc9, 0xc8, - 0xbc, 0xa0, 0x24, 0x52, 0xbc, 0x97, 0x88, 0x48, 0x8b, 0xda, 0xbf, 0x4e, 0x34, 0xfa, 0x44, 0xb9, - 0x72, 0x0a, 0xa1, 0x84, 0x40, 0xb2, 0x24, 0xa6, 0x94, 0x94, 0x12, 0x22, 0x54, 0xca, 0x9a, 0x08, - 0x46, 0xc0, 0xcf, 0x93, 0x8c, 0xde, 0x13, 0x69, 0x73, 0xa4, 0x80, 0xf0, 0x04, 0x79, 0x00, 0x02, - 0xab, 0x19, 0xb2, 0x9b, 0xc6, 0x04, 0x44, 0x4a, 0xee, 0x68, 0xca, 0xee, 0x65, 0xaf, 0x02, 0xc3, - 0xe9, 0xd1, 0x34, 0xcc, 0x26, 0x94, 0x24, 0xa2, 0x50, 0xa0, 0x8e, 0x5e, 0x10, 0xa1, 0x9a, 0xf3, - 0x20, 0x1c, 0x81, 0xeb, 0xc6, 0x55, 0xcd, 0x30, 0x8b, 0x31, 0x8f, 0x1c, 0x27, 0x7c, 0x66, 0x21, - 0x12, 0x15, 0x7d, 0x1d, 0xf2, 0x31, 0x8b, 0x28, 0x19, 0x27, 0x59, 0x52, 0x4c, 0x69, 0x6c, 0x83, - 0x14, 0x4b, 0x4c, 0x21, 0xe7, 0x88, 0x21, 0x42, 0x36, 0x18, 0x99, 0xe7, 0x29, 0x0b, 0x63, 0x40, - 0x08, 0x6d, 0x1c, 0x8d, 0x69, 0x91, 0xe0, 0x5a, 0x45, 0xca, 0x84, 0x43, 0x2e, 0x99, 0xe4, 0x8e, - 0xd0, 0x87, 0x04, 0x64, 0x94, 0x4d, 0x4a, 0x19, 0xd7, 0xf1, 0xe5, 0x34, 0x8b, 0x92, 0x54, 0x22, - 0x74, 0xc8, 0x8b, 0x2d, 0x42, 0xff, 0x7e, 0x99, 0x4b, 0xed, 0x2c, 0x04, 0x38, 0xa5, 0xe8, 0x53, - 0xa5, 0x2f, 0x5f, 0x51, 0x17, 0x04, 0xdf, 0xa9, 0x32, 0x6f, 0x17, 0x61, 0x92, 0x86, 0x77, 0x29, - 0x48, 0x5b, 0x62, 0xfd, 0x9a, 0xae, 0xc8, 0x9f, 0x51, 0x57, 0x3b, 0x24, 0x9d, 0x6d, 0xbf, 0xd8, - 0x95, 0x6e, 0x63, 0x6a, 0x5c, 0x6a, 0x03, 0x7a, 0x01, 0xcc, 0xba, 0x9b, 0x06, 0x64, 0xd9, 0x11, - 0xac, 0x18, 0x05, 0x1d, 0xcf, 0xce, 0x1f, 0x4e, 0x59, 0x1a, 0x2c, 0x57, 0xb6, 0xd0, 0xbf, 0x9c, - 0x46, 0x22, 0xa8, 0x4d, 0xc7, 0x24, 0xfd, 0x67, 0xcc, 0x01, 0x40, 0xde, 0xb0, 0xff, 0xd0, 0xf9, - 0x0f, 0x80, 0x30, 0x2d, 0xbb, 0x84, 0x39, 0xa7, 0xd9, 0x44, 0x4c, 0x03, 0x9c, 0xe7, 0x48, 0x0f, - 0x65, 0xcf, 0x3e, 0x8e, 0xc7, 0x45, 0x70, 0x01, 0xfe, 0xc6, 0x91, 0xd9, 0x83, 0xd9, 0x04, 0xed, - 0xfa, 0x87, 0xbd, 0xae, 0x6f, 0x75, 0x0e, 0x6d, 0xcd, 0xf6, 0x5b, 0xce, 0xc3, 0xc7, 0xe0, 0xfa, - 0xc6, 0x06, 0x87, 0xf2, 0x39, 0x5c, 0xd0, 0xe0, 0x8d, 0x74, 0x7b, 0x0d, 0xaf, 0xe7, 0x1f, 0xae, - 0xbd, 0x1e, 0xb6, 0x5b, 0x4e, 0xce, 0x3f, 0x80, 0xbf, 0xd2, 0xc9, 0x49, 0x1f, 0x87, 0x21, 0x46, - 0xba, 0x37, 0xdf, 0xb7, 0x3d, 0xff, 0xad, 0xe7, 0xda, 0x1e, 0x02, 0xc2, 0x0f, 0xf1, 0x7c, 0xdb, - 0x6f, 0xf6, 0x6c, 0x05, 0x69, 0x42, 0x20, 0xc8, 0x45, 0x1f, 0xfe, 0x39, 0x87, 0x31, 0xaf, 0x7f, - 0xe5, 0x1d, 0x9c, 0x79, 0xbd, 0x2b, 0xcf, 0x3d, 0xf3, 0xfc, 0xab, 0xfe, 0x39, 0x0e, 0xfc, 0x77, - 0xe5, 0x14, 0xdf, 0x20, 0x27, 0xe8, 0xf3, 0xfe, 0xbd, 0x9c, 0x20, 0x51, 0xa7, 0x3d, 0xe7, 0xa0, - 0x6f, 0xfb, 0x40, 0x31, 0x36, 0x24, 0xe1, 0xa7, 0x48, 0x8f, 0x73, 0xb8, 0x4f, 0xd4, 0x90, 0xaf, - 0xf8, 0x3b, 0x95, 0x7d, 0xf8, 0xe9, 0x97, 0xe3, 0xbe, 0x82, 0xd6, 0x53, 0xf5, 0xb8, 0x84, 0xbe, - 0xf0, 0x0e, 0x1d, 0xcf, 0xee, 0x3b, 0x6e, 0xff, 0x14, 0x5a, 0xfe, 0x81, 0x6c, 0x12, 0x68, 0xee, - 0x1f, 0x41, 0xd3, 0xf3, 0xb1, 0x79, 0x08, 0x2d, 0x7f, 0xff, 0xdc, 0xeb, 0x39, 0xfd, 0xbe, 0x7d, - 0xe4, 0x1c, 0xc2, 0x02, 0xf0, 0xd3, 0x87, 0xb1, 0xbe, 0x7d, 0x2c, 0xc1, 0xe5, 0xc8, 0xb1, 0xe3, - 0x1f, 0x9d, 0x03, 0x38, 0x34, 0x3d, 0x57, 0xb6, 0xf7, 0x01, 0x08, 0x20, 0x71, 0xee, 0x01, 0x36, - 0x11, 0xcd, 0x29, 0x34, 0x8f, 0x7c, 0x8d, 0xfb, 0xc0, 0x39, 0xee, 0x55, 0x2b, 0x2a, 0x32, 0x2e, - 0x60, 0x96, 0xb7, 0x0f, 0xb3, 0x8e, 0x3c, 0x44, 0xe6, 0x1d, 0x23, 0xb2, 0xa3, 0xfe, 0xf9, 0x31, - 0xf6, 0xc2, 0x42, 0xc7, 0xfb, 0x67, 0x08, 0x76, 0x85, 0x68, 0xfa, 0xe7, 0x6b, 0xe0, 0xda, 0x1e, - 0x0c, 0xab, 0xb3, 0x24, 0xa8, 0xe6, 0xc7, 0xb1, 0x89, 0xa7, 0xc9, 0xff, 0x37, 0xd5, 0xae, 0x1d, - 0x64, 0xd3, 0xe4, 0xcb, 0xc7, 0xac, 0x4c, 0xad, 0xd4, 0xa1, 0x76, 0xc6, 0x16, 0xf4, 0x92, 0x87, - 0xc5, 0x34, 0x0a, 0x33, 0xe8, 0xb1, 0xc1, 0x51, 0x9f, 0x9a, 0x35, 0xa4, 0xd4, 0x61, 0xb0, 0x0c, - 0x15, 0xbf, 0x75, 0x9b, 0xe8, 0x7f, 0x04, 0xf4, 0x56, 0xed, 0x90, 0x2c, 0xe7, 0x51, 0x1b, 0x4e, - 0xec, 0x86, 0xb5, 0x04, 0x53, 0x22, 0x1c, 0x4d, 0x9a, 0x05, 0x2f, 0x3d, 0xc8, 0xb3, 0xb2, 0x42, - 0x90, 0xb0, 0xc1, 0xee, 0xbf, 0xe6, 0x94, 0x3f, 0x7e, 0x06, 0x87, 0x1c, 0x81, 0xab, 0x7e, 0x9b, - 0xa6, 0xa6, 0xd1, 0x38, 0x96, 0x19, 0xd6, 0x30, 0x19, 0x9b, 0xa1, 0x03, 0x47, 0xaf, 0xf7, 0x61, - 0x34, 0x35, 0x4d, 0x61, 0x73, 0x2b, 0x38, 0x59, 0x0a, 0x94, 0xd3, 0x5b, 0x21, 0x78, 0x02, 0x19, - 0x18, 0x35, 0x8d, 0x38, 0x14, 0x61, 0x47, 0xf0, 0x39, 0x85, 0x8c, 0xcd, 0xb0, 0x82, 0x80, 0xbe, - 0x7e, 0x6d, 0xc2, 0x9a, 0xae, 0xb5, 0x02, 0x4e, 0x9c, 0x54, 0x52, 0x7a, 0xe2, 0xf5, 0xcb, 0x5e, - 0x9b, 0x59, 0xea, 0x18, 0x8f, 0xd8, 0xe9, 0x89, 0xfb, 0xfa, 0x35, 0x1d, 0xf9, 0x87, 0x87, 0x16, - 0x2c, 0x63, 0xa2, 0xab, 0xca, 0x02, 0x6f, 0x98, 0x8d, 0x02, 0xaf, 0xf7, 0xfa, 0x35, 0x1f, 0x41, - 0x73, 0x6f, 0xcf, 0x92, 0x1e, 0x4b, 0x92, 0x76, 0xa1, 0x28, 0xdb, 0xcb, 0xac, 0xa7, 0x27, 0x93, - 0x07, 0x99, 0x35, 0xa4, 0x29, 0x84, 0x58, 0x1e, 0xd0, 0xa1, 0x61, 0x04, 0x81, 0x80, 0x45, 0x80, - 0xfb, 0x57, 0xc6, 0x9e, 0xe9, 0xf5, 0xfa, 0xfd, 0xbe, 0xef, 0x1d, 0xfe, 0xa8, 0xe4, 0x08, 0x71, - 0x88, 0xcd, 0x4c, 0x6b, 0x34, 0x72, 0x2d, 0x47, 0xb0, 0xcf, 0x40, 0x7c, 0x36, 0x01, 0x18, 0x0b, - 0xf2, 0xdc, 0xf8, 0xb3, 0x08, 0xb9, 0x30, 0x7b, 0xb6, 0xe1, 0x1a, 0x96, 0xa5, 0x25, 0x95, 0x06, - 0xd1, 0x7b, 0xd3, 0xc0, 0xfc, 0x04, 0xc4, 0x90, 0x3a, 0xd2, 0x65, 0xff, 0x12, 0xce, 0xc0, 0x6a, - 0x1b, 0x22, 0xb2, 0x53, 0x07, 0xbd, 0x7b, 0x83, 0x36, 0xbe, 0x5e, 0xc0, 0x82, 0xf1, 0x62, 0xb7, - 0xb0, 0x6c, 0xfa, 0x0c, 0x00, 0xe0, 0x34, 0x6c, 0xb1, 0x03, 0x40, 0xe9, 0x83, 0xa1, 0xf4, 0x0f, - 0x61, 0x60, 0xeb, 0xdf, 0x2f, 0x50, 0x31, 0x20, 0x12, 0x52, 0x48, 0x96, 0x40, 0x5e, 0x18, 0x02, - 0x0d, 0x1b, 0x72, 0x8f, 0xfc, 0xef, 0x73, 0x0e, 0xa1, 0x90, 0x7f, 0xe2, 0x2c, 0x97, 0xf8, 0xd0, - 0xfd, 0x38, 0x98, 0x18, 0x3f, 0xaf, 0xb9, 0x3f, 0x52, 0x6b, 0x4f, 0x2e, 0xb0, 0x67, 0x80, 0x5b, - 0xd2, 0x82, 0x49, 0xa4, 0x60, 0x92, 0x2c, 0x9f, 0x0b, 0x54, 0x10, 0x47, 0x45, 0x1d, 0x29, 0x00, - 0xc3, 0x4e, 0x9c, 0x45, 0x98, 0xce, 0x69, 0x20, 0xa0, 0xb5, 0x21, 0x32, 0x75, 0xd0, 0x45, 0xa0, - 0x4a, 0x64, 0x9f, 0x54, 0x57, 0x53, 0x64, 0xc9, 0x16, 0x66, 0xd4, 0x7a, 0xf6, 0x3c, 0xc7, 0x22, - 0x57, 0x69, 0x3c, 0xdb, 0x41, 0x35, 0xdf, 0x51, 0x7e, 0x9a, 0x7e, 0x29, 0x77, 0xb3, 0xa8, 0xef, - 0x66, 0xb1, 0x8b, 0xb4, 0x6a, 0x53, 0x8b, 0x36, 0x85, 0x5b, 0xb7, 0xb6, 0x78, 0x66, 0x71, 0x96, - 0xe2, 0xea, 0x00, 0x52, 0x93, 0x75, 0x5d, 0xf0, 0x40, 0xf9, 0x8e, 0x11, 0x4d, 0x71, 0x5c, 0xa7, - 0x98, 0xa3, 0x95, 0x70, 0xb4, 0x12, 0xd0, 0xef, 0xb8, 0x4e, 0x7e, 0xa3, 0x98, 0x61, 0xd8, 0xb1, - 0x24, 0x5c, 0x75, 0x6e, 0xa5, 0x39, 0xde, 0x4d, 0x33, 0x05, 0xd3, 0x56, 0x33, 0x4f, 0x91, 0x71, - 0x2c, 0xaa, 0x21, 0xfc, 0x0e, 0x32, 0xd7, 0x7a, 0xb4, 0xae, 0x0a, 0xc8, 0x79, 0x81, 0xd6, 0x81, - 0x8a, 0xf7, 0x5d, 0xe3, 0x75, 0x4f, 0x14, 0xe6, 0x90, 0xa6, 0xc5, 0xa7, 0xd3, 0x24, 0x8d, 0xcd, - 0xc4, 0xda, 0x39, 0x94, 0xee, 0x1e, 0x02, 0x23, 0x70, 0x5f, 0x06, 0xfc, 0xf5, 0x6b, 0x10, 0x92, - 0xfc, 0xdd, 0x05, 0x18, 0x5b, 0x76, 0x5d, 0x9c, 0xb3, 0xf0, 0x0b, 0xbd, 0xa0, 0xef, 0x78, 0x38, - 0x31, 0xd1, 0xcb, 0xa0, 0x39, 0x5b, 0xb0, 0x6f, 0x54, 0x5c, 0x32, 0x96, 0x8a, 0x24, 0x57, 0x52, - 0xac, 0x8f, 0x35, 0x75, 0xd0, 0xac, 0xb9, 0xdf, 0xf6, 0xc8, 0x52, 0x6d, 0x25, 0xfd, 0x4e, 0xa7, - 0xbb, 0x91, 0x82, 0xd1, 0x0d, 0x17, 0xac, 0x10, 0x33, 0x99, 0xca, 0xd1, 0x6b, 0x7e, 0x03, 0x94, - 0x39, 0x9c, 0x42, 0xfe, 0x1a, 0xd1, 0xa6, 0xa3, 0xb4, 0x1b, 0x76, 0x66, 0x59, 0x4a, 0xf6, 0xc3, - 0xef, 0x9b, 0xa7, 0xfb, 0x60, 0xf6, 0xf6, 0x1d, 0x65, 0xb6, 0xc4, 0xf5, 0xfc, 0xe0, 0x33, 0x4e, - 0x8e, 0x59, 0x55, 0x78, 0x92, 0xb0, 0xcf, 0x85, 0x17, 0x3b, 0xfb, 0x8a, 0xcf, 0x0a, 0xb5, 0x00, - 0xaf, 0xb3, 0x1b, 0x58, 0x1b, 0x45, 0x78, 0x1d, 0x42, 0x6b, 0xb5, 0x56, 0x1d, 0x65, 0x0c, 0x81, - 0x81, 0xa5, 0x82, 0x90, 0x77, 0xca, 0x6e, 0x13, 0x8e, 0x18, 0xf2, 0xcc, 0x6c, 0xd8, 0x1f, 0xef, - 0xfe, 0xc0, 0x10, 0x0f, 0x9d, 0x3c, 0xa1, 0x85, 0x29, 0xf1, 0x59, 0xeb, 0x4d, 0xb8, 0x86, 0x10, - 0x7b, 0x83, 0xdb, 0xd0, 0xc4, 0xb8, 0x17, 0xdc, 0xda, 0xe4, 0x87, 0xa5, 0x58, 0xc1, 0x3f, 0x74, - 0x95, 0x3f, 0xdc, 0x6e, 0xac, 0xb9, 0x17, 0x18, 0x96, 0xd1, 0x50, 0xe1, 0xb6, 0xcc, 0x82, 0xe6, - 0x84, 0xb5, 0x6e, 0xb5, 0xdc, 0x38, 0xe6, 0x0d, 0xd4, 0xc1, 0x4e, 0xfc, 0x0a, 0x27, 0x21, 0x02, - 0xd5, 0x75, 0x51, 0x79, 0xa0, 0xed, 0xe9, 0xc5, 0x96, 0x89, 0xb6, 0xd4, 0x08, 0xa7, 0xe0, 0x91, - 0xae, 0xa9, 0x37, 0x34, 0xa3, 0x52, 0x0a, 0xd4, 0x01, 0xe9, 0x2e, 0x1a, 0x4b, 0xe5, 0x3b, 0x56, - 0x5a, 0x48, 0x1f, 0xfd, 0x2c, 0x99, 0x35, 0xf3, 0x03, 0x04, 0x18, 0xfe, 0x45, 0xe0, 0xda, 0xfc, - 0x9b, 0xb2, 0x32, 0x16, 0x70, 0x47, 0x6e, 0x98, 0x1d, 0x42, 0x4b, 0x7a, 0xd5, 0x2c, 0x60, 0x9d, - 0x70, 0xcf, 0x5b, 0xa7, 0x7a, 0xa9, 0xc9, 0xad, 0x25, 0x24, 0x0a, 0xfc, 0xe9, 0xe9, 0x1e, 0x4e, - 0xa5, 0xec, 0xde, 0x51, 0x54, 0x39, 0x39, 0x97, 0x8d, 0x77, 0x74, 0x1c, 0xce, 0x53, 0xc4, 0x26, - 0x3a, 0x1c, 0x59, 0x83, 0xbe, 0xdf, 0x20, 0x81, 0x5a, 0xb7, 0x67, 0x0c, 0xce, 0xf0, 0x9f, 0x58, - 0xf1, 0xa1, 0xca, 0xd9, 0x02, 0xd1, 0x31, 0x61, 0x11, 0x98, 0x02, 0x4a, 0x09, 0x23, 0x75, 0x85, - 0xdc, 0x84, 0xee, 0x66, 0x32, 0x4b, 0xb3, 0x59, 0x1a, 0x5f, 0x6a, 0x78, 0xfa, 0xbc, 0x86, 0x6b, - 0xb4, 0xe8, 0xa4, 0x74, 0x53, 0xb9, 0xaa, 0x35, 0x86, 0x97, 0x81, 0x1e, 0xc0, 0x74, 0x47, 0x83, - 0xf4, 0x0e, 0x7e, 0x12, 0xd3, 0xa4, 0xf8, 0x28, 0x13, 0x83, 0xc0, 0x1d, 0x94, 0x58, 0xbc, 0x63, - 0xbf, 0x3e, 0xd0, 0x1f, 0xd4, 0x3e, 0xf6, 0xe5, 0xe6, 0x6c, 0x4b, 0x06, 0x32, 0x69, 0x4b, 0x1a, - 0x47, 0x3d, 0x0b, 0xd0, 0x8a, 0xf2, 0x7f, 0xf2, 0x1b, 0x72, 0x91, 0xfa, 0x8a, 0x5f, 0x45, 0xd6, - 0x88, 0x64, 0x7f, 0x01, 0x4f, 0xcb, 0x09, 0xee, 0x42, 0x43, 0x9f, 0x4d, 0xd6, 0x4a, 0x69, 0x6c, - 0x46, 0x07, 0xba, 0x19, 0x15, 0x6a, 0x3a, 0x9e, 0x40, 0x24, 0x88, 0x1d, 0x96, 0x49, 0xdd, 0x98, - 0xe7, 0x41, 0x36, 0x4f, 0x53, 0xbb, 0xea, 0x40, 0x93, 0x91, 0x5d, 0x2b, 0x5a, 0x76, 0x81, 0x9a, - 0x66, 0x41, 0x39, 0x7d, 0xb7, 0x61, 0x99, 0x34, 0xa0, 0x5f, 0x57, 0x6b, 0xe0, 0xb0, 0x54, 0xe5, - 0x3a, 0x11, 0x49, 0x8b, 0x82, 0x74, 0x55, 0xf3, 0x35, 0x9b, 0xfc, 0x2d, 0xdb, 0xa2, 0x91, 0x57, - 0x7d, 0x86, 0x7d, 0x0b, 0xae, 0xee, 0x59, 0x6d, 0x5e, 0x91, 0x01, 0x79, 0x06, 0x06, 0xfd, 0xbf, - 0xb5, 0xba, 0xad, 0x49, 0xab, 0x99, 0x82, 0x68, 0x97, 0x80, 0xb9, 0x50, 0x9c, 0x2c, 0x20, 0x38, - 0xa2, 0xf6, 0xbe, 0xab, 0x69, 0x45, 0x50, 0x77, 0x5a, 0x36, 0x8e, 0x9e, 0xae, 0x77, 0x5e, 0xc6, - 0xc7, 0xf6, 0x8c, 0x86, 0x9a, 0xa8, 0xd5, 0x4a, 0x0d, 0x01, 0xdd, 0x58, 0xa3, 0xa8, 0xeb, 0xf0, - 0xf7, 0x22, 0xaa, 0x54, 0xad, 0x8d, 0xee, 0x5b, 0x11, 0xed, 0xd0, 0x5c, 0x9b, 0x43, 0x1e, 0x43, - 0xb9, 0x32, 0xdf, 0xdf, 0x02, 0xcf, 0xd5, 0x1d, 0xbf, 0x35, 0xc4, 0xb0, 0xcb, 0x63, 0x3a, 0x0f, - 0x9d, 0xc6, 0x7c, 0x3d, 0xf9, 0xf7, 0x6f, 0x9b, 0xfc, 0xb8, 0x07, 0x67, 0x6a, 0x21, 0x13, 0x4c, - 0x81, 0x9a, 0x68, 0xe0, 0x47, 0x06, 0x19, 0xe4, 0xd9, 0xe5, 0xc5, 0xb9, 0x2e, 0x6c, 0x6c, 0xa9, - 0x5c, 0x90, 0x87, 0x59, 0x9a, 0x15, 0x81, 0x81, 0x37, 0xcc, 0x83, 0x6e, 0xf7, 0xfe, 0xfe, 0xde, - 0xb9, 0xdf, 0x77, 0x18, 0x9f, 0x74, 0x7d, 0xd7, 0x75, 0xf1, 0x68, 0x6e, 0x10, 0x79, 0x96, 0x0e, - 0x0c, 0xbc, 0xff, 0x33, 0x88, 0x2a, 0x85, 0xe8, 0x2f, 0x5d, 0xf7, 0xd0, 0x05, 0x13, 0x2c, 0x7f, - 0x0c, 0x5e, 0x1d, 0x1d, 0xc1, 0x44, 0x77, 0x08, 0x9d, 0x9c, 0x7d, 0xa1, 0x03, 0x02, 0x1d, 0xf8, - 0x5f, 0xd9, 0xd1, 0x51, 0x65, 0x15, 0xd2, 0xc1, 0x4b, 0x04, 0xdd, 0x15, 0x03, 0xbd, 0x21, 0x56, - 0x95, 0x06, 0xc4, 0x75, 0x3c, 0x9b, 0x1c, 0x0d, 0x55, 0xa9, 0xfb, 0xd8, 0xde, 0xbf, 0x3a, 0x38, - 0x3b, 0xb8, 0xea, 0x9d, 0x1d, 0x5e, 0x79, 0xc7, 0x6f, 0x7d, 0xdb, 0x97, 0xe5, 0x1d, 0x97, 0xf4, - 0x6d, 0xdf, 0x3b, 0xf3, 0xfa, 0xb5, 0x1e, 0x2c, 0x39, 0x1c, 0x03, 0xa0, 0xef, 0xc2, 0x0c, 0xef, - 0xf0, 0x6a, 0xff, 0xec, 0xf8, 0xa2, 0x6f, 0xf7, 0xce, 0xb0, 0xf4, 0x73, 0x7c, 0xd6, 0xbf, 0xea, - 0x01, 0xb2, 0xa3, 0x2b, 0xaf, 0x7f, 0xe6, 0x79, 0x57, 0x47, 0x30, 0x86, 0x05, 0x08, 0xf9, 0x79, - 0x08, 0x9f, 0xde, 0x7e, 0xbd, 0x18, 0x24, 0xb4, 0xcf, 0x29, 0x6f, 0x38, 0x02, 0xa3, 0xbc, 0xf3, - 0x33, 0xaa, 0x31, 0xe9, 0x9c, 0xf4, 0xe6, 0x2a, 0xc7, 0x5b, 0x8e, 0x40, 0x30, 0xd5, 0x03, 0xbf, - 0xab, 0x81, 0xd8, 0xc1, 0x42, 0x60, 0x23, 0xc9, 0x05, 0xef, 0x20, 0x9e, 0xcf, 0xf4, 0x85, 0xa3, - 0xca, 0xeb, 0xbf, 0xb0, 0x98, 0x3a, 0xca, 0xbf, 0xac, 0xa7, 0xb6, 0xf5, 0x73, 0x17, 0x68, 0x0b, - 0x6e, 0x87, 0xf1, 0x3c, 0x37, 0x7d, 0x03, 0xd8, 0x6a, 0xdb, 0xf0, 0x57, 0x67, 0xef, 0x58, 0xfb, - 0x1b, 0x57, 0xdd, 0x92, 0xcf, 0x6f, 0x4f, 0x90, 0x9e, 0x39, 0x39, 0x35, 0xdd, 0xf3, 0x57, 0x32, - 0x9e, 0x8d, 0x74, 0x6c, 0x29, 0xad, 0x49, 0x55, 0x65, 0x95, 0x61, 0x21, 0x06, 0x11, 0x72, 0x30, - 0x44, 0x0c, 0xf4, 0xd0, 0x83, 0x89, 0x80, 0xfc, 0x31, 0xe5, 0xef, 0x4e, 0xd6, 0x70, 0x10, 0x29, - 0x55, 0x9d, 0xdf, 0x44, 0x6c, 0x3d, 0x8f, 0x9b, 0x7e, 0xf9, 0xb5, 0x7e, 0x68, 0x41, 0x82, 0xe4, - 0xbd, 0x16, 0x46, 0x11, 0xf9, 0x25, 0x2f, 0x74, 0xac, 0x61, 0x59, 0x8e, 0xfa, 0x15, 0x0d, 0x6d, - 0xd4, 0x73, 0xdd, 0x9f, 0x4a, 0xdd, 0xd4, 0x45, 0x74, 0x7c, 0x60, 0x92, 0x51, 0x63, 0xb0, 0xd1, - 0xad, 0xee, 0xe7, 0x8c, 0xda, 0x9a, 0x61, 0x1a, 0xfd, 0xe7, 0xe7, 0x8f, 0xbf, 0x98, 0xaa, 0x5e, - 0x45, 0x83, 0x37, 0xcb, 0xb2, 0x84, 0x6e, 0x0c, 0xae, 0xdf, 0x0c, 0xf5, 0x83, 0x8f, 0x56, 0x42, - 0x2e, 0x5a, 0xf9, 0x38, 0x9c, 0x8a, 0x64, 0x3e, 0x2e, 0x30, 0x67, 0x32, 0x29, 0xa4, 0xd9, 0x36, - 0x0a, 0x11, 0x12, 0x72, 0x4c, 0xc7, 0x6d, 0xe3, 0x87, 0x25, 0x77, 0x0a, 0x60, 0x9f, 0x9a, 0x9e, - 0xb5, 0x32, 0x30, 0x2f, 0x47, 0x98, 0x9b, 0x15, 0x98, 0x42, 0x2d, 0x4c, 0x67, 0x60, 0x8c, 0xa0, - 0x09, 0xff, 0x25, 0xaf, 0x1c, 0x70, 0x63, 0xd4, 0xe5, 0x83, 0x24, 0x6f, 0x4d, 0xa7, 0x7d, 0xdb, - 0xd5, 0x04, 0x62, 0x96, 0xef, 0xfc, 0x51, 0xb0, 0xec, 0xb6, 0x71, 0x06, 0xac, 0xe6, 0xc0, 0x29, - 0x41, 0xc5, 0x2f, 0x1e, 0xe0, 0xad, 0xcb, 0x6f, 0x17, 0xe7, 0x67, 0xe0, 0x03, 0xff, 0x41, 0xe1, - 0x04, 0x58, 0x08, 0xc8, 0x5e, 0xb1, 0xf3, 0xe7, 0x94, 0xdd, 0xc1, 0x79, 0xe2, 0xc6, 0x5e, 0x62, - 0x1d, 0x65, 0x60, 0x80, 0x11, 0xa7, 0x78, 0x75, 0x02, 0xa8, 0xba, 0x88, 0xda, 0x58, 0xc1, 0xe9, - 0x7f, 0x8b, 0xe6, 0xe1, 0x22, 0x86, 0x6d, 0x96, 0x67, 0x41, 0x86, 0x1e, 0x83, 0x4d, 0xa4, 0x72, - 0xc3, 0xee, 0x17, 0x39, 0xf4, 0xd1, 0x4b, 0xfa, 0x20, 0x6c, 0x83, 0x74, 0x88, 0x21, 0x6d, 0xc3, - 0xc1, 0xbb, 0x85, 0x39, 0x16, 0x8b, 0x18, 0x70, 0xf3, 0x19, 0x4e, 0x9f, 0xe1, 0xa4, 0xd4, 0x9f, - 0x0f, 0x82, 0xce, 0x60, 0xb3, 0x53, 0x1a, 0x7f, 0x0a, 0x53, 0xbc, 0x0f, 0xd0, 0x59, 0x05, 0x82, - 0x22, 0x2d, 0xce, 0x94, 0xd3, 0x71, 0x60, 0x74, 0x81, 0x1c, 0x7b, 0x1b, 0x39, 0x94, 0x73, 0x2c, - 0xff, 0xd0, 0x16, 0x39, 0xc6, 0x7b, 0xec, 0x1f, 0x10, 0x59, 0xe8, 0x6a, 0x0c, 0x90, 0xcf, 0x92, - 0x98, 0x41, 0x9b, 0x36, 0x4c, 0x3d, 0x92, 0x19, 0x65, 0x73, 0x61, 0x4a, 0xe6, 0x56, 0xb6, 0x47, - 0xf7, 0x2d, 0xb9, 0x2a, 0x03, 0xf7, 0x66, 0x1a, 0x9f, 0x3e, 0x7e, 0xbe, 0x84, 0xdd, 0xed, 0x2a, - 0x39, 0x83, 0x32, 0xa2, 0x80, 0x43, 0x29, 0xcb, 0xbf, 0x33, 0x3e, 0x7b, 0x07, 0x89, 0x45, 0xa9, - 0x34, 0xa1, 0x76, 0x89, 0x2a, 0xdd, 0x80, 0x63, 0x26, 0x56, 0xd3, 0xb8, 0xbc, 0xf1, 0x35, 0x43, - 0xcb, 0x7e, 0xe9, 0xad, 0xc2, 0xe2, 0x31, 0x8b, 0xc8, 0xfa, 0x39, 0x12, 0x15, 0x1f, 0xb2, 0x31, - 0x03, 0x5d, 0x4c, 0xc6, 0xe6, 0xb4, 0x10, 0xc1, 0x9a, 0x7d, 0x06, 0x3b, 0x06, 0x3d, 0x65, 0x35, - 0xd3, 0xb5, 0x04, 0x7f, 0xac, 0x2c, 0x25, 0xbc, 0x0f, 0x13, 0x41, 0xc6, 0x54, 0x80, 0x32, 0x96, - 0x71, 0xce, 0xd8, 0x03, 0xf0, 0x3d, 0x43, 0x6e, 0x62, 0x57, 0x5e, 0xd0, 0xa1, 0x15, 0x29, 0x48, - 0x2a, 0xb5, 0xc6, 0xb4, 0x86, 0x72, 0x4a, 0x79, 0x85, 0x64, 0x9a, 0xea, 0x12, 0x46, 0x38, 0xf2, - 0x17, 0x42, 0xb0, 0xb0, 0x3a, 0xa0, 0xaf, 0x40, 0x02, 0xe0, 0xa5, 0x56, 0x25, 0x59, 0x29, 0x6c, - 0x2c, 0xcf, 0xc8, 0xd2, 0x67, 0xb3, 0xd7, 0x00, 0x9b, 0xce, 0x98, 0x20, 0x49, 0x0c, 0xfb, 0x93, - 0x8c, 0x1f, 0x09, 0x52, 0x0e, 0x19, 0x56, 0x8b, 0xd3, 0xe6, 0xc2, 0x80, 0xbb, 0x7e, 0xf3, 0xa2, - 0x99, 0x0c, 0xdc, 0x21, 0x96, 0x64, 0xd1, 0x2c, 0xe1, 0x3c, 0x31, 0x14, 0xa3, 0x80, 0x0e, 0xc5, - 0xde, 0xde, 0xda, 0x41, 0xdc, 0x6a, 0x56, 0x7f, 0x58, 0x02, 0xab, 0xab, 0xb5, 0x55, 0x08, 0x6d, - 0x15, 0xc3, 0xb5, 0x8c, 0x44, 0x43, 0x46, 0xa0, 0x0c, 0x5c, 0x77, 0x88, 0x52, 0x14, 0x0d, 0x02, - 0xf2, 0x79, 0x31, 0x85, 0x83, 0x9b, 0x66, 0x5d, 0xb4, 0x59, 0xbf, 0x95, 0x6a, 0xa5, 0x90, 0xe1, - 0xad, 0x1f, 0x5a, 0x1b, 0x19, 0x73, 0x36, 0x93, 0x07, 0xef, 0x01, 0xb9, 0x85, 0x8d, 0x5e, 0xad, - 0xb6, 0xb0, 0x34, 0xf2, 0xc0, 0x3f, 0x6c, 0xae, 0x54, 0x72, 0x3f, 0xb8, 0x76, 0xed, 0x7e, 0xf9, - 0x07, 0x47, 0xae, 0xea, 0xe3, 0x66, 0x55, 0x56, 0x28, 0x44, 0x80, 0x8b, 0xa1, 0x03, 0x2e, 0xa8, - 0xd9, 0x30, 0x24, 0x54, 0x9e, 0x96, 0x15, 0xc9, 0xfa, 0x39, 0x50, 0x8f, 0x62, 0xd4, 0x42, 0xc3, - 0x3b, 0x61, 0x08, 0xbd, 0x52, 0xc3, 0xe8, 0x89, 0x7f, 0x70, 0x68, 0xe9, 0x9a, 0x1b, 0xf6, 0x82, - 0x1f, 0xc0, 0x65, 0x44, 0x92, 0xcd, 0xe9, 0x4a, 0x4d, 0xe0, 0x81, 0xee, 0xc7, 0x6d, 0xc0, 0xf2, - 0xf9, 0xb0, 0x8e, 0x8c, 0x8d, 0x09, 0x97, 0xa8, 0x5e, 0x2a, 0x6e, 0x92, 0x42, 0xfe, 0x82, 0x80, - 0x9f, 0x9e, 0x0e, 0x5e, 0x06, 0x01, 0xd5, 0x7c, 0x5b, 0x4b, 0x79, 0x07, 0x70, 0xc7, 0x69, 0xf8, - 0x65, 0xb5, 0x46, 0x20, 0x10, 0x01, 0xb5, 0x60, 0xbe, 0x91, 0xcd, 0x67, 0x77, 0x90, 0x61, 0x42, - 0xbc, 0x01, 0x37, 0x04, 0xbd, 0xe2, 0xe9, 0x49, 0x8c, 0x5c, 0xf8, 0xe7, 0x04, 0xe4, 0xf0, 0xf4, - 0xf4, 0xf2, 0x17, 0x39, 0x0e, 0x0b, 0x7c, 0xc8, 0x04, 0x9d, 0x80, 0xc9, 0x0b, 0xab, 0x81, 0x74, - 0x85, 0x44, 0xb0, 0xaf, 0x30, 0x03, 0xc7, 0xf4, 0x6b, 0xae, 0x49, 0xea, 0x78, 0x37, 0x28, 0x1d, - 0x59, 0xad, 0x0b, 0xc2, 0x6b, 0xf7, 0x66, 0xad, 0x57, 0xd7, 0x8e, 0xe3, 0x84, 0x37, 0x43, 0x0a, - 0x9d, 0x01, 0xee, 0x02, 0x57, 0xbb, 0x04, 0x0a, 0xbf, 0x2a, 0x41, 0xda, 0xf1, 0x00, 0x24, 0xea, - 0xcc, 0xc2, 0x7c, 0x5d, 0x9a, 0x31, 0x97, 0xb0, 0x3e, 0xc4, 0x9f, 0x71, 0x1a, 0xca, 0x90, 0xbe, - 0x4d, 0xc1, 0x60, 0x99, 0xd2, 0x88, 0x60, 0x8c, 0x0b, 0xd3, 0xb8, 0xc4, 0x5b, 0x70, 0x7c, 0x17, - 0x89, 0x82, 0xa9, 0xee, 0x60, 0x21, 0xd8, 0x92, 0x59, 0x52, 0x14, 0xc9, 0x44, 0x29, 0xd9, 0x23, - 0x9b, 0x73, 0x72, 0xc7, 0xd9, 0x7d, 0x01, 0x12, 0x21, 0xbf, 0xb3, 0x39, 0x29, 0xa6, 0x6c, 0x9e, - 0xc6, 0x24, 0xe7, 0xec, 0x2e, 0xbc, 0x4b, 0x1f, 0x89, 0x76, 0x40, 0xfa, 0xce, 0x7a, 0x16, 0xc2, - 0xa6, 0x43, 0x2a, 0x00, 0xcb, 0x64, 0x31, 0xc1, 0x8d, 0x04, 0xc5, 0x97, 0xd7, 0xda, 0x30, 0x21, - 0xa7, 0x1c, 0x26, 0x8c, 0xf1, 0x82, 0x1e, 0x2f, 0xab, 0xcb, 0x35, 0x15, 0x15, 0x58, 0x91, 0x02, - 0x69, 0x83, 0x8b, 0x85, 0xb8, 0x44, 0xee, 0x28, 0x80, 0x51, 0x8d, 0x1c, 0xf5, 0x7e, 0x4a, 0x39, - 0x75, 0xc0, 0x19, 0x5e, 0x20, 0x71, 0xf0, 0x2d, 0x27, 0xc5, 0x15, 0x92, 0x97, 0xe0, 0x1d, 0xcb, - 0xc9, 0xda, 0xd6, 0xdf, 0x25, 0x8b, 0xa2, 0x9e, 0x8c, 0x6c, 0x1d, 0xae, 0x36, 0x62, 0xe3, 0x41, - 0xe6, 0xfa, 0x79, 0x04, 0x3a, 0xb3, 0xcd, 0xe1, 0xd6, 0x9d, 0x38, 0x9a, 0xb9, 0x92, 0x37, 0xca, - 0x0d, 0x0e, 0xe8, 0x11, 0xa6, 0x29, 0x90, 0xba, 0x40, 0xec, 0x4e, 0x52, 0x81, 0x07, 0xcb, 0xe0, - 0x44, 0x1e, 0xdb, 0x67, 0xd2, 0xd0, 0xbb, 0xff, 0xd4, 0xf8, 0xff, 0x27, 0xfe, 0xa1, 0x0b, 0x5b, - 0xd6, 0xd2, 0x54, 0x6e, 0xb5, 0x33, 0xd5, 0xca, 0x41, 0x71, 0x70, 0x50, 0x7c, 0xb4, 0xc5, 0xe2, - 0x87, 0x7c, 0xed, 0xb1, 0x58, 0x50, 0x07, 0xb8, 0xe6, 0x37, 0x76, 0x18, 0xb4, 0x5f, 0x92, 0xea, - 0x53, 0x66, 0xe8, 0xd4, 0x1e, 0x83, 0x18, 0x7b, 0xdc, 0x0e, 0x55, 0xd1, 0x1d, 0x23, 0x1e, 0x86, - 0xbf, 0x4a, 0x12, 0x46, 0xe9, 0x18, 0xb2, 0x52, 0x27, 0xbf, 0xd0, 0xc7, 0xc2, 0x64, 0x16, 0x28, - 0x2f, 0x60, 0xc1, 0xc0, 0x03, 0x21, 0x0d, 0x2b, 0xbc, 0xf2, 0xf8, 0xa1, 0xbc, 0x47, 0x21, 0x6b, - 0x7b, 0xe0, 0x9d, 0x4d, 0x76, 0x9d, 0xdd, 0xac, 0x6f, 0x9c, 0x76, 0x10, 0x93, 0xd6, 0x89, 0x29, - 0xf3, 0x54, 0x20, 0xaa, 0xba, 0x90, 0xd9, 0x31, 0x4f, 0xdd, 0xb3, 0xac, 0x5f, 0x49, 0x21, 0x1f, - 0x49, 0x9b, 0x8f, 0xda, 0xf0, 0xfa, 0xb2, 0x64, 0x03, 0x61, 0x75, 0x73, 0x22, 0x9f, 0x32, 0xe8, - 0x27, 0x53, 0x88, 0xaf, 0x70, 0xf0, 0x15, 0x21, 0x24, 0x96, 0xeb, 0x7a, 0x05, 0x38, 0xa4, 0x66, - 0x26, 0x05, 0x8e, 0x1c, 0x2f, 0x03, 0xb6, 0x95, 0x0f, 0x8c, 0xcf, 0x14, 0xdf, 0x87, 0xa8, 0x17, - 0x33, 0xb5, 0x97, 0x2a, 0xf8, 0xbc, 0x83, 0x00, 0x7e, 0xbc, 0x41, 0x59, 0x9f, 0x31, 0xf5, 0x73, - 0x00, 0xbb, 0x68, 0x33, 0x51, 0x51, 0x54, 0xb2, 0x10, 0xed, 0x64, 0x21, 0x92, 0x2c, 0x94, 0xef, - 0xbf, 0x90, 0x85, 0x68, 0x1b, 0x0b, 0x48, 0x38, 0xe4, 0x13, 0x78, 0x67, 0x2f, 0xe9, 0x8f, 0x76, - 0x94, 0x3f, 0x4e, 0x59, 0xfe, 0xa8, 0xa8, 0x85, 0x1c, 0x73, 0x55, 0x9a, 0x1e, 0xb2, 0xa0, 0x98, - 0xb9, 0x85, 0xbc, 0xa7, 0xc9, 0x01, 0xa2, 0x84, 0xbe, 0x16, 0x07, 0x15, 0x41, 0x78, 0xaf, 0xb2, - 0x5d, 0xd5, 0xaa, 0x07, 0x85, 0xa8, 0x12, 0xa0, 0xf3, 0x79, 0x60, 0x18, 0x95, 0x01, 0x50, 0x30, - 0x00, 0x3a, 0x42, 0x75, 0x2a, 0x15, 0x1f, 0x32, 0x5c, 0xdf, 0xaa, 0xc2, 0x2f, 0x8e, 0xa0, 0x47, - 0x46, 0x5f, 0xaf, 0xd4, 0x0f, 0x6f, 0x4b, 0x95, 0xaf, 0x57, 0x63, 0x7b, 0xde, 0x8d, 0x95, 0x43, - 0xc6, 0xfc, 0xea, 0x87, 0x65, 0xd5, 0x81, 0x95, 0x6c, 0xd1, 0x05, 0x1f, 0xfc, 0xa3, 0xe7, 0xba, - 0xab, 0xbf, 0xd9, 0xe4, 0x56, 0x5e, 0xb7, 0x2e, 0x11, 0x4e, 0xbe, 0xee, 0xad, 0xc3, 0x62, 0xed, - 0x5b, 0x7f, 0xf9, 0x8d, 0xaf, 0x7d, 0xfc, 0xf2, 0xac, 0x0d, 0x5c, 0x98, 0x84, 0xfb, 0x10, 0xa7, - 0x83, 0x5c, 0x27, 0xe7, 0xae, 0xdd, 0xf1, 0xb7, 0xdd, 0x2b, 0x7d, 0x98, 0x81, 0xf3, 0x0c, 0x6e, - 0x77, 0xd5, 0xeb, 0x71, 0xa9, 0x7c, 0x65, 0xdd, 0x96, 0x46, 0xaa, 0x6e, 0xc6, 0x36, 0xde, 0x2f, - 0x82, 0xb6, 0x95, 0x46, 0x1b, 0x04, 0xd9, 0x4f, 0x49, 0xeb, 0xfa, 0x68, 0x10, 0xe9, 0x75, 0xd5, - 0xf3, 0xb9, 0x73, 0x3c, 0x7f, 0x1b, 0xf2, 0x3d, 0x86, 0xcd, 0x5f, 0x06, 0x32, 0x25, 0x7b, 0xfd, - 0xba, 0x39, 0x29, 0xc2, 0xfb, 0xed, 0xd6, 0xfd, 0x54, 0xd8, 0xbe, 0xcb, 0x6a, 0x2c, 0x4a, 0x1b, - 0xa3, 0xa1, 0x05, 0x11, 0xaa, 0xd9, 0x51, 0xab, 0xc2, 0xd5, 0x55, 0x90, 0x62, 0x05, 0x71, 0xe3, - 0xe6, 0xe8, 0x8d, 0xbc, 0x15, 0xbd, 0x4e, 0xe2, 0x7f, 0x36, 0xaf, 0x53, 0x6f, 0xde, 0xac, 0x0f, - 0x47, 0x98, 0x92, 0xd3, 0x5d, 0x07, 0x46, 0x7d, 0xc5, 0xb7, 0x89, 0x18, 0x8d, 0xa5, 0x86, 0x57, - 0x57, 0xa3, 0x6e, 0x6c, 0xd2, 0x1a, 0x68, 0xd4, 0x79, 0x1b, 0xc3, 0x8d, 0x32, 0xd6, 0x77, 0x11, - 0x34, 0x54, 0x19, 0x67, 0x2d, 0xcf, 0x9a, 0xd4, 0x42, 0x8f, 0xb1, 0x47, 0xad, 0x6d, 0xc5, 0xc4, - 0xd2, 0xc3, 0x1a, 0x96, 0xd5, 0x32, 0x0a, 0xb1, 0xcd, 0x22, 0x20, 0xaf, 0xaa, 0xb2, 0xaa, 0xad, - 0x46, 0x21, 0x94, 0x45, 0x30, 0xf9, 0x98, 0x40, 0x7d, 0x28, 0xdd, 0x67, 0x01, 0x68, 0xfe, 0x25, - 0x3b, 0xa3, 0x0f, 0xa6, 0xea, 0xb6, 0x85, 0xd4, 0x78, 0xf9, 0xb3, 0x7f, 0x63, 0x29, 0xad, 0x96, - 0x2f, 0x31, 0xb8, 0xcd, 0xaa, 0x93, 0x54, 0x11, 0x71, 0x06, 0x92, 0x75, 0x6d, 0xb7, 0xbe, 0xc7, - 0x15, 0x2a, 0x48, 0x5b, 0x6c, 0xbe, 0x0e, 0x54, 0x26, 0x1d, 0x8d, 0xbc, 0x1e, 0xa4, 0x60, 0xa3, - 0xa3, 0x27, 0xde, 0x7c, 0xb0, 0xa0, 0x4f, 0x37, 0x48, 0x96, 0xe1, 0x1a, 0x58, 0x37, 0x04, 0x37, - 0x67, 0xf6, 0x3a, 0xac, 0xcc, 0xf5, 0xf6, 0xd8, 0xaa, 0x3a, 0xc8, 0x94, 0xab, 0xef, 0x3a, 0x40, - 0xe2, 0x91, 0xff, 0x19, 0x20, 0x38, 0x49, 0x26, 0x7f, 0xd2, 0x12, 0xac, 0x71, 0xa3, 0xba, 0xf3, - 0xe6, 0xbb, 0xf1, 0xbc, 0x45, 0x3f, 0x65, 0x71, 0xf5, 0x2f, 0x3e, 0xf3, 0xd8, 0xa8, 0xb7, 0xd4, - 0xff, 0x5f, 0x8d, 0xae, 0xfa, 0x5f, 0x63, 0xfe, 0x17, 0x66, 0xba, 0xb1, 0x98, 0x32, 0x33, 0x00, - 0x00 + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xbd, 0x3b, 0x6b, 0x73, 0xdb, 0x38, + 0x92, 0xdf, 0xf3, 0x2b, 0x10, 0x26, 0x93, 0x90, 0x63, 0x8a, 0x22, 0x29, 0x5b, 0xb2, 0x25, 0xd1, + 0xd9, 0x8c, 0x93, 0x3d, 0xe7, 0xca, 0x9e, 0xa4, 0x36, 0x3e, 0xcf, 0xcc, 0xf9, 0xbc, 0x65, 0x9a, + 0x84, 0x24, 0x4e, 0x28, 0x82, 0x0b, 0x42, 0xb2, 0x1d, 0x59, 0xff, 0xfd, 0xba, 0x01, 0x90, 0x22, + 0xf5, 0x70, 0x92, 0x9b, 0xad, 0x9b, 0x72, 0x45, 0x20, 0xd0, 0x68, 0x74, 0x37, 0xfa, 0x05, 0x34, + 0x66, 0xf8, 0xfc, 0xdd, 0xc7, 0x93, 0x8b, 0x3f, 0x3e, 0xbd, 0x27, 0x13, 0x31, 0x4d, 0x8f, 0xc9, + 0xb0, 0xfc, 0xa1, 0x61, 0x0c, 0x3f, 0x53, 0x2a, 0x42, 0x92, 0x85, 0x53, 0x1a, 0x18, 0xf3, 0x84, + 0xde, 0xe5, 0x8c, 0x0b, 0x83, 0x3c, 0x8b, 0x58, 0x26, 0x68, 0x26, 0x02, 0xe3, 0x2e, 0x89, 0xc5, + 0x24, 0x88, 0xe9, 0x3c, 0x89, 0x68, 0x4b, 0x7e, 0xd8, 0x49, 0x96, 0x88, 0x24, 0x4c, 0x5b, 0x45, + 0x14, 0xa6, 0x34, 0xf0, 0xec, 0x29, 0x74, 0x4c, 0x67, 0xd3, 0xf2, 0xdb, 0x28, 0x91, 0x3e, 0x9b, + 0x08, 0x91, 0xb7, 0xe8, 0xbf, 0x66, 0xc9, 0x3c, 0x30, 0x4e, 0xc2, 0x68, 0x42, 0x5b, 0x27, 0x80, + 0x96, 0xb3, 0xd4, 0x20, 0x15, 0xfe, 0x8c, 0xb5, 0x22, 0x1c, 0xb2, 0x09, 0xb4, 0x0a, 0xc1, 0x38, + 0xb4, 0xa6, 0xb3, 0x42, 0xb4, 0x38, 0x9d, 0x87, 0x69, 0x12, 0x87, 0x82, 0x6e, 0x47, 0xf8, 0x89, + 0x87, 0xe3, 0x69, 0xb8, 0x05, 0x53, 0x05, 0x5e, 0x87, 0x7e, 0x7f, 0x9f, 0x27, 0x9c, 0x16, 0x35, + 0x70, 0x17, 0xe0, 0x9e, 0x0d, 0x45, 0x22, 0x52, 0x7a, 0xfc, 0xdb, 0xd9, 0xfb, 0x77, 0xe4, 0x04, + 0x56, 0x65, 0x53, 0xf2, 0x09, 0x98, 0x10, 0x82, 0x92, 0xf7, 0x71, 0x02, 0xd4, 0x0c, 0xdb, 0x0a, + 0x82, 0x0c, 0x8b, 0x88, 0x27, 0xb9, 0x20, 0xe2, 0x21, 0x07, 0x49, 0x09, 0x7a, 0x2f, 0xda, 0x7f, + 0x86, 0xf3, 0x50, 0xf5, 0x1a, 0xc7, 0xcf, 0x46, 0xb3, 0x2c, 0x12, 0x09, 0xcb, 0xc8, 0xf8, 0x43, + 0x6c, 0x52, 0x6b, 0xc1, 0xa9, 0x98, 0xf1, 0x8c, 0xc4, 0xce, 0x98, 0x8a, 0xf7, 0x29, 0x9d, 0xc2, + 0x9a, 0xbf, 0x3c, 0xc8, 0xa1, 0x65, 0x05, 0x1a, 0xbd, 0x6f, 0x40, 0x46, 0x9c, 0x02, 0xb7, 0x1a, + 0x18, 0x01, 0xe7, 0x21, 0x27, 0x71, 0x10, 0xb3, 0x68, 0x86, 0x3d, 0xcf, 0x86, 0x6d, 0xb5, 0x1a, + 0x12, 0x23, 0x1e, 0x80, 0xa8, 0x67, 0xb7, 0x2c, 0x7e, 0x58, 0x8c, 0x80, 0xa3, 0xd6, 0x28, 0x9c, + 0x26, 0xe9, 0x43, 0xff, 0x2d, 0x87, 0x8d, 0xb1, 0x8b, 0x30, 0x2b, 0x5a, 0x05, 0xe5, 0xc9, 0x68, + 0x70, 0x1b, 0x46, 0x5f, 0xc6, 0x9c, 0xcd, 0xb2, 0xb8, 0x15, 0xb1, 0x94, 0xf1, 0xfe, 0x0b, 0xcf, + 0xf3, 0x06, 0x72, 0x4a, 0x91, 0x7c, 0xa5, 0x7d, 0xaf, 0x9b, 0xdf, 0x0f, 0xf4, 0x48, 0x1c, 0xc7, + 0x83, 0x69, 0xc8, 0xc7, 0x49, 0xd6, 0x77, 0x89, 0xe7, 0xc2, 0x40, 0x9a, 0x64, 0xb4, 0x35, 0xa1, + 0xc9, 0x78, 0x22, 0xfa, 0xce, 0xc1, 0xf2, 0x45, 0x1e, 0x72, 0x20, 0xa4, 0x85, 0x32, 0x0c, 0x61, + 0x88, 0x2f, 0x72, 0x56, 0x24, 0xc8, 0x4a, 0x9f, 0xd3, 0x34, 0x14, 0xc9, 0x9c, 0x0e, 0xa4, 0x8a, + 0xf4, 0x3d, 0xd7, 0xfd, 0x69, 0xa0, 0x27, 0xfa, 0x80, 0x69, 0xf9, 0xe2, 0x96, 0x09, 0x90, 0xee, + 0xc9, 0xe6, 0xcc, 0xf0, 0xb6, 0x60, 0xe9, 0x4c, 0x50, 0xbd, 0x74, 0x4b, 0xb0, 0xbc, 0x7f, 0x20, + 0xa7, 0x8c, 0x79, 0x18, 0x27, 0xb8, 0xde, 0x2d, 0xbb, 0x5f, 0x6c, 0xe2, 0xc5, 0xf6, 0xd2, 0x91, + 0xb4, 0xb7, 0x60, 0xee, 0x17, 0xca, 0x6d, 0xfd, 0x95, 0x27, 0x11, 0x7c, 0xe9, 0xce, 0x2d, 0x2b, + 0xdd, 0x32, 0x1e, 0xc3, 0x38, 0xa2, 0x9f, 0x15, 0xfd, 0x0e, 0x30, 0xba, 0x21, 0xa6, 0x22, 0x49, + 0xe7, 0x94, 0x6b, 0xc8, 0xbe, 0x9f, 0xdf, 0x13, 0x98, 0x9b, 0xc4, 0x84, 0x8f, 0x6f, 0x43, 0xb3, + 0x7b, 0x68, 0xab, 0x3f, 0xe7, 0xc0, 0x1a, 0x7c, 0x6d, 0x25, 0x59, 0x4c, 0xef, 0xfb, 0x7e, 0x93, + 0x96, 0x85, 0xa6, 0xb2, 0x83, 0x72, 0x54, 0xc4, 0xf7, 0xa0, 0xa5, 0xb8, 0xfb, 0x69, 0x20, 0x38, + 0xec, 0xd1, 0x88, 0xf1, 0x69, 0x5f, 0xb6, 0x40, 0x78, 0xf4, 0x0f, 0xb3, 0x05, 0x23, 0x16, 0x80, + 0xcc, 0xa2, 0x49, 0x2b, 0x94, 0x2a, 0xd2, 0xcf, 0x58, 0x46, 0x97, 0x5b, 0xd9, 0xd2, 0xf8, 0x7b, + 0x1b, 0xe8, 0xbd, 0x03, 0x94, 0x4b, 0x4c, 0x41, 0x8d, 0xe9, 0x6e, 0x19, 0xe8, 0xe9, 0x07, 0xd5, + 0x74, 0x6c, 0x7d, 0x87, 0x60, 0x5e, 0x8c, 0x46, 0xa3, 0x52, 0x2c, 0x9d, 0x4a, 0x2c, 0x2f, 0x8e, + 0x6e, 0xfd, 0x43, 0xff, 0x50, 0xae, 0xef, 0xfb, 0xc0, 0xdf, 0x86, 0x54, 0x14, 0xf1, 0xbb, 0x09, + 0xf1, 0x2a, 0x42, 0xbc, 0x8a, 0x10, 0xd9, 0x2c, 0x59, 0xaa, 0x50, 0x7a, 0x25, 0x99, 0x35, 0x85, + 0xde, 0xaa, 0xe6, 0x4b, 0xe7, 0x76, 0x06, 0x4a, 0x97, 0x45, 0x69, 0x58, 0x14, 0x8b, 0x3c, 0x8c, + 0xe3, 0x24, 0x1b, 0xf7, 0xdd, 0x4a, 0xc7, 0x07, 0xb0, 0xc3, 0x22, 0x01, 0xa7, 0xd5, 0x02, 0x47, + 0x33, 0xce, 0xfa, 0x4a, 0x45, 0x77, 0xe0, 0x5a, 0x57, 0x60, 0x52, 0xe4, 0x61, 0xb6, 0x88, 0x93, + 0x22, 0x4f, 0xc3, 0x87, 0x7e, 0x92, 0x49, 0x53, 0x19, 0xa5, 0xf4, 0x7e, 0x20, 0x91, 0xb5, 0x12, + 0x41, 0xa7, 0x45, 0x3f, 0x02, 0xf5, 0x05, 0x35, 0xaa, 0x89, 0xae, 0x66, 0x7a, 0xa0, 0x55, 0xeb, + 0x24, 0x4c, 0x93, 0x38, 0x4e, 0xe9, 0xf2, 0x45, 0x92, 0x8d, 0x58, 0x85, 0xdc, 0x30, 0x06, 0xe8, + 0x6f, 0x34, 0xc8, 0x37, 0x51, 0x6e, 0xda, 0x64, 0xcd, 0xb2, 0x36, 0xcc, 0x1a, 0xa4, 0x74, 0xc7, + 0xc3, 0xbc, 0x6e, 0x5f, 0x95, 0x0f, 0x08, 0x67, 0x82, 0x2d, 0xff, 0x36, 0xa5, 0x71, 0x12, 0x12, + 0x13, 0xbc, 0xbc, 0xf2, 0xff, 0xfd, 0x43, 0x17, 0x90, 0x58, 0x8b, 0xfa, 0x3c, 0xd9, 0xb5, 0x5c, + 0x3a, 0xb9, 0x72, 0x9e, 0x8b, 0xba, 0xe9, 0x97, 0x9d, 0xff, 0xa1, 0xcd, 0xb9, 0x58, 0xa0, 0x98, + 0x60, 0x1b, 0x6b, 0x40, 0x9b, 0x9a, 0x57, 0x4d, 0x2b, 0xce, 0x41, 0xde, 0x8b, 0x35, 0xe7, 0x50, + 0x73, 0x33, 0x12, 0xf0, 0x82, 0xe5, 0xe5, 0x9a, 0xa3, 0x44, 0x39, 0x28, 0x58, 0xe9, 0x2f, 0x8a, + 0x6d, 0x4d, 0x4c, 0xb0, 0x4c, 0xc9, 0xc2, 0x27, 0xe9, 0x07, 0xab, 0x0d, 0xda, 0xb5, 0xed, 0x5b, + 0x28, 0x5a, 0xdf, 0x8a, 0x7f, 0x2b, 0x85, 0x4a, 0xdd, 0x8b, 0x77, 0xc9, 0x7c, 0xab, 0x62, 0xea, + 0xb5, 0x53, 0x3a, 0x6a, 0xd8, 0xbd, 0xdc, 0x23, 0xd8, 0x63, 0xf1, 0x19, 0x34, 0xda, 0x76, 0x0a, + 0x9a, 0xc5, 0xd8, 0x5a, 0x44, 0x33, 0x5e, 0x00, 0x25, 0x39, 0x4b, 0x90, 0xae, 0xe5, 0xc4, 0x5b, + 0xd4, 0xe8, 0x71, 0xba, 0x9c, 0x4e, 0x97, 0x18, 0x92, 0x64, 0x24, 0x22, 0xc3, 0xb6, 0x4e, 0x27, + 0x30, 0x24, 0xc1, 0x4f, 0x9c, 0xcc, 0x49, 0x12, 0x43, 0xfa, 0x00, 0x3a, 0x02, 0x41, 0x17, 0x2d, + 0x50, 0x7f, 0xe8, 0xc1, 0x67, 0x72, 0x62, 0x60, 0x34, 0x64, 0xf8, 0x27, 0x84, 0xe0, 0x64, 0xf4, + 0x50, 0x4a, 0x4b, 0x8b, 0x04, 0xa7, 0x4c, 0xbc, 0xed, 0x33, 0x36, 0xa5, 0x8e, 0xd0, 0xc5, 0x7c, + 0x5c, 0x81, 0x2b, 0x2e, 0x3b, 0x18, 0xe7, 0x4a, 0x97, 0x8c, 0x6d, 0x2d, 0x0c, 0x2e, 0x7b, 0xa0, + 0xc3, 0x20, 0x98, 0xfd, 0xfc, 0xc2, 0xee, 0x21, 0x33, 0x20, 0x2e, 0xe9, 0xf8, 0xf0, 0x67, 0x1c, + 0x0f, 0xf3, 0x50, 0x4c, 0xc8, 0xb3, 0x51, 0x92, 0xa6, 0x81, 0xf1, 0xc2, 0x75, 0x3b, 0xb0, 0x2d, + 0x06, 0xc4, 0x64, 0xe3, 0xbc, 0x4b, 0x7c, 0x7f, 0x72, 0x38, 0xdf, 0x3f, 0xed, 0x7e, 0x3d, 0xf7, + 0xf6, 0x89, 0xb7, 0x3f, 0xd9, 0x9f, 0x1f, 0x4e, 0x5a, 0xfb, 0xf0, 0x75, 0x08, 0xc1, 0xb3, 0xfa, + 0xf2, 0x7d, 0xd2, 0x45, 0xb8, 0x49, 0xeb, 0xf0, 0xab, 0xd1, 0x3e, 0x06, 0x81, 0xcd, 0xc7, 0xc7, + 0xcf, 0x80, 0x44, 0x10, 0xb1, 0x94, 0x10, 0xca, 0xcd, 0x78, 0x32, 0x03, 0x41, 0x50, 0x29, 0x61, + 0x0f, 0xff, 0x05, 0xe1, 0x95, 0x22, 0xc4, 0xe9, 0xeb, 0x21, 0xd9, 0xa8, 0x09, 0xbf, 0x1e, 0x40, + 0x81, 0x17, 0x3d, 0xb5, 0x8e, 0xe1, 0xc7, 0x36, 0xa1, 0xc4, 0x5b, 0x5a, 0x26, 0xe6, 0x89, 0x6a, + 0x67, 0xeb, 0xb6, 0xba, 0x06, 0x09, 0xa6, 0x59, 0x29, 0x80, 0xfe, 0x04, 0xfe, 0x4f, 0x66, 0x1c, + 0xe9, 0x4e, 0x1f, 0x48, 0x92, 0x91, 0x59, 0x41, 0x49, 0xa4, 0x78, 0x2f, 0x11, 0x91, 0x35, 0x6a, + 0xff, 0x3a, 0xd1, 0xe8, 0x52, 0xe5, 0xca, 0x29, 0x44, 0x22, 0x02, 0xd9, 0x97, 0x98, 0x50, 0x52, + 0x4a, 0x88, 0x50, 0x29, 0x6b, 0x22, 0x18, 0x81, 0x30, 0x41, 0x32, 0x7a, 0x47, 0xa4, 0x1d, 0x92, + 0x02, 0xa2, 0x1b, 0x24, 0x16, 0x08, 0xac, 0x66, 0xc8, 0x6e, 0x1a, 0x13, 0x10, 0x29, 0xb9, 0xa5, + 0x29, 0xbb, 0x93, 0xbd, 0x0a, 0x0c, 0xa7, 0x47, 0x93, 0x30, 0x1b, 0x53, 0x92, 0x88, 0x42, 0x81, + 0x3a, 0x7a, 0x41, 0x84, 0x6a, 0xce, 0x83, 0x68, 0x06, 0x9e, 0x1f, 0x57, 0x35, 0xc3, 0x2c, 0xc6, + 0xc4, 0x74, 0x94, 0xf0, 0xa9, 0x85, 0x48, 0x54, 0xf0, 0x76, 0xc8, 0xc7, 0x2c, 0xa2, 0x64, 0x04, + 0xe9, 0x75, 0x31, 0xa1, 0xb1, 0x0d, 0x52, 0x2c, 0x31, 0x85, 0x9c, 0x23, 0x86, 0x08, 0xd9, 0x60, + 0x64, 0x96, 0xa7, 0x2c, 0x8c, 0x01, 0x21, 0xb4, 0x71, 0x34, 0xa6, 0x45, 0x82, 0x6b, 0x15, 0x29, + 0x13, 0x0e, 0xb9, 0x60, 0x92, 0x3b, 0x42, 0xef, 0x13, 0x90, 0x51, 0x36, 0x2e, 0x65, 0x5c, 0xc7, + 0x97, 0xd3, 0x2c, 0x4a, 0x52, 0x89, 0xd0, 0x81, 0xac, 0x78, 0x53, 0xe8, 0x3f, 0x2e, 0x73, 0xa9, + 0x9d, 0x85, 0x00, 0x47, 0x15, 0x7d, 0xaa, 0xf4, 0xe5, 0x1b, 0xea, 0x82, 0xe0, 0x3b, 0x55, 0xe6, + 0xed, 0x3c, 0x4c, 0xd2, 0xf0, 0x36, 0x05, 0x69, 0x4b, 0xac, 0xdf, 0xd2, 0x15, 0xf9, 0x33, 0x6c, + 0x6b, 0x87, 0xa4, 0xd3, 0xf7, 0x67, 0xbb, 0xf2, 0x77, 0xcc, 0xb5, 0x4b, 0x6d, 0x40, 0x2f, 0x80, + 0x69, 0x7c, 0xd3, 0x80, 0x2c, 0x3b, 0x82, 0x15, 0xa3, 0xa0, 0xe5, 0xd9, 0xf9, 0xfd, 0x09, 0x4b, + 0x83, 0xc5, 0xd2, 0x16, 0xfa, 0x97, 0xd3, 0x48, 0x04, 0xb5, 0xe9, 0x98, 0xf5, 0xff, 0x82, 0x29, + 0x04, 0xc8, 0x1b, 0xf6, 0x1f, 0x3a, 0xff, 0x01, 0x10, 0xa6, 0x65, 0x97, 0x30, 0x67, 0x34, 0x1b, + 0xc3, 0xb9, 0x0a, 0xe7, 0x39, 0xea, 0x54, 0x35, 0xfd, 0x38, 0x1a, 0x15, 0xc1, 0x39, 0xf8, 0x1b, + 0x47, 0x26, 0x1f, 0x66, 0x13, 0xb4, 0xed, 0x1f, 0x74, 0xdb, 0xbe, 0xd5, 0x3a, 0xb0, 0x35, 0xdb, + 0x6f, 0x39, 0x0f, 0x1f, 0x82, 0xab, 0x6b, 0x1b, 0x1c, 0xca, 0xe7, 0x70, 0x4e, 0x83, 0xd7, 0xd2, + 0xed, 0x35, 0xbc, 0x9e, 0x7f, 0xb0, 0xf2, 0x7a, 0xd8, 0x5e, 0x73, 0x72, 0xfe, 0x3e, 0xfc, 0x95, + 0x4e, 0x4e, 0xfa, 0x38, 0x0c, 0x3b, 0xd2, 0xbd, 0xf9, 0xbe, 0xed, 0xf9, 0x6f, 0x3d, 0xd7, 0xf6, + 0x10, 0x10, 0x7e, 0x88, 0xe7, 0xdb, 0x7e, 0xb3, 0x67, 0x2b, 0x48, 0x13, 0x02, 0x41, 0xce, 0x7b, + 0xf0, 0xcf, 0x19, 0x8c, 0x79, 0xbd, 0x4b, 0x6f, 0xff, 0xd4, 0xeb, 0x5e, 0x7a, 0xee, 0xa9, 0xe7, + 0x5f, 0xf6, 0xce, 0x70, 0xe0, 0xbf, 0x2b, 0xa7, 0xf8, 0x1a, 0x39, 0x41, 0x9f, 0xf7, 0xef, 0xe5, + 0x04, 0x89, 0x3a, 0xe9, 0x3a, 0xfb, 0x3d, 0xdb, 0x07, 0x8a, 0xb1, 0x21, 0x09, 0x3f, 0x41, 0x7a, + 0x9c, 0x83, 0x0e, 0x51, 0x43, 0xbe, 0xe2, 0xef, 0x44, 0xf6, 0xe1, 0xa7, 0x5f, 0x8e, 0xfb, 0x0a, + 0x5a, 0x4f, 0xd5, 0xe3, 0x12, 0xfa, 0xdc, 0x3b, 0x70, 0x3c, 0xbb, 0xe7, 0xb8, 0xbd, 0x13, 0x68, + 0xf9, 0xfb, 0xb2, 0x49, 0xa0, 0xd9, 0x39, 0x84, 0xa6, 0xe7, 0x63, 0xf3, 0x00, 0x5a, 0x7e, 0xe7, + 0xcc, 0xeb, 0x3a, 0xbd, 0x9e, 0x7d, 0xe8, 0x1c, 0xc0, 0x02, 0xf0, 0xd3, 0x83, 0xb1, 0x9e, 0x7d, + 0x24, 0xc1, 0xe5, 0xc8, 0x91, 0xe3, 0x1f, 0x9e, 0x01, 0x38, 0x34, 0x3d, 0x57, 0xb6, 0x3b, 0x00, + 0x04, 0x90, 0x38, 0x77, 0x1f, 0x9b, 0x88, 0xe6, 0x04, 0x9a, 0x87, 0xbe, 0xc6, 0xbd, 0xef, 0x1c, + 0x75, 0xab, 0x15, 0x15, 0x19, 0xe7, 0x30, 0xcb, 0xeb, 0xc0, 0xac, 0x43, 0x0f, 0x91, 0x79, 0x47, + 0x88, 0xec, 0xb0, 0x77, 0x76, 0x84, 0xbd, 0xb0, 0xd0, 0x51, 0xe7, 0x14, 0xc1, 0x2e, 0x11, 0x4d, + 0xef, 0x6c, 0x05, 0x5c, 0xdb, 0x83, 0x41, 0x75, 0x38, 0x05, 0xd5, 0xfc, 0x38, 0x32, 0xf1, 0x78, + 0xfa, 0xff, 0xa6, 0xda, 0xb5, 0x93, 0x71, 0x9a, 0x7c, 0xf9, 0x98, 0x95, 0xe9, 0x96, 0x3a, 0x25, + 0x4f, 0xd9, 0x9c, 0x5e, 0xf0, 0xb0, 0x98, 0x44, 0x61, 0x06, 0x3d, 0x36, 0x38, 0xea, 0x13, 0xb3, + 0x86, 0x94, 0x3a, 0x0c, 0x96, 0xa1, 0xe2, 0xf7, 0x76, 0x13, 0xfd, 0xcf, 0x80, 0xde, 0xaa, 0x9d, + 0xba, 0xe5, 0x3c, 0x6a, 0x8b, 0xc0, 0x30, 0xac, 0x05, 0x98, 0x12, 0xe1, 0x68, 0xd2, 0x2c, 0x78, + 0xee, 0x41, 0xee, 0x95, 0x15, 0x82, 0x84, 0x0d, 0x76, 0xff, 0x35, 0xa3, 0xfc, 0xe1, 0x33, 0x38, + 0xe4, 0x08, 0x5c, 0xf5, 0xdb, 0x34, 0x35, 0x8d, 0xc6, 0x39, 0xcf, 0xb0, 0x06, 0xc9, 0xc8, 0x0c, + 0x1d, 0x38, 0xcb, 0xbd, 0x0f, 0xa3, 0x89, 0x69, 0x0a, 0x9b, 0x5b, 0xc1, 0xf1, 0x42, 0xa0, 0x9c, + 0xde, 0x0a, 0xc1, 0x13, 0xc8, 0xca, 0xa8, 0x69, 0xc4, 0xa1, 0x08, 0x5b, 0x82, 0xcf, 0x28, 0x64, + 0x71, 0x86, 0x15, 0x04, 0xf4, 0xd5, 0x2b, 0x13, 0xd6, 0x74, 0xad, 0x25, 0x70, 0xe2, 0xa4, 0x92, + 0xd2, 0x63, 0xaf, 0x57, 0xf6, 0xda, 0xcc, 0x52, 0xf7, 0x02, 0x88, 0x9d, 0x1e, 0xbb, 0xaf, 0x5e, + 0xd1, 0xa1, 0x7f, 0x70, 0x60, 0xc1, 0x32, 0x26, 0xba, 0xaa, 0x2c, 0xf0, 0x06, 0xd9, 0x30, 0xf0, + 0xba, 0xaf, 0x5e, 0xf1, 0x21, 0x34, 0xf7, 0xf6, 0x2c, 0xe9, 0xb1, 0x24, 0x69, 0xe7, 0x8a, 0xb2, + 0xbd, 0xcc, 0x7a, 0x7c, 0x34, 0x79, 0x90, 0x59, 0x03, 0x9a, 0x42, 0x88, 0xe5, 0x01, 0x1d, 0x18, + 0x46, 0x10, 0x08, 0x58, 0x04, 0xb8, 0x7f, 0x61, 0xec, 0x99, 0x5e, 0xb7, 0xd7, 0xeb, 0xf9, 0xde, + 0xc1, 0xcf, 0x4a, 0x8e, 0x10, 0x87, 0xd8, 0xd4, 0xb4, 0x86, 0x43, 0xd7, 0x72, 0x04, 0xfb, 0x0c, + 0xc4, 0x67, 0x63, 0x80, 0xb1, 0x20, 0xf7, 0x8d, 0x3f, 0x8b, 0x90, 0x0b, 0xb3, 0x6b, 0x1b, 0xae, + 0x61, 0x59, 0x5a, 0x52, 0x69, 0x10, 0xbd, 0x37, 0x0d, 0xcc, 0x4f, 0x40, 0x0c, 0xa9, 0x23, 0x5d, + 0xf6, 0xaf, 0xf2, 0x2a, 0xa9, 0x21, 0x22, 0x3b, 0x75, 0xd0, 0xbb, 0x37, 0x68, 0xe3, 0xab, 0x05, + 0x2c, 0x18, 0x2f, 0x76, 0x0b, 0xcb, 0xa6, 0x4f, 0x00, 0x00, 0x4e, 0xc3, 0x16, 0x3b, 0x00, 0x94, + 0x3e, 0x18, 0x4a, 0xff, 0x10, 0x06, 0xb6, 0xfe, 0xfd, 0x1c, 0x15, 0x03, 0x22, 0x21, 0x85, 0x64, + 0x09, 0xe4, 0x85, 0x21, 0xd0, 0xb0, 0x21, 0xf7, 0xc8, 0xff, 0x3e, 0xe3, 0x10, 0x0a, 0xf9, 0x27, + 0xce, 0x72, 0x89, 0x0f, 0xdd, 0x8f, 0x83, 0xc9, 0xf2, 0xd3, 0x9a, 0xfb, 0x33, 0xb5, 0xf6, 0xe4, + 0x02, 0x7b, 0x06, 0xb8, 0x25, 0x2d, 0x98, 0x44, 0x0a, 0x26, 0xc9, 0xf2, 0x99, 0x40, 0x05, 0x71, + 0x54, 0xd4, 0x91, 0x02, 0x30, 0xec, 0xc4, 0x99, 0x87, 0xe9, 0x8c, 0x06, 0x02, 0x5a, 0x1b, 0x22, + 0x53, 0xe7, 0x64, 0x04, 0xaa, 0x44, 0xf6, 0x49, 0x75, 0x35, 0x45, 0x96, 0x6c, 0x61, 0x46, 0xad, + 0x67, 0xcf, 0x72, 0xbc, 0x35, 0x2b, 0x8d, 0x67, 0x3b, 0xa8, 0xe6, 0x3b, 0xca, 0x4f, 0xd2, 0x2f, + 0xe5, 0x6e, 0xc6, 0xf5, 0xdd, 0x8c, 0x77, 0x91, 0x56, 0x6d, 0x6a, 0xbc, 0x4e, 0xe1, 0xd6, 0xad, + 0x8d, 0x9f, 0x58, 0x9c, 0xa5, 0xb8, 0x3a, 0x80, 0xd4, 0x64, 0x5d, 0x17, 0x3c, 0x50, 0xbe, 0x63, + 0x44, 0x53, 0x5c, 0xd4, 0x29, 0xe6, 0x68, 0x25, 0x1c, 0xad, 0x04, 0xf4, 0xbb, 0xa8, 0x93, 0xdf, + 0xb8, 0x0b, 0x81, 0xcd, 0x96, 0x84, 0xab, 0xce, 0xad, 0x34, 0x17, 0xbb, 0x69, 0xa6, 0x60, 0xda, + 0x6a, 0xe6, 0x09, 0x32, 0x8e, 0xb7, 0x74, 0x08, 0xbf, 0x83, 0xcc, 0x95, 0x1e, 0xad, 0x2e, 0x15, + 0xe4, 0xbc, 0x40, 0xeb, 0x40, 0xc5, 0xfb, 0xae, 0xf1, 0xba, 0x27, 0x0a, 0x73, 0x48, 0xd3, 0xe2, + 0x93, 0x49, 0x92, 0xc6, 0x66, 0x62, 0xed, 0x1c, 0x4a, 0x77, 0x0f, 0xc5, 0x96, 0xed, 0x3e, 0x0f, + 0xf8, 0xab, 0x57, 0x20, 0x24, 0xf9, 0xbb, 0x0b, 0x10, 0xac, 0xa5, 0x2e, 0xce, 0x69, 0xf8, 0x85, + 0x9e, 0xd3, 0x77, 0x3c, 0x1c, 0x9b, 0xe8, 0x65, 0xd0, 0x9c, 0x2d, 0x60, 0x9b, 0x8a, 0x0b, 0xc6, + 0x52, 0x91, 0xe4, 0x4a, 0x8a, 0xf5, 0xb1, 0xa6, 0x0e, 0x9a, 0x35, 0xf7, 0xbb, 0x3e, 0xb2, 0x50, + 0x5b, 0x49, 0x7f, 0xd0, 0xe9, 0x6e, 0xa4, 0x60, 0x74, 0xc3, 0x05, 0x2b, 0xc4, 0x4c, 0xa6, 0x72, + 0xf4, 0x8a, 0x5f, 0x03, 0x65, 0x0e, 0xa7, 0x90, 0xbf, 0x46, 0xb4, 0xe9, 0x28, 0xed, 0x86, 0x9d, + 0x59, 0x96, 0x92, 0xfd, 0xe0, 0xc7, 0xe6, 0xe9, 0x3e, 0x98, 0xbd, 0x7d, 0x47, 0x99, 0x2d, 0x71, + 0x3d, 0x3d, 0xf8, 0x84, 0x93, 0x63, 0x56, 0x15, 0x9e, 0x24, 0xec, 0x53, 0xe1, 0xc5, 0xce, 0xbe, + 0xe1, 0xb3, 0x42, 0x2d, 0xc0, 0xab, 0xec, 0x1a, 0xd6, 0x46, 0x11, 0x5e, 0x85, 0xd0, 0x5a, 0xae, + 0x54, 0x47, 0x19, 0x43, 0x60, 0xe0, 0xf5, 0x41, 0xc8, 0x5b, 0x65, 0xb7, 0x09, 0x47, 0x0c, 0x79, + 0x66, 0x36, 0xec, 0x8f, 0xb7, 0x7f, 0x62, 0x88, 0x87, 0x4e, 0x9e, 0xd0, 0xc2, 0x94, 0xf8, 0xac, + 0xd5, 0x26, 0x5c, 0x41, 0x88, 0xbd, 0xc6, 0x6d, 0x68, 0x62, 0xdc, 0x0b, 0x6e, 0x6c, 0xf2, 0x72, + 0x21, 0x96, 0xf0, 0x0f, 0x5d, 0xe6, 0xf7, 0x37, 0x1b, 0x6b, 0xee, 0x05, 0x86, 0x65, 0x34, 0x54, + 0x78, 0x5d, 0x66, 0x41, 0x73, 0xc2, 0x4a, 0xb7, 0xd6, 0xdc, 0x38, 0xe6, 0x0d, 0xd4, 0xc1, 0x4e, + 0xfc, 0x0a, 0xc7, 0x21, 0x02, 0xd5, 0x75, 0x51, 0x79, 0xa0, 0xed, 0xe9, 0xc5, 0x96, 0x89, 0xb6, + 0xd4, 0x08, 0xa7, 0xe0, 0x91, 0xbe, 0xa4, 0x6f, 0x68, 0x46, 0xa5, 0x14, 0xa8, 0x03, 0xd2, 0x5d, + 0x34, 0x96, 0xca, 0x77, 0xac, 0x34, 0x97, 0x3e, 0xfa, 0x49, 0x32, 0x6b, 0xe6, 0x07, 0x08, 0x30, + 0xfc, 0x8b, 0xc0, 0xb5, 0xf9, 0x77, 0x65, 0x65, 0x2c, 0xe0, 0x8e, 0xdc, 0x30, 0x3b, 0x84, 0x96, + 0xf4, 0xaa, 0x59, 0xc0, 0x5a, 0xe1, 0x9e, 0xb7, 0x4a, 0xf5, 0xd2, 0x9d, 0x74, 0x0d, 0x70, 0x31, + 0x1e, 0x98, 0x34, 0xa0, 0x8f, 0x8f, 0x77, 0x70, 0x62, 0x65, 0x77, 0x8e, 0x1a, 0x91, 0x21, 0x0d, + 0x88, 0x86, 0xa4, 0xa0, 0xf8, 0x2d, 0x11, 0x13, 0xd3, 0x90, 0xf7, 0xd6, 0xe8, 0x87, 0x1f, 0x1f, + 0xa9, 0x93, 0x73, 0x09, 0xf6, 0x8e, 0x8e, 0xc2, 0x59, 0x8a, 0x74, 0x88, 0x80, 0xbf, 0xa1, 0x8e, + 0x84, 0xa1, 0xc5, 0x95, 0x7b, 0x8d, 0x12, 0x02, 0x80, 0xdf, 0xfb, 0xb4, 0x6c, 0x81, 0x2f, 0x64, + 0xd9, 0x94, 0xcd, 0x0a, 0x3a, 0xcb, 0x83, 0x42, 0x7e, 0x49, 0x70, 0xa0, 0x26, 0xa2, 0x69, 0xbd, + 0x07, 0xdc, 0x94, 0xfe, 0x94, 0xe0, 0x48, 0x77, 0x90, 0xac, 0x86, 0xd5, 0xf7, 0x4a, 0x7c, 0x89, + 0xc9, 0x95, 0xd4, 0x58, 0x00, 0xd9, 0x10, 0xff, 0x6e, 0x46, 0xd8, 0xe3, 0x23, 0xdf, 0x60, 0x44, + 0x4a, 0x24, 0x0d, 0xd8, 0x1b, 0xbe, 0x8d, 0x19, 0x5e, 0xb6, 0x06, 0xa2, 0x95, 0x02, 0xcf, 0xa9, + 0x2d, 0x29, 0xfc, 0xc4, 0x8a, 0x0f, 0x55, 0x36, 0x1b, 0x88, 0x96, 0x09, 0xe2, 0x07, 0x91, 0x80, + 0xb9, 0xc2, 0x48, 0xdd, 0x54, 0x37, 0xa1, 0xdb, 0x99, 0xcc, 0x5f, 0x6d, 0x96, 0xc6, 0x17, 0x1a, + 0x9e, 0x3e, 0x6d, 0xfb, 0x1a, 0x2d, 0xba, 0x6f, 0xdd, 0x54, 0x4e, 0x7c, 0x85, 0xe1, 0x79, 0xa0, + 0x07, 0x30, 0x11, 0xd4, 0x20, 0xdd, 0xfd, 0x37, 0x62, 0x92, 0x14, 0x1f, 0x65, 0xca, 0x14, 0xb8, + 0xfd, 0x12, 0x8b, 0x77, 0xe4, 0xd7, 0x07, 0x7a, 0xfd, 0xda, 0x47, 0x47, 0xaa, 0xed, 0xb6, 0x34, + 0x29, 0x93, 0x5e, 0x46, 0xe3, 0xa8, 0xe7, 0x47, 0xda, 0x84, 0xfe, 0x4f, 0x1e, 0x55, 0x2e, 0x52, + 0x5f, 0xf1, 0x9b, 0xc8, 0x1a, 0x31, 0xfe, 0x2f, 0xe0, 0x59, 0x0b, 0x0f, 0xbb, 0xd0, 0xd0, 0x27, + 0xd3, 0xd8, 0x52, 0x1a, 0x9b, 0x71, 0x93, 0x6e, 0xc6, 0xcb, 0x9a, 0xf5, 0x17, 0x10, 0x23, 0xeb, + 0xa6, 0x91, 0xcd, 0xd2, 0x74, 0xc3, 0x3a, 0x9a, 0x9d, 0x68, 0x20, 0x55, 0xcf, 0xca, 0x46, 0x9a, + 0x40, 0x55, 0xd7, 0x92, 0x96, 0x50, 0x60, 0x15, 0x19, 0xe8, 0x2c, 0x2d, 0x41, 0xa4, 0x55, 0x04, + 0x69, 0x8d, 0x96, 0x4d, 0xd2, 0x17, 0xeb, 0x5c, 0xcb, 0x82, 0xa9, 0x61, 0xdf, 0x80, 0x7f, 0x7f, + 0x52, 0x51, 0x97, 0xa4, 0x4f, 0x9e, 0x80, 0xc1, 0xa0, 0x67, 0x2d, 0x6f, 0x6a, 0x82, 0x68, 0xe6, + 0x5d, 0xda, 0x0f, 0x62, 0x02, 0x18, 0x27, 0x73, 0x30, 0x57, 0x54, 0xcc, 0x77, 0xb5, 0x0d, 0x0f, + 0xea, 0x9e, 0xda, 0xc6, 0xd1, 0x93, 0xd5, 0xa6, 0xca, 0xa4, 0x60, 0x7d, 0x46, 0x43, 0x03, 0xd4, + 0x6a, 0xe5, 0xe6, 0xc3, 0xb6, 0xaf, 0x50, 0xd4, 0xd5, 0xf3, 0x47, 0x11, 0x55, 0x5a, 0xb4, 0x8e, + 0xee, 0x7b, 0x11, 0xed, 0x50, 0x4a, 0x9b, 0xc3, 0xa6, 0x53, 0xae, 0x2c, 0xf3, 0xf7, 0xc0, 0x73, + 0x75, 0xc7, 0xef, 0x0d, 0x31, 0xec, 0x0a, 0x13, 0xce, 0x7d, 0xab, 0x31, 0x5f, 0x4f, 0xfe, 0xe3, + 0xfb, 0x26, 0x3f, 0xec, 0x79, 0x1d, 0x5b, 0xc8, 0xac, 0x5a, 0x60, 0xfc, 0x30, 0xf0, 0x23, 0x83, + 0xb4, 0xf9, 0xf4, 0xe2, 0xfc, 0x4c, 0xdf, 0xe6, 0x6c, 0xb9, 0xae, 0x21, 0xf7, 0xd3, 0x34, 0x2b, + 0x02, 0x03, 0xeb, 0xf4, 0xfd, 0x76, 0xfb, 0xee, 0xee, 0xce, 0xb9, 0xeb, 0x38, 0x8c, 0x8f, 0xdb, + 0xbe, 0xeb, 0xba, 0x78, 0x1f, 0x61, 0x10, 0xf5, 0xfc, 0xc0, 0xc0, 0x2a, 0xaa, 0x41, 0xd4, 0xfd, + 0x8f, 0xfe, 0xd2, 0x97, 0x3d, 0xfa, 0x96, 0x08, 0xef, 0x7c, 0xfa, 0x2f, 0x0e, 0x0f, 0x61, 0xa2, + 0x3b, 0x80, 0x4e, 0xce, 0xbe, 0xd0, 0x3e, 0x81, 0x0e, 0xfc, 0xaf, 0xec, 0xd0, 0xf5, 0x2b, 0xd2, + 0xc2, 0x6a, 0x8a, 0xee, 0x8a, 0x81, 0xde, 0x10, 0xaf, 0xd2, 0xfa, 0xc4, 0x75, 0x3c, 0x9b, 0x1c, + 0x0e, 0xd4, 0xfd, 0xfe, 0x91, 0xdd, 0xb9, 0xdc, 0x3f, 0xdd, 0xbf, 0xec, 0x9e, 0x1e, 0x5c, 0x7a, + 0x47, 0x6f, 0x7d, 0xdb, 0x97, 0x77, 0x5a, 0x2e, 0xe9, 0xd9, 0xbe, 0x77, 0xea, 0xf5, 0x6a, 0x3d, + 0x78, 0xcf, 0x72, 0x04, 0x80, 0xbe, 0x0b, 0x33, 0xbc, 0x83, 0xcb, 0xce, 0xe9, 0xd1, 0x79, 0xcf, + 0xee, 0x9e, 0xe2, 0x7d, 0xd7, 0xd1, 0x69, 0xef, 0xb2, 0x0b, 0xc8, 0x0e, 0x2f, 0xbd, 0xde, 0xa9, + 0xe7, 0x5d, 0x1e, 0xc2, 0x18, 0xde, 0xba, 0xc8, 0xcf, 0x03, 0xf8, 0xf4, 0x3a, 0xf5, 0x1b, 0x30, + 0xa1, 0xdd, 0x49, 0x59, 0xea, 0x09, 0x8c, 0xb2, 0x4e, 0x6a, 0x54, 0x63, 0xd2, 0xef, 0xe8, 0xcd, + 0x55, 0x3e, 0xb5, 0x1c, 0x81, 0x0c, 0x42, 0x0f, 0xfc, 0xa1, 0x06, 0x62, 0x07, 0x6f, 0x3f, 0x1b, + 0x99, 0x3d, 0xe4, 0x1a, 0xe2, 0xe9, 0xe3, 0x8d, 0x70, 0x54, 0x4d, 0xe1, 0x57, 0x16, 0x53, 0x47, + 0x65, 0x05, 0xab, 0xa9, 0xeb, 0xfa, 0xb9, 0x0b, 0x74, 0x0d, 0x6e, 0x87, 0xf1, 0x3c, 0x35, 0x7d, + 0x03, 0xd8, 0x5a, 0xb7, 0xe1, 0x6f, 0xce, 0xde, 0xb1, 0xf6, 0x77, 0xae, 0xba, 0xe5, 0x10, 0xb3, + 0x3d, 0x2b, 0x7c, 0xe2, 0x88, 0xdb, 0x4c, 0xaa, 0xbe, 0x91, 0xe6, 0x6d, 0xe4, 0xa0, 0x0b, 0x69, + 0x4d, 0xea, 0x2a, 0x5a, 0x19, 0x16, 0x62, 0x00, 0x9f, 0x0c, 0x86, 0x88, 0x31, 0x1c, 0x7a, 0x30, + 0xc6, 0xcb, 0x1f, 0x53, 0xfe, 0xee, 0x64, 0x0d, 0x07, 0x91, 0x52, 0xd5, 0xf9, 0x5d, 0xc4, 0xd6, + 0x93, 0xd7, 0xc9, 0x97, 0xdf, 0xea, 0x27, 0x35, 0x24, 0x48, 0x16, 0xf3, 0x30, 0xaf, 0x93, 0x5f, + 0xb2, 0x8a, 0x65, 0x0d, 0xca, 0x3b, 0xb8, 0xdf, 0xd0, 0xd0, 0x86, 0x5d, 0xd7, 0x7d, 0x53, 0xea, + 0xa6, 0xae, 0x1c, 0xe0, 0x33, 0x9d, 0x8c, 0x1a, 0xfd, 0x8d, 0x6e, 0x55, 0xa8, 0x34, 0x6a, 0x6b, + 0x86, 0x69, 0xf4, 0x9f, 0x9f, 0x3f, 0xfe, 0x6a, 0xaa, 0x4b, 0x3a, 0x1a, 0xbc, 0x5e, 0x94, 0x75, + 0x03, 0xa3, 0x7f, 0xf5, 0x7a, 0xa0, 0x9f, 0xcd, 0xac, 0x9d, 0x42, 0xc4, 0xda, 0x21, 0x04, 0x8e, + 0x82, 0xf2, 0x10, 0x22, 0x30, 0x1d, 0x32, 0x29, 0x9c, 0x2d, 0x6c, 0x14, 0x22, 0x9c, 0x42, 0xf0, + 0x0c, 0x62, 0x1b, 0x2f, 0x17, 0xdc, 0x29, 0x80, 0x7d, 0x6a, 0x7a, 0xd6, 0xd2, 0xc0, 0xc3, 0x08, + 0xc2, 0x5c, 0x2f, 0xc1, 0x14, 0x6a, 0x09, 0xa4, 0x7c, 0xf0, 0x24, 0xe8, 0x7f, 0xc9, 0x3a, 0x0b, + 0x6e, 0x8c, 0xaa, 0xb8, 0x48, 0xf2, 0x56, 0x74, 0xda, 0x37, 0x6d, 0x4d, 0x20, 0x1e, 0x6d, 0x9c, + 0x3f, 0x0b, 0x96, 0xdd, 0x34, 0x0e, 0xbe, 0xd5, 0x1c, 0x38, 0x1a, 0xa9, 0xf8, 0xc5, 0x03, 0x2c, + 0x35, 0xfd, 0x7e, 0x7e, 0x76, 0x0a, 0x3e, 0xf0, 0x1f, 0x14, 0x8e, 0xbd, 0x85, 0x80, 0x94, 0x1d, + 0x3b, 0x7f, 0x49, 0xd9, 0x2d, 0x1c, 0xa2, 0xae, 0xed, 0x05, 0x26, 0xa8, 0x7d, 0x03, 0x8c, 0x38, + 0xc5, 0x7a, 0x11, 0xa0, 0x6a, 0x23, 0x6a, 0x63, 0x09, 0xa9, 0xf6, 0x16, 0xcd, 0xc3, 0x45, 0x0c, + 0xdb, 0x2c, 0x0f, 0xc0, 0x0c, 0x3d, 0x06, 0x1b, 0x4b, 0xe5, 0x86, 0xdd, 0x2f, 0x72, 0xe8, 0xa3, + 0x17, 0xf4, 0x5e, 0xd8, 0x06, 0x69, 0x11, 0x43, 0xda, 0x06, 0x66, 0xbf, 0x62, 0x86, 0x37, 0x64, + 0x0c, 0xb8, 0xf9, 0x0c, 0x47, 0xee, 0x70, 0x5c, 0xea, 0xcf, 0x07, 0x41, 0xa7, 0xb0, 0xd9, 0x29, + 0x8d, 0x3f, 0x85, 0x29, 0x16, 0x41, 0x74, 0xf2, 0x8c, 0xa0, 0x48, 0x8b, 0x33, 0xe1, 0x74, 0x14, + 0x18, 0x6d, 0x20, 0xc7, 0xde, 0x46, 0x0e, 0xe5, 0x1c, 0xef, 0xbc, 0xe8, 0x1a, 0x39, 0xc6, 0x7b, + 0xec, 0xef, 0x13, 0x79, 0xbb, 0xd7, 0x18, 0x20, 0x9f, 0x25, 0x31, 0xfd, 0x75, 0xda, 0x30, 0xf5, + 0x48, 0xa6, 0x94, 0xcd, 0x84, 0x29, 0x99, 0x5b, 0xda, 0x1e, 0xed, 0x58, 0x72, 0x55, 0x06, 0xee, + 0xcd, 0x34, 0x3e, 0x7d, 0xfc, 0x7c, 0x01, 0xbb, 0xdb, 0x56, 0x72, 0x36, 0x54, 0xa6, 0x1e, 0x4a, + 0x59, 0xfe, 0x9d, 0xf1, 0xe9, 0x3b, 0x48, 0x2c, 0x4a, 0xa5, 0x09, 0xb5, 0x4b, 0x54, 0xe9, 0x06, + 0x9c, 0xad, 0xf1, 0x0a, 0x91, 0xcb, 0xd2, 0xb7, 0x19, 0x5a, 0xf6, 0x73, 0x6f, 0x19, 0x16, 0x0f, + 0x59, 0x44, 0x56, 0x8f, 0xba, 0xa8, 0xf8, 0x90, 0x8d, 0x18, 0xe8, 0x62, 0x32, 0x32, 0x21, 0x2d, + 0x0a, 0x56, 0xec, 0x33, 0xd8, 0x31, 0xe8, 0x29, 0xaf, 0x70, 0x5d, 0x4b, 0xf0, 0x87, 0xca, 0x52, + 0xc2, 0xbb, 0x30, 0x11, 0x64, 0x44, 0x05, 0x28, 0x63, 0x19, 0xe7, 0x8c, 0x3d, 0x00, 0xdf, 0x33, + 0xe4, 0x26, 0xb6, 0x65, 0x55, 0x12, 0xad, 0x48, 0x41, 0x52, 0xa9, 0x35, 0x70, 0xca, 0x90, 0x53, + 0xca, 0xba, 0x99, 0x69, 0xaa, 0xca, 0x93, 0x70, 0xe4, 0x2f, 0x84, 0x60, 0x61, 0xb5, 0x40, 0x5f, + 0x81, 0x04, 0xc0, 0x4b, 0xad, 0x4a, 0xb2, 0x52, 0xd8, 0x78, 0x27, 0x25, 0xef, 0x7b, 0x9b, 0xbd, + 0x06, 0xd8, 0x74, 0xc6, 0x04, 0x49, 0x62, 0xd8, 0x9f, 0x64, 0xf4, 0x40, 0x90, 0x72, 0xc8, 0xb0, + 0xd6, 0x38, 0x6d, 0x2e, 0x0c, 0xb8, 0xeb, 0xe5, 0x26, 0xcd, 0x64, 0xe0, 0x0e, 0xf0, 0x1e, 0x1a, + 0xcd, 0x12, 0x8e, 0x0a, 0x03, 0x31, 0x0c, 0xe8, 0x40, 0xec, 0xed, 0xad, 0x1c, 0xc4, 0x8d, 0x66, + 0xf5, 0xe5, 0x02, 0x58, 0x5d, 0xae, 0xac, 0x42, 0x68, 0xab, 0x18, 0xac, 0x64, 0x24, 0x1a, 0x32, + 0x02, 0x65, 0xe0, 0xba, 0x43, 0x94, 0xa2, 0x68, 0x10, 0x90, 0xcf, 0x8a, 0x09, 0x1c, 0xe7, 0x34, + 0xeb, 0x62, 0x9d, 0xf5, 0x1b, 0xa9, 0x56, 0x0a, 0x19, 0x96, 0x3a, 0xd1, 0xda, 0xc8, 0x88, 0xb3, + 0xa9, 0xbc, 0x6d, 0xe8, 0x93, 0x1b, 0xd8, 0xe8, 0xe5, 0x72, 0x0b, 0x4b, 0x43, 0x0f, 0xfc, 0xc3, + 0xe6, 0x4a, 0x25, 0xf7, 0xfd, 0x2b, 0xd7, 0xee, 0x95, 0x7f, 0x70, 0x9a, 0xaa, 0x3e, 0xae, 0x97, + 0xe5, 0xb5, 0x8c, 0x08, 0x70, 0x31, 0x74, 0xc0, 0x05, 0x35, 0x1b, 0x86, 0x84, 0xca, 0xb3, 0x66, + 0x45, 0xb2, 0x68, 0x00, 0xd4, 0xa3, 0x18, 0xb5, 0xd0, 0xb0, 0x10, 0x0e, 0xa1, 0x57, 0x6a, 0x18, + 0x3d, 0xf6, 0xf7, 0x0f, 0x2c, 0x7d, 0xd1, 0x88, 0xbd, 0xe0, 0x07, 0x70, 0x19, 0x91, 0x64, 0x33, + 0xba, 0x54, 0x13, 0x78, 0xa0, 0xfb, 0x71, 0x1b, 0xb0, 0x66, 0x30, 0xa8, 0x23, 0x63, 0x23, 0xc2, + 0x25, 0xaa, 0xe7, 0x8a, 0x9b, 0xa4, 0x90, 0xbf, 0x20, 0xe0, 0xc7, 0xc7, 0xfd, 0xe7, 0x41, 0x40, + 0x35, 0xdf, 0xd6, 0x42, 0x16, 0x3e, 0x6e, 0x39, 0x0d, 0xbf, 0x2c, 0x57, 0x08, 0x04, 0x22, 0xa0, + 0x16, 0xcc, 0x37, 0xb2, 0xd9, 0xf4, 0x16, 0x32, 0x4c, 0x88, 0x37, 0xe0, 0x86, 0xa0, 0x57, 0x3c, + 0x3e, 0x8a, 0xa1, 0x0b, 0xff, 0x1c, 0x83, 0x1c, 0x1e, 0x1f, 0x9f, 0xff, 0x2a, 0xc7, 0x61, 0x81, + 0x0f, 0x99, 0xa0, 0x63, 0x30, 0x79, 0x61, 0x35, 0x90, 0x2e, 0x91, 0x08, 0xf6, 0x0d, 0x66, 0xc2, + 0x80, 0x5f, 0x71, 0x4d, 0x52, 0xcb, 0xbb, 0x46, 0xe9, 0xc8, 0x2b, 0xca, 0x20, 0x84, 0xd3, 0xf5, + 0x4a, 0xaf, 0xae, 0x1c, 0xc7, 0x09, 0xaf, 0x07, 0x14, 0x3a, 0x03, 0xdc, 0x05, 0xae, 0x76, 0x09, + 0x14, 0x7e, 0x59, 0x82, 0xac, 0xc7, 0x03, 0x90, 0xa8, 0x33, 0x0d, 0xf3, 0xd5, 0x7d, 0x94, 0xb9, + 0x80, 0xf5, 0x21, 0xfe, 0x8c, 0xd2, 0x50, 0x86, 0xf4, 0x6d, 0x0a, 0x06, 0xcb, 0x94, 0x46, 0x04, + 0x63, 0x5c, 0x98, 0xc6, 0x05, 0x96, 0xfe, 0xf1, 0x75, 0x29, 0x0a, 0xa6, 0x2a, 0x3c, 0x43, 0xb0, + 0x25, 0xd3, 0xa4, 0x28, 0x92, 0xb1, 0x52, 0xb2, 0x07, 0x36, 0xe3, 0xe4, 0x96, 0xb3, 0xbb, 0x02, + 0x24, 0x42, 0xfe, 0x60, 0x33, 0x52, 0x4c, 0xd8, 0x2c, 0x8d, 0x49, 0xce, 0xd9, 0x6d, 0x78, 0x9b, + 0x3e, 0x10, 0xed, 0x80, 0x74, 0xa1, 0x7e, 0x1a, 0xc2, 0xa6, 0x43, 0x2a, 0x00, 0xcb, 0x64, 0x31, + 0xc1, 0x8d, 0x04, 0xc5, 0x97, 0xb5, 0x7c, 0x98, 0x90, 0x53, 0x0e, 0x13, 0x46, 0xf8, 0x2a, 0x01, + 0x2b, 0xf4, 0xe5, 0x9a, 0x8a, 0x0a, 0xbc, 0x86, 0x03, 0x69, 0x83, 0x8b, 0x85, 0xb8, 0x44, 0x6e, + 0x29, 0x80, 0x51, 0x8d, 0x1c, 0xf5, 0x7e, 0x42, 0x39, 0x75, 0xc0, 0x19, 0x9e, 0x23, 0x71, 0xf0, + 0x2d, 0x27, 0xc5, 0x15, 0x92, 0xe7, 0xe0, 0x1d, 0xcb, 0xc9, 0xda, 0xd6, 0xdf, 0x25, 0xf3, 0xa2, + 0x9e, 0x8c, 0x6c, 0x1d, 0xae, 0x36, 0x62, 0xe3, 0x59, 0xeb, 0xea, 0x4d, 0x08, 0x3a, 0xb3, 0xcd, + 0xe1, 0xb5, 0x87, 0x00, 0x68, 0xe6, 0x4a, 0xde, 0x28, 0x37, 0x38, 0x7b, 0x47, 0x98, 0xa6, 0x40, + 0xea, 0x02, 0xb1, 0x3b, 0x49, 0x05, 0x1e, 0x2c, 0x83, 0x63, 0x79, 0x22, 0x9f, 0x4a, 0x43, 0x6f, + 0xff, 0x53, 0xe3, 0xff, 0x9f, 0xf8, 0x65, 0x1b, 0xb6, 0x6c, 0x4d, 0x53, 0xb9, 0xb5, 0x9e, 0xa9, + 0x56, 0x0e, 0x8a, 0x83, 0x83, 0xe2, 0xc3, 0x2d, 0x16, 0x3f, 0xe0, 0x2b, 0x8f, 0xc5, 0x82, 0x3a, + 0xc0, 0x15, 0xbf, 0xb6, 0xc3, 0x60, 0xfd, 0x3d, 0xae, 0x3e, 0x65, 0x86, 0x4e, 0xed, 0x05, 0x8c, + 0xb1, 0xc7, 0xed, 0x50, 0x55, 0x1a, 0x30, 0xe2, 0x61, 0xf8, 0xab, 0x24, 0x61, 0x94, 0x8e, 0x21, + 0x2b, 0x75, 0xf2, 0x0b, 0x7d, 0x28, 0x4c, 0x66, 0x81, 0xf2, 0x02, 0x16, 0x0c, 0x3c, 0x10, 0xd2, + 0xf0, 0x5a, 0x5b, 0x1e, 0x3f, 0x94, 0xf7, 0x28, 0xe4, 0x85, 0x26, 0x78, 0x67, 0x93, 0x5d, 0x65, + 0xd7, 0xab, 0x32, 0xdb, 0x0e, 0x62, 0xd2, 0x3a, 0x31, 0x65, 0x9e, 0x0a, 0x44, 0x55, 0x55, 0xa8, + 0x1d, 0xf3, 0x54, 0x71, 0x69, 0xf5, 0x5c, 0x0c, 0xf9, 0x48, 0xd6, 0xf9, 0xa8, 0x0d, 0x5b, 0x55, + 0xbd, 0x65, 0x03, 0xa1, 0x2e, 0xbe, 0xa8, 0x9a, 0x4a, 0xf9, 0x76, 0x0c, 0xf1, 0x15, 0x0e, 0xbe, + 0xbc, 0x84, 0xc4, 0x32, 0x28, 0x75, 0x0a, 0xa3, 0x67, 0x33, 0x93, 0x02, 0x47, 0x8e, 0xc5, 0x93, + 0x6d, 0xd7, 0x07, 0xc6, 0x67, 0x8a, 0x8f, 0x62, 0xd4, 0x33, 0xa1, 0xda, 0xf3, 0x1c, 0x7c, 0xd3, + 0x42, 0x00, 0x3f, 0x56, 0x5d, 0x56, 0x67, 0x4c, 0xfd, 0x06, 0xc2, 0x2e, 0xd6, 0x99, 0xa8, 0x28, + 0x2a, 0x59, 0x88, 0x76, 0xb2, 0x10, 0x49, 0x16, 0xca, 0x87, 0x70, 0xc8, 0x42, 0xb4, 0x8d, 0x05, + 0x24, 0x1c, 0xf2, 0x09, 0x7c, 0xa8, 0x20, 0xe9, 0x8f, 0x76, 0x5c, 0x7f, 0x9c, 0xb0, 0xfc, 0x41, + 0x51, 0x0b, 0x39, 0xe6, 0xb2, 0x34, 0x3d, 0x64, 0x41, 0x31, 0x73, 0x03, 0x79, 0x4f, 0x93, 0x03, + 0x44, 0x09, 0x7d, 0x6b, 0x1c, 0x54, 0x04, 0x61, 0x31, 0x69, 0xbb, 0xaa, 0x55, 0x2f, 0x2b, 0x51, + 0x25, 0x40, 0xe7, 0xf3, 0xc0, 0x30, 0x2a, 0x03, 0xa0, 0x60, 0x00, 0x74, 0x88, 0xea, 0x54, 0x2a, + 0x3e, 0x64, 0xb8, 0xbe, 0x55, 0x85, 0x5f, 0x1c, 0x41, 0x8f, 0x8c, 0xbe, 0x5e, 0xa9, 0x1f, 0x96, + 0x88, 0x95, 0xaf, 0x57, 0x63, 0x7b, 0xde, 0xb5, 0x95, 0x43, 0xc6, 0xfc, 0xe2, 0xe5, 0xa2, 0xea, + 0xc0, 0xeb, 0x7b, 0xd1, 0x06, 0x1f, 0xfc, 0xb3, 0xe7, 0xba, 0xcb, 0x9f, 0x6c, 0x72, 0x23, 0x6b, + 0xcc, 0x0b, 0x84, 0x93, 0x6f, 0xa4, 0xeb, 0xb0, 0x78, 0xe1, 0xaf, 0xbf, 0xfc, 0xc6, 0x57, 0x07, + 0xbf, 0x3c, 0x6b, 0x03, 0x17, 0x26, 0xe1, 0x3e, 0xc4, 0xe9, 0x20, 0xd7, 0xc9, 0xb9, 0x6b, 0xb7, + 0xfc, 0x6d, 0xc5, 0xb4, 0x0f, 0x53, 0x70, 0x9e, 0xc1, 0xcd, 0xae, 0x22, 0x05, 0x2e, 0x95, 0x2f, + 0xad, 0x9b, 0xd2, 0x48, 0x55, 0x39, 0x70, 0xe3, 0x21, 0x27, 0x68, 0x5b, 0x69, 0xb4, 0x41, 0x90, + 0xbd, 0x49, 0xd6, 0x4a, 0x61, 0xfd, 0x48, 0xaf, 0xab, 0xde, 0x0c, 0x9e, 0xe1, 0xf9, 0xdb, 0x90, + 0x8f, 0x50, 0x6c, 0xfe, 0x3c, 0x90, 0x29, 0xd9, 0xab, 0x57, 0xcd, 0x49, 0x11, 0x16, 0xf5, 0xd7, + 0x8a, 0x72, 0xe1, 0x7a, 0x01, 0xaf, 0xb1, 0x28, 0x6d, 0x8c, 0x86, 0x16, 0x44, 0xa8, 0x66, 0xc7, + 0x72, 0xe5, 0x9b, 0xeb, 0x2a, 0x48, 0xf1, 0x72, 0x70, 0xa3, 0x5c, 0xf6, 0x5a, 0x96, 0x82, 0xaf, + 0x92, 0xf8, 0x9f, 0xcd, 0x1a, 0xf2, 0xf5, 0xeb, 0xd5, 0xe1, 0x08, 0x53, 0x72, 0xba, 0xeb, 0xc0, + 0x28, 0xeb, 0x9a, 0xdb, 0x10, 0xa3, 0xb1, 0xd4, 0xf0, 0xea, 0xdb, 0xa8, 0x6b, 0x9b, 0xac, 0x0d, + 0x34, 0xae, 0x70, 0x1b, 0xc3, 0x8d, 0x6b, 0xac, 0x1f, 0x22, 0x68, 0xa0, 0x32, 0xce, 0x5a, 0x9e, + 0x35, 0xae, 0x85, 0x1e, 0x63, 0x8f, 0x5a, 0xdb, 0x2e, 0x13, 0x4b, 0x0f, 0x6b, 0x58, 0xd6, 0x9a, + 0x51, 0x88, 0x6d, 0x16, 0x01, 0x79, 0x55, 0x95, 0x55, 0x6d, 0x35, 0x0a, 0xa1, 0x2c, 0x82, 0xc9, + 0x17, 0x14, 0xea, 0x43, 0xe9, 0x3e, 0x0b, 0x40, 0xf3, 0x2f, 0xd8, 0x29, 0xbd, 0x37, 0x55, 0xb7, + 0x2d, 0xa4, 0xc6, 0xcb, 0x9f, 0xce, 0xb5, 0xa5, 0xb4, 0x5a, 0x3e, 0x3f, 0xe1, 0x36, 0xab, 0x4e, + 0x52, 0x45, 0xc4, 0x19, 0x48, 0xd6, 0xb5, 0xdd, 0xfa, 0x1e, 0x57, 0xa8, 0x20, 0x6d, 0xb1, 0xf9, + 0x2a, 0x50, 0x99, 0x74, 0x38, 0xf4, 0xba, 0x90, 0x82, 0x0d, 0x0f, 0x1f, 0x79, 0xf3, 0x95, 0x86, + 0x3e, 0xdd, 0x20, 0x59, 0x86, 0x6b, 0xe0, 0xbd, 0x21, 0xb8, 0x39, 0xb3, 0xdb, 0x62, 0x65, 0xae, + 0xb7, 0xc7, 0x96, 0xd5, 0x41, 0xa6, 0x5c, 0x7d, 0xd7, 0x01, 0x12, 0x8f, 0xfc, 0x4f, 0x00, 0xc1, + 0x49, 0x32, 0xf9, 0x4a, 0x4b, 0xb0, 0x46, 0x19, 0x79, 0x67, 0xb9, 0xbf, 0xf1, 0xa6, 0x47, 0xbf, + 0xdf, 0x71, 0xf5, 0x2f, 0xbe, 0x6d, 0xd9, 0xb8, 0x6f, 0xa9, 0xff, 0x1f, 0x2f, 0x6d, 0xf5, 0x7f, + 0x2d, 0xfd, 0x2f, 0x9e, 0xe7, 0x3b, 0x04, 0xcd, 0x34, 0x00, 0x00 }; From e95629f827f14d9e107717e5ec40af773ab890f0 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 27 Dec 2023 20:14:51 +0100 Subject: [PATCH 024/694] Remote preset cancles playlist --- wled00/remote.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 582c9f984..21db7975d 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -110,6 +110,8 @@ static void setOff() { } static void presetWithFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) { + resetNightMode(); + unloadPlaylist(); applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID); } From 662870baf4aba57047fc61b7c20447373dd1ba43 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 29 Dec 2023 10:35:44 +0100 Subject: [PATCH 025/694] Changlog update, b2 release --- CHANGELOG.md | 5 +++++ wled00/wled.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab690b31..3ff931f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## WLED changelog +#### Build 2312290 +- Fix for #3622, #3613, #3609 +- Various tweaks and fixes +- changelog update + #### Build 2312230 - Version bump: 0.14.1-b2 - Fix for Pixel Magic button diff --git a/wled00/wled.h b/wled00/wled.h index eb72b4154..0c3a77192 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2312230 +#define VERSION 2312290 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 4f55be4f19daf6c81a167f5bf6bd20ff22afb664 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 1 Jan 2024 23:21:22 +0100 Subject: [PATCH 026/694] Bugfix #3632 --- wled00/data/settings_leds.htm | 2 +- wled00/html_settings.h | 20 ++++++++++---------- wled00/wled.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index e78b1ceb4..a283fcfe1 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -26,8 +26,8 @@ // success event scE.addEventListener("load", () => { GetV(); - setABL(); checkSi(); + setABL(); d.Sf.addEventListener("submit", trySubmit); if (d.um_p[0]==-1) d.um_p.shift(); pinDropdowns(); diff --git a/wled00/html_settings.h b/wled00/html_settings.h index c8cdcfa47..579359f96 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -341,7 +341,7 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0xc1, 0x07, 0x14, 0xbe, 0x06, 0xd3, 0x3b, 0x09, 0xa6, 0x0f, 0x4d, 0x7b, 0x05, 0xac, 0x99, 0x1e, 0x2d, 0x5c, 0x6f, 0xaa, 0xc6, 0x08, 0x6f, 0x4f, 0xa7, 0xef, 0x6e, 0x81, 0x8a, 0x53, 0x37, 0x02, 0x99, 0xa7, 0xa1, 0x4a, 0x90, 0x66, 0xa2, 0xab, 0x9a, 0x35, 0x7a, 0xfc, 0x40, 0xe3, 0x3f, 0x54, - 0x4d, 0x47, 0x9c, 0xe3, 0x53, 0xb8, 0x70, 0x16, 0xd4, 0xb9, 0xb9, 0x72, 0x55, 0x44, 0x77, 0x35, + 0x4d, 0x77, 0x16, 0xd4, 0xb9, 0xb9, 0x72, 0xe1, 0x0a, 0xb1, 0x8f, 0x4f, 0x55, 0x44, 0x77, 0x35, 0x2b, 0x69, 0x1b, 0xad, 0x27, 0x4b, 0x37, 0x26, 0x7a, 0x1c, 0x3e, 0x5c, 0xb1, 0x4b, 0x4d, 0x6f, 0x98, 0x96, 0xc5, 0x85, 0x0f, 0x58, 0xb4, 0xbb, 0xcb, 0x2f, 0x9b, 0xd1, 0xc2, 0x9d, 0xc5, 0x80, 0x67, 0xe5, 0xfa, 0xc7, 0x61, 0xb0, 0x82, 0x49, 0xf3, 0x23, 0x55, 0x7b, 0x2e, 0xa7, 0x08, 0x84, @@ -354,7 +354,7 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0x5f, 0xb0, 0x09, 0x74, 0x7d, 0x20, 0xf9, 0xf7, 0xeb, 0xb3, 0x53, 0x8b, 0xc2, 0x58, 0x1c, 0xcf, 0x8e, 0x22, 0x94, 0x10, 0xcb, 0x3f, 0x10, 0xc3, 0xe8, 0x13, 0xc4, 0x44, 0x74, 0xc7, 0xa3, 0x76, 0x78, 0xcd, 0x95, 0x4b, 0x15, 0x4a, 0xc6, 0x26, 0x30, 0x7e, 0x80, 0xf1, 0xd9, 0xbe, 0xbb, 0x64, - 0xa4, 0x5a, 0xc4, 0x0f, 0x7c, 0x18, 0x94, 0x80, 0xb0, 0x60, 0x2e, 0x64, 0x23, 0x55, 0xd2, 0x06, + 0xa4, 0x5a, 0xc4, 0x0f, 0x7c, 0x18, 0x94, 0x80, 0xb0, 0x60, 0x06, 0x64, 0x23, 0x55, 0xd2, 0x06, 0x82, 0x9f, 0xed, 0x2a, 0x73, 0xdd, 0x0c, 0xe9, 0xca, 0xb3, 0x1d, 0x14, 0x25, 0xd6, 0x29, 0xc1, 0x31, 0xe9, 0xed, 0xb7, 0x86, 0x91, 0x19, 0xd9, 0xe4, 0xd4, 0x85, 0x99, 0x8a, 0x70, 0x5c, 0x7a, 0xac, 0xdb, 0xba, 0xab, 0x3d, 0x32, 0x2b, 0x43, 0xb9, 0x95, 0xf1, 0xb9, 0x95, 0xb1, 0x85, 0x8d, @@ -392,13 +392,13 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0xa5, 0xed, 0x3f, 0x28, 0x10, 0xbe, 0x45, 0x0a, 0x08, 0x80, 0xb2, 0xa4, 0x4a, 0x1c, 0x28, 0x0b, 0xdb, 0x9f, 0x7a, 0x74, 0x87, 0x0c, 0xd0, 0x58, 0x0e, 0x4d, 0xda, 0xd9, 0xdd, 0x55, 0xfd, 0xba, 0x45, 0xfe, 0xf4, 0xff, 0x0c, 0x8f, 0xc0, 0x69, 0x41, 0x54, 0x14, 0x82, 0x2a, 0xa0, 0x37, 0xb2, - 0x7d, 0xe5, 0xdd, 0xd5, 0xc5, 0x5e, 0xbb, 0x49, 0xa4, 0xa3, 0xf2, 0xb5, 0x67, 0x46, 0x3a, 0xf3, - 0xaf, 0x7f, 0xd8, 0x9e, 0x3b, 0x75, 0xe3, 0x07, 0x55, 0x43, 0x6f, 0x09, 0x4f, 0xb9, 0x53, 0x55, - 0x33, 0x16, 0x9b, 0xfa, 0xcc, 0x1f, 0x0b, 0x83, 0xcb, 0x3c, 0x10, 0x86, 0x9b, 0xa0, 0xfb, 0x0c, + 0x7d, 0xe5, 0xdd, 0xd5, 0xc5, 0x5e, 0xbb, 0x49, 0xa4, 0xa3, 0xf2, 0xb5, 0x67, 0x46, 0x3a, 0x73, + 0xb4, 0x7f, 0xd8, 0x9e, 0x3b, 0x75, 0xe3, 0x07, 0x55, 0x43, 0x6f, 0x09, 0x4f, 0xb9, 0x53, 0x55, + 0x33, 0x16, 0x9b, 0xfa, 0xcc, 0x0b, 0x0b, 0x83, 0xcb, 0x3c, 0x10, 0x86, 0x9b, 0xa0, 0xfb, 0x0c, 0x03, 0x9d, 0x0e, 0x58, 0xc3, 0xd3, 0x43, 0xc1, 0x07, 0x7a, 0xc0, 0x03, 0xc8, 0xbe, 0xa1, 0x4b, 0x60, 0x80, 0xe5, 0x9e, 0x66, 0xea, 0x46, 0xe0, 0x22, 0x1e, 0x00, 0x86, 0xb8, 0xbe, 0xe7, 0x82, 0xaf, 0xe9, 0x0b, 0x97, 0xc3, 0x40, 0x57, 0xd1, 0xba, 0xfd, 0x0a, 0xd8, 0x5c, 0x7f, 0x23, 0x63, - 0x77, 0x37, 0x89, 0x19, 0x3e, 0x9d, 0xe4, 0x29, 0x3f, 0x3d, 0xcc, 0x7b, 0x8a, 0xd3, 0x43, 0x30, + 0x77, 0x37, 0x89, 0x14, 0x3e, 0x9d, 0xe4, 0x29, 0x3f, 0x3d, 0xcc, 0x7b, 0x8a, 0xd3, 0x43, 0x30, 0x5e, 0xbc, 0xdd, 0x26, 0xd5, 0x9c, 0x82, 0xd3, 0x43, 0xe8, 0xb6, 0x40, 0x42, 0xd7, 0xb0, 0x4a, 0xc8, 0xc8, 0x77, 0x26, 0x69, 0x78, 0x8c, 0xee, 0xdc, 0x18, 0x0c, 0x62, 0x09, 0xa3, 0x20, 0x2c, 0xd3, 0x37, 0xc9, 0x00, 0xdc, 0x7a, 0x46, 0x6b, 0x32, 0x24, 0x81, 0xba, 0x39, 0x76, 0x44, 0x15, @@ -424,7 +424,7 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0x60, 0xd4, 0x79, 0x7b, 0x40, 0x8e, 0xc1, 0xd8, 0x2b, 0x1f, 0x2e, 0x4e, 0xce, 0xf9, 0x13, 0xf3, 0x80, 0xe0, 0x0d, 0xbe, 0x27, 0xfc, 0xa9, 0xb0, 0x62, 0x66, 0x11, 0x71, 0xe7, 0x2d, 0xe2, 0xdd, 0xef, 0x1c, 0x90, 0x23, 0xef, 0x46, 0xe2, 0x00, 0x8f, 0x61, 0xd7, 0x2d, 0x21, 0xe0, 0x81, 0xee, - 0x41, 0xce, 0x63, 0x99, 0x03, 0x67, 0xd8, 0x1d, 0x38, 0xd2, 0xad, 0x47, 0x15, 0xc2, 0x43, 0xea, + 0x41, 0xf2, 0x63, 0x99, 0x03, 0x67, 0xd8, 0x1d, 0x38, 0xd2, 0xad, 0x47, 0x15, 0xc2, 0x43, 0xea, 0x0e, 0x74, 0x01, 0xd2, 0x33, 0x88, 0x80, 0xa5, 0x19, 0xc2, 0x77, 0x77, 0x9d, 0x61, 0xe7, 0xe9, 0x89, 0xf7, 0x08, 0xd9, 0x90, 0xc3, 0xae, 0x4d, 0x7c, 0xd9, 0x05, 0x10, 0xa7, 0xde, 0x31, 0x86, 0xc1, 0x81, 0x1a, 0x55, 0x28, 0xa4, 0x1e, 0x41, 0x14, 0xff, 0x8f, 0xb5, 0x1b, 0x32, 0x6b, 0xa5, @@ -597,7 +597,7 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0x95, 0x37, 0x28, 0xb7, 0x6f, 0x34, 0xe9, 0xc3, 0x76, 0x7f, 0xba, 0x6f, 0xff, 0x66, 0x76, 0x07, 0x32, 0x30, 0x07, 0x93, 0x29, 0xb6, 0x96, 0x36, 0x95, 0xc9, 0xca, 0x54, 0xaa, 0xc5, 0x73, 0xef, 0xca, 0x45, 0x13, 0xa0, 0xe6, 0x8e, 0x00, 0x51, 0xed, 0xe9, 0x49, 0xcd, 0x1f, 0x02, 0xda, 0x2c, - 0xf6, 0x4c, 0x8e, 0x8b, 0x3c, 0xa2, 0x61, 0xe2, 0x86, 0x72, 0xc7, 0xc4, 0xb8, 0x62, 0x00, 0xee, + 0xf6, 0x4c, 0xce, 0x8d, 0x3c, 0xa2, 0x61, 0xe2, 0x86, 0x72, 0xc7, 0xc4, 0xb8, 0x62, 0x00, 0xee, 0x6c, 0xfb, 0xf2, 0xb6, 0xb4, 0x93, 0x3e, 0x96, 0x25, 0x14, 0xb7, 0x18, 0x99, 0x81, 0xd4, 0x7e, 0x60, 0x6d, 0xdf, 0xcf, 0x2f, 0xd1, 0xef, 0x94, 0x6c, 0x5b, 0xfa, 0x12, 0x1f, 0x9a, 0x65, 0x2c, 0x0c, 0xcd, 0x26, 0xd8, 0xfe, 0x57, 0x5c, 0x9a, 0x17, 0x7b, 0x8f, 0x91, 0x41, 0x00, 0xc8, 0x28, @@ -826,8 +826,8 @@ const uint8_t PAGE_settings_leds[] PROGMEM = { 0xcb, 0xe4, 0x4a, 0x4c, 0x97, 0x2b, 0xaf, 0xd8, 0x2f, 0x0f, 0x9c, 0x6b, 0x69, 0xe4, 0xdc, 0x26, 0xca, 0xff, 0x7f, 0xe4, 0x4c, 0x64, 0x51, 0x64, 0x52, 0x3b, 0xd9, 0x86, 0xbc, 0x8e, 0x05, 0x4a, 0xe5, 0xc9, 0xf2, 0xff, 0xd2, 0x3e, 0x4e, 0x0b, 0xb7, 0xa6, 0x32, 0x2b, 0x0f, 0xfc, 0xfb, 0xe9, - 0xb2, 0xdb, 0x16, 0xee, 0x6a, 0xe1, 0x16, 0x17, 0xfe, 0xff, 0x1b, 0xfe, 0x1f, 0xcd, 0xd9, 0x9d, - 0x3a, 0xcf, 0x61, 0x00, 0x00 + 0xb2, 0xdb, 0x16, 0xee, 0x6a, 0xe1, 0x16, 0x17, 0xfe, 0xff, 0x1b, 0xfe, 0x1f, 0x72, 0x20, 0x5a, + 0x39, 0xcf, 0x61, 0x00, 0x00 }; diff --git a/wled00/wled.h b/wled00/wled.h index 0c3a77192..b052047ca 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2312290 +#define VERSION 2401010 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 4e0f3e35db356d728b589f7700a26e398ad20160 Mon Sep 17 00:00:00 2001 From: PeterP Date: Wed, 3 Jan 2024 15:08:02 -0500 Subject: [PATCH 027/694] Update README.md Pointed link to current directions for compiling WLED --- usermods/Animated_Staircase/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 618a1f7cf..320b744a5 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -11,7 +11,7 @@ The Animated Staircase can be controlled by the WLED API. Change settings such a speed, on/off time and distance by sending an HTTP request, see below. ## WLED integration -To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://github.com/Aircoookie/WLED/wiki/Compiling-WLED). +To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). Before compiling, you have to make the following modifications: From cdc86402184582b5b447d24e6c00bd87097d840e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 6 Jan 2024 17:01:34 +0100 Subject: [PATCH 028/694] Merge pull request #3648 from willmmiles/json-response-locking Expand JSON buffer lock scope to entire web reply --- wled00/fcn_declare.h | 16 ++++++++++++++++ wled00/json.cpp | 17 ++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index e046c40dc..c65f7a90b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -356,6 +356,22 @@ um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); uint8_t get_random_wheel_index(uint8_t pos); +// RAII guard class for the JSON Buffer lock +// Modeled after std::lock_guard +class JSONBufferGuard { + bool holding_lock; + public: + inline JSONBufferGuard(uint8_t module=255) : holding_lock(requestJSONBufferLock(module)) {}; + inline ~JSONBufferGuard() { if (holding_lock) releaseJSONBufferLock(); }; + inline JSONBufferGuard(const JSONBufferGuard&) = delete; // Noncopyable + inline JSONBufferGuard& operator=(const JSONBufferGuard&) = delete; + inline JSONBufferGuard(JSONBufferGuard&& r) : holding_lock(r.holding_lock) { r.holding_lock = false; }; // but movable + inline JSONBufferGuard& operator=(JSONBufferGuard&& r) { holding_lock |= r.holding_lock; r.holding_lock = false; return *this; }; + inline bool owns_lock() const { return holding_lock; } + explicit inline operator bool() const { return owns_lock(); }; + inline void release() { if (holding_lock) releaseJSONBufferLock(); holding_lock = false; } +}; + #ifdef WLED_ADD_EEPROM_SUPPORT //wled_eeprom.cpp void applyMacro(byte index); diff --git a/wled00/json.cpp b/wled00/json.cpp index fd298aef0..2e3b27b97 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1013,6 +1013,17 @@ void serializeModeNames(JsonArray arr) } } + +// Global buffer locking response helper class +class GlobalBufferAsyncJsonResponse: public JSONBufferGuard, public AsyncJsonResponse { + public: + inline GlobalBufferAsyncJsonResponse(bool isArray) : JSONBufferGuard(17), AsyncJsonResponse(&doc, isArray) {}; + virtual ~GlobalBufferAsyncJsonResponse() {}; + + // Other members are inherited +}; + + void serveJson(AsyncWebServerRequest* request) { byte subJson = 0; @@ -1043,11 +1054,12 @@ void serveJson(AsyncWebServerRequest* request) return; } - if (!requestJSONBufferLock(17)) { + GlobalBufferAsyncJsonResponse *response = new GlobalBufferAsyncJsonResponse(subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary + if (!response->owns_lock()) { request->send(503, "application/json", F("{\"error\":3}")); + delete response; return; } - AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary JsonVariant lDoc = response->getRoot(); @@ -1090,7 +1102,6 @@ void serveJson(AsyncWebServerRequest* request) DEBUG_PRINT(F("JSON content length: ")); DEBUG_PRINTLN(len); request->send(response); - releaseJSONBufferLock(); } #ifdef WLED_ENABLE_JSONLIVE From 8fb5f0ef3c1cf85e743bf033020ce8bf3cc30d4a Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 6 Jan 2024 20:34:31 +0100 Subject: [PATCH 029/694] Changelog update v0.14.1-b3 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- wled00/improv.cpp | 2 +- wled00/wled.h | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff931f58..13f139551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## WLED changelog +#### Build 2401060 +- Version bump: 0.14.1-b3 +- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447) +- Fix for #3632 +- Custom palette editor mobile UI enhancement (#3617 by @imeszaros) +- changelog update + #### Build 2312290 - Fix for #3622, #3613, #3609 - Various tweaks and fixes diff --git a/package-lock.json b/package-lock.json index 71f73fefe..293ccff54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b2", + "version": "0.14.1-b3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c4faddc7e..bcb771674 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b2", + "version": "0.14.1-b3", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 0f4081f2d..dc94226e9 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.14.1-b2/%i"), VERSION); + sprintf_P(vString, PSTR("0.14.1-b3/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index b052047ca..74d60aa00 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2401010 +#define VERSION 2401060 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 052bdd8eb04731a9e87edca1a953a48571010413 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:54:46 +0100 Subject: [PATCH 030/694] fix for #3655 make WS2814 explicit in LED driver drop-down menu --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index a283fcfe1..5bddafe5e 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -340,7 +340,7 @@ ${i+1}:

About

- WLED version ##VERSION##

+ WLED version ##VERSION##

Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

- (c) 2016-2023 Christian Schwinne
- Licensed under the MIT license

+ (c) 2016-2024 Christian Schwinne
+ Licensed under the MIT license

Server message: Response error!
diff --git a/wled00/wled.h b/wled00/wled.h index eac4cbc4d..63ab1c297 100755 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403070 +#define VERSION 2403100 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From a2368a75f7abe3a2f66104c02d0326f8afaa3f46 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 11 Mar 2024 17:41:17 +0100 Subject: [PATCH 074/694] Fix AsynWebServer lib Add 160MHz builds for ESP8266 --- platformio.ini | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 8b5c11bd4..6306595a2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -144,7 +144,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0 # for I2C interface ;Wire # ESP-NOW library @@ -314,6 +314,17 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_160] +board = nodemcuv2 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_4m1m} +board_build.f_cpu = 160000000L +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +lib_deps = ${esp8266.lib_deps} +monitor_filters = esp8266_exception_decoder + [env:esp8266_2m] board = esp_wroom_02 platform = ${common.platform_wled_default} @@ -323,6 +334,16 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_160] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +board_build.f_cpu = 160000000L +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 +lib_deps = ${esp8266.lib_deps} + [env:esp01_1m_full] board = esp01_1m platform = ${common.platform_wled_default} @@ -333,6 +354,17 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} +[env:esp01_1m_full_160] +board = esp01_1m +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_1m128k} +board_build.f_cpu = 160000000L +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA + ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM +lib_deps = ${esp8266.lib_deps} + [env:esp32dev] board = esp32dev platform = ${esp32.platform} From a4384bd340cde2fc28526327156ead52fb7a93fe Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 11 Mar 2024 17:59:13 +0100 Subject: [PATCH 075/694] Simpler boot preset save - fixes #3806 --- wled00/data/index.css | 3 +-- wled00/data/index.js | 13 ++++++++++--- wled00/json.cpp | 1 + wled00/presets.cpp | 7 +++++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/wled00/data/index.css b/wled00/data/index.css index abbb76e04..fa6e20077 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -1227,7 +1227,6 @@ TD .checkmark, TD .radiomark { text-align: left; transition: background-color .5s; border-radius: 21px; - width: 99%; } .seg { @@ -1291,6 +1290,7 @@ TD .checkmark, TD .radiomark { margin: 0 auto 12px; min-height: 40px; border: 1px solid var(--c-2); + width: 100%; } /* Simplify segments */ @@ -1421,7 +1421,6 @@ dialog { .presin { padding: 8px; position: relative; - width: calc(100% - 16px); } .btn-s, diff --git a/wled00/data/index.js b/wled00/data/index.js index 7889e3b34..36c3eb1b9 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -618,7 +618,7 @@ function populatePresets(fromls) cn += `
`; if (cfg.comp.pid) cn += `
${i}
`; - cn += `
${isPlaylist(i)?"":""}${pName(i)} + cn += `
${i==lastinfo.leds.bootps?"":""}${isPlaylist(i)?"":""}${pName(i)}
@@ -1959,6 +1959,7 @@ function plR(p) function makeP(i,pl) { var content = ""; + const bps = lastinfo.leds.bootps; if (pl) { if (i===0) plJson[0] = { ps: [1], @@ -2024,6 +2025,11 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
API command
${content}
+
Save to ID 0)?i:getLowestUnusedP()}>
@@ -2445,8 +2451,9 @@ function saveP(i,pl) if (gId(`p${i}lmp`) && gId(`p${i}lmp`).value!=="") obj.ledmap = parseInt(gId(`p${i}lmp`).value); } } - - obj.psave = pI; obj.n = pN; + if (gId(`p${i}bps`).checked) obj.bootps = pI; + obj.psave = pI; + obj.n = pN; var pQN = gId(`p${i}ql`).value; if (pQN.length > 0) obj.ql = pQN; diff --git a/wled00/json.cpp b/wled00/json.cpp index b79da8d90..389dc8ae5 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -628,6 +628,7 @@ void serializeInfo(JsonObject root) leds[F("maxseg")] = strip.getMaxSegments(); //leds[F("actseg")] = strip.getActiveSegmentsNum(); //leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config + leds[F("bootps")] = bootPreset; #ifndef WLED_DISABLE_2D if (strip.isMatrix) { diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 72833a97c..7fbe49eb8 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -224,6 +224,13 @@ void savePreset(byte index, const char* pname, JsonObject sObj) if (sObj[F("ql")].is()) strlcpy(quickLoad, sObj[F("ql")].as(), 9); // client limits QL to 2 chars, buffer for 8 bytes to allow unicode else quickLoad[0] = 0; + const char *bootPS = PSTR("bootps"); + if (!sObj[FPSTR(bootPS)].isNull()) { + bootPreset = sObj[FPSTR(bootPS)] | bootPreset; + sObj.remove(FPSTR(bootPS)); + doSerializeConfig = true; + } + if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately) includeBri = sObj["ib"].as() || sObj.size()==0 || index==255; // temporary preset needs brightness segBounds = sObj["sb"].as() || sObj.size()==0 || index==255; // temporary preset needs bounds From f55465f8b8ce1f2daecb8ee0bd489f957cda1ed2 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 11 Mar 2024 18:02:03 +0100 Subject: [PATCH 076/694] Fix AsyncWebServer library --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 78ecc8fca..edcd321dd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -181,7 +181,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following From 7f8ec59939965a83834f3e5e219e0218570d2d71 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 11 Mar 2024 23:08:50 +0100 Subject: [PATCH 077/694] Changelog update --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b22c64ac..8da317b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## WLED changelog +#### Build 2403110 +- Beta WLED 0.14.2-b2 +- New AsyncWebServer (improved performance and reduced memory use) +- New builds for ESP8266 with 160MHz CPU clock +- Fixing stairway usermod and adding buildflags (#3758 by @lost-hope) +- Fixing a potential array bounds violation in ESPDMX +- Reduced RAM usage (moved strings and TZ data (by @willmmiles) to PROGMEM) +- LockedJsonResponse: Release early if possible (by @willmmiles) + #### Build 2402120 - Beta WLED 0.14.2-b1 - Possible fix for #3589 & partial fix for #3605 From 9d70ec56f28b78b438514b17618801046dc47157 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 11 Mar 2024 23:22:14 +0100 Subject: [PATCH 078/694] Distinguish 160MHz binaries --- platformio.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platformio.ini b/platformio.ini index c749a6216..914ed05b3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -340,6 +340,7 @@ monitor_filters = esp8266_exception_decoder [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D [env:esp8266_2m] board = esp_wroom_02 @@ -353,6 +354,7 @@ lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160 [env:esp01_1m_full] board = esp01_1m @@ -367,6 +369,8 @@ lib_deps = ${esp8266.lib_deps} [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA + ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM [env:esp07] board = esp07 From 05c0febd04e2b38435e0c2e8ced7aba601c349ae Mon Sep 17 00:00:00 2001 From: BaptisteHudyma Date: Tue, 12 Mar 2024 12:01:25 +0100 Subject: [PATCH 079/694] fix the 2d drift animation --- wled00/FX.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1f626bc9a..7ceb83e26 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5011,14 +5011,17 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); + const uint16_t colsCenter = (cols>>1) + (cols%2); + const uint16_t rowsCenter = (rows>>1) + (rows%2); + SEGMENT.fadeToBlackBy(128); const uint16_t maxDim = MAX(cols, rows)/2; unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup for (float i = 1; i < maxDim; i += 0.25) { float angle = radians(t * (maxDim - i)); - uint16_t myX = (cols>>1) + (uint16_t)(sin_t(angle) * i) + (cols%2); - uint16_t myY = (rows>>1) + (uint16_t)(cos_t(angle) * i) + (rows%2); + uint16_t myX = colsCenter + (sin_t(angle) * i); + uint16_t myY = rowsCenter + (cos_t(angle) * i); SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); From 7b366d49d2c332130738bdaaac1bb46e9d4a7ac6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 12 Mar 2024 21:23:09 +0100 Subject: [PATCH 080/694] Effect: 2D Drift gets original twin option --- wled00/FX.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 7ceb83e26..14341f5b9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5018,17 +5018,18 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli const uint16_t maxDim = MAX(cols, rows)/2; unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup - for (float i = 1; i < maxDim; i += 0.25) { + for (float i = 1.0f; i < maxDim; i += 0.25f) { float angle = radians(t * (maxDim - i)); - uint16_t myX = colsCenter + (sin_t(angle) * i); - uint16_t myY = rowsCenter + (cos_t(angle) * i); - SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); + int16_t mySin = sin_t(angle) * i; + int16_t myCos = cos_t(angle) * i; + SEGMENT.setPixelColorXY(colsCenter + mySin, rowsCenter + myCos, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); + if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); return FRAMETIME; } // mode_2DDrift() -static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2"; +static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount,,,,Twin;;!;2"; ////////////////////////// @@ -6201,8 +6202,9 @@ uint16_t mode_2Ddriftrose(void) { SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { - uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cos_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; + float angle = radians(i * 10); + uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur((SEGMENT.intensity>>4)+1); From af3f27feaec79a18979cae3043d359c2bda1fdab Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 17 Jul 2023 20:54:24 +0200 Subject: [PATCH 081/694] Move CTT calculation from BusPwm to Bus class. This enable it to other bus types. --- wled00/bus_manager.cpp | 29 +++++------------------------ wled00/bus_manager.h | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 3ac12c04e..dd88bf34f 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -11,7 +11,7 @@ //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); -uint16_t approximateKelvinFromRGB(uint32_t rgb); +void colorRGBtoRGBW(byte* rgb); //udp.cpp uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); @@ -421,29 +421,10 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - uint8_t cct = 0; //0 - full warm white, 255 - full cold white - if (_cct > -1) { - if (_cct >= 1900) cct = (_cct - 1900) >> 5; - else if (_cct < 256) cct = _cct; - } else { - cct = (approximateKelvinFromRGB(c) - 1900) >> 5; - } - + uint8_t ww, cw; - #ifdef WLED_USE_IC_CCT - ww = w; - cw = cct; - #else - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); - - ww = (w * ww) / 255; //brightness scaling - cw = (w * cw) / 255; - #endif + + calculateCCT(c, ww, cw); switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -784,4 +765,4 @@ uint8_t BusManager::numBusses = 0; Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; ColorOrderMap BusManager::colorOrderMap = {}; uint16_t BusManager::_milliAmpsUsed = 0; -uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; \ No newline at end of file +uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 0b791adf3..d0d40f4f1 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -7,6 +7,9 @@ #include "const.h" +//colors.cpp +uint16_t approximateKelvinFromRGB(uint32_t rgb); + #define GET_BIT(var,bit) (((var)>>(bit))&0x01) #define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit))) #define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit)))) @@ -176,6 +179,32 @@ class Bus { if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; #endif } + static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { + uint8_t cct = 0; //0 - full warm white, 255 - full cold white + uint8_t w = byte(c >> 24); + + if (_cct > -1) { + if (_cct >= 1900) cct = (_cct - 1900) >> 5; + else if (_cct < 256) cct = _cct; + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; + } + + #ifdef WLED_USE_IC_CCT + ww = w; + cw = cct; + #else + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); + + if ((255-cct) < _cctBlend) cw = 255; + else cw = (cct * 255) / (255 - _cctBlend); + + ww = (w * ww) / 255; //brightness scaling + cw = (w * cw) / 255; + #endif + } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } From c74db95c1430e2998eae99996c01f8b2de5dca9a Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 19 Jul 2023 09:22:29 +0200 Subject: [PATCH 082/694] Add FW1906 support --- platformio.ini | 2 +- wled00/bus_manager.cpp | 5 +- wled00/bus_manager.h | 4 +- wled00/bus_wrapper.h | 128 +++++++++++++++++++++++++++++++++- wled00/const.h | 1 + wled00/data/settings_leds.htm | 4 +- 6 files changed, 137 insertions(+), 7 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6306595a2..40dceabc5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -143,7 +143,7 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.7.5 + makuna/NeoPixelBus @ 2.7.8 https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0 # for I2C interface ;Wire diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index dd88bf34f..35538a78a 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -280,6 +280,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid) return; if (Bus::hasWhite(_type)) c = autoWhiteCalc(c); if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (_type == TYPE_FW1906) calculateCCT(c, PolyBus::cctWW, PolyBus::cctCW); // FW1906 ignores W component in c if (_data) { // use _buffering this causes ~20% FPS drop size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); size_t offset = pix*channels; @@ -645,13 +646,13 @@ uint32_t BusManager::memUsage(BusConfig &bc) { uint16_t multiplier = 1; if (IS_DIGITAL(bc.type)) { // digital types if (IS_16BIT(bc.type)) len *= 2; // 16-bit LEDs - #ifdef ESP8266 if (bc.type > 28) channels = 4; //RGBW + if (bc.type == TYPE_FW1906) channels = 5; //GRBCW + #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem multiplier = 5; } #else //ESP32 RMT uses double buffer, I2S uses 5x buffer - if (bc.type > 28) channels = 4; //RGBW multiplier = 2; #endif } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index d0d40f4f1..6b8e9d7eb 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -157,7 +157,7 @@ class Bus { } virtual bool hasWhite(void) { return Bus::hasWhite(_type); } static bool hasWhite(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904) return true; // digital types with white channel + if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || type == TYPE_FW1906) return true; // digital types with white channel if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel if (type == TYPE_NET_DDP_RGBW) return true; // network types with white channel return false; @@ -165,7 +165,7 @@ class Bus { virtual bool hasCCT(void) { return Bus::hasCCT(_type); } static bool hasCCT(uint8_t type) { if (type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || - type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH) return true; + type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || type == TYPE_FW1906) return true; return false; } static void setCCT(uint16_t cct) { diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index c63e055a8..e329246f1 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -2,6 +2,7 @@ #define BusWrapper_h #include "NeoPixelBusLg.h" +#include "bus_manager.h" // temporary - these defines should actually be set in platformio.ini // C3: I2S0 and I2S1 methods not supported (has one I2S bus) @@ -63,6 +64,11 @@ #define I_8266_U1_UCS_4 54 #define I_8266_DM_UCS_4 55 #define I_8266_BB_UCS_4 56 +//FW1906 GRBCW +#define I_8266_U0_FW6_5 66 +#define I_8266_U1_FW6_5 67 +#define I_8266_DM_FW6_5 68 +#define I_8266_BB_FW6_5 69 //ESP8266 APA106 #define I_8266_U0_APA106_3 81 #define I_8266_U1_APA106_3 82 @@ -104,12 +110,17 @@ #define I_32_RN_UCS_4 60 #define I_32_I0_UCS_4 61 #define I_32_I1_UCS_4 62 +//FW1906 GRBCW +#define I_32_RN_FW6_5 63 +#define I_32_I0_FW6_5 64 +#define I_32_I1_FW6_5 65 //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) #define I_32_RN_APA106_3 85 #define I_32_I0_APA106_3 86 #define I_32_I1_APA106_3 87 #define I_32_BB_APA106_3 88 // bitbangging on ESP32 not recommended + //APA102 #define I_HS_DOT_3 39 //hardware SPI #define I_SS_DOT_3 40 //soft SPI @@ -176,6 +187,11 @@ #define B_8266_U1_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio2 #define B_8266_DM_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio3 #define B_8266_BB_APA106_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) +//FW1906 GRBCW +#define B_8266_U0_FW6_5 NeoPixelBusLg //esp8266, gpio1 +#define B_8266_U1_FW6_5 NeoPixelBusLg //esp8266, gpio2 +#define B_8266_DM_FW6_5 NeoPixelBusLg //esp8266, gpio3 +#define B_8266_BB_FW6_5 NeoPixelBusLg //esp8266, bb #endif /*** ESP32 Neopixel methods ***/ @@ -251,6 +267,14 @@ #define B_32_I1_APA106_3 NeoPixelBusLg #endif //#define B_32_BB_APA106_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod +//FW1906 GRBCW +#define B_32_RN_FW6_5 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_FW6_5 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_FW6_5 NeoPixelBusLg +#endif #endif @@ -290,6 +314,12 @@ //handles pointer type conversion for all possible bus types class PolyBus { public: + // WW and CW components for chips that support them (FW1906). They have to be set before calling + // PolyBus::setPixelColor(). In such case W component has no meaning in setPixelColor() + // as there is no support for WW/CW API yet + // if both values are 0, W component *may* be used instead (for WW & CW) + static uint8_t cctWW, cctCW; + // initialize SPI bus speed for DotStar methods template static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { @@ -353,6 +383,10 @@ class PolyBus { case I_8266_U1_APA106_3: (static_cast(busPtr))->Begin(); break; case I_8266_DM_APA106_3: (static_cast(busPtr))->Begin(); break; case I_8266_BB_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->Begin(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -404,6 +438,14 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->Begin(); break; #endif + case I_32_RN_FW6_5: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->Begin(); break; + #endif + // case I_32_BB_UCS_4: (static_cast(busPtr))->Begin(); break; case I_32_RN_APA106_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -465,6 +507,10 @@ class PolyBus { case I_8266_U1_APA106_3: busPtr = new B_8266_U1_APA106_3(len, pins[0]); break; case I_8266_DM_APA106_3: busPtr = new B_8266_DM_APA106_3(len, pins[0]); break; case I_8266_BB_APA106_3: busPtr = new B_8266_BB_APA106_3(len, pins[0]); break; + case I_8266_U0_FW6_5: busPtr = new B_8266_U0_FW6_5(len, pins[0]); break; + case I_8266_U1_FW6_5: busPtr = new B_8266_U1_FW6_5(len, pins[0]); break; + case I_8266_DM_FW6_5: busPtr = new B_8266_DM_FW6_5(len, pins[0]); break; + case I_8266_BB_FW6_5: busPtr = new B_8266_BB_FW6_5(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; @@ -525,6 +571,13 @@ class PolyBus { case I_32_I1_APA106_3: busPtr = new B_32_I1_APA106_3(len, pins[0]); break; #endif // case I_32_BB_APA106_3: busPtr = new B_32_BB_APA106_3(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: busPtr = new B_32_I1_FW6_5(len, pins[0]); break; + #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -578,6 +631,10 @@ class PolyBus { case I_8266_U1_APA106_3: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_APA106_3: (static_cast(busPtr))->Show(consistent); break; case I_8266_BB_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; @@ -638,6 +695,13 @@ class PolyBus { case I_32_I1_APA106_3: (static_cast(busPtr))->Show(consistent); break; #endif // case I_32_BB_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -687,6 +751,10 @@ class PolyBus { case I_8266_U1_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_FW6_5: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -747,6 +815,13 @@ class PolyBus { case I_32_I1_APA106_3: return (static_cast(busPtr))->CanShow(); break; #endif // case I_32_BB_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -821,6 +896,10 @@ class PolyBus { case I_8266_U1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_8266_DM_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_8266_BB_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -881,6 +960,13 @@ class PolyBus { case I_32_I1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif // case I_32_BB_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -931,6 +1017,10 @@ class PolyBus { case I_8266_U1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -991,6 +1081,14 @@ class PolyBus { case I_32_I1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; #endif // case I_32_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1042,6 +1140,10 @@ class PolyBus { case I_8266_U1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_DM_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_8266_U1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_8266_DM_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_8266_BB_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1102,6 +1204,13 @@ class PolyBus { case I_32_I1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif // case I_32_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1171,6 +1280,10 @@ class PolyBus { case I_8266_U1_APA106_3: delete (static_cast(busPtr)); break; case I_8266_DM_APA106_3: delete (static_cast(busPtr)); break; case I_8266_BB_APA106_3: delete (static_cast(busPtr)); break; + case I_8266_U0_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_U1_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_DM_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_BB_FW6_5: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -1231,6 +1344,13 @@ class PolyBus { case I_32_I1_APA106_3: delete (static_cast(busPtr)); break; #endif // case I_32_BB_APA106_3: delete (static_cast(busPtr)); break; + case I_32_RN_FW6_5: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: delete (static_cast(busPtr)); break; + #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1292,6 +1412,8 @@ class PolyBus { return I_8266_U0_UCS_4 + offset; case TYPE_APA106: return I_8266_U0_APA106_3 + offset; + case TYPE_FW1906: + return I_8266_U0_FW6_5 + offset; } #else //ESP32 uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 @@ -1332,6 +1454,8 @@ class PolyBus { return I_32_RN_UCS_4 + offset; case TYPE_APA106: return I_32_RN_APA106_3 + offset; + case TYPE_FW1906: + return I_32_RN_FW6_5 + offset; } #endif } @@ -1339,4 +1463,6 @@ class PolyBus { } }; -#endif \ No newline at end of file +uint8_t PolyBus::cctWW; +uint8_t PolyBus::cctCW; +#endif diff --git a/wled00/const.h b/wled00/const.h index dd965bc40..6f4c2649e 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -269,6 +269,7 @@ #define TYPE_TM1829 25 #define TYPE_UCS8903 26 #define TYPE_APA106 27 +#define TYPE_FW1906 28 //RGB + CW + WW + unused channel (6 channels per IC) #define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 611653a64..061d5a9ac 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -188,6 +188,7 @@ if (isDig(t)) { if (is16b(t)) len *= 2; // 16 bit LEDs if (t > 28 && t < 40) ch = 4; //RGBW + if (t == 28) ch = 5; //GRBCW if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem mul = 5; } @@ -242,7 +243,7 @@ d.Sf["MA"+n].min = (isVir(t) || isAna(t)) ? 0 : 250; } gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 - gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h + gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 27 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM gId("dig"+n+"w").style.display = (isDig(t) && isRGBW) ? "inline":"none"; // show swap channels dropdown if (!(isDig(t) && isRGBW)) d.Sf["WO"+n].value = 0; // reset swapping @@ -383,6 +384,7 @@ ${i+1}: \ \ \ +\ \ \ \ From 505768db0469ccbb2c9a1cf786cc7de52e6a4130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 11 Mar 2024 11:17:45 +0100 Subject: [PATCH 083/694] Some fixes & implement recommendation from @Aircoookie https://github.com/Aircoookie/WLED/pull/3298#issuecomment-1651910418 --- wled00/bus_manager.cpp | 28 +++++++++++++--------------- wled00/bus_wrapper.h | 25 +++++++++---------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 35538a78a..6fd55bb5d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -205,10 +205,11 @@ void BusDigital::show() { _milliAmpsTotal = 0; if (!_valid) return; + uint8_t cctWW = 0, cctCW = 0; uint8_t newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits - if (_data) { // use _buffering this causes ~20% FPS drop + if (_data) { size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); for (size_t i=0; i<_len; i++) { size_t offset = i*channels; @@ -226,7 +227,8 @@ void BusDigital::show() { uint16_t pix = i; if (_reversed) pix = _len - pix -1; pix += _skip; - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co); + if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); } #if !defined(STATUSLED) || STATUSLED>=0 if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black @@ -239,7 +241,8 @@ void BusDigital::show() { for (unsigned i = 0; i < hwLen; i++) { // use 0 as color order, actual order does not matter here as we just update the channel values as-is uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri); - PolyBus::setPixelColor(_busPtr, _iType, i, c, 0); // repaint all pixels with new brightness + if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); + PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness } } } @@ -278,10 +281,10 @@ void BusDigital::setStatusPixel(uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid) return; + uint8_t cctWW = 0, cctCW = 0; if (Bus::hasWhite(_type)) c = autoWhiteCalc(c); if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT - if (_type == TYPE_FW1906) calculateCCT(c, PolyBus::cctWW, PolyBus::cctCW); // FW1906 ignores W component in c - if (_data) { // use _buffering this causes ~20% FPS drop + if (_data) { size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); size_t offset = pix*channels; if (Bus::hasRGB(_type)) { @@ -304,14 +307,15 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co); + if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); } } // returns original color if global buffering is enabled, else returns lossly restored color from bus uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { if (!_valid) return 0; - if (_data) { // use _buffering this causes ~20% FPS drop + if (_data) { size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); size_t offset = pix*channels; uint32_t c; @@ -422,22 +426,16 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - - uint8_t ww, cw; - - calculateCCT(c, ww, cw); switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation _data[0] = w; break; case TYPE_ANALOG_2CH: //warm white + cold white - _data[1] = cw; - _data[0] = ww; + Bus::calculateCCT(c, _data[0], _data[1]); break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white - _data[4] = cw; - w = ww; + Bus::calculateCCT(c, w, _data[4]); case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index e329246f1..fafe3a460 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -314,11 +314,6 @@ //handles pointer type conversion for all possible bus types class PolyBus { public: - // WW and CW components for chips that support them (FW1906). They have to be set before calling - // PolyBus::setPixelColor(). In such case W component has no meaning in setPixelColor() - // as there is no support for WW/CW API yet - // if both values are 0, W component *may* be used instead (for WW & CW) - static uint8_t cctWW, cctCW; // initialize SPI bus speed for DotStar methods template @@ -837,12 +832,13 @@ class PolyBus { return true; } - static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co) { + static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { uint8_t r = c >> 16; uint8_t g = c >> 8; uint8_t b = c >> 0; uint8_t w = c >> 24; RgbwColor col; + uint8_t cctWW = wwcw & 0xFF, cctCW = (wwcw>>8) & 0xFF; // reorder channels to selected order switch (co & 0x0F) { @@ -1140,10 +1136,10 @@ class PolyBus { case I_8266_U1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_DM_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_8266_U0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; - case I_8266_U1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; - case I_8266_DM_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; - case I_8266_BB_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_8266_U0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_DM_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_BB_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1204,12 +1200,12 @@ class PolyBus { case I_32_I1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif // case I_32_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.WW>>8); } break; + case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1462,7 +1458,4 @@ class PolyBus { return I_NONE; } }; - -uint8_t PolyBus::cctWW; -uint8_t PolyBus::cctCW; #endif From a42f78b08bd78f93509f9e0fed388f98a680fedc Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:27:03 +0100 Subject: [PATCH 084/694] Update CONTRIBUTING.md Trying to explain why any PR should have a`description` --- CONTRIBUTING.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddf61ec80..168131160 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,16 @@ Here are a few suggestions to make it easier for you to contribute! +### Describe your PR + +Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing. + +A good description helps us to review and understand your proposed changes. For example, you could say a few words about +* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.) +* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code) +* testing you performed, known limitations, open ends you possibly could not solve. +* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉) + ### Target branch for pull requests Please make all PRs against the `0_15` branch. @@ -79,4 +89,4 @@ Good: There is no hard character limit for a comment within a line, though as a rule of thumb consider wrapping after 120 characters. -Inline comments are OK if they describe that line only and are not exceedingly wide. \ No newline at end of file +Inline comments are OK if they describe that line only and are not exceedingly wide. From 0dcb56eab50d41bb015eeba6e709ed3330507ae7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 16 Mar 2024 12:36:05 +0100 Subject: [PATCH 085/694] Modify Bus & BusManager to accommodate digital CCT - additional fix in hasWhite() & setCCT() & hasCCT() --- wled00/FX_fcn.cpp | 15 +++++------ wled00/bus_manager.cpp | 56 ++++++++++++++++++++++-------------------- wled00/bus_manager.h | 25 +++++++++++++++---- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 42e98452f..edd3a068d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -538,6 +538,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed } void Segment::setCCT(uint16_t k) { + if (!isCCT() || !correctWB) return; if (k > 255) { //kelvin value, convert to 0-255 if (k < 1900) k = 1900; if (k > 10091) k = 10091; @@ -1162,12 +1163,16 @@ void WS2812FX::service() { uint16_t delay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen + int16_t oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) _virtualSegmentLength = seg.virtualLength(); //SEGLEN _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); _colors_t[2] = gamma32(seg.currentColor(2)); seg.currentPalette(_currentPalette, seg.palette); // we need to pass reference - if (!cctFromRgb || correctWB) BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio + // when cctFromRgb is true we implicitly calculate WW and CW from RGB values + if (cctFromRgb) BusManager::setSegmentCCT(-1); + else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); // Effect blending // When two effects are being blended, each may have different segment data, this // data needs to be saved first and then restored before running previous mode. @@ -1190,6 +1195,7 @@ void WS2812FX::service() { #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + delay; @@ -1198,7 +1204,6 @@ void WS2812FX::service() { _segment_index++; } _virtualSegmentLength = 0; - BusManager::setSegmentCCT(-1); _isServicing = false; _triggered = false; @@ -1390,11 +1395,7 @@ bool WS2812FX::hasCCTBus(void) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; - switch (bus->getType()) { - case TYPE_ANALOG_5CH: - case TYPE_ANALOG_2CH: - return true; - } + if (bus->hasCCT()) return true; } return false; } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 6fd55bb5d..a9c3ac44d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -11,7 +11,6 @@ //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); -void colorRGBtoRGBW(byte* rgb); //udp.cpp uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); @@ -122,7 +121,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) } _iType = PolyBus::getI(bc.type, _pins, nr); if (_iType == I_NONE) return; - if (bc.doubleBuffer && !allocData(bc.count * (Bus::hasWhite(_type) + 3*Bus::hasRGB(_type)))) return; //warning: hardcoded channel count + if (bc.doubleBuffer && !allocData(bc.count * Bus::getNumberOfChannels(bc.type))) return; //_buffering = bc.doubleBuffer; uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus @@ -210,9 +209,10 @@ void BusDigital::show() { if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits if (_data) { - size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); + size_t channels = getNumberOfChannels(); + int16_t oldCCT = _cct; // temporarily save bus CCT for (size_t i=0; i<_len; i++) { - size_t offset = i*channels; + size_t offset = i * channels; uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); uint32_t c; if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs (_len is always a multiple of 3) @@ -222,18 +222,26 @@ void BusDigital::show() { case 2: c = RGBW32(_data[offset-2], _data[offset-1], _data[offset] , 0); break; } } else { - c = RGBW32(_data[offset],_data[offset+1],_data[offset+2],(Bus::hasWhite(_type)?_data[offset+3]:0)); + if (hasRGB()) c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); + else c = RGBW32(0, 0, 0, _data[offset]); + } + if (hasCCT()) { + // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT + // we need to extract and appy CCT value for each pixel individually even though all buses share the same _cct variable + // TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer + _cct = _data[offset+channels-1]; + Bus::calculateCCT(c, cctWW, cctCW); } uint16_t pix = i; if (_reversed) pix = _len - pix -1; pix += _skip; - if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); } #if !defined(STATUSLED) || STATUSLED>=0 if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black #endif for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black + _cct = oldCCT; } else { if (newBri < _bri) { uint16_t hwLen = _len; @@ -241,7 +249,7 @@ void BusDigital::show() { for (unsigned i = 0; i < hwLen; i++) { // use 0 as color order, actual order does not matter here as we just update the channel values as-is uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri); - if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); + if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness } } @@ -282,17 +290,19 @@ void BusDigital::setStatusPixel(uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid) return; uint8_t cctWW = 0, cctCW = 0; - if (Bus::hasWhite(_type)) c = autoWhiteCalc(c); + if (hasWhite()) c = autoWhiteCalc(c); if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT if (_data) { - size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); - size_t offset = pix*channels; - if (Bus::hasRGB(_type)) { + size_t offset = pix * getNumberOfChannels(); + if (hasRGB()) { _data[offset++] = R(c); _data[offset++] = G(c); _data[offset++] = B(c); } - if (Bus::hasWhite(_type)) _data[offset] = W(c); + if (hasWhite()) _data[offset++] = W(c); + // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT + // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) + if (hasCCT()) _data[offset] = _cct >= 1900 ? (_cct - 1900) >> 5 : (_cct < 0 ? 127 : _cct); // TODO: if _cct == -1 we simply ignore it } else { if (_reversed) pix = _len - pix -1; pix += _skip; @@ -307,7 +317,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - if (_type == TYPE_FW1906) Bus::calculateCCT(c, cctWW, cctCW); + if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); } } @@ -316,13 +326,12 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { if (!_valid) return 0; if (_data) { - size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); - size_t offset = pix*channels; + size_t offset = pix * getNumberOfChannels(); uint32_t c; - if (!Bus::hasRGB(_type)) { + if (!hasRGB()) { c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); } else { - c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], Bus::hasWhite(_type) ? _data[offset+3] : 0); + c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); } return c; } else { @@ -640,12 +649,10 @@ uint32_t BusManager::memUsage(BusConfig &bc) { if (bc.type == TYPE_ONOFF || IS_PWM(bc.type)) return 5; uint16_t len = bc.count + bc.skipAmount; - uint16_t channels = 3; + uint16_t channels = Bus::getNumberOfChannels(bc.type); uint16_t multiplier = 1; if (IS_DIGITAL(bc.type)) { // digital types if (IS_16BIT(bc.type)) len *= 2; // 16-bit LEDs - if (bc.type > 28) channels = 4; //RGBW - if (bc.type == TYPE_FW1906) channels = 5; //GRBCW #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem multiplier = 5; @@ -654,11 +661,6 @@ uint32_t BusManager::memUsage(BusConfig &bc) { multiplier = 2; #endif } - if (IS_VIRTUAL(bc.type)) { - switch (bc.type) { - case TYPE_NET_DDP_RGBW: channels = 4; break; - } - } return len * channels * multiplier; //RGB } @@ -720,7 +722,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { if (cct >= 0) { //if white balance correction allowed, save as kelvin value instead of 0-255 if (allowWBCorrection) cct = 1900 + (cct << 5); - } else cct = -1; + } else cct = -1; // will use kelvin approximation from RGB Bus::setCCT(cct); } @@ -764,4 +766,4 @@ uint8_t BusManager::numBusses = 0; Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; ColorOrderMap BusManager::colorOrderMap = {}; uint16_t BusManager::_milliAmpsUsed = 0; -uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; +uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; \ No newline at end of file diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 6b8e9d7eb..233c2a668 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -141,6 +141,8 @@ class Bus { virtual uint16_t getLEDCurrent() { return 0; } virtual uint16_t getUsedCurrent() { return 0; } virtual uint16_t getMaxCurrent() { return 0; } + virtual uint8_t getNumberOfChannels() { return hasWhite(_type) + 3*hasRGB(_type) + hasCCT(_type); } + static inline uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } inline void setReversed(bool reversed) { _reversed = reversed; } inline uint16_t getStart() { return _start; } inline void setStart(uint16_t start) { _start = start; } @@ -157,9 +159,10 @@ class Bus { } virtual bool hasWhite(void) { return Bus::hasWhite(_type); } static bool hasWhite(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || type == TYPE_FW1906) return true; // digital types with white channel + if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || + type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || type == TYPE_FW1906) return true; // digital types with white channel if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel - if (type == TYPE_NET_DDP_RGBW) return true; // network types with white channel + if (type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW) return true; // network types with white channel return false; } virtual bool hasCCT(void) { return Bus::hasCCT(_type); } @@ -168,7 +171,8 @@ class Bus { type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || type == TYPE_FW1906) return true; return false; } - static void setCCT(uint16_t cct) { + static int16_t getCCT() { return _cct; } + static void setCCT(int16_t cct) { _cct = cct; } static void setCCTBlend(uint8_t b) { @@ -196,8 +200,7 @@ class Bus { #else //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - + else ww = ((255-cct) * 255) / (255 - _cctBlend); if ((255-cct) < _cctBlend) cw = 255; else cw = (cct * 255) / (255 - _cctBlend); @@ -220,8 +223,17 @@ class Bus { bool _needsRefresh; uint8_t _autoWhiteMode; uint8_t *_data; + // global Auto White Calculation override static uint8_t _gAWM; + // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): + // -1 means to extract approximate CCT value in K from RGB (in calcualteCCT()) + // [0,255] is the exact CCT value where 0 means warm and 255 cold + // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) static int16_t _cct; + // _cctBlend determines WW/CW blending: + // 0 - linear (CCT 127 => 50% warm, 50% cold) + // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) + // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) static uint8_t _cctBlend; uint32_t autoWhiteCalc(uint32_t c); @@ -363,9 +375,12 @@ class BusManager { static void setStatusPixel(uint32_t c); static void setPixelColor(uint16_t pix, uint32_t c); static void setBrightness(uint8_t b); + // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K + // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); static void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} static uint32_t getPixelColor(uint16_t pix); + static inline int16_t getSegmentCCT() { return Bus::getCCT(); } static Bus* getBus(uint8_t busNr); From 52a1b0453c805f4e889631e622b21cc037a1a502 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 16 Mar 2024 15:26:52 +0100 Subject: [PATCH 086/694] MQTT cleanup & reduction --- wled00/led.cpp | 7 +- wled00/mqtt.cpp | 395 ++++++++++++++++++++++++------------------------ wled00/wled.h | 1 - 3 files changed, 200 insertions(+), 203 deletions(-) mode change 100755 => 100644 wled00/wled.h diff --git a/wled00/led.cpp b/wled00/led.cpp index ba772df9b..23c8d03c5 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -172,7 +172,9 @@ void updateInterfaces(uint8_t callMode) espalexaDevice->setColor(col[0], col[1], col[2]); } #endif - doPublishMqtt = true; + #ifndef WLED_DISABLE_MQTT + publishMqtt(); + #endif } @@ -180,9 +182,6 @@ void handleTransitions() { //handle still pending interface update updateInterfaces(interfaceUpdateCallMode); -#ifndef WLED_DISABLE_MQTT - if (doPublishMqtt) publishMqtt(); -#endif if (transitionActive && strip.getTransition() > 0) { float tper = (millis() - transitionStartTime)/(float)strip.getTransition(); diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 3c753a9a9..810291094 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -1,198 +1,197 @@ -#include "wled.h" - -/* - * MQTT communication protocol for home automation - */ - -#ifdef WLED_ENABLE_MQTT -#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds - -void parseMQTTBriPayload(char* payload) -{ - if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} - else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);} - else { - uint8_t in = strtoul(payload, NULL, 10); - if (in == 0 && bri > 0) briLast = bri; - bri = in; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } -} - - -void onMqttConnect(bool sessionPresent) -{ - //(re)subscribe to required topics - char subuf[38]; - - if (mqttDeviceTopic[0] != 0) { - strlcpy(subuf, mqttDeviceTopic, 33); - mqtt->subscribe(subuf, 0); - strcat_P(subuf, PSTR("/col")); - mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/api")); - mqtt->subscribe(subuf, 0); - } - - if (mqttGroupTopic[0] != 0) { - strlcpy(subuf, mqttGroupTopic, 33); - mqtt->subscribe(subuf, 0); - strcat_P(subuf, PSTR("/col")); - mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttGroupTopic, 33); - strcat_P(subuf, PSTR("/api")); - mqtt->subscribe(subuf, 0); - } - - usermods.onMqttConnect(sessionPresent); - - doPublishMqtt = true; - DEBUG_PRINTLN(F("MQTT ready")); -} - - -void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { - static char *payloadStr; - - DEBUG_PRINT(F("MQTT msg: ")); - DEBUG_PRINTLN(topic); - - // paranoia check to avoid npe if no payload - if (payload==nullptr) { - DEBUG_PRINTLN(F("no payload -> leave")); - return; - } - - if (index == 0) { // start (1st partial packet or the only packet) - if (payloadStr) delete[] payloadStr; // fail-safe: release buffer - payloadStr = new char[total+1]; // allocate new buffer - } - if (payloadStr == nullptr) return; // buffer not allocated - - // copy (partial) packet to buffer and 0-terminate it if it is last packet - char* buff = payloadStr + index; - memcpy(buff, payload, len); - if (index + len >= total) { // at end - payloadStr[total] = '\0'; // terminate c style string - } else { - DEBUG_PRINTLN(F("Partial packet received.")); - return; // process next packet - } - DEBUG_PRINTLN(payloadStr); - - size_t topicPrefixLen = strlen(mqttDeviceTopic); - if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) { - topic += topicPrefixLen; - } else { - topicPrefixLen = strlen(mqttGroupTopic); - if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) { - topic += topicPrefixLen; - } else { - // Non-Wled Topic used here. Probably a usermod subscribed to this topic. - usermods.onMqttMessage(topic, payloadStr); - delete[] payloadStr; - payloadStr = nullptr; - return; - } - } - - //Prefix is stripped from the topic at this point - - if (strcmp_P(topic, PSTR("/col")) == 0) { - colorFromDecOrHexString(col, payloadStr); - colorUpdated(CALL_MODE_DIRECT_CHANGE); - } else if (strcmp_P(topic, PSTR("/api")) == 0) { - if (!requestJSONBufferLock(15)) { - delete[] payloadStr; - payloadStr = nullptr; - return; - } - if (payloadStr[0] == '{') { //JSON API - deserializeJson(*pDoc, payloadStr); - deserializeState(pDoc->as()); - } else { //HTTP API - String apireq = "win"; apireq += '&'; // reduce flash string usage - apireq += payloadStr; - handleSet(nullptr, apireq); - } - releaseJSONBufferLock(); - } else if (strlen(topic) != 0) { - // non standard topic, check with usermods - usermods.onMqttMessage(topic, payloadStr); - } else { - // topmost topic (just wled/MAC) - parseMQTTBriPayload(payloadStr); - } - delete[] payloadStr; - payloadStr = nullptr; -} - - -void publishMqtt() -{ - doPublishMqtt = false; - if (!WLED_MQTT_CONNECTED) return; - DEBUG_PRINTLN(F("Publish MQTT")); - - #ifndef USERMOD_SMARTNEST - char s[10]; - char subuf[38]; - - sprintf_P(s, PSTR("%u"), bri); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/g")); - mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - - sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/c")); - mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/status")); - mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT - - char apires[1024]; // allocating 1024 bytes from stack can be risky - XML_response(nullptr, apires); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/v")); - mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263) - #endif -} - - -//HA autodiscovery was removed in favor of the native integration in HA v0.102.0 - -bool initMqtt() -{ - if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; - - if (mqtt == nullptr) { - mqtt = new AsyncMqttClient(); - mqtt->onMessage(onMqttMessage); - mqtt->onConnect(onMqttConnect); - } - if (mqtt->connected()) return true; - - DEBUG_PRINTLN(F("Reconnecting MQTT")); - IPAddress mqttIP; - if (mqttIP.fromString(mqttServer)) //see if server is IP or domain - { - mqtt->setServer(mqttIP, mqttPort); - } else { - mqtt->setServer(mqttServer, mqttPort); - } - mqtt->setClientId(mqttClientID); - if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); - - #ifndef USERMOD_SMARTNEST - strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); - strcat_P(mqttStatusTopic, PSTR("/status")); - mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message - #endif - mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME); - mqtt->connect(); - return true; -} -#endif +#include "wled.h" + +/* + * MQTT communication protocol for home automation + */ + +#ifdef WLED_ENABLE_MQTT +#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds + +void parseMQTTBriPayload(char* payload) +{ + if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} + else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);} + else { + uint8_t in = strtoul(payload, NULL, 10); + if (in == 0 && bri > 0) briLast = bri; + bri = in; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } +} + + +void onMqttConnect(bool sessionPresent) +{ + //(re)subscribe to required topics + char subuf[38]; + + if (mqttDeviceTopic[0] != 0) { + strlcpy(subuf, mqttDeviceTopic, 33); + mqtt->subscribe(subuf, 0); + strcat_P(subuf, PSTR("/col")); + mqtt->subscribe(subuf, 0); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/api")); + mqtt->subscribe(subuf, 0); + } + + if (mqttGroupTopic[0] != 0) { + strlcpy(subuf, mqttGroupTopic, 33); + mqtt->subscribe(subuf, 0); + strcat_P(subuf, PSTR("/col")); + mqtt->subscribe(subuf, 0); + strlcpy(subuf, mqttGroupTopic, 33); + strcat_P(subuf, PSTR("/api")); + mqtt->subscribe(subuf, 0); + } + + usermods.onMqttConnect(sessionPresent); + + DEBUG_PRINTLN(F("MQTT ready")); + publishMqtt(); +} + + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + static char *payloadStr; + + DEBUG_PRINT(F("MQTT msg: ")); + DEBUG_PRINTLN(topic); + + // paranoia check to avoid npe if no payload + if (payload==nullptr) { + DEBUG_PRINTLN(F("no payload -> leave")); + return; + } + + if (index == 0) { // start (1st partial packet or the only packet) + if (payloadStr) delete[] payloadStr; // fail-safe: release buffer + payloadStr = new char[total+1]; // allocate new buffer + } + if (payloadStr == nullptr) return; // buffer not allocated + + // copy (partial) packet to buffer and 0-terminate it if it is last packet + char* buff = payloadStr + index; + memcpy(buff, payload, len); + if (index + len >= total) { // at end + payloadStr[total] = '\0'; // terminate c style string + } else { + DEBUG_PRINTLN(F("MQTT partial packet received.")); + return; // process next packet + } + DEBUG_PRINTLN(payloadStr); + + size_t topicPrefixLen = strlen(mqttDeviceTopic); + if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) { + topic += topicPrefixLen; + } else { + topicPrefixLen = strlen(mqttGroupTopic); + if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) { + topic += topicPrefixLen; + } else { + // Non-Wled Topic used here. Probably a usermod subscribed to this topic. + usermods.onMqttMessage(topic, payloadStr); + delete[] payloadStr; + payloadStr = nullptr; + return; + } + } + + //Prefix is stripped from the topic at this point + + if (strcmp_P(topic, PSTR("/col")) == 0) { + colorFromDecOrHexString(col, payloadStr); + colorUpdated(CALL_MODE_DIRECT_CHANGE); + } else if (strcmp_P(topic, PSTR("/api")) == 0) { + if (!requestJSONBufferLock(15)) { + delete[] payloadStr; + payloadStr = nullptr; + return; + } + if (payloadStr[0] == '{') { //JSON API + deserializeJson(*pDoc, payloadStr); + deserializeState(pDoc->as()); + } else { //HTTP API + String apireq = "win"; apireq += '&'; // reduce flash string usage + apireq += payloadStr; + handleSet(nullptr, apireq); + } + releaseJSONBufferLock(); + } else if (strlen(topic) != 0) { + // non standard topic, check with usermods + usermods.onMqttMessage(topic, payloadStr); + } else { + // topmost topic (just wled/MAC) + parseMQTTBriPayload(payloadStr); + } + delete[] payloadStr; + payloadStr = nullptr; +} + + +void publishMqtt() +{ + if (!WLED_MQTT_CONNECTED) return; + DEBUG_PRINTLN(F("Publish MQTT")); + + #ifndef USERMOD_SMARTNEST + char s[10]; + char subuf[48]; + + sprintf_P(s, PSTR("%u"), bri); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/g")); + mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) + + sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/c")); + mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) + + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/status")); + mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT + + char apires[1024]; // allocating 1024 bytes from stack can be risky + XML_response(nullptr, apires); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/v")); + mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263) + #endif +} + + +//HA autodiscovery was removed in favor of the native integration in HA v0.102.0 + +bool initMqtt() +{ + if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; + + if (mqtt == nullptr) { + mqtt = new AsyncMqttClient(); + mqtt->onMessage(onMqttMessage); + mqtt->onConnect(onMqttConnect); + } + if (mqtt->connected()) return true; + + DEBUG_PRINTLN(F("Reconnecting MQTT")); + IPAddress mqttIP; + if (mqttIP.fromString(mqttServer)) //see if server is IP or domain + { + mqtt->setServer(mqttIP, mqttPort); + } else { + mqtt->setServer(mqttServer, mqttPort); + } + mqtt->setClientId(mqttClientID); + if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); + + #ifndef USERMOD_SMARTNEST + strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); + strcat_P(mqttStatusTopic, PSTR("/status")); + mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message + #endif + mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME); + mqtt->connect(); + return true; +} +#endif diff --git a/wled00/wled.h b/wled00/wled.h old mode 100755 new mode 100644 index 63ab1c297..00c73c5eb --- a/wled00/wled.h +++ b/wled00/wled.h @@ -705,7 +705,6 @@ WLED_GLOBAL byte optionType; WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate saving of config WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers -WLED_GLOBAL bool doPublishMqtt _INIT(false); // status led #if defined(STATUSLED) From 12bf04826a268963711e8ca9efee383066f140df Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:12:02 -0400 Subject: [PATCH 087/694] Update ESPAsyncWebServer to v2.2.0 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 6306595a2..3ea49c13d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -144,7 +144,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.0 # for I2C interface ;Wire # ESP-NOW library From df6c271830f567e47b310a53a46909ae20cf6922 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:07:26 -0400 Subject: [PATCH 088/694] Use web server ContentType symbols These were mostly PROGMEM already, but every little bit helps. --- wled00/fcn_declare.h | 1 - wled00/file.cpp | 4 +-- wled00/json.cpp | 4 +-- wled00/src/dependencies/json/AsyncJson-v6.h | 6 ++-- wled00/wled_server.cpp | 35 +++------------------ 5 files changed, 11 insertions(+), 39 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7e0d6f480..20ac21129 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -434,7 +434,6 @@ void handleSerial(); void updateBaudRate(uint32_t rate); //wled_server.cpp -String getFileContentType(String &filename); void createEditHandler(bool enable); void initServer(); void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); diff --git a/wled00/file.cpp b/wled00/file.cpp index 37f794424..199009e4e 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -375,6 +375,7 @@ void updateFSInfo() { #endif } + #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) // caching presets in PSRAM may prevent occasional flashes seen when HomeAssitant polls WLED // original idea by @akaricchi (https://github.com/Akaricchi) @@ -420,8 +421,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); if(path.endsWith("/")) path += "index.htm"; if(path.indexOf(F("sec")) > -1) return false; - String contentType = getFileContentType(path); - if(request->hasArg(F("download"))) contentType = F("application/octet-stream"); + String contentType = request->hasArg(F("download")) ? F("application/octet-stream") : contentTypeFor(path); /*String pathWithGz = path + ".gz"; if(WLED_FS.exists(pathWithGz)){ request->send(WLED_FS, pathWithGz, contentType); diff --git a/wled00/json.cpp b/wled00/json.cpp index 389dc8ae5..c629cbe4f 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1065,7 +1065,7 @@ void serveJson(AsyncWebServerRequest* request) } #endif else if (url.indexOf("pal") > 0) { - request->send_P(200, "application/json", JSON_palette_names); // contentType defined in AsyncJson-v6.h + request->send_P(200, FPSTR(CONTENT_TYPE_JSON), JSON_palette_names); return; } else if (url.indexOf(F("cfg")) > 0 && handleFileRead(request, F("/cfg.json"))) { @@ -1185,7 +1185,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) #endif oappend("}"); if (request) { - request->send(200, "application/json", buffer); // contentType defined in AsyncJson-v6.h + request->send(200, FPSTR(CONTENT_TYPE_JSON), buffer); } #ifdef WLED_ENABLE_WEBSOCKETS else { diff --git a/wled00/src/dependencies/json/AsyncJson-v6.h b/wled00/src/dependencies/json/AsyncJson-v6.h index 32ac54607..4a127dedb 100644 --- a/wled00/src/dependencies/json/AsyncJson-v6.h +++ b/wled00/src/dependencies/json/AsyncJson-v6.h @@ -21,8 +21,6 @@ #define DYNAMIC_JSON_DOCUMENT_SIZE 16384 #endif -constexpr const char* JSON_MIMETYPE = "application/json"; - /* * Json Response * */ @@ -66,7 +64,7 @@ class AsyncJsonResponse: public AsyncAbstractResponse { AsyncJsonResponse(JsonDocument *ref, bool isArray=false) : _jsonBuffer(1), _isValid{false} { _code = 200; - _contentType = JSON_MIMETYPE; + _contentType = FPSTR(CONTENT_TYPE_JSON); if(isArray) _root = ref->to(); else @@ -75,7 +73,7 @@ class AsyncJsonResponse: public AsyncAbstractResponse { AsyncJsonResponse(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool isArray=false) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { _code = 200; - _contentType = JSON_MIMETYPE; + _contentType = FPSTR(CONTENT_TYPE_JSON); if(isArray) _root = _jsonBuffer.createNestedArray(); else diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 4ce113cb9..8d2339312 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -18,36 +18,11 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; -static const char s_javascript[] PROGMEM = "application/javascript"; -static const char s_json[] = "application/json"; // AsyncJson-v6.h -static const char s_html[] PROGMEM = "text/html"; -static const char s_plain[] = "text/plain"; // Espalexa.h -static const char s_css[] PROGMEM = "text/css"; -static const char s_png[] PROGMEM = "image/png"; -static const char s_gif[] PROGMEM = "image/gif"; -static const char s_jpg[] PROGMEM = "image/jpeg"; -static const char s_ico[] PROGMEM = "image/x-icon"; -//static const char s_xml[] PROGMEM = "text/xml"; -//static const char s_pdf[] PROGMEM = "application/x-pdf"; -//static const char s_zip[] PROGMEM = "application/x-zip"; -//static const char s_gz[] PROGMEM = "application/x-gzip"; - -String getFileContentType(String &filename) { - if (filename.endsWith(F(".htm"))) return FPSTR(s_html); - else if (filename.endsWith(F(".html"))) return FPSTR(s_html); - else if (filename.endsWith(F(".css"))) return FPSTR(s_css); - else if (filename.endsWith(F(".js"))) return FPSTR(s_javascript); - else if (filename.endsWith(F(".json"))) return s_json; - else if (filename.endsWith(F(".png"))) return FPSTR(s_png); - else if (filename.endsWith(F(".gif"))) return FPSTR(s_gif); - else if (filename.endsWith(F(".jpg"))) return FPSTR(s_jpg); - else if (filename.endsWith(F(".ico"))) return FPSTR(s_ico); -// else if (filename.endsWith(F(".xml"))) return FPSTR(s_xml); -// else if (filename.endsWith(F(".pdf"))) return FPSTR(s_pdf); -// else if (filename.endsWith(F(".zip"))) return FPSTR(s_zip); -// else if (filename.endsWith(F(".gz"))) return FPSTR(s_gz); - return s_plain; -} +static const char* s_javascript = CONTENT_TYPE_JAVASCRIPT; +static const char* s_json = CONTENT_TYPE_JSON; +static const char* s_html = CONTENT_TYPE_HTML; +static const char* s_plain = CONTENT_TYPE_PLAIN; +static const char* s_css = CONTENT_TYPE_CSS; //Is this an IP? static bool isIp(String str) { From a1b0f8444410744f2438acfa804126337aa8d5b8 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:07:26 -0400 Subject: [PATCH 089/694] Pass PROGMEM type to server.on() Rather than relying on the exception handler, indicate the __FlashStringHelper type so the correct String constructor is used. --- wled00/wled_server.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 8d2339312..fca932972 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -234,17 +234,17 @@ void initServer() #ifdef WLED_ENABLE_WEBSOCKETS #ifndef WLED_DISABLE_2D - server.on(SET_F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) { + server.on(F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) { handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveviewws2D, PAGE_liveviewws2D_length); }); #endif #endif - server.on(SET_F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) { + server.on(F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) { handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveview, PAGE_liveview_length); }); //settings page - server.on(SET_F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); }); @@ -266,24 +266,25 @@ void initServer() request->send(response); }); - server.on(SET_F("/welcome"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/welcome"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); }); - server.on(SET_F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129); doReboot = true; }); - server.on(SET_F("/settings"), HTTP_POST, [](AsyncWebServerRequest *request){ + server.on(F("/settings"), HTTP_POST, [](AsyncWebServerRequest *request){ serveSettings(request, true); }); - server.on(SET_F("/json"), HTTP_GET, [](AsyncWebServerRequest *request){ + const static char _json[] PROGMEM = "/json"; + server.on(FPSTR(_json), HTTP_GET, [](AsyncWebServerRequest *request){ serveJson(request); }); - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler(F("/json"), [](AsyncWebServerRequest *request) { + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler(FPSTR(_json), [](AsyncWebServerRequest *request) { bool verboseResponse = false; bool isConfig = false; @@ -335,15 +336,15 @@ void initServer() }, JSON_BUFFER_SIZE); server.addHandler(handler); - server.on(SET_F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, FPSTR(s_plain), (String)VERSION); }); - server.on(SET_F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, FPSTR(s_plain), (String)millis()); }); - server.on(SET_F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, FPSTR(s_plain), (String)ESP.getFreeHeap()); }); @@ -353,11 +354,11 @@ void initServer() }); #endif - server.on(SET_F("/teapot"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/teapot"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254); }); - server.on(SET_F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {}, + server.on(F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {}, [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {handleUpload(request, filename, index, data, len, final);} ); From 323c70dcdf4803cd3d25a3e500d398b0630555b2 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:07:26 -0400 Subject: [PATCH 090/694] Update for new AsyncWebSocketBuffer Eliminate the extra indirection and allocate shared buffers directly. --- wled00/ws.cpp | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 1dd141a68..16636bb1e 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -102,7 +102,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp void sendDataWs(AsyncWebSocketClient * client) { if (!ws.count()) return; - AsyncWebSocketMessageBuffer * buffer; if (!requestJSONBufferLock(12)) { if (client) { @@ -129,7 +128,7 @@ void sendDataWs(AsyncWebSocketClient * client) return; } #endif - buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes on ESP8266 + AsyncWebSocketBuffer buffer(len); #ifdef ESP8266 size_t heap2 = ESP.getFreeHeap(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); @@ -141,23 +140,18 @@ void sendDataWs(AsyncWebSocketClient * client) DEBUG_PRINTLN(F("WS buffer allocation failed.")); ws.closeAll(1013); //code 1013 = temporary overload, try again later ws.cleanupClients(0); //disconnect all clients to release memory - ws._cleanBuffers(); return; //out of memory } - - buffer->lock(); - serializeJson(*pDoc, (char *)buffer->get(), len); + serializeJson(*pDoc, (char *)buffer.data(), len); DEBUG_PRINT(F("Sending WS data ")); if (client) { - client->text(buffer); + client->text(std::move(buffer)); DEBUG_PRINTLN(F("to a single client.")); } else { - ws.textAll(buffer); + ws.textAll(std::move(buffer)); DEBUG_PRINTLN(F("to multiple clients.")); } - buffer->unlock(); - ws._cleanBuffers(); releaseJSONBufferLock(); } @@ -187,11 +181,10 @@ bool sendLiveLedsWs(uint32_t wsClient) #endif size_t bufSize = pos + (used/n)*3; - AsyncWebSocketMessageBuffer * wsBuf = ws.makeBuffer(bufSize); + AsyncWebSocketBuffer wsBuf(bufSize); if (!wsBuf) return false; //out of memory - uint8_t* buffer = wsBuf->get(); + uint8_t* buffer = reinterpret_cast(wsBuf.data()); if (!buffer) return false; //out of memory - wsBuf->lock(); // protect buffer from being cleaned by another WS instance buffer[0] = 'L'; buffer[1] = 1; //version @@ -218,9 +211,7 @@ bool sendLiveLedsWs(uint32_t wsClient) buffer[pos++] = scale8(qadd8(w, b), strip.getBrightness()); //B } - wsc->binary(wsBuf); - wsBuf->unlock(); // un-protect buffer - ws._cleanBuffers(); + wsc->binary(std::move(wsBuf)); return true; } From 0593a078c6560ad0507ac3673fa749e85815c18b Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:07:26 -0400 Subject: [PATCH 091/694] handleFileRead: Leverage AWS code No need to filter or look up content type, just pitch it over the wall. Also fixes .gz'd content processing. --- wled00/file.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 199009e4e..6511d4d07 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -421,25 +421,19 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); if(path.endsWith("/")) path += "index.htm"; if(path.indexOf(F("sec")) > -1) return false; - String contentType = request->hasArg(F("download")) ? F("application/octet-stream") : contentTypeFor(path); - /*String pathWithGz = path + ".gz"; - if(WLED_FS.exists(pathWithGz)){ - request->send(WLED_FS, pathWithGz, contentType); - return true; - }*/ #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) if (path.endsWith(FPSTR(getPresetsFileName()))) { size_t psize; const uint8_t *presets = getPresetCache(psize); if (presets) { - AsyncWebServerResponse *response = request->beginResponse_P(200, contentType, presets, psize); + AsyncWebServerResponse *response = request->beginResponse_P(200, FPSTR(CONTENT_TYPE_JSON), presets, psize); request->send(response); return true; } } #endif - if(WLED_FS.exists(path)) { - request->send(WLED_FS, path, contentType); + if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) { + request->send(WLED_FS, path, String(), request->hasArg(F("download"))); return true; } return false; From 5f2480c3d9c56e54084f6954eaf9e4b2ee3aa628 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 14 Mar 2024 22:06:51 -0400 Subject: [PATCH 092/694] serveLiveLeds: Use dynamic buffer There were three problems here: - AsyncWebServer is going to copy to a heap buffer anyways, so we might as well just pass it one it can use - The buffer size estimate was wrong -- we need 9 bytes per pixel ("RRGGBB",), so the buffer could overflow, and it was not considering the extra 2D requirements - On ESP8266, the stack allocation was overflowing the stack, causing corruption and crashes. --- wled00/json.cpp | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index c629cbe4f..f6cb645c0 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1152,10 +1152,10 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) } #endif - char buffer[2048]; // shoud be enough for 256 LEDs [RRGGBB] + all other text (9+25) - strcpy_P(buffer, PSTR("{\"leds\":[")); - obuf = buffer; // assign buffer for oappnd() functions - olen = 9; + DynamicBuffer buffer(9 + (9*MAX_LIVE_LEDS) + 7 + 5 + 6 + 5 + 6 + 5 + 2); + char* buf = buffer.data(); // assign buffer for oappnd() functions + strncpy_P(buffer.data(), PSTR("{\"leds\":["), buffer.size()); + buf += 9; // sizeof(PSTR()) from last line for (size_t i = 0; i < used; i += n) { @@ -1170,29 +1170,27 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) r = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map g = scale8(qadd8(w, g), strip.getBrightness()); //G b = scale8(qadd8(w, b), strip.getBrightness()); //B - olen += sprintf_P(obuf + olen, PSTR("\"%06X\","), RGBW32(r,g,b,0)); + buf += sprintf_P(buf, PSTR("\"%06X\","), RGBW32(r,g,b,0)); } - olen -= 1; - oappend((const char*)F("],\"n\":")); - oappendi(n); + buf--; // remove last comma + buf += sprintf_P(buf, PSTR("],\"n\":%d"), n); #ifndef WLED_DISABLE_2D if (strip.isMatrix) { - oappend((const char*)F(",\"w\":")); - oappendi(Segment::maxWidth/n); - oappend((const char*)F(",\"h\":")); - oappendi(Segment::maxHeight/n); + buf += sprintf_P(buf, PSTR(",\"w\":%d"), Segment::maxWidth/n); + buf += sprintf_P(buf, PSTR(",\"h\":%d"), Segment::maxHeight/n); } #endif - oappend("}"); + (*buf++) = '}'; + (*buf++) = 0; + if (request) { - request->send(200, FPSTR(CONTENT_TYPE_JSON), buffer); + request->send(200, FPSTR(CONTENT_TYPE_JSON), toString(std::move(buffer))); } #ifdef WLED_ENABLE_WEBSOCKETS else { - wsc->text(obuf, olen); + wsc->text(toString(std::move(buffer))); } - #endif - obuf = nullptr; + #endif return true; } #endif From acf6736afdc579e0bb498b9111e2b8ff7cbaab63 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 17 Mar 2024 11:52:09 +0100 Subject: [PATCH 093/694] WLED 0.14.2 release --- CHANGELOG.md | 3 + package-lock.json | 4 +- package.json | 2 +- wled00/html_other.h | 68 ++++----- wled00/html_settings.h | 314 ++++++++++++++++++++--------------------- wled00/improv.cpp | 2 +- wled00/wled.h | 4 +- 7 files changed, 200 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da317b8b..8095a15d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## WLED changelog +#### Build 2403170 +- WLED 0.14.2 release + #### Build 2403110 - Beta WLED 0.14.2-b2 - New AsyncWebServer (improved performance and reduced memory use) diff --git a/package-lock.json b/package-lock.json index 7653dea53..9bf4449eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.2-b2", + "version": "0.14.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.2-b2", + "version": "0.14.2", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 2da6f7a8e..8781cd6c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.2-b2", + "version": "0.14.2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/html_other.h b/wled00/html_other.h index c7a895916..2799fcdf3 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -41,47 +41,47 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; #endif // Autogenerated from wled00/data/update.htm, do not edit!! -const uint16_t PAGE_update_length = 616; +const uint16_t PAGE_update_length = 613; const uint8_t PAGE_update[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0x5d, 0x6f, 0xd4, 0x30, - 0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x71, 0x36, 0x54, 0xbc, 0x50, 0x92, 0x14, 0x4a, + 0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x51, 0x1b, 0x2a, 0x5e, 0x28, 0x49, 0x0a, 0x47, 0x2b, 0x54, 0x09, 0xa9, 0x95, 0xda, 0x82, 0x78, 0x42, 0x8e, 0xbd, 0xb9, 0x98, 0x73, 0xec, 0xd4, 0xde, 0xdc, 0xe9, 0x84, 0xfa, 0xdf, 0xd9, 0x38, 0x77, 0x05, 0xf1, 0xf1, 0x12, 0xc5, 0xd9, 0xd9, 0xf1, 0xee, 0xcc, 0xa4, 0x3c, 0xb9, 0xbc, 0xf9, 0x70, 0xff, 0xf5, 0xf6, 0x8a, 0x75, 0xd8, 0xbb, 0xba, 0x3c, 0x3c, 0x41, 0x99, 0xba, 0xec, 0x01, 0x15, 0xd3, 0xc1, 0x23, 0x78, 0xac, 0xf8, 0xce, - 0x1a, 0xec, 0x2a, 0x03, 0x5b, 0xab, 0x61, 0x95, 0x0f, 0x9c, 0x79, 0xd5, 0x43, 0xc5, 0xb7, 0x16, - 0x76, 0x43, 0x88, 0xc8, 0xeb, 0xa2, 0x44, 0x8b, 0x0e, 0xea, 0x2f, 0x9f, 0xae, 0x2e, 0xd9, 0xc3, - 0x60, 0x14, 0x42, 0x29, 0xe7, 0x4f, 0x65, 0xd2, 0xd1, 0x0e, 0x58, 0x17, 0xed, 0xe8, 0x35, 0xda, - 0xe0, 0xd9, 0xc5, 0x62, 0xf9, 0x63, 0x67, 0xbd, 0x09, 0x3b, 0xd1, 0xd9, 0x84, 0x21, 0xee, 0x45, + 0x1a, 0xec, 0x2a, 0x03, 0x5b, 0xab, 0xe1, 0x34, 0x1f, 0x38, 0xf3, 0xaa, 0x87, 0x8a, 0x6f, 0x2d, + 0xec, 0x86, 0x10, 0x91, 0xd7, 0x45, 0x89, 0x16, 0x1d, 0xd4, 0x5f, 0x3e, 0x5d, 0x5d, 0xb2, 0x87, + 0xc1, 0x28, 0x84, 0x52, 0xce, 0x9f, 0xca, 0xa4, 0xa3, 0x1d, 0xb0, 0x2e, 0xda, 0xd1, 0x6b, 0xb4, + 0xc1, 0xb3, 0xd5, 0x62, 0xf9, 0x63, 0x67, 0xbd, 0x09, 0x3b, 0xd1, 0xd9, 0x84, 0x21, 0xee, 0x45, 0xa3, 0xf4, 0x66, 0xb1, 0x7c, 0x7a, 0x86, 0x3c, 0x10, 0xc4, 0x04, 0x3d, 0xf6, 0x34, 0x81, 0x58, - 0x03, 0x5e, 0x39, 0x98, 0x5e, 0x2f, 0xf6, 0xd7, 0x66, 0xc1, 0xc7, 0x96, 0x2f, 0x45, 0xc2, 0xbd, - 0x03, 0x61, 0x6c, 0x1a, 0x9c, 0xda, 0x57, 0xdc, 0x07, 0x0f, 0xfc, 0xc5, 0x7f, 0x5b, 0xfa, 0xb4, - 0xfe, 0xbb, 0xa7, 0x71, 0x41, 0x6f, 0xf8, 0x53, 0x51, 0xca, 0xc3, 0x88, 0x87, 0x51, 0x59, 0x8a, - 0xba, 0xe2, 0x32, 0x01, 0xa2, 0xf5, 0xeb, 0x24, 0x93, 0xf8, 0x9e, 0xce, 0x87, 0xea, 0x0d, 0xaf, - 0x7f, 0x43, 0x4e, 0x54, 0x75, 0xf1, 0xce, 0xf6, 0x93, 0x00, 0x6c, 0x8c, 0x6e, 0xc1, 0x67, 0x7a, - 0x9d, 0x12, 0x5f, 0xbe, 0x25, 0x64, 0x46, 0x94, 0x72, 0x96, 0xb4, 0x09, 0x66, 0xcf, 0x82, 0x77, - 0x41, 0x99, 0x8a, 0x7f, 0x04, 0xfc, 0xbc, 0x58, 0x12, 0x5d, 0x77, 0x5a, 0x17, 0x59, 0xb2, 0xbb, - 0xd0, 0xe2, 0x4e, 0x45, 0x78, 0xd6, 0x8e, 0x2a, 0x65, 0x1b, 0x62, 0xcf, 0xc8, 0x8b, 0x2e, 0x50, - 0xcf, 0xed, 0xcd, 0xdd, 0x3d, 0x67, 0x2a, 0xcb, 0x53, 0x71, 0x21, 0xc7, 0x0c, 0xe4, 0xcc, 0x52, - 0x8d, 0x04, 0x61, 0x05, 0x90, 0x74, 0xfb, 0x81, 0x5c, 0xe9, 0x47, 0x87, 0x76, 0x50, 0x11, 0xe5, - 0x44, 0xb0, 0x22, 0x98, 0xe2, 0x74, 0x75, 0x1a, 0x9b, 0xde, 0x92, 0x9d, 0x0f, 0xd3, 0xcd, 0xd7, - 0x3e, 0xa1, 0x72, 0x0e, 0x0c, 0xdb, 0x42, 0x4c, 0x44, 0x79, 0xc6, 0xca, 0x34, 0x28, 0xcf, 0x0a, - 0xed, 0x54, 0x4a, 0x15, 0x4f, 0x76, 0xe0, 0xf5, 0x4b, 0xf1, 0xea, 0xb5, 0x38, 0x5d, 0x35, 0xa7, - 0xb4, 0x0d, 0x15, 0x69, 0x8b, 0x58, 0x5f, 0x86, 0x5d, 0xde, 0x82, 0x61, 0x07, 0xcc, 0xd1, 0x08, - 0x09, 0x59, 0x63, 0xbd, 0x8a, 0x7b, 0xa2, 0x50, 0xac, 0xe8, 0x22, 0xb4, 0x15, 0xef, 0x10, 0x87, - 0x74, 0x26, 0xe5, 0xda, 0x62, 0x37, 0x36, 0x42, 0x87, 0x5e, 0xbe, 0xb7, 0x51, 0x87, 0x10, 0x36, - 0x16, 0xe4, 0xb4, 0xb2, 0x8c, 0xe0, 0x40, 0x25, 0x48, 0x9c, 0xa1, 0x8a, 0xe4, 0x57, 0xc5, 0xbf, - 0x35, 0x4e, 0xf9, 0x0d, 0xc9, 0x62, 0xfb, 0x35, 0x2b, 0xb2, 0x09, 0x47, 0x1e, 0xfa, 0x22, 0x52, - 0x67, 0xc1, 0x99, 0x24, 0x6c, 0x38, 0xd0, 0x1e, 0x29, 0xfe, 0xa4, 0x16, 0x69, 0xbb, 0x3e, 0xcf, - 0xf2, 0x57, 0x2d, 0x4d, 0xb8, 0x4a, 0x8f, 0x23, 0x49, 0x3b, 0x85, 0x54, 0xaa, 0xbc, 0x43, 0x69, - 0xfd, 0x30, 0x22, 0x9b, 0xe5, 0x6a, 0xad, 0x83, 0x63, 0xa0, 0x8f, 0xa2, 0x46, 0x78, 0x1c, 0x6d, - 0x04, 0x33, 0xa3, 0x9b, 0x11, 0x91, 0x32, 0x39, 0xc3, 0x67, 0x19, 0x89, 0x6c, 0x76, 0xea, 0xa4, - 0x94, 0x73, 0xf9, 0x1f, 0xd0, 0xf9, 0x30, 0x69, 0xaf, 0x9d, 0xd5, 0x9b, 0x8a, 0x5f, 0x4c, 0xd2, - 0x5f, 0x50, 0xd4, 0x7f, 0x35, 0x65, 0x8f, 0xea, 0xd2, 0xd8, 0x6d, 0x91, 0xad, 0x9c, 0x82, 0x4a, - 0x34, 0x75, 0x66, 0xa7, 0xf4, 0x09, 0x21, 0x08, 0x9c, 0xc9, 0x6f, 0xf3, 0xb2, 0xcc, 0x04, 0xe6, - 0x03, 0x32, 0xed, 0x02, 0x1d, 0x42, 0xa4, 0x59, 0xdb, 0x08, 0xa9, 0xcb, 0x7e, 0x0c, 0x6a, 0x0d, - 0xec, 0x6c, 0x59, 0x4a, 0xe2, 0x9b, 0xd6, 0x9d, 0x52, 0x37, 0x45, 0x70, 0xfa, 0xb7, 0x7f, 0x02, - 0x70, 0xe8, 0x3d, 0x35, 0xf1, 0x03, 0x00, 0x00 + 0x03, 0x5e, 0x39, 0x98, 0x5e, 0x57, 0xfb, 0x6b, 0xb3, 0xe0, 0x63, 0xcb, 0x97, 0x22, 0xe1, 0xde, + 0x81, 0x30, 0x36, 0x0d, 0x4e, 0xed, 0x2b, 0xee, 0x83, 0x07, 0xfe, 0xe2, 0xbf, 0x2d, 0x7d, 0x5a, + 0xff, 0xdd, 0xd3, 0xb8, 0xa0, 0x37, 0xfc, 0xa9, 0x28, 0xe5, 0x61, 0xc4, 0xc3, 0xa8, 0x2c, 0x45, + 0x5d, 0x71, 0x99, 0x00, 0xd1, 0xfa, 0x75, 0x92, 0x49, 0x7c, 0x4f, 0x17, 0x43, 0xf5, 0x86, 0xd7, + 0xbf, 0x21, 0x27, 0xaa, 0xba, 0x78, 0x67, 0xfb, 0x49, 0x00, 0x36, 0x46, 0xb7, 0xe0, 0x33, 0xbd, + 0x4e, 0x89, 0x2f, 0xdf, 0x12, 0x32, 0x23, 0x4a, 0x39, 0x4b, 0xda, 0x04, 0xb3, 0x67, 0xc1, 0xbb, + 0xa0, 0x4c, 0xc5, 0x3f, 0x02, 0x7e, 0x5e, 0x2c, 0x89, 0xae, 0x3b, 0xab, 0x8b, 0x2c, 0xd9, 0x5d, + 0x68, 0x71, 0xa7, 0x22, 0x3c, 0x6b, 0x47, 0x95, 0xb2, 0x0d, 0xb1, 0x67, 0xe4, 0x45, 0x17, 0xa8, + 0xe7, 0xf6, 0xe6, 0xee, 0x9e, 0x33, 0x95, 0xe5, 0xa9, 0xb8, 0x90, 0x63, 0x06, 0x72, 0x66, 0xa9, + 0x46, 0x82, 0xb0, 0x02, 0x48, 0xba, 0xfd, 0x40, 0xae, 0xf4, 0xa3, 0x43, 0x3b, 0xa8, 0x88, 0x72, + 0x22, 0x38, 0x25, 0x98, 0xe2, 0x74, 0x75, 0x1a, 0x9b, 0xde, 0x92, 0x9d, 0x0f, 0xd3, 0xcd, 0xd7, + 0x3e, 0xa1, 0x72, 0x0e, 0x0c, 0xdb, 0x42, 0x4c, 0x44, 0x79, 0xce, 0xca, 0x34, 0x28, 0xcf, 0x0a, + 0xed, 0x54, 0x4a, 0x15, 0x4f, 0x76, 0xe0, 0xf5, 0x4b, 0xf1, 0xea, 0xb5, 0x38, 0xa3, 0x55, 0xa8, + 0x42, 0x2b, 0xc4, 0xfa, 0x32, 0xec, 0xf2, 0x0a, 0x0c, 0x3b, 0x60, 0x8e, 0xee, 0x4f, 0xc8, 0x1a, + 0xeb, 0x55, 0xdc, 0x53, 0xbf, 0x62, 0x45, 0x17, 0xa1, 0xad, 0x78, 0x87, 0x38, 0xa4, 0x73, 0x29, + 0xd7, 0x16, 0xbb, 0xb1, 0x11, 0x3a, 0xf4, 0xf2, 0xbd, 0x8d, 0x3a, 0x84, 0xb0, 0xb1, 0x20, 0xa7, + 0x7d, 0x65, 0x04, 0x07, 0x2a, 0x41, 0xe2, 0x0c, 0x55, 0x24, 0xb3, 0x2a, 0xfe, 0xad, 0x71, 0xca, + 0x6f, 0x48, 0x13, 0xdb, 0xaf, 0x59, 0x91, 0x1d, 0x38, 0xf2, 0xd0, 0x17, 0x91, 0x3a, 0x0b, 0xce, + 0x24, 0x61, 0xc3, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x48, 0xdb, 0xf5, 0x45, 0xd6, 0xbe, 0x6a, + 0x69, 0xc2, 0xd3, 0xf4, 0x38, 0x92, 0xae, 0x53, 0x42, 0xa5, 0xca, 0x3b, 0x94, 0xd6, 0x0f, 0x23, + 0xb2, 0x59, 0xab, 0xd6, 0x3a, 0x38, 0xa6, 0xf9, 0xa8, 0x68, 0x84, 0xc7, 0xd1, 0x46, 0x30, 0x33, + 0xba, 0x19, 0x11, 0x29, 0x90, 0x33, 0x7c, 0xd6, 0x90, 0xc8, 0x66, 0x9b, 0x4e, 0x4a, 0x39, 0x97, + 0xff, 0x01, 0x9d, 0x0f, 0x93, 0xf0, 0xda, 0x59, 0xbd, 0xa9, 0xf8, 0x6a, 0xd2, 0x7d, 0x45, 0x39, + 0xff, 0xd5, 0x94, 0x0d, 0xaa, 0x4b, 0x63, 0xb7, 0x45, 0xf6, 0x71, 0x4a, 0x29, 0xd1, 0xd4, 0x99, + 0x9d, 0xa2, 0x27, 0x84, 0x20, 0x70, 0x26, 0xbf, 0xcd, 0xcb, 0x32, 0x13, 0x98, 0x0f, 0xc8, 0xb4, + 0x0b, 0x74, 0x08, 0x91, 0x66, 0x6d, 0x23, 0xa4, 0x2e, 0xfb, 0x31, 0xa8, 0x35, 0xb0, 0xf3, 0x65, + 0x29, 0x89, 0x6f, 0x5a, 0x77, 0x8a, 0xdc, 0x94, 0xbf, 0xe9, 0xc7, 0xfe, 0x09, 0x3d, 0x8c, 0x87, + 0xc8, 0xee, 0x03, 0x00, 0x00 }; diff --git a/wled00/html_settings.h b/wled00/html_settings.h index e13d71a8e..62d3404dc 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1607,168 +1607,168 @@ const uint8_t PAGE_settings_time[] PROGMEM = { // Autogenerated from wled00/data/settings_sec.htm, do not edit!! -const uint16_t PAGE_settings_sec_length = 2551; +const uint16_t PAGE_settings_sec_length = 2548; const uint8_t PAGE_settings_sec[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x9d, 0x58, 0x6d, 0x53, 0xdb, 0x48, 0x12, 0xfe, 0xee, 0x5f, 0x31, 0x9e, 0xad, 0xca, 0x4a, 0x17, 0x21, 0x03, 0x49, 0x6d, 0x25, 0x60, 0x99, 0x83, 0x40, 0x36, 0x5c, 0x41, 0xa0, 0xb0, 0xd9, 0xdc, 0x55, 0x2e, 0x95, 0x92, 0xa5, 0xb1, 0x35, 0xb1, 0xac, 0xd1, 0xce, 0x8c, 0x70, 0x7c, 0xd9, 0xfc, 0xf7, 0x7b, 0x7a, 0x24, 0xf9, 0x85, - 0x40, 0x72, 0xb9, 0x0f, 0x60, 0x6b, 0x34, 0xd3, 0x2f, 0x4f, 0x77, 0x3f, 0xdd, 0xe3, 0x7e, 0xf7, - 0xf4, 0xea, 0xd5, 0xe8, 0x5f, 0xd7, 0x67, 0x2c, 0xb3, 0xf3, 0x7c, 0xd0, 0xa7, 0xff, 0x2c, 0x8f, - 0x8b, 0x69, 0xc4, 0x45, 0xc1, 0xf1, 0x2c, 0xe2, 0x74, 0xd0, 0x9f, 0x0b, 0x1b, 0xb3, 0x4e, 0xa2, - 0x0a, 0x2b, 0x0a, 0x1b, 0xf1, 0x85, 0x4c, 0x6d, 0x16, 0xa5, 0xe2, 0x4e, 0x26, 0x62, 0xc7, 0x3d, - 0x04, 0xb2, 0x90, 0x56, 0xc6, 0xf9, 0x8e, 0x49, 0xe2, 0x5c, 0x44, 0x7b, 0xc1, 0x3c, 0xfe, 0x2c, - 0xe7, 0xd5, 0x7c, 0xf5, 0x5c, 0x19, 0xa1, 0xdd, 0x43, 0x3c, 0xc6, 0x73, 0xa1, 0x38, 0xeb, 0x14, - 0xf1, 0x5c, 0x44, 0xfc, 0x4e, 0x8a, 0x45, 0xa9, 0xb4, 0xe5, 0x8d, 0x96, 0x24, 0x8b, 0xb5, 0x11, - 0x50, 0x52, 0xd9, 0xc9, 0xce, 0x0b, 0xac, 0x5a, 0x69, 0x73, 0x31, 0xb8, 0x94, 0x26, 0x61, 0x43, - 0x61, 0xad, 0x2c, 0xa6, 0xa6, 0xdf, 0xab, 0x17, 0xfb, 0x26, 0xd1, 0xb2, 0xb4, 0x83, 0xce, 0x5d, - 0xac, 0x59, 0xae, 0x12, 0x59, 0x06, 0x56, 0xce, 0x85, 0xaa, 0x6c, 0x90, 0x46, 0xa9, 0x4a, 0xaa, - 0x39, 0xcc, 0x0d, 0xf0, 0x22, 0xea, 0xee, 0xd1, 0x47, 0xa9, 0x95, 0x55, 0x11, 0xcf, 0xac, 0x2d, - 0x0f, 0xf8, 0xe1, 0xa4, 0x2a, 0x12, 0x2b, 0x55, 0xc1, 0xde, 0x78, 0xfe, 0x97, 0x85, 0x2c, 0x52, - 0xb5, 0x08, 0x55, 0x29, 0x0a, 0xcf, 0x6d, 0x30, 0x07, 0xbd, 0xde, 0xac, 0x50, 0xe1, 0x22, 0x17, - 0x69, 0x38, 0x15, 0xbd, 0x89, 0x88, 0x6d, 0xa5, 0x85, 0xe9, 0x99, 0xc6, 0x88, 0xde, 0x2f, 0x46, - 0x24, 0x95, 0x96, 0x76, 0xb9, 0xd3, 0x2e, 0x71, 0xff, 0xeb, 0x4a, 0xe8, 0xc9, 0x3d, 0xa1, 0x53, - 0x61, 0x6f, 0x6f, 0x2e, 0x3c, 0xde, 0x5b, 0x6f, 0x0e, 0xf8, 0x47, 0x23, 0xf2, 0xc9, 0xe6, 0xa9, - 0xdb, 0xc7, 0x4e, 0x55, 0x65, 0x1a, 0x5b, 0xf1, 0xe0, 0x99, 0xe9, 0x79, 0xea, 0x59, 0xff, 0x8b, - 0x16, 0xb0, 0xaf, 0x60, 0x64, 0xac, 0x3d, 0xcb, 0x05, 0xb9, 0x7e, 0xb2, 0x74, 0xaf, 0xd6, 0x5b, - 0xa5, 0xb9, 0x1a, 0x7f, 0xda, 0xd8, 0x6c, 0x9f, 0x3c, 0xe1, 0x6a, 0xfc, 0x49, 0x24, 0x96, 0x47, - 0x91, 0x5d, 0x96, 0x42, 0x4d, 0x68, 0xad, 0x7b, 0xac, 0x75, 0xbc, 0x0c, 0xa5, 0x71, 0x9f, 0x5b, - 0x12, 0x72, 0x15, 0xa7, 0xff, 0x18, 0x7a, 0x36, 0x10, 0x51, 0x77, 0xd7, 0xff, 0x92, 0x0b, 0xcb, - 0x54, 0x94, 0x86, 0x89, 0x06, 0x3c, 0xa2, 0x51, 0xeb, 0xf1, 0x3a, 0x2e, 0xdc, 0x3f, 0x54, 0x21, - 0xdc, 0x3d, 0xb6, 0x56, 0xcb, 0x71, 0x65, 0x05, 0x5e, 0xe8, 0x84, 0x07, 0xd6, 0x0f, 0xee, 0xaf, - 0x93, 0x6e, 0x1e, 0x70, 0x2b, 0x3e, 0xdb, 0xde, 0xa7, 0xf8, 0x2e, 0x6e, 0x05, 0x7c, 0xb3, 0x31, - 0x36, 0xcb, 0x02, 0x22, 0x84, 0x1f, 0xa4, 0xe1, 0x58, 0xa5, 0xcb, 0x30, 0x2e, 0x81, 0x53, 0xfa, - 0x2a, 0x93, 0x79, 0xea, 0x29, 0xda, 0x1f, 0xa7, 0xe9, 0xd9, 0x1d, 0xac, 0xb8, 0x90, 0x06, 0xd9, - 0x2a, 0xb4, 0xc7, 0xc9, 0x66, 0x1e, 0x78, 0x7e, 0x34, 0xf8, 0xf2, 0xbb, 0xb0, 0x7f, 0x78, 0x7e, - 0x00, 0x99, 0x27, 0xc9, 0xec, 0xb5, 0xcc, 0x05, 0x25, 0xa1, 0x47, 0x08, 0xf2, 0x71, 0x32, 0x4b, - 0x26, 0x53, 0xee, 0x3f, 0xfa, 0xb6, 0x44, 0xf4, 0x85, 0x45, 0xdc, 0xfc, 0xaf, 0x0f, 0xeb, 0x11, - 0x5a, 0x2b, 0x0d, 0xf7, 0xa0, 0x07, 0xa5, 0x62, 0x54, 0x2e, 0xc2, 0x5c, 0x4d, 0x3d, 0x7e, 0x46, - 0xeb, 0xac, 0x01, 0x0f, 0xa1, 0x67, 0x13, 0x88, 0x76, 0x30, 0xa0, 0x36, 0x34, 0xe0, 0xba, 0x68, - 0xd6, 0x81, 0x3e, 0x0e, 0x4e, 0xe4, 0xb4, 0xd2, 0xb1, 0x43, 0xbb, 0x86, 0x81, 0x4d, 0x62, 0x49, - 0x59, 0xf8, 0xef, 0xe2, 0xbc, 0x48, 0xd4, 0xbc, 0x04, 0xe8, 0x82, 0x95, 0xf1, 0x54, 0x30, 0xe4, - 0x44, 0xdc, 0x45, 0x2e, 0x6c, 0x04, 0xc8, 0x64, 0x6a, 0x31, 0x52, 0xb1, 0xb1, 0x75, 0x8c, 0xf6, - 0xfc, 0x2f, 0x54, 0x1c, 0x2a, 0x72, 0x5e, 0x58, 0x7a, 0xe1, 0xc2, 0x22, 0x0b, 0x98, 0xfc, 0x66, - 0x74, 0x79, 0x11, 0x59, 0xf8, 0x92, 0xe4, 0xb1, 0x31, 0xe4, 0x08, 0x79, 0xe5, 0x89, 0xa3, 0xc6, - 0x95, 0x03, 0x4e, 0xd2, 0x10, 0x85, 0x24, 0x17, 0xb1, 0x1e, 0xd5, 0xa5, 0xe5, 0x35, 0x25, 0xe6, - 0x62, 0x63, 0x97, 0x70, 0x32, 0x2e, 0xe4, 0xdc, 0xd9, 0x1b, 0xf1, 0x42, 0x15, 0xe4, 0x59, 0xbd, - 0x23, 0x02, 0x5c, 0xed, 0x21, 0xaf, 0x35, 0x10, 0x09, 0xbe, 0xa9, 0x4f, 0x8b, 0xb9, 0xba, 0xa3, - 0xc4, 0x70, 0x8a, 0x00, 0xec, 0xfe, 0xcb, 0xdd, 0xdd, 0x0d, 0x77, 0xaa, 0x92, 0x40, 0xa3, 0x58, - 0x90, 0x3f, 0xad, 0x33, 0x85, 0x58, 0xb0, 0x7f, 0x5e, 0x5e, 0xbc, 0x41, 0x9d, 0xde, 0x88, 0x3f, - 0x2b, 0x61, 0xec, 0xe1, 0x77, 0x02, 0xbf, 0xa1, 0x7a, 0x03, 0x9d, 0x4c, 0x1a, 0x68, 0x37, 0x25, - 0x22, 0x25, 0x46, 0xc8, 0xbb, 0xc0, 0xad, 0x18, 0x8b, 0x32, 0x37, 0x83, 0xe8, 0x39, 0x59, 0xe1, - 0x7f, 0x37, 0xce, 0x6b, 0xb9, 0x76, 0x4b, 0x30, 0xc9, 0x48, 0x66, 0x41, 0xb7, 0x15, 0x50, 0x73, - 0xca, 0xf5, 0xd5, 0x70, 0xc4, 0x83, 0x8d, 0x7a, 0x76, 0xc6, 0xf9, 0xfe, 0x21, 0x79, 0x54, 0x38, - 0x8f, 0x5e, 0x2b, 0x3d, 0x3f, 0x45, 0x44, 0x0f, 0x9b, 0xea, 0x2c, 0x9a, 0xe4, 0xf6, 0x38, 0xc5, - 0x19, 0xb0, 0x86, 0x94, 0x38, 0xe6, 0xfd, 0xee, 0x07, 0xca, 0x7f, 0xaa, 0x0c, 0xbc, 0x2b, 0x7c, - 0xac, 0xdf, 0xc5, 0x79, 0x05, 0x2e, 0xe5, 0x41, 0x77, 0x6f, 0x0d, 0x5d, 0x92, 0x89, 0x64, 0xf6, - 0xb6, 0x9a, 0xaf, 0xeb, 0xbd, 0xeb, 0x75, 0x05, 0xb9, 0x12, 0xce, 0xc4, 0x32, 0x44, 0xc8, 0x92, - 0xcc, 0xeb, 0xbd, 0xdf, 0xdd, 0x79, 0xf9, 0xa1, 0xe7, 0xa3, 0xe8, 0xdf, 0xf3, 0x13, 0xd8, 0x6d, - 0xca, 0x38, 0xa1, 0x52, 0x1c, 0xc5, 0x63, 0xfc, 0x3f, 0x03, 0xe3, 0xc3, 0x55, 0x3e, 0xcc, 0xe4, - 0xc4, 0xe2, 0xf3, 0x15, 0x5a, 0x80, 0x56, 0x39, 0xbe, 0x1d, 0xe7, 0xf4, 0x7c, 0x1d, 0x83, 0xd8, - 0x69, 0x3d, 0x2e, 0xcd, 0x85, 0x4a, 0x66, 0x74, 0x04, 0x2c, 0xef, 0x8a, 0x79, 0xd8, 0x48, 0xba, - 0x46, 0xa6, 0xde, 0x96, 0xcd, 0x97, 0x53, 0xb5, 0x28, 0x9c, 0x5c, 0x04, 0x86, 0xbf, 0x51, 0x73, - 0xda, 0x00, 0x96, 0x51, 0x8b, 0x0b, 0xe1, 0x14, 0xb8, 0xef, 0x6e, 0xb7, 0xfb, 0x76, 0x23, 0xa7, - 0xd9, 0x6a, 0xb9, 0x39, 0x7b, 0x8e, 0x80, 0x69, 0x5a, 0x3c, 0x15, 0x54, 0x09, 0xfc, 0x03, 0x92, - 0x39, 0xc9, 0xab, 0x54, 0x18, 0x6f, 0xe5, 0x9d, 0xef, 0xff, 0xf5, 0x57, 0xf3, 0x84, 0xb2, 0xa5, - 0xcf, 0x53, 0x31, 0x89, 0xab, 0xdc, 0xa2, 0xf8, 0x51, 0x13, 0x1b, 0xe5, 0xb2, 0x5d, 0xeb, 0x80, - 0xca, 0xde, 0x63, 0x1c, 0x70, 0x71, 0x51, 0x27, 0x12, 0xa7, 0x5e, 0xf0, 0x91, 0x3f, 0xb5, 0x44, - 0xb1, 0x0f, 0xed, 0xf0, 0x9f, 0x7a, 0xfc, 0xdd, 0xc5, 0xd9, 0x29, 0xc8, 0xd4, 0xa4, 0x47, 0x1c, - 0xf5, 0x83, 0xdd, 0x26, 0xf5, 0x37, 0xf4, 0x0d, 0xbd, 0x9a, 0x34, 0x6d, 0xd4, 0xd0, 0x3c, 0xda, - 0x92, 0xab, 0x9d, 0x43, 0x39, 0xf1, 0x38, 0xc5, 0xf7, 0x80, 0xa8, 0x38, 0x74, 0xad, 0x2a, 0x51, - 0xb9, 0xef, 0xba, 0xd7, 0x6e, 0xe0, 0xb9, 0xf6, 0x16, 0xd1, 0xee, 0x7c, 0x68, 0x95, 0x06, 0x94, - 0x64, 0xc5, 0xb9, 0x15, 0x73, 0xca, 0xf3, 0xe4, 0xbc, 0xe4, 0xce, 0xe7, 0x7a, 0x1b, 0x4e, 0xcf, - 0x4b, 0x10, 0x0b, 0xf9, 0xc5, 0x2e, 0x55, 0x2a, 0x42, 0x76, 0x8d, 0x12, 0x36, 0x82, 0x09, 0x0a, - 0x28, 0x23, 0x23, 0xd9, 0xf9, 0x35, 0xa8, 0x23, 0xd8, 0x92, 0x68, 0xb6, 0x25, 0x06, 0x4e, 0x1a, - 0x72, 0x54, 0xe4, 0x46, 0x38, 0xb3, 0x05, 0x99, 0x16, 0xdb, 0x8c, 0xc0, 0x0a, 0x54, 0x84, 0x13, - 0x39, 0x66, 0x00, 0x6f, 0x2f, 0x10, 0x21, 0x92, 0xd1, 0xbc, 0x93, 0x36, 0x43, 0x72, 0x73, 0xff, - 0x68, 0x67, 0xef, 0xe0, 0x4e, 0xc9, 0x94, 0xed, 0xfa, 0xa1, 0x29, 0x73, 0x69, 0xdd, 0x2a, 0x8a, - 0x14, 0x38, 0x4f, 0x6d, 0x36, 0xd8, 0x7f, 0xf2, 0xc4, 0x5b, 0x35, 0xe4, 0xb5, 0xb7, 0x41, 0xe3, - 0x6d, 0xed, 0x85, 0x0d, 0x33, 0x65, 0x2c, 0xa9, 0x7a, 0x8a, 0xaa, 0xa2, 0x21, 0xe1, 0x08, 0x90, - 0x3e, 0xad, 0xbf, 0x1e, 0x70, 0xc0, 0x0d, 0xa1, 0x4f, 0x15, 0xea, 0xc1, 0xff, 0x8a, 0x13, 0x10, - 0xb9, 0x45, 0xe4, 0xf7, 0xe2, 0x98, 0x69, 0x31, 0x59, 0x15, 0xe0, 0xf6, 0xc6, 0xd6, 0x23, 0x94, - 0xeb, 0x37, 0x64, 0xff, 0xbf, 0x48, 0x59, 0x6f, 0x5e, 0x4b, 0x22, 0x64, 0x5d, 0xab, 0xfc, 0xa6, - 0xf1, 0xf7, 0x4c, 0xf8, 0xc9, 0x1c, 0x95, 0xd1, 0x6f, 0xdc, 0xe5, 0x62, 0x50, 0x9b, 0x9e, 0x86, - 0xc3, 0x49, 0x18, 0xbb, 0x24, 0x89, 0x1e, 0x38, 0x22, 0x12, 0xbe, 0x99, 0x46, 0xcd, 0x8e, 0x55, - 0x65, 0x13, 0x98, 0x47, 0x2d, 0xa0, 0x80, 0x05, 0xb8, 0x38, 0x0c, 0x1d, 0x4a, 0xf6, 0x6b, 0xa7, - 0xdf, 0x6b, 0x46, 0xa5, 0xbe, 0xa3, 0xec, 0xc1, 0xdf, 0xe5, 0x9c, 0x40, 0x64, 0x95, 0xce, 0xc1, - 0xbd, 0x8e, 0xc5, 0x13, 0x03, 0x07, 0x0e, 0xb1, 0xd1, 0x6d, 0xe8, 0xf7, 0xea, 0xc9, 0x8f, 0x7a, - 0x2c, 0x5a, 0x17, 0xf9, 0x12, 0x71, 0x24, 0x2f, 0x06, 0xb2, 0x09, 0x68, 0xaa, 0xc3, 0x24, 0x9e, - 0xe9, 0xdb, 0x47, 0xc3, 0x59, 0x3d, 0xc9, 0x0d, 0x27, 0x9c, 0x61, 0x84, 0xcb, 0x14, 0xde, 0x94, - 0x08, 0x1c, 0xb6, 0xa6, 0xf2, 0x8e, 0x39, 0xae, 0x8f, 0xd0, 0x7a, 0x60, 0xdb, 0x62, 0x7b, 0x2d, - 0x13, 0x79, 0x79, 0xc2, 0x07, 0x9d, 0x3e, 0xb0, 0xb5, 0xf0, 0x8a, 0xa6, 0x80, 0x88, 0xd7, 0x0f, - 0x1c, 0x5a, 0x13, 0x24, 0xd7, 0x2c, 0xe2, 0x6f, 0x48, 0xed, 0x51, 0xbf, 0x57, 0xbf, 0x80, 0x69, - 0x10, 0x31, 0x78, 0xf8, 0x4c, 0x67, 0x75, 0xe8, 0x84, 0x0e, 0x11, 0xa1, 0xad, 0xcf, 0x6d, 0x9d, - 0x30, 0xd5, 0x78, 0x2e, 0x61, 0xe3, 0x30, 0xbe, 0x13, 0xeb, 0x2d, 0x99, 0x6e, 0xc5, 0x67, 0xfb, - 0x83, 0xce, 0xb0, 0x19, 0xf0, 0xd8, 0x13, 0x76, 0xeb, 0xe6, 0x2f, 0xa2, 0x8b, 0xaa, 0x04, 0x36, - 0xfb, 0x83, 0x76, 0x16, 0x65, 0xd7, 0xe7, 0x6f, 0x0f, 0x58, 0x5f, 0x16, 0x65, 0x65, 0x1b, 0xd1, - 0x25, 0x9c, 0x5b, 0x28, 0x9d, 0x72, 0x07, 0x12, 0xde, 0xaf, 0x86, 0x5d, 0xf7, 0xdd, 0xc8, 0xff, - 0xe0, 0xeb, 0x73, 0x80, 0x15, 0x7f, 0xae, 0xcb, 0xa1, 0x7e, 0x92, 0xc5, 0xc6, 0x93, 0x2a, 0xc0, - 0x62, 0x44, 0x2c, 0x11, 0x5f, 0xf3, 0x38, 0x1a, 0x94, 0x0f, 0x59, 0x48, 0x32, 0x14, 0x31, 0xde, - 0x38, 0xee, 0xfe, 0x1b, 0xd4, 0x90, 0xf2, 0x39, 0xca, 0x1c, 0x0d, 0x18, 0x33, 0xaf, 0x96, 0x09, - 0x67, 0x6e, 0x42, 0x86, 0xc6, 0xcd, 0xb2, 0x8f, 0xd9, 0x73, 0x96, 0xca, 0xa9, 0xb4, 0x0c, 0xdb, - 0xc6, 0x20, 0x76, 0x60, 0xa2, 0x01, 0xff, 0x46, 0x48, 0x16, 0xb1, 0xc6, 0xd8, 0xdf, 0x79, 0xf2, - 0xcb, 0xcb, 0x17, 0x2f, 0x5e, 0x1c, 0xb2, 0xdb, 0x42, 0x14, 0x89, 0x5e, 0x96, 0x56, 0xa4, 0xcc, - 0xea, 0xb8, 0x30, 0x73, 0x69, 0x0c, 0x12, 0x30, 0x64, 0x27, 0x18, 0x41, 0x34, 0x48, 0xb7, 0xb0, - 0x6c, 0x91, 0x09, 0x22, 0xd2, 0x1c, 0x63, 0x24, 0x4d, 0x31, 0x70, 0x32, 0x60, 0xa9, 0x62, 0x6f, - 0xaf, 0x46, 0x0c, 0xdd, 0x81, 0x2d, 0x55, 0xa5, 0xd9, 0x38, 0x2e, 0x66, 0x78, 0x49, 0x2f, 0x94, - 0x0e, 0xd8, 0xf0, 0xfc, 0x32, 0x60, 0xc2, 0x26, 0x21, 0x2b, 0x65, 0xd1, 0xed, 0xb4, 0x21, 0xd5, - 0x03, 0x6a, 0x22, 0x6c, 0x21, 0x35, 0xa4, 0x19, 0xc3, 0xbc, 0xab, 0xd1, 0xb1, 0xcf, 0x8c, 0x9a, - 0x58, 0xd8, 0x25, 0x58, 0x3d, 0x04, 0xdf, 0x43, 0xdb, 0xc1, 0x33, 0x56, 0x9f, 0xdb, 0x34, 0x7c, - 0x7b, 0xe5, 0x32, 0x4a, 0x0f, 0xae, 0xe1, 0x50, 0x99, 0x69, 0xb8, 0xff, 0x68, 0x7c, 0xea, 0x13, - 0x57, 0xd7, 0x5b, 0xb1, 0x78, 0xb6, 0xdf, 0xe0, 0x32, 0x52, 0xc0, 0x8d, 0xee, 0x2b, 0x0c, 0x66, - 0x04, 0x0c, 0xd9, 0xce, 0xda, 0x91, 0x9f, 0x61, 0xde, 0x35, 0x98, 0x1c, 0xc8, 0x39, 0x56, 0x08, - 0x42, 0x47, 0xb1, 0x38, 0x37, 0xaa, 0x41, 0xda, 0x66, 0x02, 0xa3, 0x9c, 0xd6, 0x80, 0x84, 0xb5, - 0xea, 0xba, 0xce, 0xaa, 0x51, 0x26, 0x56, 0x2b, 0x34, 0xac, 0x55, 0x79, 0xca, 0xc6, 0x82, 0xae, - 0x3d, 0xc5, 0x14, 0x62, 0x1c, 0x96, 0x50, 0x87, 0x51, 0xbd, 0x51, 0x9e, 0x86, 0x74, 0xac, 0x3f, - 0x1e, 0x74, 0x4e, 0xa5, 0x69, 0xad, 0xa9, 0xf7, 0x15, 0xca, 0x22, 0xf6, 0x04, 0x72, 0xc0, 0x14, - 0x54, 0xea, 0x85, 0x04, 0xde, 0x71, 0xc1, 0x90, 0x20, 0xc8, 0x7c, 0xd8, 0x91, 0xe0, 0x01, 0x8c, - 0x85, 0xd8, 0x66, 0xac, 0xbe, 0xaf, 0xad, 0xd0, 0x24, 0xd4, 0xc7, 0xce, 0xd1, 0xbe, 0x5c, 0x67, - 0x33, 0xd5, 0x07, 0xd2, 0xac, 0x1e, 0x2f, 0x09, 0x73, 0xd4, 0xfe, 0xb2, 0xb6, 0xce, 0xe9, 0x96, - 0x13, 0xa7, 0x3e, 0xa7, 0x30, 0x61, 0x5b, 0x5a, 0x9b, 0x94, 0x76, 0xfb, 0x3d, 0x59, 0xa3, 0x7e, - 0x2a, 0x8a, 0x25, 0x8b, 0x93, 0x84, 0xc2, 0x07, 0x4c, 0xde, 0xc9, 0xd7, 0x92, 0xb5, 0x34, 0x46, - 0xa7, 0xe9, 0xa4, 0x48, 0x7f, 0x10, 0xc3, 0xab, 0x77, 0x4d, 0x0c, 0xe9, 0xef, 0x35, 0x58, 0x51, - 0x69, 0x82, 0x1c, 0x72, 0x7e, 0x70, 0xf0, 0x66, 0xd8, 0xc4, 0xee, 0x38, 0xcf, 0xd7, 0x6a, 0xe3, - 0x22, 0x65, 0x0d, 0x47, 0x23, 0xb7, 0xf0, 0x06, 0x80, 0x0b, 0x4a, 0x8c, 0x16, 0x5b, 0x3d, 0xf8, - 0xbf, 0x6a, 0xe0, 0x78, 0x03, 0x69, 0x07, 0x1c, 0xd0, 0x85, 0x19, 0x48, 0x08, 0x8b, 0xf0, 0xce, - 0x1c, 0xfa, 0x92, 0xf2, 0x21, 0x11, 0x34, 0xc5, 0x83, 0x2d, 0xeb, 0x89, 0xbd, 0xcd, 0x77, 0x62, - 0x9b, 0xec, 0xd9, 0x60, 0xd8, 0xe6, 0x77, 0x4d, 0x32, 0xa0, 0x97, 0x67, 0x83, 0x1f, 0x50, 0xe2, - 0x2d, 0xb1, 0x5b, 0xe7, 0x32, 0x2e, 0xaa, 0x38, 0x77, 0x01, 0x69, 0x8f, 0xae, 0xb8, 0x4e, 0x0f, - 0xce, 0xea, 0xdc, 0x3d, 0xd6, 0x69, 0x25, 0x0b, 0x85, 0x4d, 0x8f, 0x62, 0xd7, 0xb0, 0xd3, 0xf1, - 0x15, 0x5f, 0xd9, 0x44, 0xcc, 0x59, 0x95, 0x20, 0xbe, 0x1b, 0x0c, 0xd7, 0x4a, 0x37, 0x46, 0xc5, - 0x2d, 0x44, 0x63, 0x8b, 0xdb, 0x4c, 0x31, 0xab, 0x19, 0xae, 0xe9, 0xa3, 0xac, 0x43, 0xcd, 0x31, - 0xe2, 0xbd, 0x06, 0x6a, 0x74, 0x39, 0xb2, 0xb9, 0x9d, 0x8e, 0x50, 0x77, 0x4d, 0x9b, 0x6c, 0x85, - 0x37, 0xcf, 0xfd, 0x5e, 0xbc, 0x0a, 0xc1, 0xa0, 0xd3, 0xe8, 0x5b, 0xbd, 0x74, 0xf9, 0xb9, 0x61, - 0xb5, 0xbb, 0x3b, 0x35, 0xd1, 0x76, 0x63, 0xb1, 0x4b, 0xb6, 0xd2, 0x46, 0xbc, 0xd6, 0x37, 0x60, - 0x2d, 0x74, 0x9d, 0x87, 0xb1, 0xfb, 0x75, 0xe3, 0x4a, 0xe1, 0x9a, 0x2e, 0x49, 0x09, 0xee, 0x59, - 0xed, 0xff, 0x3a, 0xb8, 0x75, 0xdb, 0x56, 0x88, 0xd6, 0xe9, 0xb8, 0xe2, 0xa9, 0xc7, 0xa1, 0x68, - 0xbd, 0x64, 0x0d, 0x1a, 0x80, 0xa6, 0x41, 0xa2, 0xb3, 0x86, 0x82, 0xf0, 0x6a, 0x61, 0xd8, 0xba, - 0xf2, 0x6d, 0x81, 0xd1, 0x62, 0xb1, 0xbd, 0x63, 0x8d, 0x48, 0xe7, 0x61, 0x48, 0xf6, 0x1f, 0xc7, - 0xe4, 0x91, 0x6e, 0xf9, 0x20, 0x26, 0xfb, 0xc1, 0x86, 0xf1, 0xdf, 0x02, 0xb2, 0x81, 0x07, 0xd5, - 0x4e, 0xe7, 0xe1, 0xe2, 0xa9, 0x5d, 0xa0, 0xb6, 0xd0, 0xe0, 0xd2, 0xdb, 0xbe, 0xe1, 0xba, 0x72, - 0xbc, 0xfa, 0xe3, 0xec, 0xe6, 0xdd, 0xcd, 0xf9, 0xe8, 0xac, 0xee, 0x15, 0xa0, 0x58, 0x4d, 0x7d, - 0xe5, 0xc1, 0x13, 0xa1, 0x0b, 0x44, 0x87, 0x6e, 0xc2, 0x35, 0xbd, 0x6e, 0xcb, 0x9b, 0xc7, 0x44, - 0x14, 0x7f, 0x56, 0xe8, 0x20, 0x68, 0x77, 0x93, 0x4d, 0xea, 0x60, 0xe0, 0x6f, 0x2d, 0x76, 0x1c, - 0x19, 0x36, 0xb7, 0x6d, 0xa7, 0xee, 0x6c, 0x78, 0x1d, 0x36, 0x15, 0xf9, 0xfa, 0x01, 0x8a, 0x0f, - 0x56, 0x74, 0x6d, 0x1c, 0x21, 0x12, 0xe9, 0x8e, 0xa9, 0xe6, 0x53, 0xf4, 0xa3, 0xb0, 0xad, 0x97, - 0xe3, 0x31, 0x6e, 0xb9, 0x6d, 0x91, 0x34, 0x85, 0xd0, 0xfe, 0x8c, 0x84, 0x86, 0x9b, 0x55, 0xe3, - 0x10, 0x57, 0xf7, 0xde, 0xb1, 0xd4, 0x89, 0x52, 0x6a, 0x26, 0x45, 0x8f, 0x66, 0xf0, 0x1e, 0xfa, - 0x74, 0xac, 0xa7, 0xf4, 0x73, 0xd7, 0xc7, 0x71, 0x8e, 0x16, 0xc9, 0x07, 0xb4, 0x4c, 0x49, 0xd0, - 0x61, 0x77, 0x42, 0x13, 0xcd, 0xb0, 0xdd, 0x70, 0xef, 0x79, 0xb8, 0xbf, 0x33, 0xde, 0x5f, 0x71, - 0xd5, 0xcf, 0x68, 0x58, 0xc8, 0x99, 0xec, 0xb9, 0x6b, 0x1b, 0x4d, 0xb0, 0x4a, 0x9b, 0x1d, 0xb0, - 0xe1, 0x4e, 0xa2, 0x45, 0x2a, 0x29, 0x49, 0x3b, 0xf7, 0xf5, 0x6f, 0x6e, 0x45, 0xb7, 0x16, 0x74, - 0x01, 0x05, 0xff, 0x49, 0x51, 0xd3, 0xa8, 0x29, 0x45, 0x22, 0xc1, 0x39, 0x16, 0x1d, 0x61, 0xb6, - 0x2a, 0xdd, 0xce, 0x31, 0xcb, 0x2a, 0x34, 0x0c, 0xb7, 0xea, 0x7a, 0x22, 0xa8, 0x1f, 0xd7, 0x2e, - 0xbd, 0x54, 0x85, 0x40, 0xaf, 0x52, 0x8c, 0x46, 0x3d, 0x20, 0x06, 0x72, 0xac, 0x7f, 0x2a, 0x72, - 0x57, 0x90, 0x6e, 0xeb, 0x51, 0xc7, 0x4b, 0x7c, 0xb6, 0xbf, 0xbb, 0xf7, 0xdb, 0xce, 0xfe, 0xee, - 0xfe, 0x33, 0xf6, 0x2a, 0xd3, 0xb8, 0x6c, 0x4b, 0xb0, 0xe7, 0x30, 0xc9, 0x16, 0xf4, 0x53, 0x45, - 0xd3, 0xa6, 0x2e, 0xd0, 0xc0, 0x70, 0xfb, 0x03, 0xf2, 0x30, 0xaa, 0xee, 0xb2, 0x3f, 0x05, 0xc6, - 0x38, 0x57, 0xe3, 0xde, 0x1c, 0xb7, 0x74, 0xa1, 0x7b, 0x17, 0xe7, 0xaf, 0xce, 0xde, 0x0e, 0xcf, - 0xbe, 0x8d, 0x40, 0xe7, 0xf2, 0x7c, 0xc4, 0xf2, 0x5a, 0x93, 0x73, 0x10, 0xad, 0xad, 0x35, 0x74, - 0x28, 0x34, 0xbc, 0x82, 0x1b, 0xc6, 0xa0, 0x41, 0x82, 0x50, 0x71, 0x5d, 0x2e, 0x5a, 0x26, 0x30, - 0xb2, 0xe4, 0x54, 0xb3, 0xee, 0x27, 0x05, 0xe6, 0x7e, 0x25, 0x20, 0xaa, 0xa7, 0x2d, 0x35, 0xaf, - 0x52, 0x9d, 0x10, 0x4b, 0xd4, 0x3f, 0xc3, 0x7c, 0x77, 0x90, 0xfd, 0x76, 0x8e, 0xed, 0xfc, 0xd4, - 0x20, 0xdb, 0xa3, 0x6e, 0x83, 0x0f, 0x9a, 0xdf, 0x69, 0x98, 0xa7, 0x1f, 0x77, 0xff, 0x0b, 0xaf, - 0x1a, 0xd5, 0x6f, 0xec, 0x15, 0x00, 0x00 + 0x40, 0x72, 0xb9, 0x0f, 0x60, 0x69, 0x5e, 0x7a, 0xba, 0x9f, 0xee, 0x7e, 0xba, 0x47, 0xfd, 0xee, + 0xe9, 0xd5, 0xab, 0xd1, 0xbf, 0xae, 0xcf, 0x58, 0x66, 0xe7, 0xf9, 0xa0, 0x4f, 0xff, 0x59, 0x1e, + 0x17, 0xd3, 0x88, 0x8b, 0x82, 0xe3, 0x5d, 0xc4, 0xe9, 0xa0, 0x3f, 0x17, 0x36, 0x66, 0x9d, 0x44, + 0x15, 0x56, 0x14, 0x36, 0xe2, 0x0b, 0x99, 0xda, 0x2c, 0x4a, 0xc5, 0x9d, 0x4c, 0xc4, 0x8e, 0x7b, + 0x09, 0x64, 0x21, 0xad, 0x8c, 0xf3, 0x1d, 0x93, 0xc4, 0xb9, 0x88, 0xf6, 0x82, 0x79, 0xfc, 0x59, + 0xce, 0xab, 0xf9, 0xea, 0xbd, 0x32, 0x42, 0xbb, 0x97, 0x78, 0x8c, 0xf7, 0x42, 0x71, 0xd6, 0x29, + 0xe2, 0xb9, 0x88, 0xf8, 0x9d, 0x14, 0x8b, 0x52, 0x69, 0xcb, 0x9b, 0x53, 0x92, 0x2c, 0xd6, 0x46, + 0xe0, 0x90, 0xca, 0x4e, 0x76, 0x5e, 0x60, 0xd4, 0x4a, 0x9b, 0x8b, 0xc1, 0xa5, 0x34, 0x09, 0x1b, + 0x0a, 0x6b, 0x65, 0x31, 0x35, 0xfd, 0x5e, 0x3d, 0xd8, 0x37, 0x89, 0x96, 0xa5, 0x1d, 0x74, 0xee, + 0x62, 0xcd, 0x72, 0x95, 0xc8, 0x32, 0xb0, 0x72, 0x2e, 0x54, 0x65, 0x83, 0x34, 0x4a, 0x55, 0x52, + 0xcd, 0xa1, 0x6e, 0x80, 0x89, 0xa8, 0xbb, 0x47, 0x3f, 0xa5, 0x56, 0x56, 0x45, 0x3c, 0xb3, 0xb6, + 0x3c, 0xe0, 0x87, 0x93, 0xaa, 0x48, 0xac, 0x54, 0x05, 0x7b, 0xe3, 0xf9, 0x5f, 0x16, 0xb2, 0x48, + 0xd5, 0x22, 0x54, 0xa5, 0x28, 0x3c, 0xb7, 0xc0, 0x1c, 0xf4, 0x7a, 0xb3, 0x42, 0x85, 0x8b, 0x5c, + 0xa4, 0xe1, 0x54, 0xf4, 0x26, 0x22, 0xb6, 0x95, 0x16, 0xa6, 0x67, 0x1a, 0x25, 0x7a, 0xbf, 0x18, + 0x91, 0x54, 0x5a, 0xda, 0xe5, 0x4e, 0x3b, 0xc4, 0xfd, 0xaf, 0x2b, 0xa1, 0x27, 0xf7, 0x84, 0x4e, + 0x85, 0xbd, 0xbd, 0xb9, 0xf0, 0x78, 0x6f, 0xbd, 0x38, 0xe0, 0x1f, 0x8d, 0xc8, 0x27, 0x9b, 0xbb, + 0x6e, 0x1f, 0xdb, 0x55, 0x95, 0x69, 0x6c, 0xc5, 0x83, 0x7b, 0xa6, 0xe7, 0xa9, 0x67, 0xfd, 0x2f, + 0x5a, 0x40, 0xbf, 0x82, 0x91, 0xb2, 0xf6, 0x2c, 0x17, 0x64, 0xfa, 0xc9, 0xd2, 0x4d, 0xad, 0x97, + 0x4a, 0x73, 0x35, 0xfe, 0xb4, 0xb1, 0xd8, 0x3e, 0x79, 0xc2, 0xd5, 0xf8, 0x93, 0x48, 0x2c, 0x8f, + 0x22, 0xbb, 0x2c, 0x85, 0x9a, 0xd0, 0x58, 0xf7, 0x58, 0xeb, 0x78, 0x19, 0x4a, 0xe3, 0x7e, 0xb7, + 0x24, 0xe4, 0x2a, 0x4e, 0xff, 0x31, 0xf4, 0x6c, 0x20, 0xa2, 0xee, 0xae, 0xff, 0x25, 0x17, 0x96, + 0xa9, 0x28, 0x0d, 0x13, 0x0d, 0x78, 0x44, 0x73, 0xac, 0xc7, 0x6b, 0xbf, 0x70, 0xff, 0x50, 0x85, + 0x30, 0xf7, 0xd8, 0x5a, 0x2d, 0xc7, 0x95, 0x15, 0x98, 0xd0, 0x09, 0x0f, 0xac, 0x1f, 0xdc, 0x1f, + 0xa7, 0xb3, 0x79, 0xc0, 0xad, 0xf8, 0x6c, 0x7b, 0x9f, 0xe2, 0xbb, 0xb8, 0x15, 0xf0, 0xcd, 0xc2, + 0xd8, 0x2c, 0x0b, 0x88, 0x10, 0x7e, 0x90, 0x86, 0x63, 0x95, 0x2e, 0xc3, 0xb8, 0x04, 0x4e, 0xe9, + 0xab, 0x4c, 0xe6, 0xa9, 0xa7, 0x68, 0x7d, 0x9c, 0xa6, 0x67, 0x77, 0xd0, 0xe2, 0x42, 0x1a, 0x44, + 0xab, 0xd0, 0x1e, 0x27, 0x9d, 0x79, 0xe0, 0xf9, 0xd1, 0xe0, 0xcb, 0xef, 0xc2, 0xfe, 0xe1, 0xf9, + 0x01, 0x64, 0x9e, 0x24, 0xb3, 0xd7, 0x32, 0x17, 0x14, 0x84, 0x1e, 0x21, 0xc8, 0xc7, 0xc9, 0x2c, + 0x99, 0x4c, 0xb9, 0xff, 0xe8, 0x6c, 0x09, 0xef, 0x0b, 0x0b, 0xbf, 0xf9, 0x5f, 0x1f, 0x3e, 0x47, + 0x68, 0xad, 0x34, 0xcc, 0xc3, 0x39, 0x48, 0x15, 0xa3, 0x72, 0x11, 0xe6, 0x6a, 0xea, 0xf1, 0x33, + 0x1a, 0x67, 0x0d, 0x78, 0x70, 0x3d, 0x9b, 0x40, 0xb4, 0x83, 0x01, 0xb9, 0xa1, 0x01, 0xd7, 0x45, + 0x33, 0x0e, 0xf4, 0xb1, 0x71, 0x22, 0xa7, 0x95, 0x8e, 0x1d, 0xda, 0x35, 0x0c, 0x6c, 0x12, 0x4b, + 0x8a, 0xc2, 0x7f, 0x17, 0xe7, 0x45, 0xa2, 0xe6, 0x25, 0x40, 0x17, 0xac, 0x8c, 0xa7, 0x82, 0x21, + 0x26, 0xe2, 0x2e, 0x62, 0x61, 0xc3, 0x41, 0x26, 0x53, 0x8b, 0x91, 0x8a, 0x8d, 0xad, 0x7d, 0xb4, + 0xe7, 0x7f, 0xa1, 0xe4, 0x50, 0x91, 0xb3, 0xc2, 0xd2, 0x84, 0x73, 0x8b, 0x2c, 0xa0, 0xf2, 0x9b, + 0xd1, 0xe5, 0x45, 0x64, 0x61, 0x4b, 0x92, 0xc7, 0xc6, 0x90, 0x21, 0x64, 0x95, 0x27, 0x8e, 0x1a, + 0x53, 0x0e, 0x38, 0x49, 0x83, 0x17, 0x92, 0x5c, 0xc4, 0x7a, 0x54, 0xa7, 0x96, 0xd7, 0xa4, 0x98, + 0xf3, 0x8d, 0x5d, 0xc2, 0xc8, 0xb8, 0x90, 0x73, 0xa7, 0x6f, 0xc4, 0x0b, 0x55, 0x90, 0x65, 0xf5, + 0x8a, 0x08, 0x70, 0xb5, 0x9b, 0xbc, 0x56, 0x41, 0x04, 0xf8, 0xe6, 0x79, 0x5a, 0xcc, 0xd5, 0x1d, + 0x05, 0x86, 0x3b, 0x08, 0xc0, 0xee, 0xbf, 0xdc, 0xdd, 0xdd, 0x30, 0xa7, 0x2a, 0x09, 0x34, 0xf2, + 0x05, 0xd9, 0xd3, 0x1a, 0x53, 0x88, 0x05, 0xfb, 0xe7, 0xe5, 0xc5, 0x1b, 0xe4, 0xe9, 0x8d, 0xf8, + 0xb3, 0x12, 0xc6, 0x1e, 0x7e, 0xc7, 0xf1, 0x1b, 0x47, 0x6f, 0xa0, 0x93, 0x49, 0x83, 0xd3, 0x4d, + 0x09, 0x4f, 0x89, 0x11, 0xe2, 0x2e, 0x70, 0x23, 0xc6, 0x22, 0xcd, 0xcd, 0x20, 0x7a, 0x4e, 0x5a, + 0xf8, 0xdf, 0xf5, 0xf3, 0x5a, 0xae, 0xdd, 0x12, 0x4c, 0x32, 0x92, 0x59, 0xd0, 0x6d, 0x05, 0xd4, + 0x9c, 0x72, 0x7d, 0x35, 0x1c, 0xf1, 0x60, 0x23, 0x9f, 0x9d, 0x72, 0xbe, 0x7f, 0x48, 0x16, 0x15, + 0xce, 0xa2, 0xd7, 0x4a, 0xcf, 0x4f, 0xe1, 0xd1, 0xc3, 0x26, 0x3b, 0x8b, 0x26, 0xb8, 0x3d, 0x4e, + 0x7e, 0x06, 0xac, 0x21, 0x05, 0x8e, 0x79, 0xbf, 0xfb, 0x81, 0xe2, 0x9f, 0x32, 0x03, 0x73, 0x85, + 0x8f, 0xf1, 0xbb, 0x38, 0xaf, 0xc0, 0xa5, 0x3c, 0xe8, 0xee, 0xad, 0xa1, 0x4b, 0x32, 0x91, 0xcc, + 0xde, 0x56, 0xf3, 0x75, 0xbe, 0x77, 0xbd, 0xae, 0x20, 0x53, 0xc2, 0x99, 0x58, 0x86, 0x70, 0x59, + 0x92, 0x79, 0xbd, 0xf7, 0xbb, 0x3b, 0x2f, 0x3f, 0xf4, 0x7c, 0x24, 0xfd, 0x7b, 0x7e, 0x02, 0xbd, + 0x4d, 0x19, 0x27, 0x94, 0x8a, 0xa3, 0x78, 0x8c, 0xff, 0x67, 0x60, 0x7c, 0x98, 0xca, 0x87, 0x99, + 0x9c, 0x58, 0xfc, 0xbe, 0x42, 0x09, 0xd0, 0x2a, 0xc7, 0xd3, 0x71, 0x4e, 0xef, 0xd7, 0x31, 0x88, + 0x9d, 0xc6, 0xe3, 0xd2, 0x5c, 0xa8, 0x64, 0x46, 0x5b, 0xc0, 0xf2, 0x2e, 0x99, 0x87, 0x8d, 0xa4, + 0x6b, 0x44, 0xea, 0x6d, 0xd9, 0x3c, 0x9c, 0xaa, 0x45, 0xe1, 0xe4, 0xc2, 0x31, 0xfc, 0x8d, 0x9a, + 0xd3, 0x02, 0xb0, 0x8c, 0x5a, 0x5c, 0x08, 0x77, 0x80, 0x7b, 0x76, 0xab, 0xdd, 0xd3, 0x8d, 0x9c, + 0x66, 0xab, 0xe1, 0x66, 0xef, 0x39, 0x1c, 0xa6, 0x69, 0xf0, 0x54, 0x50, 0x26, 0xf0, 0x0f, 0x08, + 0xe6, 0x24, 0xaf, 0x52, 0x61, 0xbc, 0x95, 0x75, 0xbe, 0xff, 0xd7, 0x5f, 0xcd, 0x1b, 0xd2, 0x96, + 0x7e, 0x4f, 0xc5, 0x24, 0xae, 0x72, 0x8b, 0xe4, 0x47, 0x4e, 0x6c, 0xa4, 0xcb, 0x76, 0xae, 0x03, + 0x2a, 0x7b, 0x8f, 0x71, 0xc0, 0xc5, 0x45, 0x1d, 0x48, 0x9c, 0x6a, 0xc1, 0x47, 0xfe, 0xd4, 0x12, + 0xc5, 0x3e, 0xb4, 0xc2, 0x7f, 0xea, 0xf1, 0x77, 0x17, 0x67, 0xa7, 0x20, 0x53, 0x93, 0x1e, 0x71, + 0xe4, 0x0f, 0x56, 0x9b, 0xd4, 0xdf, 0x38, 0x6f, 0xe8, 0xd5, 0xa4, 0x69, 0xa3, 0x86, 0xe6, 0x51, + 0x96, 0x5c, 0xee, 0x1c, 0xca, 0x89, 0xc7, 0xc9, 0xbf, 0x07, 0x44, 0xc5, 0xa1, 0x2b, 0x55, 0x89, + 0xca, 0x7d, 0x57, 0xbd, 0x76, 0x03, 0xcf, 0x95, 0xb7, 0x88, 0x56, 0xe7, 0x43, 0xab, 0x34, 0xa0, + 0x24, 0x2d, 0xce, 0xad, 0x98, 0x53, 0x9c, 0x27, 0xe7, 0x25, 0x77, 0x36, 0xd7, 0xcb, 0xb0, 0x7b, + 0x5e, 0x82, 0x58, 0xc8, 0x2e, 0x76, 0xa9, 0x52, 0x11, 0xb2, 0x6b, 0xa4, 0xb0, 0x11, 0x4c, 0x90, + 0x43, 0x19, 0x29, 0xc9, 0xce, 0xaf, 0x41, 0x1d, 0xc1, 0x96, 0x44, 0xb3, 0x2d, 0x31, 0x70, 0xd2, + 0x10, 0xa3, 0x22, 0x37, 0xc2, 0xa9, 0x2d, 0x48, 0xb5, 0xd8, 0x66, 0x04, 0x56, 0xa0, 0x22, 0xec, + 0xc8, 0xd1, 0x03, 0x78, 0x7b, 0x81, 0x08, 0x11, 0x8c, 0xe6, 0x9d, 0xb4, 0x19, 0x82, 0x9b, 0xfb, + 0x47, 0x3b, 0x7b, 0x07, 0x77, 0x4a, 0xa6, 0x6c, 0xd7, 0x0f, 0x4d, 0x99, 0x4b, 0xeb, 0x46, 0x91, + 0xa4, 0xc0, 0x79, 0x6a, 0xb3, 0xc1, 0xfe, 0x93, 0x27, 0xde, 0xaa, 0x20, 0xaf, 0xad, 0x0d, 0x1a, + 0x6b, 0x6b, 0x2b, 0x6c, 0x98, 0x29, 0x63, 0xe9, 0xa8, 0xa7, 0xc8, 0x2a, 0x6a, 0x12, 0x8e, 0x00, + 0xe9, 0xd3, 0xfa, 0xf1, 0x80, 0x03, 0x6e, 0x08, 0x7d, 0xaa, 0x90, 0x0f, 0xfe, 0x57, 0xec, 0x80, + 0xc8, 0x2d, 0x22, 0xbf, 0xe7, 0xc7, 0x4c, 0x8b, 0xc9, 0x2a, 0x01, 0xb7, 0x17, 0xb6, 0x16, 0x21, + 0x5d, 0xbf, 0x21, 0xfb, 0xff, 0x45, 0xca, 0x7a, 0xf1, 0x5a, 0x12, 0x21, 0xeb, 0x4a, 0xe5, 0x37, + 0x85, 0xbf, 0x67, 0xc2, 0x4f, 0xe6, 0xa8, 0x8c, 0x7e, 0xe3, 0x2e, 0x16, 0x83, 0x5a, 0xf5, 0x34, + 0x1c, 0x4e, 0xc2, 0xd8, 0x05, 0x49, 0xf4, 0xc0, 0x16, 0x91, 0xf0, 0xcd, 0x30, 0x6a, 0x56, 0xac, + 0x32, 0x9b, 0xc0, 0x3c, 0x6a, 0x01, 0x05, 0x2c, 0xc0, 0xc5, 0x61, 0xe8, 0x50, 0xb2, 0x5f, 0x3b, + 0xfd, 0x5e, 0xd3, 0x2a, 0xf5, 0x1d, 0x65, 0x0f, 0xfe, 0x2e, 0xe7, 0x04, 0x22, 0xab, 0x74, 0x0e, + 0xee, 0x75, 0x2c, 0x9e, 0x18, 0x18, 0x70, 0x88, 0x85, 0x6e, 0x41, 0xbf, 0x57, 0x77, 0x7e, 0x54, + 0x63, 0x51, 0xba, 0xc8, 0x96, 0x88, 0x23, 0x78, 0xd1, 0x90, 0x4d, 0x40, 0x53, 0x1d, 0x26, 0xf1, + 0x4e, 0x4f, 0x1f, 0x0d, 0x67, 0x75, 0x27, 0x37, 0x9c, 0x70, 0x86, 0x16, 0x2e, 0x53, 0x98, 0x29, + 0xe1, 0x38, 0x2c, 0x4d, 0xe5, 0x1d, 0x73, 0x5c, 0x1f, 0xa1, 0xf4, 0x40, 0xb7, 0xc5, 0xf6, 0x58, + 0x26, 0xf2, 0xf2, 0x84, 0x0f, 0x3a, 0x7d, 0x60, 0x6b, 0x61, 0x15, 0x75, 0x01, 0x11, 0xaf, 0x5f, + 0x38, 0x4e, 0x4d, 0x10, 0x5c, 0xb3, 0x88, 0xbf, 0xa1, 0x63, 0x8f, 0xfa, 0xbd, 0x7a, 0x02, 0xaa, + 0x41, 0xc4, 0xe0, 0xe1, 0x3d, 0x9d, 0xd5, 0xa6, 0x13, 0xda, 0x44, 0x84, 0xb6, 0xde, 0xb7, 0xb5, + 0xc3, 0x54, 0xe3, 0xb9, 0x84, 0x8e, 0xc3, 0xf8, 0x4e, 0xac, 0x97, 0x64, 0xba, 0x15, 0x9f, 0xed, + 0x0f, 0x3a, 0xc3, 0xa6, 0xc1, 0x63, 0x4f, 0xd8, 0xad, 0xeb, 0xbf, 0x88, 0x2e, 0xaa, 0x12, 0xd8, + 0xec, 0x0f, 0xda, 0x5e, 0x94, 0x5d, 0x9f, 0xbf, 0x3d, 0x60, 0x7d, 0x59, 0x94, 0x95, 0x6d, 0x44, + 0x97, 0x30, 0x6e, 0xa1, 0x74, 0xca, 0x1d, 0x48, 0x98, 0x5f, 0x35, 0xbb, 0xee, 0xd9, 0xc8, 0xff, + 0xe0, 0xf1, 0x39, 0xc0, 0x8a, 0x3f, 0xd7, 0xe9, 0x50, 0xbf, 0xc9, 0x62, 0xe3, 0x4d, 0x15, 0x60, + 0x31, 0x22, 0x96, 0x88, 0xaf, 0x79, 0x1c, 0x05, 0xca, 0x87, 0x2c, 0x04, 0x19, 0x92, 0x18, 0x33, + 0x8e, 0xbb, 0xff, 0x86, 0x63, 0xe8, 0xf0, 0x39, 0xd2, 0x1c, 0x05, 0x18, 0x3d, 0xaf, 0x96, 0x09, + 0x67, 0xae, 0x43, 0xc6, 0x89, 0x9b, 0x69, 0x1f, 0xb3, 0xe7, 0x2c, 0x95, 0x53, 0x69, 0x19, 0x96, + 0x8d, 0x41, 0xec, 0xc0, 0x44, 0x03, 0xfe, 0x0d, 0x97, 0x2c, 0x62, 0x8d, 0xb6, 0xbf, 0xf3, 0xe4, + 0x97, 0x97, 0x2f, 0x5e, 0xbc, 0x38, 0x64, 0xb7, 0x85, 0x28, 0x12, 0xbd, 0x2c, 0xad, 0x48, 0x99, + 0xd5, 0x71, 0x61, 0xe6, 0xd2, 0x18, 0x04, 0x60, 0xc8, 0x4e, 0xd0, 0x82, 0x68, 0x90, 0x6e, 0x61, + 0xd9, 0x22, 0x13, 0x44, 0xa4, 0x39, 0xda, 0x48, 0xea, 0x62, 0x60, 0x64, 0xc0, 0x52, 0xc5, 0xde, + 0x5e, 0x8d, 0x18, 0xaa, 0x03, 0x5b, 0xaa, 0x4a, 0xb3, 0x71, 0x5c, 0xcc, 0x30, 0x49, 0x13, 0x4a, + 0x07, 0x6c, 0x78, 0x7e, 0x19, 0x30, 0x61, 0x93, 0x90, 0x95, 0xb2, 0xe8, 0x76, 0x5a, 0x97, 0xea, + 0x01, 0x15, 0x11, 0xb6, 0x90, 0x1a, 0xd2, 0x8c, 0x61, 0xde, 0xd5, 0xe8, 0xd8, 0x67, 0x46, 0x4d, + 0x2c, 0xf4, 0x12, 0xac, 0x6e, 0x82, 0xef, 0xa1, 0xed, 0xe0, 0x19, 0xab, 0xcf, 0x6d, 0x18, 0xbe, + 0xbd, 0x72, 0x11, 0xa5, 0x07, 0xd7, 0x30, 0xa8, 0xcc, 0x34, 0xcc, 0x7f, 0xd4, 0x3f, 0xf5, 0x8e, + 0xab, 0xeb, 0x2d, 0x5f, 0x3c, 0xdb, 0x6f, 0x70, 0x19, 0x29, 0xe0, 0x46, 0xf7, 0x15, 0x06, 0x35, + 0x02, 0x86, 0x68, 0x67, 0x6d, 0xcb, 0xcf, 0xd0, 0xef, 0x1a, 0x74, 0x0e, 0x64, 0x1c, 0x2b, 0x04, + 0xa1, 0xa3, 0x58, 0x9c, 0x1b, 0xd5, 0x20, 0x6d, 0x33, 0x81, 0x56, 0x4e, 0x6b, 0x40, 0xc2, 0xda, + 0xe3, 0xba, 0x4e, 0xab, 0x51, 0x26, 0x56, 0x23, 0xd4, 0xac, 0x55, 0x79, 0xca, 0xc6, 0x82, 0xae, + 0x3d, 0xc5, 0x14, 0x62, 0x1c, 0x96, 0x38, 0x0e, 0xad, 0x7a, 0x73, 0x78, 0x1a, 0xd2, 0xb6, 0xfe, + 0x78, 0xd0, 0x39, 0x95, 0xa6, 0xd5, 0xa6, 0x5e, 0x57, 0x28, 0x0b, 0xdf, 0x13, 0xc8, 0x01, 0x53, + 0x38, 0x52, 0x2f, 0x24, 0xf0, 0x8e, 0x0b, 0x86, 0x00, 0x41, 0xe4, 0x43, 0x8f, 0x04, 0x2f, 0x60, + 0x2c, 0xf8, 0x36, 0x63, 0xf5, 0x7d, 0x6d, 0x85, 0x26, 0xa1, 0x3e, 0x76, 0x86, 0xf6, 0xe5, 0x3a, + 0x9a, 0x29, 0x3f, 0x10, 0x66, 0x75, 0x7b, 0x49, 0x98, 0x23, 0xf7, 0x97, 0xb5, 0x76, 0xee, 0x6c, + 0x39, 0x71, 0xc7, 0xe7, 0xe4, 0x26, 0x2c, 0x4b, 0x6b, 0x95, 0xd2, 0x6e, 0xbf, 0x27, 0x6b, 0xd4, + 0x4f, 0x45, 0xb1, 0x64, 0x71, 0x92, 0x90, 0xfb, 0x80, 0xc9, 0x3b, 0xf9, 0x5a, 0xb2, 0x96, 0xc6, + 0x68, 0x37, 0xed, 0x14, 0xe9, 0x0f, 0x7c, 0x78, 0xf5, 0xae, 0xf1, 0x21, 0xfd, 0xbd, 0x06, 0x2b, + 0x2a, 0x4d, 0x90, 0x43, 0xce, 0x0f, 0x36, 0xde, 0x0c, 0x1b, 0xdf, 0x1d, 0xe7, 0xf9, 0xfa, 0xd8, + 0xb8, 0x48, 0x59, 0xc3, 0xd1, 0x88, 0x2d, 0xcc, 0x00, 0x70, 0x41, 0x81, 0xd1, 0x62, 0xab, 0x07, + 0xff, 0x57, 0x0e, 0x1c, 0x6f, 0x20, 0xed, 0x80, 0x03, 0xba, 0x50, 0x03, 0x01, 0x61, 0xe1, 0xde, + 0x99, 0x43, 0x5f, 0x52, 0x3c, 0x24, 0x82, 0xba, 0x78, 0xb0, 0x65, 0xdd, 0xb1, 0xb7, 0xf1, 0x4e, + 0x6c, 0x93, 0x3d, 0x1b, 0x0c, 0xdb, 0xf8, 0xae, 0x49, 0x06, 0xf4, 0xf2, 0x6c, 0xf0, 0x03, 0x4a, + 0xbc, 0x25, 0x76, 0xeb, 0x5c, 0xc6, 0x45, 0x15, 0xe7, 0xce, 0x21, 0xed, 0xd6, 0x15, 0xd7, 0xe9, + 0xc1, 0x59, 0x1d, 0xbb, 0xc7, 0x3a, 0xad, 0x64, 0xa1, 0xb0, 0xe8, 0x51, 0xec, 0x1a, 0x76, 0x3a, + 0xbe, 0xe2, 0x2b, 0x9d, 0x88, 0x39, 0xab, 0x12, 0xc4, 0x77, 0x83, 0xe6, 0x5a, 0xe9, 0x46, 0xa9, + 0xb8, 0x85, 0x68, 0x6c, 0x71, 0x9b, 0x29, 0x66, 0x35, 0xc3, 0x35, 0x75, 0x94, 0x75, 0xa8, 0x38, + 0x46, 0xbc, 0xd7, 0x40, 0x8d, 0x2a, 0x47, 0x3a, 0xb7, 0xdd, 0x11, 0xf2, 0xae, 0x29, 0x93, 0xad, + 0xf0, 0xe6, 0xbd, 0xdf, 0x8b, 0x57, 0x2e, 0x18, 0x74, 0x9a, 0xf3, 0x56, 0x93, 0x2e, 0x3e, 0x37, + 0xb4, 0x76, 0x77, 0xa7, 0xc6, 0xdb, 0xae, 0x2d, 0x76, 0xc1, 0x56, 0xda, 0x88, 0xd7, 0xe7, 0x0d, + 0x58, 0x0b, 0x5d, 0xe7, 0x61, 0xec, 0x7e, 0xdd, 0xb8, 0x52, 0xb8, 0xa2, 0x4b, 0x52, 0x82, 0x7b, + 0x5a, 0xfb, 0xbf, 0x0e, 0x6e, 0xdd, 0xb2, 0x15, 0xa2, 0x75, 0x38, 0xae, 0x78, 0xea, 0x71, 0x28, + 0x5a, 0x2b, 0x59, 0x83, 0x06, 0xa0, 0x69, 0x90, 0xe8, 0xac, 0xa1, 0x20, 0xbc, 0x5a, 0x18, 0xb6, + 0xae, 0x7c, 0x5b, 0x60, 0xb4, 0x58, 0x6c, 0xaf, 0x58, 0x23, 0xd2, 0x79, 0x18, 0x92, 0xfd, 0xc7, + 0x31, 0x79, 0xa4, 0x5a, 0x3e, 0x88, 0xc9, 0x7e, 0xb0, 0xa1, 0xfc, 0xb7, 0x80, 0x6c, 0xe0, 0x41, + 0xb9, 0xd3, 0x79, 0x38, 0x79, 0x6a, 0x13, 0xa8, 0x2c, 0x34, 0xb8, 0xf4, 0xb6, 0x6f, 0xb8, 0x2e, + 0x1d, 0xaf, 0xfe, 0x38, 0xbb, 0x79, 0x77, 0x73, 0x3e, 0x3a, 0xab, 0x6b, 0x05, 0x28, 0x56, 0x53, + 0x5d, 0x79, 0x70, 0x47, 0xe8, 0x1c, 0xd1, 0xa1, 0x9b, 0x70, 0x4d, 0xaf, 0xdb, 0xf2, 0xe6, 0x31, + 0x11, 0xc5, 0x9f, 0x15, 0x2a, 0x08, 0xca, 0xdd, 0x64, 0x93, 0x3a, 0x18, 0xf8, 0x5b, 0x8b, 0x1d, + 0x47, 0x86, 0xcd, 0x6d, 0xdb, 0x1d, 0x77, 0x36, 0xbc, 0x0e, 0x9b, 0x8c, 0x7c, 0xfd, 0x00, 0xc5, + 0x07, 0x2b, 0xba, 0x36, 0x8e, 0x10, 0x89, 0x74, 0xc7, 0x94, 0xf3, 0x29, 0xea, 0x51, 0xd8, 0xe6, + 0xcb, 0xf1, 0x18, 0xb7, 0xdc, 0x36, 0x49, 0x9a, 0x44, 0x68, 0x3f, 0x23, 0xa1, 0xe0, 0x66, 0xd5, + 0x38, 0xc4, 0xd5, 0xbd, 0x77, 0x2c, 0x75, 0xa2, 0x94, 0x9a, 0x49, 0xd1, 0xa3, 0x1e, 0xbc, 0x87, + 0x3a, 0x1d, 0xeb, 0x29, 0x7d, 0xee, 0xfa, 0x38, 0xce, 0x51, 0x22, 0xf9, 0x80, 0x86, 0x29, 0x08, + 0x3a, 0xec, 0x4e, 0x68, 0xa2, 0x19, 0xb6, 0x1b, 0xee, 0x3d, 0x0f, 0xf7, 0x57, 0x44, 0xf5, 0x33, + 0xe2, 0x17, 0x72, 0x26, 0x7b, 0xee, 0xce, 0x46, 0xed, 0xab, 0xd2, 0x66, 0x07, 0x54, 0xb8, 0x93, + 0x68, 0x91, 0x4a, 0x8a, 0xd0, 0xce, 0xfd, 0xc3, 0x37, 0x97, 0xa2, 0x54, 0x0b, 0xba, 0x7d, 0x82, + 0xfc, 0xa4, 0xa8, 0x39, 0xd4, 0x94, 0x22, 0x91, 0x20, 0x1c, 0x8b, 0x72, 0x30, 0x5b, 0xe5, 0x6d, + 0xe7, 0x98, 0x65, 0x15, 0xaa, 0x85, 0x1b, 0x75, 0x05, 0x11, 0xbc, 0x8f, 0x3b, 0x97, 0x5e, 0xaa, + 0x42, 0xa0, 0x50, 0x29, 0x46, 0x7d, 0x1e, 0xe0, 0x02, 0x33, 0xd6, 0xdf, 0x89, 0xdc, 0xfd, 0xa3, + 0xdb, 0x5a, 0xd4, 0xf1, 0x12, 0x9f, 0xed, 0xef, 0xee, 0xfd, 0xb6, 0xb3, 0xbf, 0xbb, 0xff, 0x8c, + 0xbd, 0xca, 0x34, 0x6e, 0xda, 0x12, 0xd4, 0x39, 0x4c, 0xb2, 0x05, 0x7d, 0xa7, 0x68, 0x6a, 0xd4, + 0x05, 0xaa, 0x17, 0xae, 0x7e, 0x80, 0x1d, 0x4a, 0xd5, 0x25, 0xf6, 0xa7, 0xc0, 0x18, 0xe7, 0x6a, + 0xdc, 0x9b, 0xe3, 0x8a, 0x2e, 0x74, 0xef, 0xe2, 0xfc, 0xd5, 0xd9, 0xdb, 0xe1, 0xd9, 0xb7, 0xf0, + 0x77, 0x2e, 0xcf, 0x47, 0x2c, 0xaf, 0x4f, 0x72, 0x06, 0xa2, 0xae, 0xb5, 0x8a, 0x0e, 0x85, 0x86, + 0x55, 0x30, 0xc3, 0x18, 0x54, 0x47, 0xb0, 0x29, 0xee, 0xca, 0x45, 0x4b, 0x03, 0x46, 0x96, 0x9c, + 0x12, 0xd6, 0x7d, 0x4f, 0x60, 0xee, 0x13, 0x01, 0xf1, 0x3c, 0x2d, 0xa9, 0x49, 0x95, 0x92, 0x84, + 0x28, 0xa2, 0xfe, 0x06, 0xf3, 0xdd, 0x2e, 0xf6, 0xdb, 0x26, 0xb6, 0xf3, 0x53, 0x5d, 0x6c, 0x8f, + 0x4a, 0x0d, 0x7e, 0xa8, 0x79, 0xa7, 0x4e, 0x9e, 0xbe, 0xec, 0xfe, 0x17, 0x52, 0x67, 0xe8, 0x26, + 0xe9, 0x15, 0x00, 0x00 }; diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 64b76418b..2267c3591 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.14.2-b2/%i"), VERSION); + sprintf_P(vString, PSTR("0.14.2/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index 8c5576547..9f6ba88fd 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.14.2-b1 + @version 0.14.2 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2403110 +#define VERSION 2403170 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 6de617ecd5e835488a95f77c28fef8e5d824c964 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 17 Mar 2024 15:11:08 -0400 Subject: [PATCH 094/694] getSettingsJS: Fix missing DISABLE_INFRARED guard --- wled00/xml.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 04e0ebfdf..fddc242ea 100755 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -471,8 +471,10 @@ void getSettingsJS(byte subPage, char* dest) } sappend('c',SET_F("IP"),disablePullUp); sappend('v',SET_F("TT"),touchThreshold); +#ifndef WLED_DISABLE_INFRARED sappend('v',SET_F("IR"),irPin); sappend('v',SET_F("IT"),irEnabled); +#endif sappend('c',SET_F("MSO"),!irApplyToAllSelected); } From b849ea8eaacdf1b24becc998f6bbbc06e052d71f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 18 Mar 2024 20:01:32 +0100 Subject: [PATCH 095/694] Fix for #3827 --- wled00/button.cpp | 3 --- wled00/e131.cpp | 1 - wled00/json.cpp | 2 -- wled00/ntp.cpp | 3 --- wled00/presets.cpp | 1 + wled00/remote.cpp | 1 - wled00/set.cpp | 1 - 7 files changed, 1 insertion(+), 11 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 29cb0abeb..cf4599834 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -21,7 +21,6 @@ void shortPressAction(uint8_t b) case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; } } else { - unloadPlaylist(); // applying a preset unloads the playlist applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); } @@ -43,7 +42,6 @@ void longPressAction(uint8_t b) case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action } } else { - unloadPlaylist(); // applying a preset unloads the playlist applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); } @@ -65,7 +63,6 @@ void doublePressAction(uint8_t b) case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { - unloadPlaylist(); // applying a preset unloads the playlist applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET); } diff --git a/wled00/e131.cpp b/wled00/e131.cpp index e54f60bf3..ec2efb50e 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -184,7 +184,6 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ // only apply preset if not in playlist, or playlist changed (currentPlaylist < 0 || dmxValPreset != currentPlaylist)) { presetCycCurr = dmxValPreset; - unloadPlaylist(); // applying a preset unloads the playlist applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION); } diff --git a/wled00/json.cpp b/wled00/json.cpp index 389dc8ae5..84caedd22 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -442,13 +442,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) currentPreset = root[F("pd")] | currentPreset; if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise it was set in handleSet() [set.cpp] presetToRestore = currentPreset; // stateUpdated() will clear the preset, so we need to restore it after - //unloadPlaylist(); // applying a preset unloads the playlist, may be needed here too? } else if (!root["ps"].isNull()) { ps = presetCycCurr; if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) { // b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal()) presetCycCurr = ps; - unloadPlaylist(); // applying a preset unloads the playlist applyPreset(ps, callMode); // async load from file system (only preset ID was specified) return stateResponse; } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 0b2cf3665..d473186ed 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -399,7 +399,6 @@ void checkTimers() && isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i]) ) { - unloadPlaylist(); applyPreset(timerMacro[i]); } } @@ -413,7 +412,6 @@ void checkTimers() && (timerWeekday[8] & 0x01) //timer is enabled && ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week { - unloadPlaylist(); applyPreset(timerMacro[8]); DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."),timerMacro[8]); } @@ -428,7 +426,6 @@ void checkTimers() && (timerWeekday[9] & 0x01) //timer is enabled && ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week { - unloadPlaylist(); applyPreset(timerMacro[9]); DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."),timerMacro[9]); } diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 7fbe49eb8..6efe3bb31 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -119,6 +119,7 @@ void initPresetsFile() bool applyPreset(byte index, byte callMode) { + unloadPlaylist(); // applying a preset unloads the playlist (#3827) DEBUG_PRINT(F("Request to apply preset: ")); DEBUG_PRINTLN(index); presetToApply = index; diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 49fbc4b02..54cdf31f6 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -108,7 +108,6 @@ static void setOff() { void presetWithFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) { resetNightMode(); - unloadPlaylist(); applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID); } diff --git a/wled00/set.cpp b/wled00/set.cpp index 4e2e60b3d..6e7064bb3 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -883,7 +883,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //apply preset if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) { - unloadPlaylist(); applyPreset(presetCycCurr); } From 7a9eff7f35a51ceb0cae3f80fa0491e3497e7dc7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 18 Mar 2024 20:23:30 +0100 Subject: [PATCH 096/694] Fix. --- wled00/FX_fcn.cpp | 1 - wled00/bus_manager.cpp | 9 +++++++++ wled00/bus_manager.h | 5 ----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index edd3a068d..3566755f0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -538,7 +538,6 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed } void Segment::setCCT(uint16_t k) { - if (!isCCT() || !correctWB) return; if (k > 255) { //kelvin value, convert to 0-255 if (k < 1900) k = 1900; if (k > 10091) k = 10091; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index a9c3ac44d..eeb9a15e4 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -441,10 +441,19 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { _data[0] = w; break; case TYPE_ANALOG_2CH: //warm white + cold white + #ifdef WLED_USE_IC_CCT + _data[0] = w; + _data[1] = cct; + #else Bus::calculateCCT(c, _data[0], _data[1]); + #endif break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white + #ifdef WLED_USE_IC_CCT + _data[4] = cct; + #else Bus::calculateCCT(c, w, _data[4]); + #endif case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 233c2a668..d4facb33b 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -194,10 +194,6 @@ class Bus { cct = (approximateKelvinFromRGB(c) - 1900) >> 5; } - #ifdef WLED_USE_IC_CCT - ww = w; - cw = cct; - #else //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) if (cct < _cctBlend) ww = 255; else ww = ((255-cct) * 255) / (255 - _cctBlend); @@ -206,7 +202,6 @@ class Bus { ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; - #endif } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } From 62f845a94e7f84b1e2525c18ac3c9f5c0ae63010 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 18 Mar 2024 19:46:33 -0400 Subject: [PATCH 097/694] DDP: Support sources that don't push If the source never sends the push flag, WLED buffers the update but never publishes it to the LEDs. This causes the confusing case where the peek display shows one thing but the LEDs themselves something else. Add a static flag that tracks if we've seen a push from the source; until we do, apply every update as soon as it's received, per the DDP specification. --- wled00/e131.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wled00/e131.cpp b/wled00/e131.cpp index ec2efb50e..a67a672c2 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -11,6 +11,7 @@ //DDP protocol support, called by handleE131Packet //handles RGB data only void handleDDPPacket(e131_packet_t* p) { + static bool ddpSeenPush = false; // have we seen a push yet? int lastPushSeq = e131LastSequenceNumber[0]; //reject late packets belonging to previous frame (assuming 4 packets max. before push) @@ -34,6 +35,7 @@ void handleDDPPacket(e131_packet_t* p) { uint16_t c = 0; if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later + if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { @@ -44,7 +46,8 @@ void handleDDPPacket(e131_packet_t* p) { } bool push = p->flags & DDP_PUSH_FLAG; - if (push) { + ddpSeenPush |= push; + if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display e131NewData = true; byte sn = p->sequenceNum & 0xF; if (sn) e131LastSequenceNumber[0] = sn; From 505d319e01368b9ea0fd80926fdbbfedd5b43612 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 18 Mar 2024 19:49:56 -0400 Subject: [PATCH 098/694] Fix avgLoopMillis --- wled00/wled.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index fc16dabcc..07304e77a 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -269,6 +269,7 @@ void WLED::loop() maxLoopMillis = 0; maxUsermodMillis = 0; maxStripMillis = 0; + avgLoopMillis = 0; avgUsermodMillis = 0; avgStripMillis = 0; debugTime = millis(); From 5d3f22e06a2dfe248099978f7c611f249f63b8fd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 19 Mar 2024 15:18:17 +0100 Subject: [PATCH 099/694] Changelog update and build bump. --- CHANGELOG.md | 13 ++++++++++++- wled00/wled.h | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba1c987a..5a9a60dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,18 @@ ## WLED changelog +#### Build 2403190 +- limit max PWM frequency (fix incorrect PWM resolution) +- Segment UI bugfix +- Updated AsyncWebServer (by @wlillmmiles) +- Simpler boot preset (fix for #3806) +- Effect: Fix for 2D Drift animation (#3816 by @BaptisteHudyma) +- Effect: Add twin option to 2D Drift +- MQTT cleanup +- DDP: Support sources that don't push (#3833 by @willmmiles) +- Usermod: Tetris AI usermod (#3711 by @muebau) + #### Build 2403171 -- merge 0.14.2 changes +- merge 0.14.2 changes into 0.15 #### Build 2403070 - Add additional segment options when controlling over e1.31 (#3616 by @demophoon) diff --git a/wled00/wled.h b/wled00/wled.h index 4eb6c8e17..36cd40164 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403171 +#define VERSION 2403190 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From e3271b8082e6fa776c659e22f1f2987011cf48b8 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 19 Mar 2024 18:15:18 +0100 Subject: [PATCH 100/694] Change path for ESP02-binary --- .github/workflows/wled-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index f9ffb6481..f9721fb27 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -61,7 +61,7 @@ jobs: name: firmware-${{ matrix.environment }} path: | build_output/release/*.bin - build_output/release/*_ESP02.bin.gz + build_output/release_gz/*_ESP02.bin.gz release: name: Create Release runs-on: ubuntu-latest From 7ee4b54154cdb21e4d066fd22af8a02b553898f3 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 19 Mar 2024 20:06:09 +0100 Subject: [PATCH 101/694] optimize output_bins.py --- pio-scripts/output_bins.py | 55 +++++++++----------------------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index cfedacead..092754c38 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -3,7 +3,7 @@ import os import shutil import gzip -OUTPUT_DIR = "build_output{}".format(os.path.sep) +OUTPUT_DIR = os.path.join("build_output") def _get_cpp_define_value(env, define): define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define] @@ -13,27 +13,20 @@ def _get_cpp_define_value(env, define): return None -def _create_dirs(dirs=["firmware", "map"]): - # check if output directories exist and create if necessary - if not os.path.isdir(OUTPUT_DIR): - os.mkdir(OUTPUT_DIR) - +def _create_dirs(dirs=["map", "release", "release_gz"]): for d in dirs: - if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): - os.mkdir("{}{}".format(OUTPUT_DIR, d)) + os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") - # get file extension of source file (.bin or .bin.gz) - ext = source.split(".", 1)[1] if release_name: - folder = "release" - if ext == "bin.gz": - folder = "release_gz" - _create_dirs([folder]) version = _get_cpp_define_value(env, "WLED_VERSION") - release_file = "{}{}{}WLED_{}_{}.{}".format(OUTPUT_DIR, folder, os.path.sep, version, release_name, ext) + release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") + release_gz_file = os.path.join(OUTPUT_DIR, "release_gz", f"WLED_{version}_{release_name}.bin.gz") + print(f"Copying {source} to {release_file}") shutil.copy(source, release_file) + print(f"Creating gzip file {release_gz_file} from {release_file}") + bin_gzip(release_file, release_gz_file) def bin_rename_copy(source, target, env): _create_dirs() @@ -41,38 +34,16 @@ def bin_rename_copy(source, target, env): # create string with location and file names based on variant map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - # check if new target files exist and remove if necessary - for f in [map_file, bin_file]: - if os.path.isfile(f): - os.remove(f) - - # copy firmware.bin to firmware/.bin - shutil.copy(str(target[0]), bin_file) - - create_release(bin_file) + create_release(str(target[0])) # copy firmware.map to map/.map if os.path.isfile("firmware.map"): shutil.move("firmware.map", map_file) -def bin_gzip(source, target, env): - _create_dirs() - variant = env["PIOENV"] - - # create string with location and file names based on variant - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant) - - # check if new target files exist and remove if necessary - if os.path.isfile(gzip_file): os.remove(gzip_file) - - # write gzip firmware file - with open(bin_file,"rb") as fp: - with gzip.open(gzip_file, "wb", compresslevel = 9) as f: +def bin_gzip(source, target): + with open(source,"rb") as fp: + with gzip.open(target, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) - create_release(gzip_file) - -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy, bin_gzip]) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", bin_rename_copy) From 6d1b9ffad276aa0269fc482ffadb85a529b237f5 Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Tue, 19 Mar 2024 14:04:09 -0700 Subject: [PATCH 102/694] Add SSD1309_64 I2C Support to FDL Usermod --- .../usermod_v2_four_line_display_ALT.h | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 82a5e1a81..75aa90cfe 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -17,7 +17,7 @@ // for WLED. // // Dependencies -// * This Usermod works best, by far, when coupled +// * This Usermod works best, by far, when coupled // with RotaryEncoderUI ALT Usermod. // // Make sure to enable NTP and set your time zone in WLED Config | Time. @@ -87,6 +87,7 @@ typedef enum { SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C + SSD1309_64, // U8X8_SSD1309_128X64_NONAME0_HW_I2C SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI SSD1306_SPI64, // U8X8_SSD1306_128X64_NONAME_HW_SPI SSD1309_SPI64 // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI @@ -235,7 +236,7 @@ class FourLineDisplayUsermod : public Usermod { void updateSpeed(); void updateIntensity(); void drawStatusIcons(); - + /** * marks the position of the arrow showing * the current setting being changed @@ -246,8 +247,8 @@ class FourLineDisplayUsermod : public Usermod { //Draw the arrow for the current setting being changed void drawArrow(); - //Display the current effect or palette (desiredEntry) - // on the appropriate line (row). + //Display the current effect or palette (desiredEntry) + // on the appropriate line (row). void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row); /** @@ -314,14 +315,14 @@ class FourLineDisplayUsermod : public Usermod { * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * If you want to force saving the current state, use serializeConfig() in your loop(). - * + * * CAUTION: serializeConfig() will initiate a filesystem write operation. * It might cause the LEDs to stutter and will cause flash wear if called too often. * Use it sparingly and always in the loop, never in network callbacks! - * + * * addToConfig() will also not yet add your setting to one of the settings pages automatically. * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * + * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void addToConfig(JsonObject& root) override; @@ -329,7 +330,7 @@ class FourLineDisplayUsermod : public Usermod { /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) - * + * * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) @@ -494,7 +495,7 @@ void FourLineDisplayUsermod::showTime() { } if (knownHour != hourCurrent) { // only update date when hour changes - sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day } sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); @@ -556,6 +557,7 @@ void FourLineDisplayUsermod::setup() { case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break; case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break; case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break; + case SSD1309_64: u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_HW_I2C(); break; // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32 case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset @@ -581,7 +583,7 @@ void FourLineDisplayUsermod::setup() { // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here void FourLineDisplayUsermod::connected() { - knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : + knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); networkOverlay(PSTR("NETWORK INFO"),7000); } @@ -637,7 +639,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { powerON = !powerON; drawStatusIcons(); return; - } else if (knownnightlight != nightlightActive) { //trigger moon icon + } else if (knownnightlight != nightlightActive) { //trigger moon icon knownnightlight = nightlightActive; drawStatusIcons(); if (knownnightlight) { @@ -652,7 +654,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { return; } else if (knownMode != effectCurrent || knownPalette != effectPalette) { if (displayTurnedOff) needRedraw = true; - else { + else { if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } lastRedraw = now; @@ -703,7 +705,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { drawArrow(); drawStatusIcons(); - // Second row + // Second row updateBrightness(); updateSpeed(); updateIntensity(); @@ -805,8 +807,8 @@ void FourLineDisplayUsermod::drawArrow() { lockRedraw = false; } -//Display the current effect or palette (desiredEntry) -// on the appropriate line (row). +//Display the current effect or palette (desiredEntry) +// on the appropriate line (row). void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); @@ -857,7 +859,7 @@ void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const c while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; smallBuffer1[smallChars1] = 0; drawString(1, row*lineHeight, smallBuffer1, true); - while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; + while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; smallBuffer2[smallChars2] = 0; drawString(1, row*lineHeight+1, smallBuffer2, true); } @@ -1150,7 +1152,7 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { xTaskCreatePinnedToCore( [](void * par) { // Function to implement the task // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; + const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; TickType_t xLastWakeTime = xTaskGetTickCount(); for(;;) { delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. @@ -1205,9 +1207,10 @@ void FourLineDisplayUsermod::appendConfigData() { oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); oappend(SET_F("addOption(dd,'SSD1305',4);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); + oappend(SET_F("addOption(dd,'SSD1309 128x64',6);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI',7);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',8);")); + oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',9);")); oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); @@ -1218,14 +1221,14 @@ void FourLineDisplayUsermod::appendConfigData() { * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * If you want to force saving the current state, use serializeConfig() in your loop(). - * + * * CAUTION: serializeConfig() will initiate a filesystem write operation. * It might cause the LEDs to stutter and will cause flash wear if called too often. * Use it sparingly and always in the loop, never in network callbacks! - * + * * addToConfig() will also not yet add your setting to one of the settings pages automatically. * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * + * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void FourLineDisplayUsermod::addToConfig(JsonObject& root) { @@ -1252,7 +1255,7 @@ void FourLineDisplayUsermod::addToConfig(JsonObject& root) { /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) - * + * * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) @@ -1346,6 +1349,10 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); break; + case SSD1309_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1309_128x64_noname0, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; case SSD1306_SPI: u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset From ecee073e080e36b281836533b3997e5bacd37244 Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Tue, 19 Mar 2024 14:04:24 -0700 Subject: [PATCH 103/694] Fix palette names when palette ID > 58 and not custom --- wled00/util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index fa6c8faff..1bd8ec319 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -265,8 +265,8 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe } else return 0; } - if (src == JSON_palette_names && mode > GRADIENT_PALETTE_COUNT) { - snprintf_P(dest, maxLen, PSTR("~ Custom %d~"), 255-mode); + if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) { + snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); dest[maxLen-1] = '\0'; return strlen(dest); } From 92ebeddcb0761c9279d8fb97f58b2730f8bdd89b Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Tue, 19 Mar 2024 14:48:04 -0700 Subject: [PATCH 104/694] Update Readme for Four Line Display Usermod --- .../readme.md | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md index ea9f43610..35a55a88f 100644 --- a/usermods/usermod_v2_four_line_display_ALT/readme.md +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -1,4 +1,4 @@ -# I2C 4 Line Display Usermod ALT +# I2C/SPI 4 Line Display Usermod ALT Thank you to the authors of the original version of these usermods. It would not have been possible without them! "usermod_v2_four_line_display" @@ -8,21 +8,20 @@ The core of these usermods are a copy of the originals. The main changes are to The display usermod UI has been completely changed. -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display it, functions identical to the original. +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +Without the display, it functions identical to the original. The original "usermod_v2_auto_save" will not work with the display just yet. Press the encoder to cycle through the options: - *Brightness - *Speed - *Intensity - *Palette - *Effect - *Main Color (only if display is used) - *Saturation (only if display is used) +* Brightness +* Speed +* Intensity +* Palette +* Effect +* Main Color (only if display is used) +* Saturation (only if display is used) -Press and hold the encoder to display Network Info - if AP is active, it will display AP, SSID and password +Press and hold the encoder to display Network Info. If AP is active, it will display AP, SSID and password Also shows if the timer is enabled @@ -30,11 +29,47 @@ Also shows if the timer is enabled ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions -Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, +Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions. + +Copy the example `platformio_override.sample.ini` from the usermod_v2_rotary_encoder_ui_ALT folder to the root directory of your particular build and rename it to `platformio_override.ini`. + +This file should be placed in the same directory as `platformio.ini`. + +Then, to activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file +## Configuration + +These options are configurable in Config > Usermods + +### Usermod Setup + +* Global I2C GPIOs (HW) - Set the SDA and SCL pins + +### 4LineDisplay + +* `enabled` - enable/disable usermod +* `type` - display type in numeric format + * 1 = I2C SSD1306 128x32 + * 2 = I2C SH1106 128x32 + * 3 = I2C SSD1306 128x64 (4 double-height lines) + * 4 = I2C SSD1305 128x32 + * 5 = I2C SSD1305 128x64 (4 double-height lines) + * 6 = I2C SSD1309 128x64 (4 double-height lines) + * 7 = SPI SSD1306 128x32 + * 8 = SPI SSD1306 128x64 (4 double-height lines) + * 9 = SPI SSD1309 128x64 (4 double-height lines) +* `pin` - GPIO pins used for display; SPI displays can use SCK, MOSI, CS, DC & RST +* `flip` - flip/rotate display 180° +* `contrast` - set display contrast (higher contrast may reduce display lifetime) +* `screenTimeOutSec` - screen saver time-out in seconds +* `sleepMode` - enable/disable screen saver +* `clockMode` - enable/disable clock display in screen saver mode +* `showSeconds` - Show seconds on the clock display +* `i2c-freq-kHz` - I2C clock frequency in kHz (may help reduce dropped frames, range: 400-3400) + + ### PlatformIO requirements Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. From 2dafa9644ffaf48e62d89b2ef6e3cf5b674e1006 Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Tue, 19 Mar 2024 14:48:51 -0700 Subject: [PATCH 105/694] Update Readme for Rotary Encoder Usermod Add example platformio_override.sample.ini --- .../platformio–override.sample.ini | 17 +++++++++ .../readme.md | 35 +++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini new file mode 100644 index 000000000..6b32c71fb --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini @@ -0,0 +1,17 @@ +[platformio] +default_envs = esp32dev + +[env:esp32dev] +board = esp32dev +platform = ${esp32.platform} +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp32} + -D USERMOD_FOUR_LINE_DISPLAY -D USE_ALT_DISPlAY + -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 +upload_speed = 460800 +lib_deps = + ${esp32.lib_deps} + U8g2@~2.34.4 + Wire + diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md index 516362380..10db879fb 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md @@ -8,18 +8,18 @@ The core of these usermods are a copy of the originals. The main changes are to The display usermod UI has been completely changed. -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. Without the display, it functions identical to the original. The original "usermod_v2_auto_save" will not work with the display just yet. Press the encoder to cycle through the options: - *Brightness - *Speed - *Intensity - *Palette - *Effect - *Main Color (only if display is used) - *Saturation (only if display is used) +* Brightness +* Speed +* Intensity +* Palette +* Effect +* Main Color (only if display is used) +* Saturation (only if display is used) Press and hold the encoder to display Network Info if AP is active, it will display the AP, SSID and Password @@ -30,10 +30,23 @@ Also shows if the timer is enabled. ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions.
-To activate this alternative usermod, add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, -or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file. +Copy the example `platformio_override.sample.ini` to the root directory of your particular build and rename it to `platformio_override.ini`. +To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to your `platformio_override.ini` file + +### Define Your Options + +* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp + also tells this usermod that the display is available + (see the Four Line Display usermod `readme.md` for more details) +* `USE_ALT_DISPlAY` - Mandatory to use Four Line Display +* `ENCODER_DT_PIN` - defaults to 18 +* `ENCODER_CLK_PIN` - defaults to 5 +* `ENCODER_SW_PIN` - defaults to 19 +* `USERMOD_ROTARY_ENCODER_GPIO` - GPIO functionality: + `INPUT_PULLUP` to use internal pull-up + `INPUT` to use pull-up on the PCB ### PlatformIO requirements From 2640203c88d0d68eb9848eab5509e35cdd309ca4 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 19 Mar 2024 23:46:55 -0400 Subject: [PATCH 106/694] wled_server: Remove local content type variables Use the CONTENT_TYPEs exported by AsyncWebServer directly. --- wled00/wled_server.cpp | 55 +++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index fca932972..d184e9878 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -18,11 +18,6 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; -static const char* s_javascript = CONTENT_TYPE_JAVASCRIPT; -static const char* s_json = CONTENT_TYPE_JSON; -static const char* s_html = CONTENT_TYPE_HTML; -static const char* s_plain = CONTENT_TYPE_PLAIN; -static const char* s_css = CONTENT_TYPE_CSS; //Is this an IP? static bool isIp(String str) { @@ -158,7 +153,7 @@ static String msgProcessor(const String& var) static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { if (!correctPIN) { - if (final) request->send(401, FPSTR(s_plain), FPSTR(s_unlock_cfg)); + if (final) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); return; } if (!index) { @@ -179,10 +174,10 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, request->_tempFile.close(); if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; - request->send(200, FPSTR(s_plain), F("Configuration restore successful.\nRebooting...")); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting...")); } else { if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) strip.loadCustomPalettes(); - request->send(200, FPSTR(s_plain), F("File Uploaded!")); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!")); } cacheInvalidate++; } @@ -235,12 +230,12 @@ void initServer() #ifdef WLED_ENABLE_WEBSOCKETS #ifndef WLED_DISABLE_2D server.on(F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveviewws2D, PAGE_liveviewws2D_length); + handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_liveviewws2D, PAGE_liveviewws2D_length); }); #endif #endif server.on(F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveview, PAGE_liveview_length); + handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_liveview, PAGE_liveview_length); }); //settings page @@ -251,7 +246,7 @@ void initServer() // "/settings/settings.js&p=x" request also handled by serveSettings() static const char _style_css[] PROGMEM = "/style.css"; server.on(_style_css, HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, FPSTR(_style_css), 200, FPSTR(s_css), PAGE_settingsCss, PAGE_settingsCss_length); + handleStaticContent(request, FPSTR(_style_css), 200, FPSTR(CONTENT_TYPE_CSS), PAGE_settingsCss, PAGE_settingsCss_length); }); static const char _favicon_ico[] PROGMEM = "/favicon.ico"; @@ -262,7 +257,7 @@ void initServer() static const char _skin_css[] PROGMEM = "/skin.css"; server.on(_skin_css, HTTP_GET, [](AsyncWebServerRequest *request) { if (handleFileRead(request, FPSTR(_skin_css))) return; - AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(s_css)); + AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(CONTENT_TYPE_CSS)); request->send(response); }); @@ -332,25 +327,25 @@ void initServer() doSerializeConfig = true; //serializeConfig(); //Save new settings to FS } } - request->send(200, s_json, F("{\"success\":true}")); + request->send(200, CONTENT_TYPE_JSON, F("{\"success\":true}")); }, JSON_BUFFER_SIZE); server.addHandler(handler); server.on(F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, FPSTR(s_plain), (String)VERSION); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)VERSION); }); server.on(F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, FPSTR(s_plain), (String)millis()); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)millis()); }); server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, FPSTR(s_plain), (String)ESP.getFreeHeap()); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)ESP.getFreeHeap()); }); #ifdef WLED_ENABLE_USERMOD_PAGE server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_usermod, PAGE_usermod_length); + handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_usermod, PAGE_usermod_length); }); #endif @@ -429,7 +424,7 @@ void initServer() #ifdef WLED_ENABLE_DMX server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, FPSTR(s_html), PAGE_dmxmap , dmxProcessor); + request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor); }); #else server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ @@ -440,7 +435,7 @@ void initServer() server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { if (captivePortal(request)) return; if (!showWelcomePage || request->hasArg(F("sliders"))) { - handleStaticContent(request, F("/index.htm"), 200, FPSTR(s_html), PAGE_index, PAGE_index_L); + handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_L); } else { serveSettings(request); } @@ -449,20 +444,20 @@ void initServer() #ifdef WLED_ENABLE_PIXART static const char _pixart_htm[] PROGMEM = "/pixart.htm"; server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(s_html), PAGE_pixart, PAGE_pixart_L); + handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_L); }); #endif #ifndef WLED_DISABLE_PXMAGIC static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm"; server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(s_html), PAGE_pxmagic, PAGE_pxmagic_L); + handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_L); }); #endif static const char _cpal_htm[] PROGMEM = "/cpal.htm"; server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(s_html), PAGE_cpal, PAGE_cpal_L); + handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_L); }); #ifdef WLED_ENABLE_WEBSOCKETS @@ -487,7 +482,7 @@ void initServer() #ifndef WLED_DISABLE_ALEXA if(espalexa.handleAlexaApiCall(request)) return; #endif - handleStaticContent(request, request->url(), 404, FPSTR(s_html), PAGE_404, PAGE_404_length); + handleStaticContent(request, request->url(), 404, FPSTR(CONTENT_TYPE_HTML), PAGE_404, PAGE_404_length); }); } @@ -498,7 +493,7 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h messageSub = subl; optionType = optionT; - request->send_P(code, FPSTR(s_html), PAGE_msg, msgProcessor); + request->send_P(code, FPSTR(CONTENT_TYPE_HTML), PAGE_msg, msgProcessor); } @@ -506,7 +501,7 @@ void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t erro { AsyncJsonResponse *response = new AsyncJsonResponse(64); if (error < ERR_NOT_IMPL) response->addHeader(F("Retry-After"), F("1")); - response->setContentType(s_json); + response->setContentType(CONTENT_TYPE_JSON); response->setCode(code); JsonObject obj = response->getRoot(); obj[F("error")] = error; @@ -522,12 +517,12 @@ void serveSettingsJS(AsyncWebServerRequest* request) byte subPage = request->arg(F("p")).toInt(); if (subPage > 10) { strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');")); - request->send(501, FPSTR(s_javascript), buf); + request->send(501, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); return; } if (subPage > 0 && !correctPIN && strlen(settingsPIN)>0) { strcpy_P(buf, PSTR("alert('PIN incorrect.');")); - request->send(401, FPSTR(s_javascript), buf); + request->send(401, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); return; } strcat_P(buf,PSTR("function GetV(){var d=document;")); @@ -535,7 +530,7 @@ void serveSettingsJS(AsyncWebServerRequest* request) strcat_P(buf,PSTR("}")); AsyncWebServerResponse *response; - response = request->beginResponse(200, FPSTR(s_javascript), buf); + response = request->beginResponse(200, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); response->addHeader(F("Cache-Control"), F("no-store")); response->addHeader(F("Expires"), F("0")); request->send(response); @@ -616,7 +611,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { } int code = 200; - String contentType = FPSTR(s_html); + String contentType = FPSTR(CONTENT_TYPE_HTML); const uint8_t* content; size_t len; @@ -642,7 +637,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { return; } case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break; - case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = FPSTR(s_css); break; + case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = FPSTR(CONTENT_TYPE_CSS); break; case SUBPAGE_JS : serveSettingsJS(request); return; case SUBPAGE_WELCOME : content = PAGE_welcome; len = PAGE_welcome_length; break; default: content = PAGE_settings; len = PAGE_settings_length; break; From 8b6bf08a2361aba3ec61cea88d1bd7bb741c937a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 19 Mar 2024 23:50:32 -0400 Subject: [PATCH 107/694] serveLiveLeds: Use variable buffer size Allocate the serialization buffer size at the required length, rather than always allocating the maximum size. --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index f6cb645c0..c493ae2f2 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1152,7 +1152,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) } #endif - DynamicBuffer buffer(9 + (9*MAX_LIVE_LEDS) + 7 + 5 + 6 + 5 + 6 + 5 + 2); + DynamicBuffer buffer(9 + (9*(1+(used/n))) + 7 + 5 + 6 + 5 + 6 + 5 + 2); char* buf = buffer.data(); // assign buffer for oappnd() functions strncpy_P(buffer.data(), PSTR("{\"leds\":["), buffer.size()); buf += 9; // sizeof(PSTR()) from last line From b031fa15316a3aaebf79410268c8b386ed970984 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 20 Mar 2024 20:12:21 +0100 Subject: [PATCH 108/694] Palette cycling fix. Updated getPaletteCount() to return count of all available palettes, including custom ones. --- wled00/FX.h | 2 +- wled00/json.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3aa19bc35..58c192bc9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -810,7 +810,7 @@ class WS2812FX { // 96 bytes inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments inline uint8_t getCurrSegmentId(void) { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getMainSegmentId(void) { return _mainSegment; } // returns main segment index - inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count + inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } inline uint8_t getTargetFps() { return _targetFps; } // returns rough FPS value for las 2s interval inline uint8_t getModeCount() { return _modeCount; } // returns number of registered modes/effects diff --git a/wled00/json.cpp b/wled00/json.cpp index 14b92c7e7..113b2b1f5 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -226,14 +226,19 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) getVal(elem["ix"], &seg.intensity); uint8_t pal = seg.palette; + last = strip.getPaletteCount(); + if (!elem["pal"].isNull() && elem["pal"].is()) { + const char *tmp = elem["pal"].as(); + if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form + } if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments - if (getVal(elem["pal"], &pal)) seg.setPalette(pal); + if (getVal(elem["pal"], &pal, 0, last)) seg.setPalette(pal); } getVal(elem["c1"], &seg.custom1); getVal(elem["c2"], &seg.custom2); uint8_t cust3 = seg.custom3; - getVal(elem["c3"], &cust3); // we can't pass reference to bitfield + getVal(elem["c3"], &cust3, 0, 31); // we can't pass reference to bitfield seg.custom3 = constrain(cust3, 0, 31); seg.check1 = getBoolVal(elem["o1"], seg.check1); @@ -850,8 +855,8 @@ void serializePalettes(JsonObject root, int page) int itemPerPage = 8; #endif - int palettesCount = strip.getPaletteCount(); int customPalettes = strip.customPalettes.size(); + int palettesCount = strip.getPaletteCount() - customPalettes; int maxPage = (palettesCount + customPalettes -1) / itemPerPage; if (page > maxPage) page = maxPage; From 33fe68d7eb2b3963f2dee9c3f4d7e615849dff6f Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Wed, 20 Mar 2024 13:48:15 -0700 Subject: [PATCH 109/694] Revert breaking change --- usermods/usermod_v2_four_line_display_ALT/readme.md | 8 ++++---- .../usermod_v2_four_line_display_ALT.h | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md index 35a55a88f..a8f386dac 100644 --- a/usermods/usermod_v2_four_line_display_ALT/readme.md +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -56,10 +56,10 @@ These options are configurable in Config > Usermods * 3 = I2C SSD1306 128x64 (4 double-height lines) * 4 = I2C SSD1305 128x32 * 5 = I2C SSD1305 128x64 (4 double-height lines) - * 6 = I2C SSD1309 128x64 (4 double-height lines) - * 7 = SPI SSD1306 128x32 - * 8 = SPI SSD1306 128x64 (4 double-height lines) - * 9 = SPI SSD1309 128x64 (4 double-height lines) + * 6 = SPI SSD1306 128x32 + * 7 = SPI SSD1306 128x64 (4 double-height lines) + * 8 = SPI SSD1309 128x64 (4 double-height lines) + * 9 = I2C SSD1309 128x64 (4 double-height lines) * `pin` - GPIO pins used for display; SPI displays can use SCK, MOSI, CS, DC & RST * `flip` - flip/rotate display 180° * `contrast` - set display contrast (higher contrast may reduce display lifetime) diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 75aa90cfe..24eb9794f 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -87,10 +87,10 @@ typedef enum { SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C - SSD1309_64, // U8X8_SSD1309_128X64_NONAME0_HW_I2C SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI SSD1306_SPI64, // U8X8_SSD1306_128X64_NONAME_HW_SPI - SSD1309_SPI64 // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI + SSD1309_SPI64, // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI + SSD1309_64 // U8X8_SSD1309_128X64_NONAME0_HW_I2C } DisplayType; @@ -1207,10 +1207,10 @@ void FourLineDisplayUsermod::appendConfigData() { oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); oappend(SET_F("addOption(dd,'SSD1305',4);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1309 128x64',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',7);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',8);")); - oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',9);")); + oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); From 7bafe995e5f3a4ed0c8248d362095f4188db2bba Mon Sep 17 00:00:00 2001 From: Woody Date: Thu, 21 Mar 2024 12:00:18 +0100 Subject: [PATCH 110/694] Remove zlib dependency from package.json --- package-lock.json | 563 +++++++++++++++++++++++----------------------- package.json | 3 +- 2 files changed, 277 insertions(+), 289 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd3fab2e6..055437e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,7 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2", - "zlib": "^1.0.5" + "nodemon": "^3.0.2" } }, "node_modules/@jridgewell/gen-mapping": { @@ -102,7 +101,7 @@ "node_modules/align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", "dependencies": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -115,7 +114,7 @@ "node_modules/ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", "engines": { "node": ">=0.10.0" } @@ -123,7 +122,7 @@ "node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "engines": { "node": ">=0.10.0" } @@ -131,7 +130,7 @@ "node_modules/ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "engines": { "node": ">=0.10.0" } @@ -159,12 +158,12 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dependencies": { "safer-buffer": "~2.1.0" } @@ -172,7 +171,7 @@ "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "engines": { "node": ">=0.8" } @@ -180,20 +179,20 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -203,23 +202,26 @@ "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dependencies": { "tweetnacl": "^0.14.3" } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -258,7 +260,7 @@ "node_modules/camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", "engines": { "node": ">=0.10.0" } @@ -266,12 +268,12 @@ "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "node_modules/center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", "dependencies": { "align-text": "^0.1.3", "lazy-cache": "^1.0.3" @@ -283,7 +285,7 @@ "node_modules/chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -306,7 +308,7 @@ "node_modules/cheerio": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", - "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "integrity": "sha512-Fwcm3zkR37STnPC8FepSHeSYJM5Rd596TZOcfDUdojR4Q735aK1Xn+M+ISagNneuCwMjK28w4kX+ETILGNT/UQ==", "dependencies": { "css-select": "~1.0.0", "dom-serializer": "~0.1.0", @@ -318,16 +320,15 @@ "node": ">= 0.6" } }, + "node_modules/cheerio/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -340,6 +341,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -366,18 +370,10 @@ "node": ">= 10.0" } }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", "dependencies": { "center-align": "^0.1.1", "right-align": "^0.1.1", @@ -387,7 +383,7 @@ "node_modules/coa": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "integrity": "sha512-KAGck/eNAmCL0dcT3BiuYwLbExK6lduR8DxM3C1TyDzaXhZHyZ8ooX5I5+na2e3dPFuibfxrGdorr0/Lr7RYCQ==", "dependencies": { "q": "^1.1.2" }, @@ -398,7 +394,7 @@ "node_modules/colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", "engines": { "node": ">=0.1.90" } @@ -430,7 +426,7 @@ "node_modules/configstore": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", - "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", + "integrity": "sha512-Zcx2SVdZC06IuRHd2MhkVYFNJBkZBj166LGdsJXRcqNC8Gs5Bwh8mosStNeCBBmtIm4wNii2uarD50qztjKOjw==", "dependencies": { "graceful-fs": "^4.1.2", "mkdirp": "^0.5.0", @@ -448,18 +444,18 @@ "node_modules/configstore/node_modules/uuid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/css-select": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", - "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "integrity": "sha512-/xPlD7betkfd7ChGkLGGWx5HWyiHDOSn7aACLzdH0nwucPvB0EAm8hMBm7Xn7vGfAeRRN7KZ8wumGm8NoNcMRw==", "dependencies": { "boolbase": "~1.0.0", "css-what": "1.0", @@ -470,7 +466,7 @@ "node_modules/css-what": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", - "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=", + "integrity": "sha512-60SUMPBreXrLXgvpM8kYpO0AOyMRhdRlXFX5BMQbZq1SIJCyNE56nqFQhmvREQdUJpedbGRYZ5wOyq3/F6q5Zw==", "engines": { "node": "*" } @@ -478,7 +474,7 @@ "node_modules/csso": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/csso/-/csso-2.0.0.tgz", - "integrity": "sha1-F4tDpEYhIhwndWCG9THgL0KQDug=", + "integrity": "sha512-tckZA0LhyEnToPoQDmncCA+TUS3aoIVl/MsSaoipR52Sfa+H83fJvIHRVOHMFn9zW6kIV1L0D7tUDFFjvN28lg==", "dependencies": { "clap": "^1.0.9", "source-map": "^0.5.3" @@ -490,10 +486,18 @@ "node": ">=0.10.0" } }, + "node_modules/csso/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dependencies": { "assert-plus": "^1.0.0" }, @@ -512,7 +516,7 @@ "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "engines": { "node": ">=0.10.0" } @@ -528,7 +532,7 @@ "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { "node": ">=0.4.0" } @@ -542,6 +546,11 @@ "entities": "^1.1.1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "node_modules/domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", @@ -550,7 +559,7 @@ "node_modules/domhandler": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", "dependencies": { "domelementtype": "1" } @@ -558,7 +567,7 @@ "node_modules/domutils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", - "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "integrity": "sha512-ZkVgS/PpxjyJMb+S2iVHHEZjVnOUtjGp0/zstqKGTE9lrZtNHlNQmLwP/lhLMEApYbzc08BKMx9IFpKhaSbW1w==", "dependencies": { "domelementtype": "1" } @@ -586,12 +595,12 @@ "node_modules/duplexify/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/duplexify/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -618,7 +627,7 @@ "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -633,19 +642,25 @@ } }, "node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/es6-promise": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", - "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" + "integrity": "sha512-oyOjMhyKMLEjOOtvkwg0G4pAzLQ9WdbbeX7WdqKzvYXu+UFgD0Zo/Brq5Q49zNmnGPPzV5rmYvrr0jz1zWx8Iw==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { "node": ">=0.8.0" } @@ -653,7 +668,7 @@ "node_modules/esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -670,15 +685,15 @@ "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "engines": [ "node >=0.6.0" ] }, "node_modules/fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -699,7 +714,7 @@ "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "engines": { "node": "*" } @@ -718,9 +733,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -733,7 +748,7 @@ "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dependencies": { "assert-plus": "^1.0.0" } @@ -752,7 +767,7 @@ "node_modules/got": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", - "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "integrity": "sha512-7chPlc0pWHjvq7B6dEEXz4GphoDupOvBSSl6AwRsAJX7GPTZ+bturaZiIigX4Dp6KrAP67nvzuKkNc0SLA0DKg==", "dependencies": { "duplexify": "^3.2.0", "infinity-agent": "^2.0.0", @@ -772,31 +787,31 @@ "node_modules/got/node_modules/object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", "engines": { "node": ">=0.10.0" } }, "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "engines": { "node": ">=4" } }, "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "deprecated": "this library is no longer supported", "dependencies": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" }, "engines": { @@ -806,7 +821,7 @@ "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -842,21 +857,10 @@ "node": "^14.13.1 || >=16.0.0" } }, - "node_modules/html-minifier-terser/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", "dependencies": { "domelementtype": "1", "domhandler": "2.3", @@ -868,7 +872,7 @@ "node_modules/htmlparser2/node_modules/domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", "dependencies": { "dom-serializer": "0", "domelementtype": "1" @@ -877,12 +881,12 @@ "node_modules/htmlparser2/node_modules/entities": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -912,7 +916,7 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "engines": { "node": ">=0.8.19" } @@ -920,7 +924,7 @@ "node_modules/infinity-agent": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", - "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=" + "integrity": "sha512-CnfUJe5o2S9aAQWXGMhDZI4UL39MAJV3guOTfHHIdos4tuVHkl1j/J+1XLQn+CLIvqcpgQR/p+xXYXzcrhCe5w==" }, "node_modules/inherits": { "version": "2.0.4", @@ -935,7 +939,7 @@ "node_modules/inliner": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/inliner/-/inliner-1.13.1.tgz", - "integrity": "sha1-5QApgev1Dp2fMTcRSBz/Ei1PP8s=", + "integrity": "sha512-yoS+56puOu+Ug8FBRtxtTFnEn2NHqFs8BNQgSOvzh3J0ommbwNw8VKiaVNYjWK6fgPuByq95KyV0LC+qV9IwLw==", "dependencies": { "ansi-escapes": "^1.4.0", "ansi-styles": "^2.2.1", @@ -1010,7 +1014,7 @@ "node_modules/is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==", "engines": { "node": ">=0.10.0" } @@ -1026,7 +1030,7 @@ "node_modules/is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", "engines": { "node": ">=0.10.0" } @@ -1034,7 +1038,7 @@ "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "engines": { "node": ">=0.10.0" } @@ -1042,22 +1046,22 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/js-yaml": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", + "integrity": "sha512-BLv3oxhfET+w5fjPwq3PsAsxzi9i3qzU//HMpWVz0A6KplF86HdR9x2TGnv9DXhSUrO7LO8czUiTd3yb3mLSvg==", "dependencies": { "argparse": "^1.0.7", "esprima": "^2.6.0" @@ -1069,7 +1073,7 @@ "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "node_modules/jschardet": { "version": "1.6.0", @@ -1080,9 +1084,9 @@ } }, "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -1092,26 +1096,26 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "engines": [ - "node >=0.6.0" - ], + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dependencies": { "is-buffer": "^1.1.5" }, @@ -1122,7 +1126,7 @@ "node_modules/latest-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", - "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=", + "integrity": "sha512-HERbxp4SBlmI380+eM0B0u4nxjfTaPeydIMzl9+9UQ4nSu3xMWKlX9WoT34e4wy7VWe67c53Nv9qPVjS8fHKgg==", "dependencies": { "package-json": "^1.0.0" }, @@ -1136,7 +1140,7 @@ "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", "engines": { "node": ">=0.10.0" } @@ -1144,17 +1148,17 @@ "node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" }, "node_modules/lodash._arrayeach": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + "integrity": "sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ==" }, "node_modules/lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", "dependencies": { "lodash._basecopy": "^3.0.0", "lodash.keys": "^3.0.0" @@ -1163,12 +1167,12 @@ "node_modules/lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" }, "node_modules/lodash._baseeach": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "integrity": "sha512-IqUZ9MQo2UT1XPGuBntInqTOlc+oV+bCo0kMp+yuKGsfvRSNgUW0YjWVZUrG/gs+8z/Eyuc0jkJjOBESt9BXxg==", "dependencies": { "lodash.keys": "^3.0.0" } @@ -1176,12 +1180,12 @@ "node_modules/lodash._bindcallback": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==" }, "node_modules/lodash._createassigner": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", "dependencies": { "lodash._bindcallback": "^3.0.0", "lodash._isiterateecall": "^3.0.0", @@ -1191,17 +1195,17 @@ "node_modules/lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" }, "node_modules/lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" }, "node_modules/lodash.assign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", "dependencies": { "lodash._baseassign": "^3.0.0", "lodash._createassigner": "^3.0.0", @@ -1211,7 +1215,7 @@ "node_modules/lodash.defaults": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", "dependencies": { "lodash.assign": "^3.0.0", "lodash.restparam": "^3.0.0" @@ -1220,7 +1224,7 @@ "node_modules/lodash.foreach": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", - "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", + "integrity": "sha512-PA7Lp7pe2HMJBoB1vELegEIF3waUFnM0fWDKJVYolwZ4zHh6WTmnq0xmzfQksD66gx2quhDNyBdyaE2T8/DP3Q==", "dependencies": { "lodash._arrayeach": "^3.0.0", "lodash._baseeach": "^3.0.0", @@ -1231,17 +1235,17 @@ "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, "node_modules/lodash.isarray": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", "dependencies": { "lodash._getnative": "^3.0.0", "lodash.isarguments": "^3.0.0", @@ -1251,12 +1255,12 @@ "node_modules/lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" }, "node_modules/longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", "engines": { "node": ">=0.10.0" } @@ -1300,19 +1304,19 @@ } }, "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -1330,16 +1334,19 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -1348,12 +1355,12 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/nested-error-stacks": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", - "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "integrity": "sha512-o32anp9JA7oezPOFSfG2BBXSdHepOm5FpJvwxHWDtfJ3Bg3xdi68S6ijPlEOfUg6quxZWyvJM+8fHk1yMDKspA==", "dependencies": { "inherits": "~2.0.1" } @@ -1415,20 +1422,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/nodemon/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1481,7 +1474,7 @@ "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "engines": { "node": ">=0.10.0" } @@ -1489,7 +1482,7 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { "wrappy": "1" } @@ -1497,7 +1490,7 @@ "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", "engines": { "node": ">=0.10.0" } @@ -1505,7 +1498,7 @@ "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "engines": { "node": ">=0.10.0" } @@ -1522,7 +1515,7 @@ "node_modules/package-json": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", - "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", + "integrity": "sha512-knDtirWWqKVJrLY3gEBLflVvueTMpyjbAwX/9j/EKi2DsjNemp5voS8cyKyGh57SNaMJNhNRZbIaWdneOcLU1g==", "dependencies": { "got": "^3.2.0", "registry-url": "^3.0.0" @@ -1552,7 +1545,7 @@ "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1568,7 +1561,7 @@ "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", "engines": { "node": ">=0.10.0" } @@ -1576,7 +1569,7 @@ "node_modules/pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "dependencies": { "pinkie": "^2.0.0" }, @@ -1587,7 +1580,7 @@ "node_modules/prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", "engines": { "node": ">=0.10.0" } @@ -1606,9 +1599,9 @@ } }, "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/pstree.remy": { "version": "1.1.8", @@ -1616,9 +1609,9 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -1626,7 +1619,7 @@ "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "engines": { "node": ">=0.6.0", "teleport": ">=0.2.0" @@ -1657,7 +1650,7 @@ "node_modules/read-all-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", - "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", "dependencies": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" @@ -1669,12 +1662,12 @@ "node_modules/read-all-stream/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/read-all-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1701,7 +1694,7 @@ "node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -1723,7 +1716,7 @@ "node_modules/registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", "dependencies": { "rc": "^1.0.1" }, @@ -1734,7 +1727,7 @@ "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "engines": { "node": ">= 0.10" } @@ -1742,7 +1735,7 @@ "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "engines": { "node": ">=0.10" } @@ -1750,7 +1743,7 @@ "node_modules/repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "integrity": "sha512-Nh30JLeMHdoI+AsQ5eblhZ7YlTsM9wiJQe/AHIunlK3KWzvXhXb36IJ7K1IOeRjIOtzMjdUHjwXUFxKJoPTSOg==", "dependencies": { "is-finite": "^1.0.0" }, @@ -1795,7 +1788,7 @@ "node_modules/right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", "dependencies": { "align-text": "^0.1.1" }, @@ -1833,36 +1826,6 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dependencies": { - "semver": "^5.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", @@ -1876,18 +1839,48 @@ "node": ">=10" } }, + "node_modules/semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", + "dependencies": { + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", "engines": { "node": "*" } }, "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "engines": { "node": ">=0.10.0" } @@ -1901,23 +1894,15 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -1939,19 +1924,19 @@ } }, "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, "node_modules/string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", "dependencies": { "strip-ansi": "^3.0.0" }, @@ -1962,7 +1947,7 @@ "node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -1973,7 +1958,7 @@ "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "engines": { "node": ">=0.10.0" } @@ -1981,7 +1966,7 @@ "node_modules/supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "engines": { "node": ">=0.8.0" } @@ -1989,7 +1974,7 @@ "node_modules/svgo": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.6.6.tgz", - "integrity": "sha1-s0CIkDbyD5tEdUMHfQ9Vc+0ETAg=", + "integrity": "sha512-C5A1r5SjFesNoKsmc+kWBxmB04iBGH2D/nFy8HJaME9+SyZKcmqcN8QG+GwxIc7D2+JWhaaW7uaM9+XwfplTEQ==", "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", "dependencies": { "coa": "~1.0.1", @@ -2032,7 +2017,7 @@ "node_modules/then-fs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", - "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "integrity": "sha512-5ffcBcU+vFUCYDNi/o507IqjqrTkuGsLVZ1Fp50hwgZRY7ufVFa9jFfTy5uZ2QnSKacKigWKeaXkOqLa4DsjLw==", "dependencies": { "promise": ">=3.2 <8" } @@ -2040,7 +2025,7 @@ "node_modules/timed-out": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", "engines": { "node": ">=0.10.0" } @@ -2087,7 +2072,7 @@ "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -2098,12 +2083,12 @@ "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, "node_modules/uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", "dependencies": { "source-map": "~0.5.1", "yargs": "~3.10.0" @@ -2118,10 +2103,18 @@ "uglify-to-browserify": "~1.0.0" } }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", "optional": true }, "node_modules/undefsafe": { @@ -2132,7 +2125,7 @@ "node_modules/update-notifier": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", - "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", + "integrity": "sha512-zOGOlUKDAgDlLHLv7Oiszz3pSj8fKlSJ3i0u49sEakjXUEVJ6DMjo/Mh/B6mg2eOALvRTJkd0kbChcipQoYCng==", "dependencies": { "chalk": "^1.0.0", "configstore": "^1.0.0", @@ -2147,9 +2140,9 @@ } }, "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { "punycode": "^2.1.0" } @@ -2157,7 +2150,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { "version": "3.4.0", @@ -2171,7 +2164,7 @@ "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "engines": [ "node >=0.6.0" ], @@ -2181,10 +2174,15 @@ "extsprintf": "^1.2.0" } }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, "node_modules/whet.extend": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "integrity": "sha512-mmIPAft2vTgEILgPeZFqE/wWh24SEsR/k+N9fJ3Jxrz44iDFy9aemCxdksfURSHYFCLmvs/d/7Iso5XjPpNfrA==", "engines": { "node": ">=0.6.0" } @@ -2192,7 +2190,7 @@ "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", "engines": { "node": ">= 0.8.0" } @@ -2200,7 +2198,7 @@ "node_modules/wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", "engines": { "node": ">=0.4.0" } @@ -2208,12 +2206,12 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -2223,7 +2221,7 @@ "node_modules/xdg-basedir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", - "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "integrity": "sha512-NF1pPn594TaRSUO/HARoB4jK8I+rWgcpVlpQCK6/6o5PHyLUt2CSiDrpUZbQ6rROck+W2EwF8mBJcTs+W98J9w==", "dependencies": { "os-homedir": "^1.0.0" }, @@ -2239,22 +2237,13 @@ "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", "dependencies": { "camelcase": "^1.0.2", "cliui": "^2.1.0", "decamelize": "^1.0.0", "window-size": "0.1.0" } - }, - "node_modules/zlib": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", - "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=", - "hasInstallScript": true, - "engines": { - "node": ">=0.2.0" - } } } } diff --git a/package.json b/package.json index f2c0e3d65..413fa8cf3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2", - "zlib": "^1.0.5" + "nodemon": "^3.0.2" } } From 1c1c3fd8325932c07ad82a3d8a38f0ebfa09524a Mon Sep 17 00:00:00 2001 From: Woody Date: Thu, 21 Mar 2024 12:03:35 +0100 Subject: [PATCH 111/694] Use npm ci instead of npm install --- .github/workflows/wled-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index f9ffb6481..ab5c7be37 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -37,7 +37,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'npm' - - run: npm install + - run: npm ci - name: Cache PlatformIO uses: actions/cache@v4 with: From ecfdc6f0a804e00c3362bfb02968b99ed00d64a3 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 22 Mar 2024 20:49:13 +0100 Subject: [PATCH 112/694] Bugfixes: - #3843 - #3844 - network scan on new install - misc optimization --- wled00/cfg.cpp | 4 ++-- wled00/const.h | 1 + wled00/data/index.js | 2 +- wled00/data/settings_wifi.htm | 2 +- wled00/fcn_declare.h | 1 + wled00/playlist.cpp | 11 +++++++---- wled00/presets.cpp | 9 +++++++++ wled00/wled.h | 2 +- 8 files changed, 23 insertions(+), 9 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index e51b666e4..6ccf8aa44 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -632,12 +632,12 @@ static const char s_cfg_json[] PROGMEM = "/cfg.json"; void deserializeConfigFromFS() { bool success = deserializeConfigSec(); + #ifdef WLED_ADD_EEPROM_SUPPORT if (!success) { //if file does not exist, try reading from EEPROM - #ifdef WLED_ADD_EEPROM_SUPPORT deEEPSettings(); return; - #endif } + #endif if (!requestJSONBufferLock(1)) return; diff --git a/wled00/const.h b/wled00/const.h index 540d0946b..73873d041 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -375,6 +375,7 @@ //Playlist option byte #define PL_OPTION_SHUFFLE 0x01 +#define PL_OPTION_RESTORE 0x02 // Segment capability byte #define SEG_CAPABILITY_RGB 0x01 diff --git a/wled00/data/index.js b/wled00/data/index.js index 36c3eb1b9..4ad2044ad 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1984,7 +1984,7 @@ function makeP(i,pl)
End preset:
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 76e733671..3577e80d2 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -84,7 +84,7 @@ option.textContent = "Other network..."; select.appendChild(option); - if (input.value === "" || found) input.replaceWith(select); + if (input.value === "" || input.value === "Your_Network" || found) input.replaceWith(select); else select.remove(); } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 20ac21129..f1b013e99 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -233,6 +233,7 @@ const char *getPresetsFileName(bool persistent = true); void initPresetsFile(); void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); +bool applyPresetFromPlaylist(byte index); void applyPresetWithFallback(uint8_t presetID, uint8_t callMode, uint8_t effectID = 0, uint8_t paletteID = 0); inline bool applyTemporaryPreset() {return applyPreset(255);}; void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject()); diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index bcbcb4516..882ccb0e0 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -109,7 +109,10 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { if (playlistRepeat > 0) playlistRepeat++; //add one extra repetition immediately since it will be deducted on first start playlistEndPreset = playlistObj["end"] | 0; // if end preset is 255 restore original preset (if any running) upon playlist end - if (playlistEndPreset == 255 && currentPreset > 0) playlistEndPreset = currentPreset; + if (playlistEndPreset == 255 && currentPreset > 0) { + playlistEndPreset = currentPreset; + playlistOptions |= PL_OPTION_RESTORE; // for async save operation + } if (playlistEndPreset > 250) playlistEndPreset = 0; shuffle = shuffle || playlistObj["r"]; if (shuffle) playlistOptions |= PL_OPTION_SHUFFLE; @@ -135,7 +138,7 @@ void handlePlaylist() { if (!playlistIndex) { if (playlistRepeat == 1) { //stop if all repetitions are done unloadPlaylist(); - if (playlistEndPreset) applyPreset(playlistEndPreset); + if (playlistEndPreset) applyPresetFromPlaylist(playlistEndPreset); return; } if (playlistRepeat > 1) playlistRepeat--; // decrease repeat count on each index reset if not an endless playlist @@ -146,7 +149,7 @@ void handlePlaylist() { jsonTransitionOnce = true; strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0); playlistEntryDur = playlistEntries[playlistIndex].dur; - applyPreset(playlistEntries[playlistIndex].preset); + applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); } } @@ -157,7 +160,7 @@ void serializePlaylist(JsonObject sObj) { JsonArray dur = playlist.createNestedArray("dur"); JsonArray transition = playlist.createNestedArray(F("transition")); playlist[F("repeat")] = (playlistIndex < 0 && playlistRepeat > 0) ? playlistRepeat - 1 : playlistRepeat; // remove added repetition count (if not yet running) - playlist["end"] = playlistEndPreset; + playlist["end"] = playlistOptions & PL_OPTION_RESTORE ? 255 : playlistEndPreset; playlist["r"] = playlistOptions & PL_OPTION_SHUFFLE; for (int i=0; i Date: Sun, 24 Mar 2024 12:27:32 +0100 Subject: [PATCH 113/694] Update requirements.txt to solve CI build errors use latest platformIO package, to avoid build errors due to missing 'scons' > Tool Manager: Installing platformio/tool-scons @ ~4.40400.0 > Error: Could not find the package with 'platformio/tool-scons @ ~4.40400.0' requirements for your system 'linux_x86_64' > Error: Process completed with exit code 1. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1c0644f98..d6f86e202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ marshmallow==3.19.0 # via platformio packaging==23.1 # via marshmallow -platformio==6.1.6 +platformio==6.1.14 # via -r requirements.in pyelftools==0.29 # via platformio From c32ee40ca0215ddb22a69ed92516f133aac8cb75 Mon Sep 17 00:00:00 2001 From: Woody Date: Sun, 24 Mar 2024 14:19:49 +0100 Subject: [PATCH 114/694] =?UTF-8?q?add=20node:=20prefix=20to=20Core=20modu?= =?UTF-8?q?les=20The=20node:=20prefix=20makes=20it=20clear=20that=20you?= =?UTF-8?q?=E2=80=99re=20importing=20a=20built-in=20module,=20not=20a=20us?= =?UTF-8?q?er-defined=20or=20third-party=20module.=20This=20can=20help=20a?= =?UTF-8?q?void=20confusion.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/cdata.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cdata.js b/tools/cdata.js index 16475d811..c7ab6ec7d 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -15,10 +15,10 @@ * It uses NodeJS packages to inline, minify and GZIP files. See writeHtmlGzipped and writeChunks invocations at the bottom of the page. */ -const fs = require("fs"); +const fs = require("node:fs"); const path = require("path"); const inliner = require("inliner"); -const zlib = require("zlib"); +const zlib = require("node:zlib"); const CleanCSS = require("clean-css"); const minifyHtml = require("html-minifier-terser").minify; const packageJson = require("../package.json"); From 23d80002397047b59a06a962a0e9f7203ddd0092 Mon Sep 17 00:00:00 2001 From: Woody Date: Sun, 24 Mar 2024 14:34:17 +0100 Subject: [PATCH 115/694] Remove accidental space in cdata.js --- tools/cdata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cdata.js b/tools/cdata.js index c7ab6ec7d..3b8af46da 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -18,7 +18,7 @@ const fs = require("node:fs"); const path = require("path"); const inliner = require("inliner"); -const zlib = require("node:zlib"); +const zlib = require("node:zlib"); const CleanCSS = require("clean-css"); const minifyHtml = require("html-minifier-terser").minify; const packageJson = require("../package.json"); From 0a344ada976abedb1f8eb33c860e58cca583a111 Mon Sep 17 00:00:00 2001 From: Woody Date: Sun, 24 Mar 2024 15:23:33 +0100 Subject: [PATCH 116/694] Add bin.gz binaries for 160mhz builds --- .github/workflows/wled-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index f9721fb27..feb0e531a 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -61,7 +61,7 @@ jobs: name: firmware-${{ matrix.environment }} path: | build_output/release/*.bin - build_output/release_gz/*_ESP02.bin.gz + build_output/release_gz/*_ESP02*.bin.gz release: name: Create Release runs-on: ubuntu-latest From 47f44680a31fee019a1417e0043484a77ed8b168 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 24 Mar 2024 17:37:11 +0100 Subject: [PATCH 117/694] Multi-update - 0.15.0-b2 - WS2805 support (NeoPixelBus 2.7.9) - generic PSRAM support (ESP32 rev.1 still needs compile fix) - BREAKING: default LEDPIN 2 (due to PSRAM) - PinManager optimisations --- CHANGELOG.md | 12 +++ package-lock.json | 4 +- package.json | 2 +- platformio.ini | 12 +-- platformio_override.sample.ini | 5 +- wled00/FX.h | 9 +- wled00/FX_fcn.cpp | 6 ++ wled00/bus_manager.h | 14 +-- wled00/bus_wrapper.h | 182 +++++++++++++++++++++------------ wled00/cfg.cpp | 2 +- wled00/const.h | 12 ++- wled00/data/settings_leds.htm | 13 ++- wled00/file.cpp | 13 +-- wled00/improv.cpp | 2 +- wled00/json.cpp | 2 +- wled00/pin_manager.cpp | 14 ++- wled00/pin_manager.h | 6 +- wled00/presets.cpp | 2 - wled00/wled.cpp | 42 +------- wled00/wled.h | 8 +- wled00/xml.cpp | 2 +- 21 files changed, 193 insertions(+), 171 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a9a60dac..00d70866f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ## WLED changelog +#### Build 2403240 +- v0.15.0-b2 +- WS2805 support (RGB + WW + CW, 600kbps) +- Unified PSRAM use +- NeoPixelBus v2.7.9 (for future WS2805 support) +- Ubiquitous PSRAM mode for all variants of ESP32 +- SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC) +- Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`) +- FW1906 Support (#3810 by @deece and @Robert-github-com) +- ESPAsyncWebServer 2.2.0 (#3828 by @willmmiles) +- Bugfixes: #3843, #3844 + #### Build 2403190 - limit max PWM frequency (fix incorrect PWM resolution) - Segment UI bugfix diff --git a/package-lock.json b/package-lock.json index dd3fab2e6..037ad0eee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b1", + "version": "0.15.0-b2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b1", + "version": "0.15.0-b2", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index f2c0e3d65..ef28439e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b1", + "version": "0.15.0-b2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 947578d98..c0467c8f3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,8 +47,8 @@ platform_packages = platformio/framework-arduinoespressif8266 platformio/tool-esptoolpy #@ ~1.30000.0 ## previous platform for 8266, in case of problems with the new one -## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_2_0, which does not support Ucs890x -;; platform_wled_default = ${common.arduino_core_3_2_0} +## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x +;; platform_wled_default = ${common.arduino_core_3_0_2} ;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 ;; platformio/toolchain-xtensa @ ~2.40802.200502 ;; platformio/tool-esptool @ ~1.413.0 @@ -143,7 +143,7 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.7.8 + makuna/NeoPixelBus @ 2.7.9 https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.0 # for I2C interface ;Wire @@ -392,8 +392,7 @@ board_build.flash_mode = qio board_build.partitions = ${esp32.default_partitions} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER - -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D LEDPIN=25 lib_deps = ${esp32.lib_deps} @@ -445,7 +444,6 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM - -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used lib_deps = ${esp32s3.lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.f_flash = 80000000L @@ -461,12 +459,10 @@ board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv ;board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 - -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this - -D WLED_USE_PSRAM -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D LEDPIN=16 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 29f5c6b57..d7d41f3a6 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -155,9 +155,8 @@ build_flags = ${common.build_flags_esp8266} ; set default color order of your led strip ; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB ; -; use PSRAM if a device (ESP) has one -; -DBOARD_HAS_PSRAM -; -D WLED_USE_PSRAM +; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) +; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ; ; configure I2C and SPI interface (for various hardware) ; -D I2CSDAPIN=33 # initialise interface diff --git a/wled00/FX.h b/wled00/FX.h index 58c192bc9..1089a0b8b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -59,13 +59,12 @@ /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 - #define MAX_NUM_SEGMENTS 12 + #define MAX_NUM_SEGMENTS 16 /* How much data bytes all segments combined may allocate */ #define MAX_SEGMENT_DATA 5120 #else @@ -73,11 +72,7 @@ #define MAX_NUM_SEGMENTS 32 #endif #if defined(ARDUINO_ARCH_ESP32S2) - #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1024 // 32k by default - #else - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default - #endif + #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM) #else #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default #endif diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3566755f0..766c71e59 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1098,6 +1098,12 @@ void WS2812FX::finalizeInit(void) { uint16_t prevLen = 0; for (int i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { uint8_t defPin[] = {defDataPins[i]}; + // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc + if (pinManager.isPinAllocated(defPin[0])) { + defPin[0] = 1; // start with GPIO1 and work upwards + while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + } uint16_t start = prevLen; uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index d4facb33b..c128f8c09 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -35,7 +35,7 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; + uint8_t pins[5] = {255, 255, 255, 255, 255}; uint16_t frequency; bool doubleBuffer; uint8_t milliAmpsPerLed; @@ -56,9 +56,9 @@ struct BusConfig { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) size_t nPins = 1; - if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (type > 47) nPins = 2; - else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); + if (IS_VIRTUAL(type)) nPins = 4; //virtual network bus. 4 "pins" store IP address + else if (IS_2PIN(type)) nPins = 2; + else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } @@ -160,7 +160,8 @@ class Bus { virtual bool hasWhite(void) { return Bus::hasWhite(_type); } static bool hasWhite(uint8_t type) { if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || - type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || type == TYPE_FW1906) return true; // digital types with white channel + type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || + type == TYPE_FW1906 || type == TYPE_WS2805) return true; // digital types with white channel if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel if (type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW) return true; // network types with white channel return false; @@ -168,7 +169,8 @@ class Bus { virtual bool hasCCT(void) { return Bus::hasCCT(_type); } static bool hasCCT(uint8_t type) { if (type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || - type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || type == TYPE_FW1906) return true; + type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || + type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } static int16_t getCCT() { return _cct; } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index fafe3a460..d13b1a945 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -74,38 +74,37 @@ #define I_8266_U1_APA106_3 82 #define I_8266_DM_APA106_3 83 #define I_8266_BB_APA106_3 84 +//WS2805 +#define I_8266_U0_2805_5 89 +#define I_8266_U1_2805_5 90 +#define I_8266_DM_2805_5 91 +#define I_8266_BB_2805_5 92 /*** ESP32 Neopixel methods ***/ //RGB #define I_32_RN_NEO_3 21 #define I_32_I0_NEO_3 22 #define I_32_I1_NEO_3 23 -#define I_32_BB_NEO_3 24 // bitbanging on ESP32 not recommended //RGBW #define I_32_RN_NEO_4 25 #define I_32_I0_NEO_4 26 #define I_32_I1_NEO_4 27 -#define I_32_BB_NEO_4 28 // bitbanging on ESP32 not recommended //400Kbps #define I_32_RN_400_3 29 #define I_32_I0_400_3 30 #define I_32_I1_400_3 31 -#define I_32_BB_400_3 32 // bitbanging on ESP32 not recommended //TM1814 (RGBW) #define I_32_RN_TM1_4 33 #define I_32_I0_TM1_4 34 #define I_32_I1_TM1_4 35 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) #define I_32_RN_TM2_3 36 #define I_32_I0_TM2_3 37 #define I_32_I1_TM2_3 38 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8903 (RGB) #define I_32_RN_UCS_3 57 #define I_32_I0_UCS_3 58 #define I_32_I1_UCS_3 59 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8904 (RGBW) #define I_32_RN_UCS_4 60 #define I_32_I0_UCS_4 61 @@ -114,11 +113,14 @@ #define I_32_RN_FW6_5 63 #define I_32_I0_FW6_5 64 #define I_32_I1_FW6_5 65 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//APA106 #define I_32_RN_APA106_3 85 #define I_32_I0_APA106_3 86 #define I_32_I1_APA106_3 87 -#define I_32_BB_APA106_3 88 // bitbangging on ESP32 not recommended +//WS2805 +#define I_32_RN_2805_5 93 +#define I_32_I0_2805_5 94 +#define I_32_I1_2805_5 95 //APA102 @@ -192,6 +194,11 @@ #define B_8266_U1_FW6_5 NeoPixelBusLg //esp8266, gpio2 #define B_8266_DM_FW6_5 NeoPixelBusLg //esp8266, gpio3 #define B_8266_BB_FW6_5 NeoPixelBusLg //esp8266, bb +//WS2805 GRBCW +#define B_8266_U0_2805_5 NeoPixelBusLg //esp8266, gpio1 +#define B_8266_U1_2805_5 NeoPixelBusLg //esp8266, gpio2 +#define B_8266_DM_2805_5 NeoPixelBusLg //esp8266, gpio3 +#define B_8266_BB_2805_5 NeoPixelBusLg //esp8266, bb #endif /*** ESP32 Neopixel methods ***/ @@ -204,7 +211,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_NEO_3 NeoPixelBusLg #endif -//#define B_32_BB_NEO_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //RGBW #define B_32_RN_NEO_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -213,7 +219,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_NEO_4 NeoPixelBusLg #endif -//#define B_32_BB_NEO_4 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //400Kbps #define B_32_RN_400_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -222,7 +227,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_400_3 NeoPixelBusLg #endif -//#define B_32_BB_400_3 NeoPixelBusLg // NeoEsp8266BitBang400KbpsMethod //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -231,7 +235,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM1_4 NeoPixelBusLg #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -240,7 +243,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM2_3 NeoPixelBusLg #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -249,7 +251,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_3 NeoPixelBusLg #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -258,7 +259,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_4 NeoPixelBusLg #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) #define B_32_RN_APA106_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_APA106_3 NeoPixelBusLg @@ -266,7 +266,6 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_APA106_3 NeoPixelBusLg #endif -//#define B_32_BB_APA106_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -275,7 +274,14 @@ #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_FW6_5 NeoPixelBusLg #endif - +//WS2805 RGBWC +#define B_32_RN_2805_5 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_2805_5 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_2805_5 NeoPixelBusLg +#endif #endif //APA102 @@ -382,6 +388,10 @@ class PolyBus { case I_8266_U1_FW6_5: (static_cast(busPtr))->Begin(); break; case I_8266_DM_FW6_5: (static_cast(busPtr))->Begin(); break; case I_8266_BB_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->Begin(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -391,7 +401,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_NEO_4: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; @@ -399,7 +408,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->Begin(); break; case I_32_RN_400_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; @@ -407,7 +415,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_TM1_4: beginTM1814(busPtr); break; case I_32_RN_TM2_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -425,7 +432,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_UCS_4: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; @@ -440,8 +446,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: (static_cast(busPtr))->Begin(); break; #endif - -// case I_32_BB_UCS_4: (static_cast(busPtr))->Begin(); break; case I_32_RN_APA106_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->Begin(); break; @@ -449,7 +453,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_32_RN_2805_5: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->Begin(); break; + #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; case I_HS_LPD_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -506,6 +516,10 @@ class PolyBus { case I_8266_U1_FW6_5: busPtr = new B_8266_U1_FW6_5(len, pins[0]); break; case I_8266_DM_FW6_5: busPtr = new B_8266_DM_FW6_5(len, pins[0]); break; case I_8266_BB_FW6_5: busPtr = new B_8266_BB_FW6_5(len, pins[0]); break; + case I_8266_U0_2805_5: busPtr = new B_8266_U0_2805_5(len, pins[0]); break; + case I_8266_U1_2805_5: busPtr = new B_8266_U1_2805_5(len, pins[0]); break; + case I_8266_DM_2805_5: busPtr = new B_8266_DM_2805_5(len, pins[0]); break; + case I_8266_BB_2805_5: busPtr = new B_8266_BB_2805_5(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; @@ -515,7 +529,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break; #endif -// case I_32_BB_NEO_3: busPtr = new B_32_BB_NEO_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; @@ -523,7 +536,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break; #endif -// case I_32_BB_NEO_4: busPtr = new B_32_BB_NEO_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; @@ -531,7 +543,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break; #endif -// case I_32_BB_400_3: busPtr = new B_32_BB_400_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -549,7 +560,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: busPtr = new B_32_I1_UCS_3(len, pins[0]); break; #endif -// case I_32_BB_UCS_3: busPtr = new B_32_BB_UCS_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; @@ -557,7 +567,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: busPtr = new B_32_I1_UCS_4(len, pins[0]); break; #endif -// case I_32_BB_UCS_4: busPtr = new B_32_BB_UCS_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: busPtr = new B_32_I0_APA106_3(len, pins[0]); break; @@ -565,7 +574,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: busPtr = new B_32_I1_APA106_3(len, pins[0]); break; #endif -// case I_32_BB_APA106_3: busPtr = new B_32_BB_APA106_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; @@ -573,6 +581,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: busPtr = new B_32_I1_FW6_5(len, pins[0]); break; #endif + case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: busPtr = new B_32_I0_2805_5(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: busPtr = new B_32_I1_2805_5(len, pins[0]); break; + #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -630,6 +645,10 @@ class PolyBus { case I_8266_U1_FW6_5: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_FW6_5: (static_cast(busPtr))->Show(consistent); break; case I_8266_BB_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; @@ -639,7 +658,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_NEO_4: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; @@ -647,7 +665,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_400_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; @@ -655,7 +672,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_TM1_4: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_TM2_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -673,7 +689,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_UCS_4: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->Show(consistent); break; @@ -681,7 +696,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_APA106_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->Show(consistent); break; @@ -689,7 +703,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_APA106_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_FW6_5: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; @@ -697,6 +710,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: (static_cast(busPtr))->Show(consistent); break; #endif + case I_32_RN_2805_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->Show(consistent); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -750,6 +770,10 @@ class PolyBus { case I_8266_U1_FW6_5: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_FW6_5: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_2805_5: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -759,7 +783,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_NEO_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; @@ -767,7 +790,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_NEO_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; @@ -775,7 +797,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_400_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_TM2_3: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -793,7 +814,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_UCS_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_UCS_4: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; @@ -801,7 +821,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_APA106_3: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; @@ -809,7 +828,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_FW6_5: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; @@ -817,6 +835,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: return (static_cast(busPtr))->CanShow(); break; #endif + case I_32_RN_2805_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: return (static_cast(busPtr))->CanShow(); break; + #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -896,6 +921,10 @@ class PolyBus { case I_8266_U1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; case I_8266_DM_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; case I_8266_BB_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -905,7 +934,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; @@ -913,7 +941,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -921,7 +948,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(colB)); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -939,7 +965,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; case I_32_RN_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; @@ -947,7 +972,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; case I_32_RN_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -955,7 +979,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; @@ -963,6 +986,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #endif + case I_32_RN_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1017,6 +1047,10 @@ class PolyBus { case I_8266_U1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_BB_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1026,7 +1060,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; @@ -1034,7 +1067,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1042,7 +1074,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -1060,7 +1091,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; @@ -1068,7 +1098,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1076,7 +1105,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; @@ -1084,7 +1112,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; #endif - + case I_32_RN_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1140,6 +1174,10 @@ class PolyBus { case I_8266_U1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W case I_8266_DM_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W case I_8266_BB_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_DM_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_BB_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1149,7 +1187,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1157,7 +1194,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1165,7 +1201,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -1183,7 +1218,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; #endif -// case I_32_BB_UCS_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; @@ -1191,7 +1225,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #endif -// case I_32_BB_UCS_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1199,7 +1232,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W @@ -1207,6 +1239,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif + case I_32_RN_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1280,6 +1319,10 @@ class PolyBus { case I_8266_U1_FW6_5: delete (static_cast(busPtr)); break; case I_8266_DM_FW6_5: delete (static_cast(busPtr)); break; case I_8266_BB_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_U0_2805_5: delete (static_cast(busPtr)); break; + case I_8266_U1_2805_5: delete (static_cast(busPtr)); break; + case I_8266_DM_2805_5: delete (static_cast(busPtr)); break; + case I_8266_BB_2805_5: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -1289,7 +1332,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_NEO_3: delete (static_cast(busPtr)); break; case I_32_RN_NEO_4: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; @@ -1297,7 +1339,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_NEO_4: delete (static_cast(busPtr)); break; case I_32_RN_400_3: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: delete (static_cast(busPtr)); break; @@ -1305,7 +1346,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_400_3: delete (static_cast(busPtr)); break; case I_32_RN_TM1_4: delete (static_cast(busPtr)); break; case I_32_RN_TM2_3: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -1323,7 +1363,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_UCS_3: delete (static_cast(busPtr)); break; case I_32_RN_UCS_4: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; @@ -1331,7 +1370,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_UCS_4: delete (static_cast(busPtr)); break; case I_32_RN_APA106_3: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: delete (static_cast(busPtr)); break; @@ -1339,7 +1377,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_APA106_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_APA106_3: delete (static_cast(busPtr)); break; case I_32_RN_FW6_5: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; @@ -1347,6 +1384,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_FW6_5: delete (static_cast(busPtr)); break; #endif + case I_32_RN_2805_5: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: delete (static_cast(busPtr)); break; + #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1410,6 +1454,8 @@ class PolyBus { return I_8266_U0_APA106_3 + offset; case TYPE_FW1906: return I_8266_U0_FW6_5 + offset; + case TYPE_WS2805: + return I_8266_U0_2805_5 + offset; } #else //ESP32 uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 @@ -1452,6 +1498,8 @@ class PolyBus { return I_32_RN_APA106_3 + offset; case TYPE_FW1906: return I_32_RN_FW6_5 + offset; + case TYPE_WS2805: + return I_32_RN_2805_5 + offset; } #endif } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 6ccf8aa44..a1794a755 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -185,7 +185,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t maPerLed = elm[F("ledma")] | 55; uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists // To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current) - if ((ledType > TYPE_TM1814 && ledType < TYPE_WS2801) || ledType >= TYPE_NET_DDP_RGB) { // analog and virtual + if (IS_PWM(ledType) || IS_ONOFF(ledType) || IS_VIRTUAL(ledType)) { // analog and virtual maPerLed = 0; maMax = 0; } diff --git a/wled00/const.h b/wled00/const.h index 73873d041..de07c314a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -274,6 +274,7 @@ #define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 +#define TYPE_WS2805 32 //RGB + WW + CW //"Analog" types (40-47) #define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) #define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel @@ -298,6 +299,7 @@ #define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63 #define IS_2PIN(t) ((t) > 47 && (t) < 64) #define IS_16BIT(t) ((t) == TYPE_UCS8903 || (t) == TYPE_UCS8904) +#define IS_ONOFF(t) ((t) == 40) #define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111 @@ -513,11 +515,11 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO) - #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, and on boards where GPIO16 is not available -#else - #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards -#endif +//#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO) + #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board +//#else +// #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards +//#endif #endif #ifdef WLED_ENABLE_DMX diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 061d5a9ac..fee523ffc 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -23,6 +23,8 @@ function isD2P(t) { return t > 47 && t < 64; } // is digital 2 pin type function is16b(t) { return t == 26 || t == 29 } // is digital 16 bit type function isVir(t) { return t >= 80 && t < 96; } // is virtual type + function hasW(t) { return (t >= 18 && t <= 21) || (t >= 28 && t <= 32) || (t >= 44 && t <= 45) || (t >= 88 && t <= 89); } + function hasCCT(t) { return t == 20 || t == 21 || t == 42 || t == 45 || t == 28 || t == 32; } // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript function loadJS(FILE_URL, async = true) { let scE = d.createElement("script"); @@ -203,7 +205,7 @@ function UI(change=false) { - let isRGBW = false, gRGBW = false, memu = 0; + let gRGBW = false, memu = 0; let busMA = 0; let sLC = 0, sPC = 0, sDI = 0, maxLC = 0; const ablEN = d.Sf.ABL.checked; @@ -243,15 +245,15 @@ d.Sf["MA"+n].min = (isVir(t) || isAna(t)) ? 0 : 250; } gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 - gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 27 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h + gRGBW |= hasW(t); // RGBW checkbox, TYPE_xxxx values from const.h gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM - gId("dig"+n+"w").style.display = (isDig(t) && isRGBW) ? "inline":"none"; // show swap channels dropdown - if (!(isDig(t) && isRGBW)) d.Sf["WO"+n].value = 0; // reset swapping + gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown + if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog gId("dig"+n+"f").style.display = (isDig(t)) ? "inline":"none"; // hide refresh - gId("dig"+n+"a").style.display = (isRGBW) ? "inline":"none"; // auto calculate white + gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog //gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description @@ -386,6 +388,7 @@ ${i+1}: \ \ \ +\ \ \ \ diff --git a/wled00/file.cpp b/wled00/file.cpp index 6511d4d07..dc41c6f12 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -376,20 +376,15 @@ void updateFSInfo() { } -#if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) +#ifdef ARDUINO_ARCH_ESP32 // caching presets in PSRAM may prevent occasional flashes seen when HomeAssitant polls WLED // original idea by @akaricchi (https://github.com/Akaricchi) -// returns a pointer to the PSRAM buffer updates size parameter +// returns a pointer to the PSRAM buffer, updates size parameter static const uint8_t *getPresetCache(size_t &size) { static unsigned long presetsCachedTime; static uint8_t *presetsCached; static size_t presetsCachedSize; - if (!psramFound()) { - size = 0; - return nullptr; - } - if (presetsModifiedTime != presetsCachedTime) { if (presetsCached) { free(presetsCached); @@ -421,8 +416,8 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); if(path.endsWith("/")) path += "index.htm"; if(path.indexOf(F("sec")) > -1) return false; - #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - if (path.endsWith(FPSTR(getPresetsFileName()))) { + #ifdef ARDUINO_ARCH_ESP32 + if (psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) { size_t psize; const uint8_t *presets = getPresetCache(psize); if (presets) { diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 9c2fb20e7..0090b4bd6 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.15.0-b1/%i"), VERSION); + sprintf_P(vString, PSTR("0.15.0-b2/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/json.cpp b/wled00/json.cpp index 113b2b1f5..29464a9d9 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -752,7 +752,7 @@ void serializeInfo(JsonObject root) #endif root[F("freeheap")] = ESP.getFreeHeap(); - #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) + #if defined(ARDUINO_ARCH_ESP32) if (psramFound()) root[F("psram")] = ESP.getFreePsram(); #endif root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 044dc6c92..34705ee94 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -209,11 +209,10 @@ bool PinManagerClass::allocatePin(byte gpio, bool output, PinOwner tag) // if tag is set to PinOwner::None, checks for ANY owner of the pin. // if tag is set to any other value, checks if that tag is the current owner of the pin. -bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) +bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const { if (!isPinOk(gpio, false)) return true; if ((tag != PinOwner::None) && (ownerTag[gpio] != tag)) return false; - if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access byte by = gpio >> 3; byte bi = gpio - (by<<3); return bitRead(pinAlloc[by], bi); @@ -236,8 +235,9 @@ bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) */ // Check if supplied GPIO is ok to use -bool PinManagerClass::isPinOk(byte gpio, bool output) +bool PinManagerClass::isPinOk(byte gpio, bool output) const { + if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access #ifdef ARDUINO_ARCH_ESP32 if (digitalPinIsValid(gpio)) { #if defined(CONFIG_IDF_TARGET_ESP32C3) @@ -257,9 +257,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) // GPIO46 is input only and pulled down #else if (gpio > 5 && gpio < 12) return false; //SPI flash pins - #ifdef BOARD_HAS_PSRAM - if (gpio == 16 || gpio == 17) return false; //PSRAM pins - #endif + if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) #endif if (output) return digitalPinCanOutput(gpio); else return true; @@ -272,8 +270,8 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) return false; } -PinOwner PinManagerClass::getPinOwner(byte gpio) { - if (gpio >= WLED_NUM_PINS) return PinOwner::None; // catch error case, to avoid array out-of-bounds access +PinOwner PinManagerClass::getPinOwner(byte gpio) const +{ if (!isPinOk(gpio, false)) return PinOwner::None; return ownerTag[gpio]; } diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 6a50df588..464bd54ae 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -108,11 +108,11 @@ class PinManagerClass { inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } // will return true for reserved pins - bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None) const; // will return false for reserved pins - bool isPinOk(byte gpio, bool output = true); + bool isPinOk(byte gpio, bool output = true) const; - PinOwner getPinOwner(byte gpio); + PinOwner getPinOwner(byte gpio) const; #ifdef ARDUINO_ARCH_ESP32 byte allocateLedc(byte channels); diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 1b13184d2..5a0af0e7f 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -56,11 +56,9 @@ static void doSaveState() { size_t len = measureJson(*fileDoc) + 1; DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 - #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) if (psramFound()) tmpRAMbuffer = (char*) ps_malloc(len); else - #endif tmpRAMbuffer = (char*) malloc(len); if (tmpRAMbuffer!=nullptr) { serializeJson(*fileDoc, tmpRAMbuffer, len); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 07304e77a..878eb26a4 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -240,7 +240,7 @@ void WLED::loop() DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) + #if defined(ARDUINO_ARCH_ESP32) if (psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); @@ -366,42 +366,13 @@ void WLED::setup() #endif DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); -#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) -/* - * The following code is obsolete as PinManager::isPinOK() will return false for reserved GPIO. - * Additionally xml.cpp will inform UI about reserved GPIO. - * - - #if defined(CONFIG_IDF_TARGET_ESP32S3) - // S3: reserve GPIO 33-37 for "octal" PSRAM - managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); - #elif defined(CONFIG_IDF_TARGET_ESP32S2) - // S2: reserve GPIO 26-32 for PSRAM (may fail due to isPinOk() but that will also prevent other allocation) - managed_pin_type pins[] = { {26, true}, {27, true}, {28, true}, {29, true}, {30, true}, {31, true}, {32, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - // C3: reserve GPIO 12-17 for PSRAM (may fail due to isPinOk() but that will also prevent other allocation) - managed_pin_type pins[] = { {12, true}, {13, true}, {14, true}, {15, true}, {16, true}, {17, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); - #else - // GPIO16/GPIO17 reserved for SPI RAM - managed_pin_type pins[] = { {16, true}, {17, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); - #endif -*/ - #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - pDoc = new PSRAMDynamicJsonDocument(2*JSON_BUFFER_SIZE); - if (!pDoc) pDoc = new PSRAMDynamicJsonDocument(JSON_BUFFER_SIZE); // falback if double sized buffer could not be allocated - // if the above still fails requestJsonBufferLock() will always return false preventing crashes +#if defined(ARDUINO_ARCH_ESP32) + pDoc = new PSRAMDynamicJsonDocument((psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); + // if the above fails requestJsonBufferLock() will always return false preventing crashes if (psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } - #else - if (!pDoc) pDoc = &gDoc; // just in case ... (it should be globally assigned) - DEBUG_PRINTLN(F("PSRAM not used.")); - #endif #endif #if defined(ARDUINO_ESP32_PICO) // special handling for PICO-D4: gpio16+17 are in use for onboard SPI FLASH (not PSRAM) @@ -409,9 +380,6 @@ void WLED::setup() pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); #endif - //DEBUG_PRINT(F("LEDs inited. heap usage ~")); - //DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); - #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif @@ -555,7 +523,7 @@ void WLED::setup() void WLED::beginStrip() { // Initialize NeoPixel Strip and button - strip.finalizeInit(); // busses created during deserializeConfig() + strip.finalizeInit(); // busses created during deserializeConfig() if config existed strip.makeAutoSegments(); strip.setBrightness(0); strip.setShowCallback(handleOverlayDraw); diff --git a/wled00/wled.h b/wled00/wled.h index 3e1e9f80f..62fd4ec4d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-b1 + @version 0.15.0-b2 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2403220 +#define VERSION 2403240 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -158,7 +158,7 @@ // The following is a construct to enable code to compile without it. // There is a code that will still not use PSRAM though: // AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h) -#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) +#if defined(ARDUINO_ARCH_ESP32) struct PSRAM_Allocator { void* allocate(size_t size) { if (psramFound()) return ps_malloc(size); // use PSRAM if it exists @@ -781,7 +781,7 @@ WLED_GLOBAL int8_t spi_sclk _INIT(SPISCLKPIN); #endif // global ArduinoJson buffer -#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) +#if defined(ARDUINO_ARCH_ESP32) WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr); #else WLED_GLOBAL StaticJsonDocument gDoc; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index fddc242ea..fc754c4a2 100755 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -163,7 +163,7 @@ void appendGPIOinfo() { //Note: Using pin 3 (RX) disables Adalight / Serial JSON - #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) + #if defined(ARDUINO_ARCH_ESP32) #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM on ESP32 (not on S2, S3 or C3) #elif defined(CONFIG_IDF_TARGET_ESP32S3) From 4b19759dd6845f6eba8a8e38937e6939a86d2896 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 24 Mar 2024 20:34:22 +0100 Subject: [PATCH 118/694] Rotary encoder palette count bugfix --- .../usermod_v2_rotary_encoder_ui_ALT.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 6a15b520b..e5a5f24f7 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -392,26 +392,26 @@ byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { * modes_alpha_indexes and palettes_alpha_indexes. */ void RotaryEncoderUIUsermod::sortModesAndPalettes() { - DEBUG_PRINTLN(F("Sorting modes and palettes.")); + DEBUG_PRINT(F("Sorting modes: ")); DEBUG_PRINTLN(strip.getModeCount()); //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); modes_qstrings = strip.getModeDataSrc(); modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); - palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()+strip.customPalettes.size()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()+strip.customPalettes.size()); + DEBUG_PRINT(F("Sorting palettes: ")); DEBUG_PRINT(strip.getPaletteCount()); DEBUG_PRINT('/'); DEBUG_PRINTLN(strip.customPalettes.size()); + palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); + palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); if (strip.customPalettes.size()) { for (int i=0; i Date: Tue, 26 Mar 2024 17:18:52 +0100 Subject: [PATCH 119/694] fileDoc removal (optimisation) --- wled00/json.cpp | 2 +- wled00/playlist.cpp | 3 +-- wled00/presets.cpp | 29 +++++++++++++---------------- wled00/util.cpp | 2 -- wled00/wled.h | 3 +-- wled00/ws.cpp | 2 +- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 29464a9d9..76cb4667f 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -303,7 +303,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) return true; } -// deserializes WLED state (fileDoc points to doc object if called from web server) +// deserializes WLED state // presetId is non-0 if called from handlePreset() bool deserializeState(JsonObject root, byte callMode, byte presetId) { diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 882ccb0e0..67c4f6049 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -125,8 +125,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { void handlePlaylist() { static unsigned long presetCycledTime = 0; - // if fileDoc is not null JSON buffer is in use so just quit - if (currentPlaylist < 0 || playlistEntries == nullptr || fileDoc != nullptr) return; + if (currentPlaylist < 0 || playlistEntries == nullptr) return; if (millis() - presetCycledTime > (100*playlistEntryDur)) { presetCycledTime = millis(); diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 5a0af0e7f..857b2fbb3 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -27,7 +27,7 @@ static void doSaveState() { unsigned long start = millis(); while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames - if (!requestJSONBufferLock(10)) return; // will set fileDoc + if (!requestJSONBufferLock(10)) return; initPresetsFile(); // just in case if someone deleted presets.json using /edit JsonObject sObj = pDoc->to(); @@ -53,7 +53,7 @@ static void doSaveState() { #if defined(ARDUINO_ARCH_ESP32) if (!persist) { if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer); - size_t len = measureJson(*fileDoc) + 1; + size_t len = measureJson(*pDoc) + 1; DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 if (psramFound()) @@ -61,13 +61,13 @@ static void doSaveState() { else tmpRAMbuffer = (char*) malloc(len); if (tmpRAMbuffer!=nullptr) { - serializeJson(*fileDoc, tmpRAMbuffer, len); + serializeJson(*pDoc, tmpRAMbuffer, len); } else { - writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, fileDoc); + writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, pDoc); } } else #endif - writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, fileDoc); + writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, pDoc); if (persist) presetsModifiedTime = toki.second(); //unix time releaseJSONBufferLock(); @@ -152,7 +152,7 @@ void handlePresets() return; } - if (presetToApply == 0 || fileDoc) return; // no preset waiting to apply, or JSON buffer is already allocated, return to loop until free + if (presetToApply == 0 || !requestJSONBufferLock(9)) return; // no preset waiting to apply, or JSON buffer is already allocated, return to loop until free bool changePreset = false; uint8_t tmpPreset = presetToApply; // store temporary since deserializeState() may call applyPreset() @@ -160,9 +160,6 @@ void handlePresets() JsonObject fdo; - // allocate buffer - if (!requestJSONBufferLock(9)) return; // will also assign fileDoc - presetToApply = 0; //clear request for preset callModeToApply = 0; @@ -171,14 +168,14 @@ void handlePresets() #ifdef ARDUINO_ARCH_ESP32 if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { - deserializeJson(*fileDoc,tmpRAMbuffer); + deserializeJson(*pDoc,tmpRAMbuffer); errorFlag = ERR_NONE; } else #endif { - errorFlag = readObjectFromFileUsingId(getPresetsFileName(tmpPreset < 255), tmpPreset, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; + errorFlag = readObjectFromFileUsingId(getPresetsFileName(tmpPreset < 255), tmpPreset, pDoc) ? ERR_NONE : ERR_FS_PLOAD; } - fdo = fileDoc->as(); + fdo = pDoc->as(); //HTTP API commands const char* httpwin = fdo["win"]; @@ -205,13 +202,13 @@ void handlePresets() } #endif - releaseJSONBufferLock(); // will also clear fileDoc + releaseJSONBufferLock(); if (changePreset) notify(tmpMode); // force UDP notification stateUpdated(tmpMode); // was colorUpdated() if anything breaks updateInterfaces(tmpMode); } -//called from handleSet(PS=) [network callback (fileDoc==nullptr), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] +//called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject sObj) { if (!saveName) saveName = new char[33]; @@ -249,7 +246,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) if (sObj[F("playlist")].isNull()) { // we will save API call immediately (often causes presets.json corruption) presetToSave = 0; - if (index <= 250 && fileDoc) { // cannot save API calls to temporary preset (255) + if (index <= 250) { // cannot save API calls to temporary preset (255) sObj.remove("o"); sObj.remove("v"); sObj.remove("time"); @@ -257,7 +254,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) sObj.remove(F("psave")); if (sObj["n"].isNull()) sObj["n"] = saveName; initPresetsFile(); // just in case if someone deleted presets.json using /edit - writeObjectToFileUsingId(getPresetsFileName(), index, fileDoc); + writeObjectToFileUsingId(getPresetsFileName(), index, pDoc); presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } diff --git a/wled00/util.cpp b/wled00/util.cpp index 1bd8ec319..ad7e4b670 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -228,7 +228,6 @@ bool requestJSONBufferLock(uint8_t module) DEBUG_PRINT(F("JSON buffer locked. (")); DEBUG_PRINT(jsonBufferLock); DEBUG_PRINTLN(")"); - fileDoc = pDoc; // used for applying presets (presets.cpp) pDoc->clear(); return true; } @@ -239,7 +238,6 @@ void releaseJSONBufferLock() DEBUG_PRINT(F("JSON buffer released. (")); DEBUG_PRINT(jsonBufferLock); DEBUG_PRINTLN(")"); - fileDoc = nullptr; jsonBufferLock = 0; } diff --git a/wled00/wled.h b/wled00/wled.h index 62fd4ec4d..1361f9f7e 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403240 +#define VERSION 2403260 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -692,7 +692,6 @@ WLED_GLOBAL uint16_t olen _INIT(0); WLED_GLOBAL size_t fsBytesUsed _INIT(0); WLED_GLOBAL size_t fsBytesTotal _INIT(0); WLED_GLOBAL unsigned long presetsModifiedTime _INIT(0L); -WLED_GLOBAL JsonDocument* fileDoc; WLED_GLOBAL bool doCloseFile _INIT(false); // presets diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 16636bb1e..307a0959e 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -55,7 +55,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp } else { verboseResponse = deserializeState(root); } - releaseJSONBufferLock(); // will clean fileDoc + releaseJSONBufferLock(); if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon if (verboseResponse) { From d6e73fde50bd5836299c2a1b3226dfba6e640f48 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 26 Mar 2024 17:34:01 +0100 Subject: [PATCH 120/694] only create bin.gz files for esp8266 --- pio-scripts/output_bins.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 092754c38..10077bca4 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -22,7 +22,7 @@ def create_release(source): if release_name: version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") - release_gz_file = os.path.join(OUTPUT_DIR, "release_gz", f"WLED_{version}_{release_name}.bin.gz") + release_gz_file = release_file + ".gz" print(f"Copying {source} to {release_file}") shutil.copy(source, release_file) print(f"Creating gzip file {release_gz_file} from {release_file}") @@ -42,6 +42,10 @@ def bin_rename_copy(source, target, env): shutil.move("firmware.map", map_file) def bin_gzip(source, target): + # only create gzip for esp8266 + if not env["PIOPLATFORM"] == "espressif8266": + return + with open(source,"rb") as fp: with gzip.open(target, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) From d7739f9764a72e9174e584d3da1cd39def980cea Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 26 Mar 2024 17:41:28 +0100 Subject: [PATCH 121/694] Revert "Change path for ESP02-binary" This reverts commit e3271b8082e6fa776c659e22f1f2987011cf48b8. --- .github/workflows/wled-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index feb0e531a..32dae8777 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -61,7 +61,7 @@ jobs: name: firmware-${{ matrix.environment }} path: | build_output/release/*.bin - build_output/release_gz/*_ESP02*.bin.gz + build_output/release/*_ESP02*.bin.gz release: name: Create Release runs-on: ubuntu-latest From 5d152baac019c435d01880af5aaf07de67526b1d Mon Sep 17 00:00:00 2001 From: Woody Date: Wed, 27 Mar 2024 21:19:03 +0100 Subject: [PATCH 122/694] Do not create release_gz folder --- pio-scripts/output_bins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 10077bca4..6df06d8c8 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -13,7 +13,7 @@ def _get_cpp_define_value(env, define): return None -def _create_dirs(dirs=["map", "release", "release_gz"]): +def _create_dirs(dirs=["map", "release"]): for d in dirs: os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) From 6f6356e617caa890e0be8fdf6f8864a2389a337f Mon Sep 17 00:00:00 2001 From: Woody Date: Wed, 27 Mar 2024 21:34:51 +0100 Subject: [PATCH 123/694] fix print output for bin.gz file --- pio-scripts/output_bins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 6df06d8c8..e4a458a9e 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -25,7 +25,6 @@ def create_release(source): release_gz_file = release_file + ".gz" print(f"Copying {source} to {release_file}") shutil.copy(source, release_file) - print(f"Creating gzip file {release_gz_file} from {release_file}") bin_gzip(release_file, release_gz_file) def bin_rename_copy(source, target, env): @@ -45,7 +44,8 @@ def bin_gzip(source, target): # only create gzip for esp8266 if not env["PIOPLATFORM"] == "espressif8266": return - + + print(f"Creating gzip file {target} from {source}") with open(source,"rb") as fp: with gzip.open(target, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) From 5f37c19d42051f570b801d520c5f59616d01fbd5 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 28 Mar 2024 16:03:06 +0100 Subject: [PATCH 124/694] PSRAM fix & CCT IC - prevent PSRAM use on ESP32 rev.1 without compile fix - add runtime selection for CCT IC (Athom 15W bulb) --- platformio.ini | 2 +- tools/WLED_ESP32-wrover_4MB.csv | 6 ++--- wled00/bus_manager.cpp | 39 +++++++++++++++++---------------- wled00/cfg.cpp | 2 ++ wled00/data/settings_leds.htm | 1 + wled00/file.cpp | 2 +- wled00/json.cpp | 2 +- wled00/presets.cpp | 2 +- wled00/set.cpp | 1 + wled00/wled.cpp | 9 +++++--- wled00/wled.h | 18 ++++++++++----- wled00/xml.cpp | 1 + 12 files changed, 51 insertions(+), 34 deletions(-) mode change 100755 => 100644 wled00/set.cpp mode change 100755 => 100644 wled00/xml.cpp diff --git a/platformio.ini b/platformio.ini index c0467c8f3..345d66d91 100644 --- a/platformio.ini +++ b/platformio.ini @@ -389,7 +389,7 @@ platform = ${esp32.platform} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio -board_build.partitions = ${esp32.default_partitions} +board_build.partitions = tools/WLED_ESP32-wrover_4MB.csv build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html diff --git a/tools/WLED_ESP32-wrover_4MB.csv b/tools/WLED_ESP32-wrover_4MB.csv index a179a89d0..39c88e543 100644 --- a/tools/WLED_ESP32-wrover_4MB.csv +++ b/tools/WLED_ESP32-wrover_4MB.csv @@ -1,6 +1,6 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x180000, -app1, app, ota_1, 0x190000,0x180000, -spiffs, data, spiffs, 0x310000,0xF0000, +app0, app, ota_0, 0x10000, 0x1A0000, +app1, app, ota_1, 0x1B0000,0x1A0000, +spiffs, data, spiffs, 0x350000,0xB0000, diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index eeb9a15e4..88b4cc32b 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -9,6 +9,8 @@ #include "bus_wrapper.h" #include "bus_manager.h" +extern bool cctICused; + //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); @@ -210,7 +212,7 @@ void BusDigital::show() { if (_data) { size_t channels = getNumberOfChannels(); - int16_t oldCCT = _cct; // temporarily save bus CCT + int16_t oldCCT = Bus::_cct; // temporarily save bus CCT for (size_t i=0; i<_len; i++) { size_t offset = i * channels; uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); @@ -229,7 +231,7 @@ void BusDigital::show() { // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // we need to extract and appy CCT value for each pixel individually even though all buses share the same _cct variable // TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer - _cct = _data[offset+channels-1]; + Bus::_cct = _data[offset+channels-1]; Bus::calculateCCT(c, cctWW, cctCW); } uint16_t pix = i; @@ -241,7 +243,7 @@ void BusDigital::show() { if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black #endif for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black - _cct = oldCCT; + Bus::_cct = oldCCT; } else { if (newBri < _bri) { uint16_t hwLen = _len; @@ -291,7 +293,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid) return; uint8_t cctWW = 0, cctCW = 0; if (hasWhite()) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (_data) { size_t offset = pix * getNumberOfChannels(); if (hasRGB()) { @@ -302,7 +304,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { if (hasWhite()) _data[offset++] = W(c); // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) - if (hasCCT()) _data[offset] = _cct >= 1900 ? (_cct - 1900) >> 5 : (_cct < 0 ? 127 : _cct); // TODO: if _cct == -1 we simply ignore it + if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it } else { if (_reversed) pix = _len - pix -1; pix += _skip; @@ -428,8 +430,8 @@ BusPwm::BusPwm(BusConfig &bc) void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); - if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { - c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { + c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } uint8_t r = R(c); uint8_t g = G(c); @@ -441,19 +443,18 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { _data[0] = w; break; case TYPE_ANALOG_2CH: //warm white + cold white - #ifdef WLED_USE_IC_CCT - _data[0] = w; - _data[1] = cct; - #else - Bus::calculateCCT(c, _data[0], _data[1]); - #endif + if (cctICused) { + _data[0] = w; + _data[1] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; + } else { + Bus::calculateCCT(c, _data[0], _data[1]); + } break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white - #ifdef WLED_USE_IC_CCT - _data[4] = cct; - #else - Bus::calculateCCT(c, w, _data[4]); - #endif + if (cctICused) + _data[4] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; + else + Bus::calculateCCT(c, w, _data[4]); case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB @@ -618,7 +619,7 @@ BusNetwork::BusNetwork(BusConfig &bc) void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; if (_rgbw) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index a1794a755..4dd1d133a 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -110,6 +110,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { Bus::setGlobalAWMode(hw_led[F("rgbwm")] | AW_GLOBAL_DISABLED); CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); + CJSON(cctICused, hw_led[F("ic")]); CJSON(strip.cctBlending, hw_led[F("cb")]); Bus::setCCTBlend(strip.cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS @@ -767,6 +768,7 @@ void serializeConfig() { hw_led[F("ledma")] = 0; // no longer used hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; + hw_led[F("ic")] = cctICused; hw_led[F("cb")] = strip.cctBlending; hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index fee523ffc..dddedd471 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -866,6 +866,7 @@ Swap:
Calculate CCT from RGB:
+ CCT IC used (Athom 15W):
CCT additive blending: %

Advanced

diff --git a/wled00/file.cpp b/wled00/file.cpp index dc41c6f12..eae50ff1d 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -417,7 +417,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ if(path.endsWith("/")) path += "index.htm"; if(path.indexOf(F("sec")) > -1) return false; #ifdef ARDUINO_ARCH_ESP32 - if (psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) { + if (psramSafe && psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) { size_t psize; const uint8_t *presets = getPresetCache(psize); if (presets) { diff --git a/wled00/json.cpp b/wled00/json.cpp index 76cb4667f..02eb22ba4 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -753,7 +753,7 @@ void serializeInfo(JsonObject root) root[F("freeheap")] = ESP.getFreeHeap(); #if defined(ARDUINO_ARCH_ESP32) - if (psramFound()) root[F("psram")] = ESP.getFreePsram(); + if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram(); #endif root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 857b2fbb3..2916d337a 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -56,7 +56,7 @@ static void doSaveState() { size_t len = measureJson(*pDoc) + 1; DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 - if (psramFound()) + if (psramSafe && psramFound()) tmpRAMbuffer = (char*) ps_malloc(len); else tmpRAMbuffer = (char*) malloc(len); diff --git a/wled00/set.cpp b/wled00/set.cpp old mode 100755 new mode 100644 index 6e7064bb3..9b3b6bea7 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -123,6 +123,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) autoSegments = request->hasArg(F("MS")); correctWB = request->hasArg(F("CCT")); cctFromRgb = request->hasArg(F("CR")); + cctICused = request->hasArg(F("IC")); strip.cctBlending = request->arg(F("CB")).toInt(); Bus::setCCTBlend(strip.cctBlending); Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 878eb26a4..7b753da3b 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -241,7 +241,7 @@ void WLED::loop() DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) - if (psramFound()) { + if (psramSafe && psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } @@ -367,9 +367,12 @@ void WLED::setup() DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) - pDoc = new PSRAMDynamicJsonDocument((psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); + #ifndef BOARD_HAS_PSRAM + if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false; + #endif + pDoc = new PSRAMDynamicJsonDocument((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); // if the above fails requestJsonBufferLock() will always return false preventing crashes - if (psramFound()) { + if (psramSafe && psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } diff --git a/wled00/wled.h b/wled00/wled.h index 1361f9f7e..35b99260a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403260 +#define VERSION 2403280 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -159,14 +159,15 @@ // There is a code that will still not use PSRAM though: // AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h) #if defined(ARDUINO_ARCH_ESP32) +extern bool psramSafe; struct PSRAM_Allocator { void* allocate(size_t size) { - if (psramFound()) return ps_malloc(size); // use PSRAM if it exists - else return malloc(size); // fallback + if (psramSafe && psramFound()) return ps_malloc(size); // use PSRAM if it exists + else return malloc(size); // fallback } void* reallocate(void* ptr, size_t new_size) { - if (psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists - else return realloc(ptr, new_size); // fallback + if (psramSafe && psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists + else return realloc(ptr, new_size); // fallback } void deallocate(void* pointer) { free(pointer); @@ -348,6 +349,11 @@ WLED_GLOBAL bool useGlobalLedBuffer _INIT(true); // double buffering enabled on #endif WLED_GLOBAL bool correctWB _INIT(false); // CCT color correction of RGB color WLED_GLOBAL bool cctFromRgb _INIT(false); // CCT is calculated from RGB instead of using seg.cct +#ifdef WLED_USE_IC_CCT +WLED_GLOBAL bool cctICused _INIT(true); // CCT IC used (Athom 15W bulbs) +#else +WLED_GLOBAL bool cctICused _INIT(false); // CCT IC used (Athom 15W bulbs) +#endif WLED_GLOBAL bool gammaCorrectCol _INIT(true); // use gamma correction on colors WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value @@ -705,6 +711,8 @@ WLED_GLOBAL byte optionType; WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate saving of config WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers +WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue") + // status led #if defined(STATUSLED) WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0); diff --git a/wled00/xml.cpp b/wled00/xml.cpp old mode 100755 new mode 100644 index fc754c4a2..c91f0dd7e --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -360,6 +360,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("MS"),autoSegments); sappend('c',SET_F("CCT"),correctWB); + sappend('c',SET_F("IC"),cctICused); sappend('c',SET_F("CR"),cctFromRgb); sappend('v',SET_F("CB"),strip.cctBlending); sappend('v',SET_F("FR"),strip.getTargetFps()); From 2900bda8f971c72879f1c7684376472f06ae477a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Fri, 29 Mar 2024 09:33:12 -0400 Subject: [PATCH 125/694] Select ESP8266 framework version via platform By explicitly listing an unversioned framework dependency in 'platform_packages', we were overriding the selection via the 'platform' specification, allowing PlatformIO to select any random version. Remove this line to allow 'platform' to add the framework dependency with the expected version. --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 345d66d91..0e09b499b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,8 +41,7 @@ arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/ platform_wled_default = ${common.arduino_core_3_1_2} # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization #platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 -platform_packages = platformio/framework-arduinoespressif8266 - platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 +platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 platformio/tool-esptool #@ ~1.413.0 platformio/tool-esptoolpy #@ ~1.30000.0 From f21ab3588d797e0de38fce58e0670a00fdde9775 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 29 Mar 2024 16:43:37 +0100 Subject: [PATCH 126/694] Individual color channel control for JSON API - fixes #3860 - debug verbose - PSRAM detection --- wled00/FX_fcn.cpp | 26 +++----------------------- wled00/json.cpp | 40 +++++++++++++++++++++++++++------------- wled00/wled.cpp | 8 ++++++-- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 766c71e59..f97268f9b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1205,7 +1205,6 @@ void WS2812FX::service() { seg.next_time = nowUp + delay; } -// if (_segment_index == _queuedChangesSegId) setUpSegmentFromQueuedChanges(); _segment_index++; } _virtualSegmentLength = 0; @@ -1213,7 +1212,7 @@ void WS2812FX::service() { _triggered = false; #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTLN(F("Slow effects.")); + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif if (doShow) { yield(); @@ -1221,7 +1220,7 @@ void WS2812FX::service() { show(); } #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTLN(F("Slow strip.")); + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif } @@ -1431,31 +1430,12 @@ void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t group appendSegment(Segment(0, strip.getLengthTotal())); segId = getSegmentsNum()-1; // segments are added at the end of list } -/* - if (_queuedChangesSegId == segId) _queuedChangesSegId = 255; // cancel queued change if already queued for this segment - - if (segId < getMaxSegments() && segId == getCurrSegmentId() && isServicing()) { // queue change to prevent concurrent access - // queuing a change for a second segment will lead to the loss of the first change if not yet applied - // however this is not a problem as the queued change is applied immediately after the effect function in that segment returns - _qStart = i1; _qStop = i2; _qStartY = startY; _qStopY = stopY; - _qGrouping = grouping; _qSpacing = spacing; _qOffset = offset; - _queuedChangesSegId = segId; - DEBUG_PRINT(F("Segment queued: ")); DEBUG_PRINTLN(segId); - return; // queued changes are applied immediately after effect function returns - } -*/ suspend(); _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); resume(); if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector } -/* -void WS2812FX::setUpSegmentFromQueuedChanges() { - if (_queuedChangesSegId >= getSegmentsNum()) return; - _segments[_queuedChangesSegId].setUp(_qStart, _qStop, _qGrouping, _qSpacing, _qOffset, _qStartY, _qStopY); - _queuedChangesSegId = 255; -} -*/ + void WS2812FX::resetSegments() { _segments.clear(); // destructs all Segment as part of clearing #ifndef WLED_DISABLE_2D diff --git a/wled00/json.cpp b/wled00/json.cpp index 02eb22ba4..fd1527a21 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -142,28 +142,42 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) { if (seg.getLightCapabilities() & 3) { // segment has RGB or White - for (size_t i = 0; i < 3; i++) - { + for (size_t i = 0; i < NUM_COLORS; i++) { + // JSON "col" array can contain the following values for each of segment's colors (primary, background, custom): + // "col":[int|string|object|array, int|string|object|array, int|string|object|array] + // int = Kelvin temperature or 0 for black + // string = hex representation of [WW]RRGGBB + // object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {}) + // array = direct channel values [r,g,b,w] (w element being optional) int rgbw[] = {0,0,0,0}; bool colValid = false; 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); - if (kelvin > 0) colorKtoRGB(kelvin, brgbw); + JsonObject oCol = colarr[i]; + if (!oCol.isNull()) { + // we have a JSON object for color {"w":123,"r":123,...}; allows individual channel control + rgbw[0] = oCol["r"] | R(seg.colors[i]); + rgbw[1] = oCol["g"] | G(seg.colors[i]); + rgbw[2] = oCol["b"] | B(seg.colors[i]); + rgbw[3] = oCol["w"] | W(seg.colors[i]); colValid = true; - } else { //HEX string, e.g. "FFAA00" - colValid = colorFromHexString(brgbw, hexCol); + } else { + 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); + if (kelvin > 0) colorKtoRGB(kelvin, brgbw); + colValid = true; + } else { //HEX string, e.g. "FFAA00" + colValid = colorFromHexString(brgbw, hexCol); + } + for (size_t c = 0; c < 4; c++) rgbw[c] = brgbw[c]; } - for (size_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 - copyArray(colX, rgbw, 4); colValid = true; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 7b753da3b..c64ea935c 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -241,9 +241,10 @@ void WLED::loop() DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) - if (psramSafe && psramFound()) { + if (psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); + if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); } #endif DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status()); @@ -369,10 +370,12 @@ void WLED::setup() #if defined(ARDUINO_ARCH_ESP32) #ifndef BOARD_HAS_PSRAM if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false; + if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); #endif pDoc = new PSRAMDynamicJsonDocument((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); + DEBUG_PRINT(F("JSON buffer allocated: ")); DEBUG_PRINTLN((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); // if the above fails requestJsonBufferLock() will always return false preventing crashes - if (psramSafe && psramFound()) { + if (psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } @@ -423,6 +426,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Reading config")); deserializeConfigFromFS(); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(STATUSLED) && STATUSLED>=0 if (!pinManager.isPinAllocated(STATUSLED)) { From 157dbffc593a967f9ac15719de243a4628d57bea Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 29 Mar 2024 18:56:43 +0100 Subject: [PATCH 127/694] Changelog update --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d70866f..c79827017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ## WLED changelog +#### Build 2403280 +- Individual color channel control for JSON API (fixes #3860) + - "col":[int|string|object|array, int|string|object|array, int|string|object|array] + int = Kelvin temperature or 0 for black + string = hex representation of [WW]RRGGBB + object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {}) + array = direct channel values [r,g,b,w] (w element being optional) +- runtime selection for CCT IC (Athom 15W bulb) +- #3850 (by @w00000dy) +- Rotary encoder palette count bugfix +- bugfixes and optimisations + #### Build 2403240 - v0.15.0-b2 - WS2805 support (RGB + WW + CW, 600kbps) From 6d278994ec1a4421580883f79701e96b9e58a67f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 29 Mar 2024 20:05:56 +0100 Subject: [PATCH 128/694] WLED 0.14.3 release - Fix for transition 0 (#3854, #3832, #3720) - copyright year update - updated AsyncWebServer to v2.2.0 --- CHANGELOG.md | 4 +++ package-lock.json | 4 +-- package.json | 2 +- platformio.ini | 7 ++-- wled00/data/settings_sec.htm | 2 +- wled00/html_other.h | 66 ++++++++++++++++++------------------ wled00/html_settings.h | 30 ++++++++-------- wled00/improv.cpp | 2 +- wled00/led.cpp | 2 ++ wled00/wled.h | 2 +- 10 files changed, 63 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8095a15d2..1cff856a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## WLED changelog +#### Build 2403290 +- WLED 0.14.3 release +- Fix for transition 0 (#3854, #3832, #3720) + #### Build 2403170 - WLED 0.14.2 release diff --git a/package-lock.json b/package-lock.json index 9bf4449eb..a7330143f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.2", + "version": "0.14.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.2", + "version": "0.14.3", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 8781cd6c8..3bd73b76d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.2", + "version": "0.14.3", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 914ed05b3..0adc18cf2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -63,7 +63,7 @@ arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_3_0_0 = espressif8266@3.0.0 arduino_core_3_2_0 = espressif8266@3.2.0 arduino_core_4_1_0 = espressif8266@4.1.0 -arduino_core_3_1_2 = espressif8266@4.2.0 +arduino_core_3_1_2 = espressif8266@4.2.1 # Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop @@ -73,8 +73,7 @@ arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/ platform_wled_default = ${common.arduino_core_3_1_2} # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization #platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 -platform_packages = platformio/framework-arduinoespressif8266 - platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 +platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 platformio/tool-esptool #@ ~1.413.0 platformio/tool-esptoolpy #@ ~1.30000.0 @@ -181,7 +180,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.0 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 0d9ec256a..2cb4a264a 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -134,7 +134,7 @@ WLED version ##VERSION##

Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

- (c) 2016-2023 Christian Schwinne
+ (c) 2016-2024 Christian Schwinne
Licensed under the MIT license

Server message: Response error!
diff --git a/wled00/html_other.h b/wled00/html_other.h index 2799fcdf3..3f9402615 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -43,45 +43,45 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; // Autogenerated from wled00/data/update.htm, do not edit!! const uint16_t PAGE_update_length = 613; const uint8_t PAGE_update[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0x5d, 0x6f, 0xd4, 0x30, - 0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x51, 0x1b, 0x2a, 0x5e, 0x28, 0x49, 0x0a, 0x47, - 0x2b, 0x54, 0x09, 0xa9, 0x95, 0xda, 0x82, 0x78, 0x42, 0x8e, 0xbd, 0xb9, 0x98, 0x73, 0xec, 0xd4, - 0xde, 0xdc, 0xe9, 0x84, 0xfa, 0xdf, 0xd9, 0x38, 0x77, 0x05, 0xf1, 0xf1, 0x12, 0xc5, 0xd9, 0xd9, - 0xf1, 0xee, 0xcc, 0xa4, 0x3c, 0xb9, 0xbc, 0xf9, 0x70, 0xff, 0xf5, 0xf6, 0x8a, 0x75, 0xd8, 0xbb, - 0xba, 0x3c, 0x3c, 0x41, 0x99, 0xba, 0xec, 0x01, 0x15, 0xd3, 0xc1, 0x23, 0x78, 0xac, 0xf8, 0xce, - 0x1a, 0xec, 0x2a, 0x03, 0x5b, 0xab, 0xe1, 0x34, 0x1f, 0x38, 0xf3, 0xaa, 0x87, 0x8a, 0x6f, 0x2d, - 0xec, 0x86, 0x10, 0x91, 0xd7, 0x45, 0x89, 0x16, 0x1d, 0xd4, 0x5f, 0x3e, 0x5d, 0x5d, 0xb2, 0x87, - 0xc1, 0x28, 0x84, 0x52, 0xce, 0x9f, 0xca, 0xa4, 0xa3, 0x1d, 0xb0, 0x2e, 0xda, 0xd1, 0x6b, 0xb4, - 0xc1, 0xb3, 0xd5, 0x62, 0xf9, 0x63, 0x67, 0xbd, 0x09, 0x3b, 0xd1, 0xd9, 0x84, 0x21, 0xee, 0x45, - 0xa3, 0xf4, 0x66, 0xb1, 0x7c, 0x7a, 0x86, 0x3c, 0x10, 0xc4, 0x04, 0x3d, 0xf6, 0x34, 0x81, 0x58, - 0x03, 0x5e, 0x39, 0x98, 0x5e, 0x57, 0xfb, 0x6b, 0xb3, 0xe0, 0x63, 0xcb, 0x97, 0x22, 0xe1, 0xde, - 0x81, 0x30, 0x36, 0x0d, 0x4e, 0xed, 0x2b, 0xee, 0x83, 0x07, 0xfe, 0xe2, 0xbf, 0x2d, 0x7d, 0x5a, - 0xff, 0xdd, 0xd3, 0xb8, 0xa0, 0x37, 0xfc, 0xa9, 0x28, 0xe5, 0x61, 0xc4, 0xc3, 0xa8, 0x2c, 0x45, - 0x5d, 0x71, 0x99, 0x00, 0xd1, 0xfa, 0x75, 0x92, 0x49, 0x7c, 0x4f, 0x17, 0x43, 0xf5, 0x86, 0xd7, - 0xbf, 0x21, 0x27, 0xaa, 0xba, 0x78, 0x67, 0xfb, 0x49, 0x00, 0x36, 0x46, 0xb7, 0xe0, 0x33, 0xbd, - 0x4e, 0x89, 0x2f, 0xdf, 0x12, 0x32, 0x23, 0x4a, 0x39, 0x4b, 0xda, 0x04, 0xb3, 0x67, 0xc1, 0xbb, - 0xa0, 0x4c, 0xc5, 0x3f, 0x02, 0x7e, 0x5e, 0x2c, 0x89, 0xae, 0x3b, 0xab, 0x8b, 0x2c, 0xd9, 0x5d, - 0x68, 0x71, 0xa7, 0x22, 0x3c, 0x6b, 0x47, 0x95, 0xb2, 0x0d, 0xb1, 0x67, 0xe4, 0x45, 0x17, 0xa8, - 0xe7, 0xf6, 0xe6, 0xee, 0x9e, 0x33, 0x95, 0xe5, 0xa9, 0xb8, 0x90, 0x63, 0x06, 0x72, 0x66, 0xa9, - 0x46, 0x82, 0xb0, 0x02, 0x48, 0xba, 0xfd, 0x40, 0xae, 0xf4, 0xa3, 0x43, 0x3b, 0xa8, 0x88, 0x72, - 0x22, 0x38, 0x25, 0x98, 0xe2, 0x74, 0x75, 0x1a, 0x9b, 0xde, 0x92, 0x9d, 0x0f, 0xd3, 0xcd, 0xd7, - 0x3e, 0xa1, 0x72, 0x0e, 0x0c, 0xdb, 0x42, 0x4c, 0x44, 0x79, 0xce, 0xca, 0x34, 0x28, 0xcf, 0x0a, - 0xed, 0x54, 0x4a, 0x15, 0x4f, 0x76, 0xe0, 0xf5, 0x4b, 0xf1, 0xea, 0xb5, 0x38, 0xa3, 0x55, 0xa8, - 0x42, 0x2b, 0xc4, 0xfa, 0x32, 0xec, 0xf2, 0x0a, 0x0c, 0x3b, 0x60, 0x8e, 0xee, 0x4f, 0xc8, 0x1a, - 0xeb, 0x55, 0xdc, 0x53, 0xbf, 0x62, 0x45, 0x17, 0xa1, 0xad, 0x78, 0x87, 0x38, 0xa4, 0x73, 0x29, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0xcb, 0x6e, 0xdb, 0x30, + 0x10, 0xbc, 0xeb, 0x2b, 0x18, 0x9e, 0x6c, 0xa0, 0x21, 0xfb, 0xba, 0x34, 0x95, 0x94, 0xd6, 0x4d, + 0x50, 0x04, 0x28, 0x90, 0x00, 0x49, 0x5a, 0xf4, 0x54, 0x50, 0xe4, 0xca, 0x62, 0x4d, 0x91, 0x0a, + 0xb9, 0xb2, 0x61, 0x14, 0xf9, 0xf7, 0xae, 0x28, 0x3b, 0x2d, 0xfa, 0xb8, 0x08, 0xa2, 0x76, 0x76, + 0xb8, 0x3b, 0x33, 0x2a, 0x4f, 0x2e, 0xae, 0x3f, 0xdc, 0x7d, 0xbd, 0xb9, 0x64, 0x1d, 0xf6, 0xae, + 0x2e, 0x0f, 0x4f, 0x50, 0xa6, 0x2e, 0x7b, 0x40, 0xc5, 0x74, 0xf0, 0x08, 0x1e, 0x2b, 0xbe, 0xb3, + 0x06, 0xbb, 0xca, 0xc0, 0xd6, 0x6a, 0x38, 0xcd, 0x07, 0xce, 0xbc, 0xea, 0xa1, 0xe2, 0x5b, 0x0b, + 0xbb, 0x21, 0x44, 0xe4, 0x75, 0x51, 0xa2, 0x45, 0x07, 0xf5, 0x97, 0x4f, 0x97, 0x17, 0xec, 0x7e, + 0x30, 0x0a, 0xa1, 0x94, 0xf3, 0xa7, 0x32, 0xe9, 0x68, 0x07, 0xac, 0x8b, 0x76, 0xf4, 0x1a, 0x6d, + 0xf0, 0x6c, 0xb5, 0x58, 0xfe, 0xd8, 0x59, 0x6f, 0xc2, 0x4e, 0x74, 0x36, 0x61, 0x88, 0x7b, 0xd1, + 0x28, 0xbd, 0x59, 0x2c, 0x1f, 0x9f, 0x20, 0xf7, 0x04, 0x31, 0x41, 0x8f, 0x3d, 0x4d, 0x20, 0xd6, + 0x80, 0x97, 0x0e, 0xa6, 0xd7, 0xd5, 0xfe, 0xca, 0x2c, 0xf8, 0xd8, 0xf2, 0xa5, 0x48, 0xb8, 0x77, + 0x20, 0x8c, 0x4d, 0x83, 0x53, 0xfb, 0x8a, 0xfb, 0xe0, 0x81, 0x3f, 0xfb, 0x6f, 0x4b, 0x9f, 0xd6, + 0x7f, 0xf7, 0x34, 0x2e, 0xe8, 0x0d, 0x7f, 0x2c, 0x4a, 0x79, 0x18, 0xf1, 0x30, 0x2a, 0x4b, 0x51, + 0x57, 0x5c, 0x26, 0x40, 0xb4, 0x7e, 0x9d, 0x64, 0x12, 0xdf, 0xd3, 0xf9, 0x50, 0xbd, 0xe1, 0xf5, + 0x6f, 0xc8, 0x89, 0xaa, 0x2e, 0xde, 0xd9, 0x7e, 0x12, 0x80, 0x8d, 0xd1, 0x2d, 0xf8, 0x4c, 0xaf, + 0x53, 0xe2, 0xcb, 0xb7, 0x84, 0xcc, 0x88, 0x52, 0xce, 0x92, 0x36, 0xc1, 0xec, 0x59, 0xf0, 0x2e, + 0x28, 0x53, 0xf1, 0x8f, 0x80, 0x9f, 0x17, 0x4b, 0xa2, 0xeb, 0x5e, 0xd6, 0x45, 0x96, 0xec, 0x36, + 0xb4, 0xb8, 0x53, 0x11, 0x9e, 0xb4, 0xa3, 0x4a, 0xd9, 0x86, 0xd8, 0x33, 0xf2, 0xa2, 0x0b, 0xd4, + 0x73, 0x73, 0x7d, 0x7b, 0xc7, 0x99, 0xca, 0xf2, 0x54, 0x5c, 0xc8, 0x31, 0x03, 0x39, 0xb3, 0x54, + 0x23, 0x41, 0x58, 0x01, 0x24, 0xdd, 0x7e, 0x20, 0x57, 0xfa, 0xd1, 0xa1, 0x1d, 0x54, 0x44, 0x39, + 0x11, 0x9c, 0x12, 0x4c, 0x71, 0xba, 0x3a, 0x8d, 0x4d, 0x6f, 0xc9, 0xce, 0xfb, 0xe9, 0xe6, 0x2b, + 0x9f, 0x50, 0x39, 0x07, 0x86, 0x6d, 0x21, 0x26, 0xa2, 0x3c, 0x63, 0x65, 0x1a, 0x94, 0x67, 0x85, + 0x76, 0x2a, 0xa5, 0x8a, 0x27, 0x3b, 0xf0, 0xfa, 0xb9, 0x78, 0xf1, 0x5a, 0xbc, 0xa2, 0x55, 0xa8, + 0x42, 0x2b, 0xc4, 0xfa, 0x22, 0xec, 0xf2, 0x0a, 0x0c, 0x3b, 0x60, 0x8e, 0xee, 0x4f, 0xc8, 0x1a, + 0xeb, 0x55, 0xdc, 0x53, 0xbf, 0x62, 0x45, 0x17, 0xa1, 0xad, 0x78, 0x87, 0x38, 0xa4, 0x33, 0x29, 0xd7, 0x16, 0xbb, 0xb1, 0x11, 0x3a, 0xf4, 0xf2, 0xbd, 0x8d, 0x3a, 0x84, 0xb0, 0xb1, 0x20, 0xa7, 0x7d, 0x65, 0x04, 0x07, 0x2a, 0x41, 0xe2, 0x0c, 0x55, 0x24, 0xb3, 0x2a, 0xfe, 0xad, 0x71, 0xca, 0x6f, 0x48, 0x13, 0xdb, 0xaf, 0x59, 0x91, 0x1d, 0x38, 0xf2, 0xd0, 0x17, 0x91, 0x3a, 0x0b, 0xce, - 0x24, 0x61, 0xc3, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x48, 0xdb, 0xf5, 0x45, 0xd6, 0xbe, 0x6a, - 0x69, 0xc2, 0xd3, 0xf4, 0x38, 0x92, 0xae, 0x53, 0x42, 0xa5, 0xca, 0x3b, 0x94, 0xd6, 0x0f, 0x23, - 0xb2, 0x59, 0xab, 0xd6, 0x3a, 0x38, 0xa6, 0xf9, 0xa8, 0x68, 0x84, 0xc7, 0xd1, 0x46, 0x30, 0x33, + 0x24, 0x61, 0xc3, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x48, 0xdb, 0xf5, 0x79, 0xd6, 0xbe, 0x6a, + 0x69, 0xc2, 0xd3, 0xf4, 0x30, 0x92, 0xae, 0x53, 0x42, 0xa5, 0xca, 0x3b, 0x94, 0xd6, 0x0f, 0x23, + 0xb2, 0x59, 0xab, 0xd6, 0x3a, 0x38, 0xa6, 0xf9, 0xa8, 0x68, 0x84, 0x87, 0xd1, 0x46, 0x30, 0x33, 0xba, 0x19, 0x11, 0x29, 0x90, 0x33, 0x7c, 0xd6, 0x90, 0xc8, 0x66, 0x9b, 0x4e, 0x4a, 0x39, 0x97, 0xff, 0x01, 0x9d, 0x0f, 0x93, 0xf0, 0xda, 0x59, 0xbd, 0xa9, 0xf8, 0x6a, 0xd2, 0x7d, 0x45, 0x39, 0xff, 0xd5, 0x94, 0x0d, 0xaa, 0x4b, 0x63, 0xb7, 0x45, 0xf6, 0x71, 0x4a, 0x29, 0xd1, 0xd4, 0x99, - 0x9d, 0xa2, 0x27, 0x84, 0x20, 0x70, 0x26, 0xbf, 0xcd, 0xcb, 0x32, 0x13, 0x98, 0x0f, 0xc8, 0xb4, - 0x0b, 0x74, 0x08, 0x91, 0x66, 0x6d, 0x23, 0xa4, 0x2e, 0xfb, 0x31, 0xa8, 0x35, 0xb0, 0xf3, 0x65, - 0x29, 0x89, 0x6f, 0x5a, 0x77, 0x8a, 0xdc, 0x94, 0xbf, 0xe9, 0xc7, 0xfe, 0x09, 0x3d, 0x8c, 0x87, - 0xc8, 0xee, 0x03, 0x00, 0x00 + 0x9d, 0xa2, 0x27, 0x84, 0x20, 0x70, 0x26, 0xbf, 0xc9, 0xcb, 0x32, 0x13, 0x98, 0x0f, 0xc8, 0xb4, + 0x0b, 0x74, 0x08, 0x91, 0x66, 0x6d, 0x23, 0xa4, 0x2e, 0xfb, 0x31, 0xa8, 0x35, 0xb0, 0xb3, 0x65, + 0x29, 0x89, 0x6f, 0x5a, 0x77, 0x8a, 0xdc, 0x94, 0xbf, 0xe9, 0xc7, 0xfe, 0x09, 0x81, 0xae, 0xc2, + 0xc6, 0xee, 0x03, 0x00, 0x00 }; diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 62d3404dc..a1bd61e9e 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1753,21 +1753,21 @@ const uint8_t PAGE_settings_sec[] PROGMEM = { 0xcb, 0xf1, 0x18, 0xb7, 0xdc, 0x36, 0x49, 0x9a, 0x44, 0x68, 0x3f, 0x23, 0xa1, 0xe0, 0x66, 0xd5, 0x38, 0xc4, 0xd5, 0xbd, 0x77, 0x2c, 0x75, 0xa2, 0x94, 0x9a, 0x49, 0xd1, 0xa3, 0x1e, 0xbc, 0x87, 0x3a, 0x1d, 0xeb, 0x29, 0x7d, 0xee, 0xfa, 0x38, 0xce, 0x51, 0x22, 0xf9, 0x80, 0x86, 0x29, 0x08, - 0x3a, 0xec, 0x4e, 0x68, 0xa2, 0x19, 0xb6, 0x1b, 0xee, 0x3d, 0x0f, 0xf7, 0x57, 0x44, 0xf5, 0x33, - 0xe2, 0x17, 0x72, 0x26, 0x7b, 0xee, 0xce, 0x46, 0xed, 0xab, 0xd2, 0x66, 0x07, 0x54, 0xb8, 0x93, - 0x68, 0x91, 0x4a, 0x8a, 0xd0, 0xce, 0xfd, 0xc3, 0x37, 0x97, 0xa2, 0x54, 0x0b, 0xba, 0x7d, 0x82, - 0xfc, 0xa4, 0xa8, 0x39, 0xd4, 0x94, 0x22, 0x91, 0x20, 0x1c, 0x8b, 0x72, 0x30, 0x5b, 0xe5, 0x6d, - 0xe7, 0x98, 0x65, 0x15, 0xaa, 0x85, 0x1b, 0x75, 0x05, 0x11, 0xbc, 0x8f, 0x3b, 0x97, 0x5e, 0xaa, - 0x42, 0xa0, 0x50, 0x29, 0x46, 0x7d, 0x1e, 0xe0, 0x02, 0x33, 0xd6, 0xdf, 0x89, 0xdc, 0xfd, 0xa3, - 0xdb, 0x5a, 0xd4, 0xf1, 0x12, 0x9f, 0xed, 0xef, 0xee, 0xfd, 0xb6, 0xb3, 0xbf, 0xbb, 0xff, 0x8c, - 0xbd, 0xca, 0x34, 0x6e, 0xda, 0x12, 0xd4, 0x39, 0x4c, 0xb2, 0x05, 0x7d, 0xa7, 0x68, 0x6a, 0xd4, - 0x05, 0xaa, 0x17, 0xae, 0x7e, 0x80, 0x1d, 0x4a, 0xd5, 0x25, 0xf6, 0xa7, 0xc0, 0x18, 0xe7, 0x6a, - 0xdc, 0x9b, 0xe3, 0x8a, 0x2e, 0x74, 0xef, 0xe2, 0xfc, 0xd5, 0xd9, 0xdb, 0xe1, 0xd9, 0xb7, 0xf0, - 0x77, 0x2e, 0xcf, 0x47, 0x2c, 0xaf, 0x4f, 0x72, 0x06, 0xa2, 0xae, 0xb5, 0x8a, 0x0e, 0x85, 0x86, - 0x55, 0x30, 0xc3, 0x18, 0x54, 0x47, 0xb0, 0x29, 0xee, 0xca, 0x45, 0x4b, 0x03, 0x46, 0x96, 0x9c, - 0x12, 0xd6, 0x7d, 0x4f, 0x60, 0xee, 0x13, 0x01, 0xf1, 0x3c, 0x2d, 0xa9, 0x49, 0x95, 0x92, 0x84, - 0x28, 0xa2, 0xfe, 0x06, 0xf3, 0xdd, 0x2e, 0xf6, 0xdb, 0x26, 0xb6, 0xf3, 0x53, 0x5d, 0x6c, 0x8f, - 0x4a, 0x0d, 0x7e, 0xa8, 0x79, 0xa7, 0x4e, 0x9e, 0xbe, 0xec, 0xfe, 0x17, 0x52, 0x67, 0xe8, 0x26, + 0x3a, 0xec, 0x4e, 0x68, 0xa2, 0x19, 0xb6, 0x1b, 0xee, 0x3d, 0x0f, 0x9f, 0xad, 0x88, 0xea, 0x67, + 0xc4, 0x2f, 0xe4, 0x4c, 0xf6, 0xdc, 0x9d, 0x8d, 0xda, 0x57, 0xa5, 0xcd, 0x0e, 0xa8, 0x70, 0x27, + 0xd1, 0x22, 0x95, 0x14, 0xa1, 0x9d, 0xfb, 0x87, 0x6f, 0x2e, 0x45, 0xa9, 0x16, 0x74, 0xfb, 0x04, + 0xf9, 0x49, 0x51, 0x73, 0xa8, 0x29, 0x45, 0x22, 0x41, 0x38, 0x16, 0xe5, 0x60, 0xb6, 0xca, 0xdb, + 0xce, 0x31, 0xcb, 0x2a, 0x54, 0x0b, 0x37, 0xea, 0x0a, 0x22, 0x78, 0x1f, 0x77, 0x2e, 0xbd, 0x54, + 0x85, 0x40, 0xa1, 0x52, 0x8c, 0xfa, 0x3c, 0xc0, 0x05, 0x66, 0xac, 0xbf, 0x13, 0xb9, 0xfb, 0x47, + 0xb7, 0xb5, 0xa8, 0xe3, 0x25, 0x3e, 0xdb, 0xdf, 0xdd, 0xfb, 0x6d, 0x67, 0x7f, 0x77, 0xff, 0x39, + 0x7b, 0x95, 0x69, 0xdc, 0xb4, 0x25, 0xa8, 0x73, 0x98, 0x64, 0x0b, 0xfa, 0x4e, 0xd1, 0xd4, 0xa8, + 0x0b, 0x54, 0x2f, 0x5c, 0xfd, 0x00, 0x3b, 0x94, 0xaa, 0x4b, 0xec, 0x4f, 0x81, 0x31, 0xce, 0xd5, + 0xb8, 0x37, 0xc7, 0x15, 0x5d, 0xe8, 0xde, 0xc5, 0xf9, 0xab, 0xb3, 0xb7, 0xc3, 0xb3, 0x6f, 0xe1, + 0xef, 0x5c, 0x9e, 0x8f, 0x58, 0x5e, 0x9f, 0xe4, 0x0c, 0x44, 0x5d, 0x6b, 0x15, 0x1d, 0x0a, 0x0d, + 0xab, 0x60, 0x86, 0x31, 0xa8, 0x8e, 0x60, 0x53, 0xdc, 0x95, 0x8b, 0x96, 0x06, 0x8c, 0x2c, 0x39, + 0x25, 0xac, 0xfb, 0x9e, 0xc0, 0xdc, 0x27, 0x02, 0xe2, 0x79, 0x5a, 0x52, 0x93, 0x2a, 0x25, 0x09, + 0x51, 0x44, 0xfd, 0x0d, 0xe6, 0xbb, 0x5d, 0xec, 0xb7, 0x4d, 0x6c, 0xe7, 0xa7, 0xba, 0xd8, 0x1e, + 0x95, 0x1a, 0xfc, 0x50, 0xf3, 0x4e, 0x9d, 0x3c, 0x7d, 0xd9, 0xfd, 0x2f, 0x9b, 0x42, 0x3b, 0x5f, 0xe9, 0x15, 0x00, 0x00 }; diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 2267c3591..d7d9ad05d 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.14.2/%i"), VERSION); + sprintf_P(vString, PSTR("0.14.3/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/led.cpp b/wled00/led.cpp index e668f3340..efaab2706 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -137,6 +137,8 @@ void stateUpdated(byte callMode) { if (strip.getTransition() == 0) { jsonTransitionOnce = false; transitionActive = false; + applyFinalBri(); + strip.trigger(); return; } diff --git a/wled00/wled.h b/wled00/wled.h index 9f6ba88fd..ba38c5c79 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403170 +#define VERSION 2403290 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 20ed81cd86a7eb3ee1ffcfad4d7c3b2cf84d2a3a Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 30 Mar 2024 11:02:28 +0100 Subject: [PATCH 129/694] Prefer I2S1 over RMT on ESP32 - remove Audioreactive limitation --- wled00/bus_manager.cpp | 2 +- wled00/bus_wrapper.h | 37 +++++++++++++++++++++++++++++-------- wled00/const.h | 20 ++++++-------------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 88b4cc32b..764ab6e2b 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -129,7 +129,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); _valid = (_busPtr != nullptr); - DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n", _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], _pins[1], _iType, _milliAmpsPerLed, _milliAmpsMax); + DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], IS_2PIN(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); } //fine tune power estimation constants for your setup diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index d13b1a945..ebbeca4ad 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -206,81 +206,101 @@ //RGB #define B_32_RN_NEO_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_3 NeoPixelBusLg +#define B_32_I0_NEO_3 NeoPixelBusLg +//#define B_32_I0_NEO_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_3 NeoPixelBusLg +#define B_32_I1_NEO_3 NeoPixelBusLg +//#define B_32_I1_NEO_3 NeoPixelBusLg // parallel I2S #endif //RGBW -#define B_32_RN_NEO_4 NeoPixelBusLg +#define B_32_RN_NEO_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_4 NeoPixelBusLg +#define B_32_I0_NEO_4 NeoPixelBusLg +//#define B_32_I0_NEO_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_4 NeoPixelBusLg +#define B_32_I1_NEO_4 NeoPixelBusLg +//#define B_32_I1_NEO_4 NeoPixelBusLg // parallel I2S #endif //400Kbps #define B_32_RN_400_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_400_3 NeoPixelBusLg +//#define B_32_I0_400_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_400_3 NeoPixelBusLg +//#define B_32_I1_400_3 NeoPixelBusLg // parallel I2S #endif //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1_4 NeoPixelBusLg +//#define B_32_I0_TM1_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM1_4 NeoPixelBusLg +//#define B_32_I1_TM1_4 NeoPixelBusLg // parallel I2S #endif //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM2_3 NeoPixelBusLg +//#define B_32_I0_TM2_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM2_3 NeoPixelBusLg +//#define B_32_I1_TM2_3 NeoPixelBusLg // parallel I2S #endif //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_3 NeoPixelBusLg +//#define B_32_I0_UCS_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_3 NeoPixelBusLg +//#define B_32_I1_UCS_3 NeoPixelBusLg // parallel I2S #endif //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_4 NeoPixelBusLg +//#define B_32_I0_UCS_4 NeoPixelBusLg// parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_4 NeoPixelBusLg +//#define B_32_I1_UCS_4 NeoPixelBusLg// parallel I2S #endif #define B_32_RN_APA106_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_APA106_3 NeoPixelBusLg +//#define B_32_I0_APA106_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_APA106_3 NeoPixelBusLg +//#define B_32_I1_APA106_3 NeoPixelBusLg // parallel I2S #endif //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_FW6_5 NeoPixelBusLg +//#define B_32_I0_FW6_5 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_FW6_5 NeoPixelBusLg +//#define B_32_I1_FW6_5 NeoPixelBusLg // parallel I2S #endif //WS2805 RGBWC #define B_32_RN_2805_5 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_2805_5 NeoPixelBusLg +//#define B_32_I0_2805_5 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_2805_5 NeoPixelBusLg +//#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S #endif #endif @@ -1458,11 +1478,11 @@ class PolyBus { return I_8266_U0_2805_5 + offset; } #else //ESP32 - uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 + uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 #if defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 only has 4 RMT channels if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S + if (num > 3) offset = 1; // only one I2S (use last to allow Audioreactive) #elif defined(CONFIG_IDF_TARGET_ESP32C3) // On ESP32-C3 only the first 2 RMT channels are usable for transmitting if (num > 1) return I_NONE; @@ -1474,7 +1494,8 @@ class PolyBus { #else // standard ESP32 has 8 RMT and 2 I2S channels if (num > 9) return I_NONE; - if (num > 7) offset = num -7; + if (num > 8) offset = 1; + if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) #endif switch (busType) { case TYPE_WS2812_1CH_X3: diff --git a/wled00/const.h b/wled00/const.h index de07c314a..c8a4b7ffe 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -53,24 +53,16 @@ #define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around) #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB - #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 - #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog - #define WLED_MIN_VIRTUAL_BUSSES 4 - #else - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog - #define WLED_MIN_VIRTUAL_BUSSES 3 - #endif + // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) + #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog + #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog #define WLED_MIN_VIRTUAL_BUSSES 4 #else - #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 - #define WLED_MAX_BUSSES 8 - #define WLED_MIN_VIRTUAL_BUSSES 2 - #else - #define WLED_MAX_BUSSES 10 - #define WLED_MIN_VIRTUAL_BUSSES 0 - #endif + // the 10th digital bus (I2S0) will prevent Audioreactive usermod from functioning (it is last used though) + #define WLED_MAX_BUSSES 10 + #define WLED_MIN_VIRTUAL_BUSSES 0 #endif #endif #else From 6f7ac93d84fbc3a6b40e3833f98f422eb8fdd9f3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 1 Apr 2024 11:25:31 -0400 Subject: [PATCH 130/694] Update to AsyncWebServer v2.2.1 Fix use-after-free issue and slightly improve code size. Note that the version is changed to a hard pin, so future updates can be validated before getting picked up by new clones, and old version builds are reproducible. --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 0e09b499b..c65805622 100644 --- a/platformio.ini +++ b/platformio.ini @@ -143,7 +143,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.9 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.0 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 # for I2C interface ;Wire # ESP-NOW library From a7e17eabff784daa1599ad0dd21a4bb6e1a529a9 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 2 Apr 2024 20:12:14 +0200 Subject: [PATCH 131/694] add cdata test for package.json & fix a small bug --- tools/cdata-test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/cdata-test.js b/tools/cdata-test.js index 55f068073..7c2f11bd9 100644 --- a/tools/cdata-test.js +++ b/tools/cdata-test.js @@ -131,7 +131,7 @@ describe('Script', () => { // run script cdata.js again and wait for it to finish await execPromise('node tools/cdata.js'); - checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); + await checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); } describe('should build if', () => { @@ -182,6 +182,10 @@ describe('Script', () => { it('cdata.js changes', async () => { await testFileModification('tools/cdata.js', 'html_ui.h'); }); + + it('package.json changes', async () => { + await testFileModification('package.json', 'html_ui.h'); + }); }); describe('should not build if', () => { From 4db88cf86b7e91fd9c2f50656c17da1fd7bf0ccb Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 2 Apr 2024 20:38:56 +0200 Subject: [PATCH 132/694] update cdata.js to rebuild if package.json changes --- tools/cdata.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/cdata.js b/tools/cdata.js index 3b8af46da..12dda1cbe 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -2,7 +2,7 @@ * Writes compressed C arrays of data files (web interface) * How to use it? * - * 1) Install Node 11+ and npm + * 1) Install Node 20+ and npm * 2) npm install * 3) npm run build * @@ -207,7 +207,7 @@ function isAnyFileInFolderNewerThan(folderPath, time) { } // Check if the web UI is already built -function isAlreadyBuilt(folderPath) { +function isAlreadyBuilt(webUIPath, packageJsonPath = "package.json") { let lastBuildTime = Infinity; for (const file of output) { @@ -220,7 +220,7 @@ function isAlreadyBuilt(folderPath) { } } - return !isAnyFileInFolderNewerThan(folderPath, lastBuildTime) && !isFileNewerThan("tools/cdata.js", lastBuildTime); + return !isAnyFileInFolderNewerThan(webUIPath, lastBuildTime) && !isFileNewerThan(packageJsonPath, lastBuildTime) && !isFileNewerThan(__filename, lastBuildTime); } // Don't run this script if we're in a test environment From 78b37b592e72e5abdf9d06a307bc1b56277a306e Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 2 Apr 2024 20:59:31 +0200 Subject: [PATCH 133/694] backup & restore package.json in cdata tests --- tools/cdata-test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/cdata-test.js b/tools/cdata-test.js index 7c2f11bd9..6f27fb717 100644 --- a/tools/cdata-test.js +++ b/tools/cdata-test.js @@ -83,6 +83,7 @@ describe('Script', () => { // Backup files fs.cpSync("wled00/data", "wled00Backup", { recursive: true }); fs.cpSync("tools/cdata.js", "cdata.bak.js"); + fs.cpSync("package.json", "package.bak.json"); }); after(() => { // Restore backup @@ -90,6 +91,8 @@ describe('Script', () => { fs.renameSync("wled00Backup", "wled00/data"); fs.rmSync("tools/cdata.js"); fs.renameSync("cdata.bak.js", "tools/cdata.js"); + fs.rmSync("package.json"); + fs.renameSync("package.bak.json", "package.json"); }); // delete all html_*.h files From 02405b4856d75a0cd29bf8ff5ba9239562ec8072 Mon Sep 17 00:00:00 2001 From: Woody Date: Wed, 3 Apr 2024 12:05:15 +0200 Subject: [PATCH 134/694] Create stale.yml --- .github/workflows/stale.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..4d531fd51 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '0 12 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 120 + days-before-close: 7 + exempt-issue-labels: 'pinned,keep,enhancement,confirmed' + exempt-pr-labels: 'pinned,keep,enhancement,confirmed' + stale-issue-message: > + Hey! This issue has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for using WLED! ✨ + stale-pr-message: > + Hey! This pull request has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for using WLED! ✨ + debug-only: true From bff6697690cd6a3de72f183403949e75644a3e00 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 1 Apr 2024 11:25:31 -0400 Subject: [PATCH 135/694] Update to AsyncWebServer v2.2.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 0adc18cf2..cbe13c0dd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -180,7 +180,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.0 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following From 24c593566182c78510d1070f7247aca48f5f47fc Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 18:31:17 +0200 Subject: [PATCH 136/694] Fix for missing firmware build updated changelog (missing credit) --- CHANGELOG.md | 2 +- pio-scripts/output_bins.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c79827017..46f6df2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,7 +76,7 @@ #### Build 2309120 till build 2402010 - WLED version 0.15.0-a0 -- Multi-WiFi support. Add up to 3 (or more via cusom compile) WiFis to connect to +- Multi-WiFi support. Add up to 3 (or more via cusom compile) WiFis to connect to (with help from @JPZV) - Temporary AP. Use your WLED in public with temporary AP. - Github CI build system enhancements (#3718 by @WoodyLetsCode) - Accessibility: Node list ( #3715 by @WoodyLetsCode) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index e4a458a9e..c0e85dcbb 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -3,7 +3,8 @@ import os import shutil import gzip -OUTPUT_DIR = os.path.join("build_output") +OUTPUT_DIR = "build_output{}".format(os.path.sep) +#OUTPUT_DIR = os.path.join("build_output") def _get_cpp_define_value(env, define): define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define] @@ -13,7 +14,7 @@ def _get_cpp_define_value(env, define): return None -def _create_dirs(dirs=["map", "release"]): +def _create_dirs(dirs=["map", "release", "firmware"]): for d in dirs: os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) @@ -26,6 +27,11 @@ def create_release(source): print(f"Copying {source} to {release_file}") shutil.copy(source, release_file) bin_gzip(release_file, release_gz_file) + else: + variant = env["PIOENV"] + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + print(f"Copying {source} to {bin_file}") + shutil.copy(source, bin_file) def bin_rename_copy(source, target, env): _create_dirs() From 00f5471270757e435753d7bbce46870ba1144cca Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 4 Apr 2024 21:59:41 +0200 Subject: [PATCH 137/694] Build bump, changelog udate --- CHANGELOG.md | 3 ++- wled00/wled.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cff856a4..f1fe5cbdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ ## WLED changelog -#### Build 2403290 +#### Build 2404040 - WLED 0.14.3 release - Fix for transition 0 (#3854, #3832, #3720) +- Fix for #3855 via #3873 (by @willmmiles) #### Build 2403170 - WLED 0.14.2 release diff --git a/wled00/wled.h b/wled00/wled.h index ba38c5c79..5834025c6 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.14.2 + @version 0.14.3 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2403290 +#define VERSION 2404040 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From a106342439017882a7756e00ed1d1696108e7a88 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 4 Apr 2024 23:09:59 +0200 Subject: [PATCH 138/694] PICO & WROVER runtime support return of GPIO16 --- wled00/const.h | 8 ++++---- wled00/pin_manager.cpp | 3 ++- wled00/wled.cpp | 8 ++------ wled00/xml.cpp | 11 +++-------- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/wled00/const.h b/wled00/const.h index cd0deed57..0ce7b27d5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -508,11 +508,11 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -//#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO) +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) //|| (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARDUINO_ESP32_PICO) #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board -//#else -// #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards -//#endif +#else + #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) +#endif #endif #ifdef WLED_ENABLE_DMX diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 34705ee94..dd00943d8 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -248,7 +248,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const // 00 to 18 are for general use. Be careful about straping pins GPIO0 and GPIO3 - these may be pulled-up or pulled-down on your board. if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses. if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH - //if (gpio > 32 && gpio < 38) return false; // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM // 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // strapping pins: 0, 45 & 46 @@ -257,6 +257,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const // GPIO46 is input only and pulled down #else if (gpio > 5 && gpio < 12) return false; //SPI flash pins + if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) #endif if (output) return digitalPinCanOutput(gpio); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c64ea935c..c0d17beb7 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -368,7 +368,8 @@ void WLED::setup() DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) - #ifndef BOARD_HAS_PSRAM + // BOARD_HAS_PSRAM also means that a compiler flag "-mfix-esp32-psram-cache-issue" was used and so PSRAM is safe to use on rev.1 ESP32 + #if !defined(BOARD_HAS_PSRAM) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false; if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); #endif @@ -380,11 +381,6 @@ void WLED::setup() DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } #endif -#if defined(ARDUINO_ESP32_PICO) - // special handling for PICO-D4: gpio16+17 are in use for onboard SPI FLASH (not PSRAM) - managed_pin_type pins[] = { {16, true}, {17, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); -#endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output diff --git a/wled00/xml.cpp b/wled00/xml.cpp index c91f0dd7e..3915d9b0e 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -145,10 +145,13 @@ void appendGPIOinfo() { oappend(SET_F("d.rsvd=[22,23,24,25,26,27,28,29,30,31,32")); #elif defined(CONFIG_IDF_TARGET_ESP32S3) oappend(SET_F("d.rsvd=[19,20,22,23,24,25,26,27,28,29,30,31,32")); // includes 19+20 for USB OTG (JTAG) + if (psramFound()) oappend(SET_F(",33,34,35,36,37")); // in use for "octal" PSRAM or "octal" FLASH -seems that octal PSRAM is very common on S3. #elif defined(CONFIG_IDF_TARGET_ESP32C3) oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17")); #elif defined(ESP32) oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31,37,38")); + if (!pinManager.isPinOk(16,false)) oappend(SET_F(",16")); // covers PICO & WROVER + if (!pinManager.isPinOk(17,false)) oappend(SET_F(",17")); // covers PICO & WROVER #else oappend(SET_F("d.rsvd=[6,7,8,9,10,11")); #endif @@ -163,14 +166,6 @@ void appendGPIOinfo() { //Note: Using pin 3 (RX) disables Adalight / Serial JSON - #if defined(ARDUINO_ARCH_ESP32) - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM on ESP32 (not on S2, S3 or C3) - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - if (psramFound()) oappend(SET_F(",33,34,35,36,37")); // in use for "octal" PSRAM or "octal" FLASH -seems that octal PSRAM is very common on S3. - #endif - #endif - #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { for (uint8_t p=0; p Date: Fri, 5 Apr 2024 07:23:28 +0200 Subject: [PATCH 139/694] Bugfix: millis rollover (fix for #3870) millis()/1000 rollover after 18h was not handled, truncating to 16bits after division fixes it. --- wled00/FX_fcn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 617558ffa..fcf6cd0cb 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -455,9 +455,9 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { + if (((millis()/1000U) & 0xFFFF) - _lastPaletteChange > randomPaletteChangeTime) { _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; + _lastPaletteChange = millis()/1000U; //take lower 16bits _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately } @@ -466,7 +466,7 @@ void Segment::handleRandomPalette() { // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) // in reality there need to be 255 blends to fully blend two entirely different palettes if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = millis(); + _lastPaletteBlend = millis(); //take lower 16bits } nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } From aa970d6ca5773affe33a1c5a322bfe359802e048 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 5 Apr 2024 19:26:09 +0200 Subject: [PATCH 140/694] Extend JSON API info object - add "clock" - CPU clock in MHz - add "flash" - flash size in MB Fix for #3879 --- wled00/data/index.js | 6 +++--- wled00/json.cpp | 4 ++++ wled00/wled.cpp | 3 ++- wled00/wled.h | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 4ad2044ad..03ee276a8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -694,8 +694,6 @@ function parseInfo(i) { function populateInfo(i) { var cn=""; - var heap = i.freeheap/1024; - heap = heap.toFixed(1); var pwr = i.leds.pwr; var pwru = "Not calculated"; if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} @@ -720,11 +718,13 @@ ${inforow("Build",i.vid)} ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} ${inforow("Uptime",getRuntimeStr(i.uptime))} ${inforow("Time",i.time)} -${inforow("Free heap",heap," kB")} +${inforow("Free heap",(i.freeheap/1024).toFixed(1)," kB")} ${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""} ${inforow("Estimated current",pwru)} ${inforow("Average FPS",i.leds.fps)} ${inforow("MAC address",i.mac)} +${inforow("CPU clock",i.clock," MHz")} +${inforow("Flash size",i.flash," MB")} ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} `; diff --git a/wled00/json.cpp b/wled00/json.cpp index fd1527a21..bc4d1808a 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -749,6 +749,8 @@ void serializeInfo(JsonObject root) root[F("arch")] = ESP.getChipModel(); #endif root[F("core")] = ESP.getSdkVersion(); + root[F("clock")] = ESP.getCpuFreqMHz(); + root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024; #ifdef WLED_DEBUG root[F("maxalloc")] = ESP.getMaxAllocHeap(); root[F("resetReason0")] = (int)rtc_get_reset_reason(0); @@ -758,6 +760,8 @@ void serializeInfo(JsonObject root) #else root[F("arch")] = "esp8266"; root[F("core")] = ESP.getCoreVersion(); + root[F("clock")] = ESP.getCpuFreqMHz(); + root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024; #ifdef WLED_DEBUG root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c0d17beb7..eb7860851 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -362,8 +362,9 @@ void WLED::setup() DEBUG_PRINT(F(", speed ")); DEBUG_PRINT(ESP.getFlashChipSpeed()/1000000);DEBUG_PRINTLN(F("MHz.")); #else - DEBUG_PRINT(F("esp8266 ")); + DEBUG_PRINT(F("esp8266 @ ")); DEBUG_PRINT(ESP.getCpuFreqMHz()); DEBUG_PRINT(F("MHz.\nCore: ")); DEBUG_PRINTLN(ESP.getCoreVersion()); + DEBUG_PRINT(F("FLASH: ")); DEBUG_PRINT((ESP.getFlashChipSize()/1024)/1024); DEBUG_PRINTLN(F(" MB")); #endif DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); diff --git a/wled00/wled.h b/wled00/wled.h index 35b99260a..f96be3c1b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403280 +#define VERSION 2404050 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 9ffcde878ac3500d8c00e3288f9296a6dceac6e8 Mon Sep 17 00:00:00 2001 From: gaaat98 <67930088+gaaat98@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:40:32 +0200 Subject: [PATCH 141/694] fix for #3884 --- wled00/bus_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 764ab6e2b..82e81a387 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -505,7 +505,7 @@ void BusPwm::show() { uint8_t numPins = NUM_PWM_PINS(_type); unsigned maxBri = (1<<_depth) - 1; #ifdef ESP8266 - unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri + 0.5f)); // using gamma 1.7 to extrapolate PWM duty cycle + unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri)); // using gamma 1.7 to extrapolate PWM duty cycle #else unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT #endif From d3a97f106222d208011dd84f3aa5781579afd20e Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 7 Apr 2024 13:49:17 +0200 Subject: [PATCH 142/694] removed detachinterrupt from pin manager, added it to set.cpp instead --- wled00/pin_manager.cpp | 5 ----- wled00/set.cpp | 6 +++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index e80583464..044dc6c92 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -32,11 +32,6 @@ bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) return false; } - #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state, detach any previous assignments - if (digitalPinToTouchChannel(gpio) >= 0) //if touch capable pin - touchDetachInterrupt(gpio); - #endif - byte by = gpio >> 3; byte bi = gpio - 8*by; bitWrite(pinAlloc[by], bi, false); diff --git a/wled00/set.cpp b/wled00/set.cpp index 8a461fbc8..0920588fe 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -108,6 +108,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) for (uint8_t s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { pinManager.deallocatePin(btnPin[s], PinOwner::Button); + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt + if (digitalPinToTouchChannel(btnPin[i]) >= 0) // if touch capable pin + touchDetachInterrupt(btnPin[i]); // if not assigned previously, this will do nothing + #endif } } @@ -270,7 +274,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) #endif } else -#endif + #endif { if (disablePullUp) { pinMode(btnPin[i], INPUT); From 18c17168e19eb41f63dd29319146a96160192393 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 7 Apr 2024 16:07:37 +0200 Subject: [PATCH 143/694] replaced &0xFFFF with explicit casts --- wled00/FX_fcn.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index fcf6cd0cb..2a3cd0897 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -455,18 +455,18 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if (((millis()/1000U) & 0xFFFF) - _lastPaletteChange > randomPaletteChangeTime) { + if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){ _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; //take lower 16bits - _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + _lastPaletteChange = (uint16_t)(millis() / 1000U); + _lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately } // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) if (strip.paletteFade) { // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = millis(); //take lower 16bits + if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); } nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } From 954f26308b66e19f4a1ad11fc5772b9c79f1e508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 7 Apr 2024 22:12:01 +0200 Subject: [PATCH 144/694] Update button.cpp Indentation fix --- wled00/button.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index e3bd47882..06683e17a 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -100,18 +100,14 @@ bool isButtonPressed(uint8_t i) case BTN_TYPE_TOUCH_SWITCH: #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt) - if (touchInterruptGetLastStatus(pin)) - return true; + if (touchInterruptGetLastStatus(pin)) return true; #else - if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) - { - return true; - } + if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; #endif #endif break; - } - return false; + } + return false; } void handleSwitch(uint8_t b) @@ -418,4 +414,4 @@ void handleIO() void IRAM_ATTR touchButtonISR() { // used for ESP32 S2 and S3: nothing to do, ISR is just used to update registers of HAL driver -} \ No newline at end of file +} From d1d54ce9c8aacf93541dba34b457a947eed5e625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 7 Apr 2024 22:15:58 +0200 Subject: [PATCH 145/694] Update cfg.cpp Indentation fix --- wled00/cfg.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 91689aab4..9d36134f0 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -255,10 +255,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } //if touch pin, enable the touch interrupt on ESP32 S2 & S3 #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so - else if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH)) - { - touchAttachInterrupt(btnPin[s], touchButtonISR, 256 + (touchThreshold << 4)); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) - } + if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH)) + { + touchAttachInterrupt(btnPin[s], touchButtonISR, 256 + (touchThreshold << 4)); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) + } #endif else #endif From b72f3baab7ecf74d56c7caacb1aa177b3ad72140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 7 Apr 2024 22:21:41 +0200 Subject: [PATCH 146/694] Update set.cpp Compile fix --- wled00/set.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index 0920588fe..0b84160c9 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -108,10 +108,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) for (uint8_t s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { pinManager.deallocatePin(btnPin[s], PinOwner::Button); - #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt - if (digitalPinToTouchChannel(btnPin[i]) >= 0) // if touch capable pin - touchDetachInterrupt(btnPin[i]); // if not assigned previously, this will do nothing - #endif + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt + if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin + touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing + #endif } } From ba9ce4adf2adc0261bd2572e220f413860313a69 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 8 Apr 2024 16:32:21 +0200 Subject: [PATCH 147/694] PIO env. PSRAM fix for S3 & S3 with 4M flash - audioreactive always included for S3 & S2 --- platformio.ini | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 97082693e..e334b7e35 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3_4M_PSRAM_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -425,8 +425,9 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - ;-D WLED_DEBUG + ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.f_flash = 80000000L board_build.flash_mode = qio @@ -445,13 +446,35 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM + -DBOARD_HAS_PSRAM + ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32s3_4M_PSRAM_qspi] +;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) +board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support +board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_PSRAM_qspi + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + [env:lolin_s2_mini] platform = ${esp32s2.platform} platform_packages = ${esp32s2.platform_packages} @@ -464,6 +487,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 + -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -474,4 +498,6 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -D HW_PIN_DATASPI=11 -D HW_PIN_MISOSPI=9 ; -D STATUSLED=15 + ${esp32.AR_build_flags} lib_deps = ${esp32s2.lib_deps} + ${esp32.AR_lib_deps} From 58e8346209723c27c57f7b37a4b9b5fdca94f15b Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 9 Apr 2024 08:25:07 +0200 Subject: [PATCH 148/694] Fix for #3889 --- wled00/FX_2Dfcn.cpp | 7 ++++--- wled00/FX_fcn.cpp | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7aecd2271..fde05928e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -65,9 +65,10 @@ void WS2812FX::setUpMatrix() { customMappingSize = 0; // prevent use of mapping if anything goes wrong - if (customMappingTable == nullptr) customMappingTable = new uint16_t[getLengthTotal()]; + if (customMappingTable) delete[] customMappingTable; + customMappingTable = new uint16_t[getLengthTotal()]; - if (customMappingTable != nullptr) { + if (customMappingTable) { customMappingSize = getLengthTotal(); // fill with empty in case we don't fill the entire matrix @@ -138,7 +139,7 @@ void WS2812FX::setUpMatrix() { DEBUG_PRINTLN(); #endif } else { // memory allocation error - DEBUG_PRINTLN(F("Ledmap alloc error.")); + DEBUG_PRINTLN(F("ERROR 2D LED map allocation error.")); isMatrix = false; panels = 0; panel.clear(); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 251c55ce8..c9dd082ea 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1656,19 +1656,23 @@ bool WS2812FX::deserializeMap(uint8_t n) { return false; // if file does not load properly then exit } - DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); + if (customMappingTable) delete[] customMappingTable; + customMappingTable = new uint16_t[getLengthTotal()]; - if (customMappingTable == nullptr) customMappingTable = new uint16_t[getLengthTotal()]; - - JsonObject root = pDoc->as(); - JsonArray map = root[F("map")]; - if (!map.isNull() && map.size()) { // not an empty map - customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); - for (unsigned i=0; ias(); + JsonArray map = root[F("map")]; + if (!map.isNull() && map.size()) { // not an empty map + customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); + for (unsigned i=0; i 0); } uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) { From c3787af29d317e52cb2d9a3837f882c9412360c0 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 9 Apr 2024 20:00:00 +0200 Subject: [PATCH 149/694] Send ArtnetPollReply for Art-Net proxy universe --- wled00/e131.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wled00/e131.cpp b/wled00/e131.cpp index a67a672c2..ee8fa3949 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -346,7 +346,6 @@ void handleArtnetPollReply(IPAddress ipAddress) { switch (DMXMode) { case DMX_MODE_DISABLED: - return; // nothing to do break; case DMX_MODE_SINGLE_RGB: @@ -391,9 +390,17 @@ void handleArtnetPollReply(IPAddress ipAddress) { break; } - for (uint16_t i = startUniverse; i <= endUniverse; ++i) { - sendArtnetPollReply(&artnetPollReply, ipAddress, i); + if (DMXMode != DMX_MODE_DISABLED) { + for (uint16_t i = startUniverse; i <= endUniverse; ++i) { + sendArtnetPollReply(&artnetPollReply, ipAddress, i); + } } + + #ifdef WLED_ENABLE_DMX + if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) { + sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse); + } + #endif } void prepareArtnetPollReply(ArtPollReply *reply) { From bd1c06a7a7f811e40381ee692944a2ffe1b5d062 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 10 Apr 2024 20:06:46 +0200 Subject: [PATCH 150/694] Changelog update --- CHANGELOG.md | 19 ++++++++++++++++++- wled00/wled.h | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f6df2de..f29c05f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ ## WLED changelog +#### Build 2404100 +- Internals: #3859, #3862, #3873, #3875 +- Prefer I2S1 over RMT on ESP32 +- usermod for Adafruit MAX17048 (#3667 by @ccruz09) +- Runtime detection of ESP32 PICO, general PSRAM support +- Extend JSON API "info" object + - add "clock" - CPU clock in MHz + - add "flash" - flash size in MB +- Fix for #3879 +- Analog PWM fix for ESP8266 (#3887 by @gaaat98) +- Fix for #3870 (#3880 by @DedeHai) +- ESP32 S3/S2 touch fix (#3798 by @DedeHai) +- PIO env. PSRAM fix for S3 & S3 with 4M flash + - audioreactive always included for S3 & S2 +- Fix for #3889 +- BREAKING: Effect: modified KITT (Scanner) (#3763) + #### Build 2403280 - Individual color channel control for JSON API (fixes #3860) - "col":[int|string|object|array, int|string|object|array, int|string|object|array] @@ -16,7 +33,7 @@ - v0.15.0-b2 - WS2805 support (RGB + WW + CW, 600kbps) - Unified PSRAM use -- NeoPixelBus v2.7.9 (for future WS2805 support) +- NeoPixelBus v2.7.9 - Ubiquitous PSRAM mode for all variants of ESP32 - SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC) - Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`) diff --git a/wled00/wled.h b/wled00/wled.h index f96be3c1b..ef53e643f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2404050 +#define VERSION 2404100 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 30435e640678bc6a4b3aa363015b5c7a4d6d2945 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 11 Apr 2024 20:11:29 +0200 Subject: [PATCH 151/694] 2D compile fix --- wled00/FX.h | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/FX.h b/wled00/FX.h index 2b1a05229..829307918 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -635,6 +635,7 @@ typedef struct Segment { #else inline uint16_t XY(uint16_t x, uint16_t y) { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } From 1bdf3876fc08b5b13c4fc75223c983bc95bf3723 Mon Sep 17 00:00:00 2001 From: muebau Date: Fri, 12 Apr 2024 12:00:15 +0200 Subject: [PATCH 152/694] reset usermod TetrisAI back to initial version --- usermods/TetrisAI_v2/tetrisai.h | 95 ---------------------- usermods/TetrisAI_v2/usermod_v2_tetrisai.h | 52 ++++++------ 2 files changed, 27 insertions(+), 120 deletions(-) diff --git a/usermods/TetrisAI_v2/tetrisai.h b/usermods/TetrisAI_v2/tetrisai.h index ef7868a47..12afbb2fa 100644 --- a/usermods/TetrisAI_v2/tetrisai.h +++ b/usermods/TetrisAI_v2/tetrisai.h @@ -202,101 +202,6 @@ public: } } } - - bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) - { - //vector with pieces - //for every piece - //for every - switch (expression) - { - case INIT: - break; - - default: - break; - } - } - - bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) - { - //INIT - grid.cleanupFullLines(); - Rating curRating(grid.width); - Rating deeperRating(grid.width); - Piece piece = *start; - - // for every rotation of the piece - piece.rotation = 0; - - //HANDLE - while (piece.rotation < piece.pieceData->rotCount) - { - // put piece to top left corner - piece.x = 0; - piece.y = 0; - - //test for every column - piece.x = 0; - while (piece.x <= grid.width - piece.getRotation().width) - { - - //todo optimise by the use of the previous grids height - piece.landingY = 0; - //will set landingY to final position - grid.findLandingPosition(&piece); - - // draw piece - grid.placePiece(&piece, piece.x, piece.landingY); - - if(start == end - 1) - { - //at the deepest level - updateRating(grid, &curRating); - } - else - { - //go deeper to take another piece into account - findBestMove(grid, start + 1, end, &deeperRating); - curRating = deeperRating; - } - - // eraese piece - grid.erasePiece(&piece, piece.x, piece.landingY); - - if(findWorstMove) - { - //init rating for worst - if(bestRating->score == -DBL_MAX) - { - bestRating->score = DBL_MAX; - } - - // update if we found a worse one - if (bestRating->score > curRating.score) - { - *bestRating = curRating; - (*start) = piece; - } - } - else - { - // update if we found a better one - if (bestRating->score < curRating.score) - { - *bestRating = curRating; - (*start) = piece; - } - } - piece.x++; - } - piece.rotation++; - } - - //EXIT - - return true; - } }; #endif /* __AI_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h index 1c077d048..a312508b5 100644 --- a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h +++ b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h @@ -9,8 +9,6 @@ typedef struct TetrisAI_data { - unsigned long lastTime = 0; - TetrisAIGame tetris; uint8_t intelligence; uint8_t rotate; bool showNext; @@ -96,6 +94,8 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) //////////////////////////// uint16_t mode_2DTetrisAI() { + static unsigned long lastTime = 0; + if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data))) { // not a 2D set-up @@ -116,14 +116,16 @@ uint16_t mode_2DTetrisAI() //range 0 - 16 tetrisai_data->colorInc = SEGMENT.custom2 >> 4; - if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead + static TetrisAIGame tetris(cols < 32 ? cols : 32, rows, 1, piecesData, numPieces); + + if (tetris.nLookAhead != nLookAhead || tetrisai_data->showNext != SEGMENT.check1 || tetrisai_data->showBorder != SEGMENT.check2 - ) ) { tetrisai_data->showNext = SEGMENT.check1; tetrisai_data->showBorder = SEGMENT.check2; + tetris.nLookAhead = nLookAhead; //not more than 32 as this is the limit of this implementation uint8_t gridWidth = cols < 32 ? cols : 32; @@ -142,7 +144,7 @@ uint16_t mode_2DTetrisAI() } } - tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); + tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); SEGMENT.fill(SEGCOLOR(1)); } @@ -151,48 +153,48 @@ uint16_t mode_2DTetrisAI() tetrisai_data->intelligence = SEGMENT.custom1; double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0)); - tetrisai_data->tetris.ai.aHeight = -0.510066 + dui; - tetrisai_data->tetris.ai.fullLines = 0.760666 - dui; - tetrisai_data->tetris.ai.holes = -0.35663 + dui; - tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui; + tetris.ai.aHeight = -0.510066 + dui; + tetris.ai.fullLines = 0.760666 - dui; + tetris.ai.holes = -0.35663 + dui; + tetris.ai.bumpiness = -0.184483 + dui; } - if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) + if (tetris.state == TetrisAIGame::ANIMATE_MOVE) { - if (millis() - tetrisai_data->lastTime > msDelayMove) + if (millis() - lastTime > msDelayMove) { - drawGrid(&tetrisai_data->tetris, tetrisai_data); - tetrisai_data->lastTime = millis(); - tetrisai_data->tetris.poll(); + drawGrid(&tetris, tetrisai_data); + lastTime = millis(); + tetris.poll(); } } - else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER) + else if (tetris.state == TetrisAIGame::ANIMATE_GAME_OVER) { - if (millis() - tetrisai_data->lastTime > msDelayGameOver) + if (millis() - lastTime > msDelayGameOver) { - drawGrid(&tetrisai_data->tetris, tetrisai_data); - tetrisai_data->lastTime = millis(); - tetrisai_data->tetris.poll(); + drawGrid(&tetris, tetrisai_data); + lastTime = millis(); + tetris.poll(); } } - else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE) + else if (tetris.state == TetrisAIGame::FIND_BEST_MOVE) { if (SEGMENT.check3) { if(tetrisai_data->mistaceCountdown == 0) { - tetrisai_data->tetris.ai.findWorstMove = true; - tetrisai_data->tetris.poll(); - tetrisai_data->tetris.ai.findWorstMove = false; + tetris.ai.findWorstMove = true; + tetris.poll(); + tetris.ai.findWorstMove = false; tetrisai_data->mistaceCountdown = SEGMENT.custom3; } tetrisai_data->mistaceCountdown--; } - tetrisai_data->tetris.poll(); + tetris.poll(); } else { - tetrisai_data->tetris.poll(); + tetris.poll(); } return FRAMETIME; From 94cdd884746636b5d0843ff34ac2af5fbce51d47 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 13 Apr 2024 18:25:25 +0200 Subject: [PATCH 153/694] Version bump B3 - fix for #3896 - fix WS2815 current - conditional AA setPixelColor() --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- wled00/FX.cpp | 8 ++++++-- wled00/FX.h | 6 ++++++ wled00/FX_2Dfcn.cpp | 2 ++ wled00/FX_fcn.cpp | 2 ++ wled00/data/settings_leds.htm | 4 ++-- wled00/improv.cpp | 2 +- wled00/wled.h | 4 ++-- 10 files changed, 29 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29c05f73..59c58dfa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## WLED changelog +#### Build 2404120 +- v0.15.0-b3 +- fix for #3896 & WS2815 current saving +- conditional compile for AA setPixelColor() + #### Build 2404100 - Internals: #3859, #3862, #3873, #3875 - Prefer I2S1 over RMT on ESP32 diff --git a/package-lock.json b/package-lock.json index db66b554b..b9dc5e0e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b2", + "version": "0.15.0-b3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b2", + "version": "0.15.0-b3", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index 6f7d634d3..b19ecc48a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b2", + "version": "0.15.0-b3", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c0495ea24..5592f7ba8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3018,8 +3018,12 @@ uint16_t mode_bouncing_balls(void) { } int pos = roundf(balls[i].height * (SEGLEN - 1)); + #ifdef WLED_USE_AA_PIXELS if (SEGLEN<32) SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index else SEGMENT.setPixelColor(balls[i].height + (stripNr+1)*10.0f, color); + #else + SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index + #endif } } }; @@ -6052,8 +6056,8 @@ uint16_t mode_2Dfloatingblobs(void) { } } uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); - if (blob->r[i] > 1.f) SEGMENT.fill_circle(blob->x[i], blob->y[i], roundf(blob->r[i]), c); - else SEGMENT.setPixelColorXY(blob->x[i], blob->y[i], c); + if (blob->r[i] > 1.f) SEGMENT.fill_circle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); + else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c); // move x if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f)); else if (blob->x[i] - blob->r[i] <= 0) blob->x[i] += (blob->sX[i] * (blob->x[i] / blob->r[i] + 0.005f)); diff --git a/wled00/FX.h b/wled00/FX.h index 829307918..66e748602 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -575,9 +575,11 @@ typedef struct Segment { inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + #ifdef WLED_USE_AA_PIXELS void setPixelColor(float i, uint32_t c, bool aa = true); inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + #endif uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) void blur(uint8_t); @@ -603,9 +605,11 @@ typedef struct Segment { inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + #ifdef WLED_USE_AA_PIXELS void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + #endif uint32_t getPixelColorXY(uint16_t x, uint16_t y); // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } @@ -638,9 +642,11 @@ typedef struct Segment { inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + #ifdef WLED_USE_AA_PIXELS inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + #endif inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index fde05928e..b049ab6f0 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -218,6 +218,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) } } +#ifdef WLED_USE_AA_PIXELS // anti-aliased version of setPixelColorXY() void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) { @@ -261,6 +262,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col); } } +#endif // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index c9dd082ea..9ab1f578b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -777,6 +777,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } } +#ifdef WLED_USE_AA_PIXELS // anti-aliased normalized version of setPixelColor() void Segment::setPixelColor(float i, uint32_t col, bool aa) { @@ -809,6 +810,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) setPixelColor(uint16_t(roundf(fC)) | (vStrip<<16), col); } } +#endif uint32_t IRAM_ATTR Segment::getPixelColor(int i) { diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index dddedd471..4ad4cb16e 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -153,7 +153,7 @@ { const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none"; - d.Sf["LA"+n].value = s.value==="0" ? 55 : s.value; + if (s.value!=="0") d.Sf["LA"+n].value = s.value; d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1; } function setABL() @@ -417,7 +417,7 @@ mA/LED:
- +
PSU: mA
Color Order: diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 0090b4bd6..1536218ff 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.15.0-b2/%i"), VERSION); + sprintf_P(vString, PSTR("0.15.0-b3/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index ef53e643f..b94f7790b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-b2 + @version 0.15.0-b3 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2404100 +#define VERSION 2404120 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From a418cd2a2a5afd4bedbafca15239d510797c46fa Mon Sep 17 00:00:00 2001 From: Woody Date: Sat, 13 Apr 2024 19:37:49 +0200 Subject: [PATCH 154/694] Activate stale (#3898) * Update stale.yml Update pr text Schedule action 3 times a day * Delete old .github/stale.yml * Set exempt-all-milestones to true in stale.yml --- .github/stale.yml | 20 -------------------- .github/workflows/stale.yml | 6 +++--- 2 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 811db619a..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 120 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - keep - - enhancement - - confirmed -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - Hey! This issue has been open for quite some time without any new comments now. - It will be closed automatically in a week if no further activity occurs. - - Thank you for using WLED! -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4d531fd51..aaad16a44 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,7 @@ name: 'Close stale issues and PRs' on: schedule: - - cron: '0 12 * * *' + - cron: '0 6,12,18 * * *' workflow_dispatch: jobs: @@ -14,6 +14,7 @@ jobs: days-before-close: 7 exempt-issue-labels: 'pinned,keep,enhancement,confirmed' exempt-pr-labels: 'pinned,keep,enhancement,confirmed' + exempt-all-milestones: true stale-issue-message: > Hey! This issue has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs. @@ -23,5 +24,4 @@ jobs: Hey! This pull request has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs. - Thank you for using WLED! ✨ - debug-only: true + Thank you for contributing to WLED! ❤️ From d2b4d2531752ea7548cc9ba1a4e35915f0213b74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:38:03 +0200 Subject: [PATCH 155/694] Bump idna from 3.4 to 3.7 (#3895) Bumps [idna](https://github.com/kjd/idna) from 3.4 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.4...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8b55606dc..c56efad49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ h11==0.14.0 # via # uvicorn # wsproto -idna==3.4 +idna==3.7 # via # anyio # requests @@ -50,6 +50,8 @@ starlette==0.23.1 # via platformio tabulate==0.9.0 # via platformio +typing-extensions==4.11.0 + # via starlette urllib3==1.26.18 # via requests uvicorn==0.20.0 From cd928bc586cedf5bc5f4ed418c2ce743efc29523 Mon Sep 17 00:00:00 2001 From: Woody Date: Sun, 14 Apr 2024 13:14:03 +0200 Subject: [PATCH 156/694] Increase operations-per-run in stale.yml Because of this we don't need to run this action 3 times a day --- .github/workflows/stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index aaad16a44..58a0b18d8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,7 @@ name: 'Close stale issues and PRs' on: schedule: - - cron: '0 6,12,18 * * *' + - cron: '0 12 * * *' workflow_dispatch: jobs: @@ -15,6 +15,7 @@ jobs: exempt-issue-labels: 'pinned,keep,enhancement,confirmed' exempt-pr-labels: 'pinned,keep,enhancement,confirmed' exempt-all-milestones: true + operations-per-run: 150 stale-issue-message: > Hey! This issue has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs. From 442d7a7226ff6766e3dd84cea17ad89e36a96aeb Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:08:28 +0200 Subject: [PATCH 157/694] arduino-esp32 v2.0.9 --- platformio.ini | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platformio.ini b/platformio.ini index e334b7e35..af0988a85 100644 --- a/platformio.ini +++ b/platformio.ini @@ -233,8 +233,8 @@ AR_lib_deps = kosme/arduinoFFT @ 2.0.0 ;; ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -platform = espressif32@5.3.0 -platform_packages = +platform = espressif32@ ~6.3.2 +platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one -DARDUINO_ARCH_ESP32 -DESP32 @@ -248,8 +248,8 @@ lib_deps = [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@5.3.0 -platform_packages = +platform = espressif32@ ~6.3.2 +platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv build_flags = -g -DARDUINO_ARCH_ESP32 @@ -267,8 +267,8 @@ lib_deps = [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = espressif32@ ~6.3.2 +platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 @@ -284,8 +284,8 @@ lib_deps = [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = espressif32@ ~6.3.2 +platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) build_flags = -g -DESP32 -DARDUINO_ARCH_ESP32 From 7abfe68458daf62477621a9d9487470233045e47 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 15 Apr 2024 16:13:13 +0200 Subject: [PATCH 158/694] Add support for TM1914 chip --- wled00/bus_wrapper.h | 194 +++++++++++++++++++++++++++------- wled00/const.h | 1 + wled00/data/settings_leds.htm | 1 + 3 files changed, 158 insertions(+), 38 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index ebbeca4ad..32a5c1aae 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -74,7 +74,7 @@ #define I_8266_U1_APA106_3 82 #define I_8266_DM_APA106_3 83 #define I_8266_BB_APA106_3 84 -//WS2805 +//WS2805 (RGBCW) #define I_8266_U0_2805_5 89 #define I_8266_U1_2805_5 90 #define I_8266_DM_2805_5 91 @@ -117,10 +117,14 @@ #define I_32_RN_APA106_3 85 #define I_32_I0_APA106_3 86 #define I_32_I1_APA106_3 87 -//WS2805 +//WS2805 (RGBCW) #define I_32_RN_2805_5 93 #define I_32_I0_2805_5 94 #define I_32_I1_2805_5 95 +//TM1914 (RGB) +#define I_32_RN_TM1914_3 96 +#define I_32_I0_TM1914_3 97 +#define I_32_I1_TM1914_3 98 //APA102 @@ -170,10 +174,10 @@ #define B_8266_DM_TM1_4 NeoPixelBusLg #define B_8266_BB_TM1_4 NeoPixelBusLg //TM1829 (RGB) -#define B_8266_U0_TM2_4 NeoPixelBusLg -#define B_8266_U1_TM2_4 NeoPixelBusLg -#define B_8266_DM_TM2_4 NeoPixelBusLg -#define B_8266_BB_TM2_4 NeoPixelBusLg +#define B_8266_U0_TM2_3 NeoPixelBusLg +#define B_8266_U1_TM2_3 NeoPixelBusLg +#define B_8266_DM_TM2_3 NeoPixelBusLg +#define B_8266_BB_TM2_3 NeoPixelBusLg //UCS8903 #define B_8266_U0_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio1 #define B_8266_U1_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio2 @@ -199,6 +203,11 @@ #define B_8266_U1_2805_5 NeoPixelBusLg //esp8266, gpio2 #define B_8266_DM_2805_5 NeoPixelBusLg //esp8266, gpio3 #define B_8266_BB_2805_5 NeoPixelBusLg //esp8266, bb +//TM1914 (RGB) +#define B_8266_U0_TM1914_3 NeoPixelBusLg +#define B_8266_U1_TM1914_3 NeoPixelBusLg +#define B_8266_DM_TM1914_3 NeoPixelBusLg +#define B_8266_BB_TM1914_3 NeoPixelBusLg #endif /*** ESP32 Neopixel methods ***/ @@ -302,6 +311,16 @@ #define B_32_I1_2805_5 NeoPixelBusLg //#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S #endif +//TM1914 (RGB) +#define B_32_RN_TM1914_3 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_TM1914_3 NeoPixelBusLg +//#define B_32_I0_TM1914_3 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_TM1914_3 NeoPixelBusLg +//#define B_32_I1_TM1914_3 NeoPixelBusLg +#endif #endif //APA102 @@ -363,6 +382,13 @@ class PolyBus { tm1814_strip->SetPixelSettings(NeoTm1814Settings(/*R*/225, /*G*/225, /*B*/225, /*W*/225)); } + template + static void beginTM1914(void* busPtr) { + T tm1914_strip = static_cast(busPtr); + tm1914_strip->Begin(); + tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly + } + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { switch (busType) { case I_NONE: break; @@ -383,10 +409,10 @@ class PolyBus { case I_8266_U1_TM1_4: beginTM1814(busPtr); break; case I_8266_DM_TM1_4: beginTM1814(busPtr); break; case I_8266_BB_TM1_4: beginTM1814(busPtr); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->Begin(); break; case I_HS_DOT_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; case I_HS_LPD_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; case I_HS_LPO_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; @@ -412,6 +438,10 @@ class PolyBus { case I_8266_U1_2805_5: (static_cast(busPtr))->Begin(); break; case I_8266_DM_2805_5: (static_cast(busPtr))->Begin(); break; case I_8266_BB_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_TM1914_3: beginTM1914(busPtr); break; + case I_8266_U1_TM1914_3: beginTM1914(busPtr); break; + case I_8266_DM_TM1914_3: beginTM1914(busPtr); break; + case I_8266_BB_TM1914_3: beginTM1914(busPtr); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -480,6 +510,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: (static_cast(busPtr))->Begin(); break; #endif + case I_32_RN_TM1914_3: beginTM1914(busPtr); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: beginTM1914(busPtr); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: beginTM1914(busPtr); break; + #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; case I_HS_LPD_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -516,10 +553,10 @@ class PolyBus { case I_8266_U1_TM1_4: busPtr = new B_8266_U1_TM1_4(len, pins[0]); break; case I_8266_DM_TM1_4: busPtr = new B_8266_DM_TM1_4(len, pins[0]); break; case I_8266_BB_TM1_4: busPtr = new B_8266_BB_TM1_4(len, pins[0]); break; - case I_8266_U0_TM2_3: busPtr = new B_8266_U0_TM2_4(len, pins[0]); break; - case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_4(len, pins[0]); break; - case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_4(len, pins[0]); break; - case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_4(len, pins[0]); break; + case I_8266_U0_TM2_3: busPtr = new B_8266_U0_TM2_3(len, pins[0]); break; + case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_3(len, pins[0]); break; + case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_3(len, pins[0]); break; + case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_3(len, pins[0]); break; case I_8266_U0_UCS_3: busPtr = new B_8266_U0_UCS_3(len, pins[0]); break; case I_8266_U1_UCS_3: busPtr = new B_8266_U1_UCS_3(len, pins[0]); break; case I_8266_DM_UCS_3: busPtr = new B_8266_DM_UCS_3(len, pins[0]); break; @@ -540,6 +577,10 @@ class PolyBus { case I_8266_U1_2805_5: busPtr = new B_8266_U1_2805_5(len, pins[0]); break; case I_8266_DM_2805_5: busPtr = new B_8266_DM_2805_5(len, pins[0]); break; case I_8266_BB_2805_5: busPtr = new B_8266_BB_2805_5(len, pins[0]); break; + case I_8266_U0_TM1914_3: busPtr = new B_8266_U0_TM1914_3(len, pins[0]); break; + case I_8266_U1_TM1914_3: busPtr = new B_8266_U1_TM1914_3(len, pins[0]); break; + case I_8266_DM_TM1914_3: busPtr = new B_8266_DM_TM1914_3(len, pins[0]); break; + case I_8266_BB_TM1914_3: busPtr = new B_8266_BB_TM1914_3(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; @@ -608,6 +649,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: busPtr = new B_32_I1_2805_5(len, pins[0]); break; #endif + case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: busPtr = new B_32_I0_TM1914_3(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: busPtr = new B_32_I1_TM1914_3(len, pins[0]); break; + #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -645,10 +693,10 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->Show(consistent); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->Show(consistent); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->Show(consistent); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(consistent); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(consistent); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(consistent); break; case I_8266_U0_UCS_3: (static_cast(busPtr))->Show(consistent); break; case I_8266_U1_UCS_3: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_UCS_3: (static_cast(busPtr))->Show(consistent); break; @@ -669,6 +717,10 @@ class PolyBus { case I_8266_U1_2805_5: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_2805_5: (static_cast(busPtr))->Show(consistent); break; case I_8266_BB_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_TM1914_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; @@ -737,6 +789,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: (static_cast(busPtr))->Show(consistent); break; #endif + case I_32_RN_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -771,10 +830,10 @@ class PolyBus { case I_8266_U1_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_8266_U0_TM2_3: return (static_cast(busPtr))->CanShow(); break; - case I_8266_U1_TM2_3: return (static_cast(busPtr))->CanShow(); break; - case I_8266_DM_TM2_3: return (static_cast(busPtr))->CanShow(); break; - case I_8266_BB_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_TM2_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_U0_UCS_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_UCS_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_UCS_3: return (static_cast(busPtr))->CanShow(); break; @@ -794,6 +853,10 @@ class PolyBus { case I_8266_U1_2805_5: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_2805_5: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_TM1914_3: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -862,6 +925,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: return (static_cast(busPtr))->CanShow(); break; #endif + case I_32_RN_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -921,10 +991,10 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_8266_U0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; case I_8266_U1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; case I_8266_DM_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; @@ -945,6 +1015,10 @@ class PolyBus { case I_8266_U1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; case I_8266_DM_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; case I_8266_BB_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U0_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1013,6 +1087,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #endif + case I_32_RN_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1047,10 +1128,10 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_U0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_U1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1071,6 +1152,10 @@ class PolyBus { case I_8266_U1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_2805_5: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_BB_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1139,6 +1224,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; #endif + case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1174,10 +1266,10 @@ class PolyBus { case I_8266_U1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_DM_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_BB_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_8266_U0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_8266_U1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_8266_DM_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_8266_BB_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_U0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; case I_8266_U1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; case I_8266_DM_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; @@ -1198,6 +1290,10 @@ class PolyBus { case I_8266_U1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W case I_8266_DM_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W case I_8266_BB_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U0_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1266,6 +1362,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif + case I_32_RN_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1319,10 +1422,10 @@ class PolyBus { case I_8266_U1_TM1_4: delete (static_cast(busPtr)); break; case I_8266_DM_TM1_4: delete (static_cast(busPtr)); break; case I_8266_BB_TM1_4: delete (static_cast(busPtr)); break; - case I_8266_U0_TM2_3: delete (static_cast(busPtr)); break; - case I_8266_U1_TM2_3: delete (static_cast(busPtr)); break; - case I_8266_DM_TM2_3: delete (static_cast(busPtr)); break; - case I_8266_BB_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_U0_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_U1_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_DM_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_BB_TM2_3: delete (static_cast(busPtr)); break; case I_8266_U0_UCS_3: delete (static_cast(busPtr)); break; case I_8266_U1_UCS_3: delete (static_cast(busPtr)); break; case I_8266_DM_UCS_3: delete (static_cast(busPtr)); break; @@ -1343,6 +1446,10 @@ class PolyBus { case I_8266_U1_2805_5: delete (static_cast(busPtr)); break; case I_8266_DM_2805_5: delete (static_cast(busPtr)); break; case I_8266_BB_2805_5: delete (static_cast(busPtr)); break; + case I_8266_U0_TM1914_3: delete (static_cast(busPtr)); break; + case I_8266_U1_TM1914_3: delete (static_cast(busPtr)); break; + case I_8266_DM_TM1914_3: delete (static_cast(busPtr)); break; + case I_8266_BB_TM1914_3: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -1411,6 +1518,13 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_2805_5: delete (static_cast(busPtr)); break; #endif + case I_32_RN_TM1914_3: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_TM1914_3: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_TM1914_3: delete (static_cast(busPtr)); break; + #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1476,6 +1590,8 @@ class PolyBus { return I_8266_U0_FW6_5 + offset; case TYPE_WS2805: return I_8266_U0_2805_5 + offset; + case TYPE_TM1914: + return I_8266_U0_TM1914_3 + offset; } #else //ESP32 uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 @@ -1521,6 +1637,8 @@ class PolyBus { return I_32_RN_FW6_5 + offset; case TYPE_WS2805: return I_32_RN_2805_5 + offset; + case TYPE_TM1914: + return I_32_RN_TM1914_3 + offset; } #endif } diff --git a/wled00/const.h b/wled00/const.h index 0ce7b27d5..e0509824f 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -268,6 +268,7 @@ #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 #define TYPE_WS2805 32 //RGB + WW + CW +#define TYPE_TM1914 33 //RGB //"Analog" types (40-47) #define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) #define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 4ad4cb16e..1ab3374a0 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -386,6 +386,7 @@ ${i+1}: \ \ \ +\ \ \ \ From 459156fe574b7516b9a6b00c522a5bec74bab131 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 15 Apr 2024 21:20:45 +0200 Subject: [PATCH 159/694] added improvements to color scaling and blurring -changes save roughly 600bytes of flash -made blurring faster by not writing the color and then reading it back but keeping it as a variable: on a C3, FX black hole goes from 55FPS to 71FPS -added optional parameter to blur (smear) that can be used in combination with SEGMENT.clear(), blurring the frame without dimming the current frame (repeated calls without clearing will result in white). this is useful to blur without 'motion blurring' being added -scale8 is inlined and repeated calls uses flash, plus it is slower than native 32bit, so I added 'color_scale' function which is native 32bit and scales 32bit colors (RGBW). --- wled00/FX.h | 12 +++---- wled00/FX_2Dfcn.cpp | 85 +++++++++++++++++++++++++------------------- wled00/FX_fcn.cpp | 39 +++++++++++--------- wled00/colors.cpp | 42 ++++++++++++++-------- wled00/fcn_declare.h | 1 + 5 files changed, 105 insertions(+), 74 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 66e748602..106a6712c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -582,7 +582,7 @@ typedef struct Segment { #endif uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) - void blur(uint8_t); + void blur(uint8_t, bool smear = false); void fill(uint32_t c); void fade_out(uint8_t r); void fadeToBlackBy(uint8_t fadeBy); @@ -610,7 +610,7 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } #endif - uint32_t getPixelColorXY(uint16_t x, uint16_t y); + uint32_t getPixelColorXY(int x, int y); // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } @@ -619,8 +619,8 @@ typedef struct Segment { inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) - void blurRow(uint16_t row, fract8 blur_amount); - void blurCol(uint16_t col, fract8 blur_amount); + void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); + void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); void moveX(int8_t delta, bool wrap = false); void moveY(int8_t delta, bool wrap = false); void move(uint8_t dir, uint8_t delta, bool wrap = false); @@ -655,8 +655,8 @@ typedef struct Segment { inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} - inline void blurRow(uint16_t row, fract8 blur_amount) {} - inline void blurCol(uint16_t col, fract8 blur_amount) {} + inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} + inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} inline void moveX(int8_t delta, bool wrap = false) {} inline void moveY(int8_t delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index b049ab6f0..bea551860 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -175,11 +175,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint8_t _bri_t = currentBri(); if (_bri_t < 255) { - byte r = scale8(R(col), _bri_t); - byte g = scale8(G(col), _bri_t); - byte b = scale8(B(col), _bri_t); - byte w = scale8(W(col), _bri_t); - col = RGBW32(r, g, b, w); + col = color_scale(col, _bri_t); } if (reverse ) x = virtualWidth() - x - 1; @@ -265,7 +261,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) #endif // returns RGBW values of pixel -uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { +uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { if (!isActive()) return 0; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; @@ -278,59 +274,74 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { } // blurRow: perform a blur on a row of a rectangular matrix -void Segment::blurRow(uint16_t row, fract8 blur_amount) { - if (!isActive() || blur_amount == 0) return; // not active +void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear) +{ + if (!isActive() || blur_amount == 0) + return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); - if (row >= rows) return; + if (row >= rows) + return; // blur one row - uint8_t keep = 255 - blur_amount; + uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; - CRGB carryover = CRGB::Black; + uint32_t carryover = BLACK; + uint32_t lastnew; + uint32_t last; + uint32_t curnew; for (unsigned x = 0; x < cols; x++) { - CRGB cur = getPixelColorXY(x, row); - CRGB before = cur; // remember color before blur - CRGB part = cur; - part.nscale8(seep); - cur.nscale8(keep); - cur += carryover; - if (x>0) { - CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part; - setPixelColorXY(x-1, row, prev); + uint32_t cur = getPixelColorXY(x, row); + uint32_t part = color_scale(cur, seep); + curnew = color_scale(cur, keep); + if (x > 0) { + if (carryover) + curnew = color_add(curnew, carryover, true); + uint32_t prev = color_add(lastnew, part, true); + if (last != prev) // optimization: only set pixel if color has changed + setPixelColorXY(x - 1, row, prev); } - if (before != cur) // optimization: only set pixel if color has changed - setPixelColorXY(x, row, cur); + else // first pixel or last pixel + setPixelColorXY(x, row, curnew); + lastnew = curnew; + last = cur; // save original value for comparison on next iteration carryover = part; } + setPixelColorXY(cols-1, row, curnew); // set last pixel } // blurCol: perform a blur on a column of a rectangular matrix -void Segment::blurCol(uint16_t col, fract8 blur_amount) { +void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); if (col >= cols) return; // blur one column - uint8_t keep = 255 - blur_amount; + uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; - CRGB carryover = CRGB::Black; + uint32_t carryover = BLACK; + uint32_t lastnew; + uint32_t last; + uint32_t curnew; for (unsigned y = 0; y < rows; y++) { - CRGB cur = getPixelColorXY(col, y); - CRGB part = cur; - CRGB before = cur; // remember color before blur - part.nscale8(seep); - cur.nscale8(keep); - cur += carryover; - if (y>0) { - CRGB prev = CRGB(getPixelColorXY(col, y-1)) + part; - setPixelColorXY(col, y-1, prev); + uint32_t cur = getPixelColorXY(col, y); + uint32_t part = color_scale(cur, seep); + curnew = color_scale(cur, keep); + if (y > 0) { + if (carryover) + curnew = color_add(curnew, carryover, true); + uint32_t prev = color_add(lastnew, part, true); + if (last != prev) // optimization: only set pixel if color has changed + setPixelColorXY(col, y - 1, prev); } - if (before != cur) // optimization: only set pixel if color has changed - setPixelColorXY(col, y, cur); - carryover = part; + else // first pixel + setPixelColorXY(col, y, curnew); + lastnew = curnew; + last = cur; //save original value for comparison on next iteration + carryover = part; } + setPixelColorXY(col, rows - 1, curnew); } // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 9ab1f578b..9a372ddf1 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -735,11 +735,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) uint16_t len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { - byte r = scale8(R(col), _bri_t); - byte g = scale8(G(col), _bri_t); - byte b = scale8(B(col), _bri_t); - byte w = scale8(W(col), _bri_t); - col = RGBW32(r, g, b, w); + col = color_scale(col, _bri_t); } // expand pixel (taking into account start, grouping, spacing [and offset]) @@ -995,33 +991,44 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) { /* * blurs segment content, source: FastLED colorutils.cpp */ -void Segment::blur(uint8_t blur_amount) { +void Segment::blur(uint8_t blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); - for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns + for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns return; } #endif - uint8_t keep = 255 - blur_amount; + uint8_t keep = smear ? 250 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; - uint32_t carryover = BLACK; unsigned vlength = virtualLength(); + uint32_t carryover = BLACK; + uint32_t lastnew; + uint32_t last; + uint32_t curnew; for (unsigned i = 0; i < vlength; i++) { uint32_t cur = getPixelColor(i); - uint32_t part = color_fade(cur, seep); - cur = color_add(color_fade(cur, keep), carryover, true); - if (i > 0) { - uint32_t c = getPixelColor(i-1); - setPixelColor(i-1, color_add(c, part, true)); + uint32_t part = color_scale(cur, seep); + curnew = color_scale(cur, keep); + if (i > 0) + { + if (carryover) + curnew = color_add(curnew, carryover, true); + uint32_t prev = color_add(lastnew, part, true); + if (last != prev) // optimization: only set pixel if color has changed + setPixelColor(i - 1, prev); } - setPixelColor(i, cur); + else // first pixel + setPixelColor(i, curnew); + lastnew = curnew; + last = cur; // save original value for comparison on next iteration carryover = part; } + setPixelColor(vlength - 1, curnew); } /* diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 3ed54d959..188c67f41 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -61,28 +61,40 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) } } +/* + * color scale function that replaces scale8 for 32bit colors + */ + +uint32_t color_scale(uint32_t c1, uint8_t scale) +{ + uint32_t fixedscale = 1 + scale; + uint32_t scaledcolor; //color order is: W R G B from MSB to LSB + scaledcolor = ((R(c1) * fixedscale) >> 8) << 16; + scaledcolor |= ((G(c1) * fixedscale) >> 8) << 8; + scaledcolor |= (B(c1) * fixedscale) >> 8; + scaledcolor |= ((W(c1) * fixedscale) >> 8) << 24; + return scaledcolor; +} + /* * fades color toward black * if using "video" method the resulting color will never become black unless it is already black */ + uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { - uint8_t r = R(c1); - uint8_t g = G(c1); - uint8_t b = B(c1); - uint8_t w = W(c1); - if (video) { - r = scale8_video(r, amount); - g = scale8_video(g, amount); - b = scale8_video(b, amount); - w = scale8_video(w, amount); - } else { - r = scale8(r, amount); - g = scale8(g, amount); - b = scale8(b, amount); - w = scale8(w, amount); + if (video) + { + uint8_t r = scale8_video(R(c1), amount); + uint8_t g = scale8_video(G(c1), amount); + uint8_t b = scale8_video(B(c1), amount); + uint8_t w = scale8_video(W(c1), amount); + return RGBW32(r, g, b, w); + } + else + { + return color_scale(c1, amount); } - return RGBW32(r, g, b, w); } void setRandomColor(byte* rgb) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2461ebb28..0e1329267 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -80,6 +80,7 @@ class NeoGammaWLEDMethod { #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_add(uint32_t,uint32_t, bool fast=false); +uint32_t color_scale(uint32_t c1, uint8_t scale); uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(void); From b2e68db380326804d545b7bc71233edf8c041b35 Mon Sep 17 00:00:00 2001 From: Woody Date: Mon, 15 Apr 2024 22:55:38 +0200 Subject: [PATCH 160/694] Increase operations-per-run in stale.yml again --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 58a0b18d8..5d62571e1 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: exempt-issue-labels: 'pinned,keep,enhancement,confirmed' exempt-pr-labels: 'pinned,keep,enhancement,confirmed' exempt-all-milestones: true - operations-per-run: 150 + operations-per-run: 1000 stale-issue-message: > Hey! This issue has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs. From 084fc2fcd1b8a5c8ca4be7ab7e9dc04df8f92067 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 16 Apr 2024 10:43:06 +0200 Subject: [PATCH 161/694] bugfix & code formatting, removed color_scale also replaced scale8_video with 32bit calculation in color_fade for consistency and speed. --- wled00/FX_2Dfcn.cpp | 18 ++++++++---------- wled00/FX_fcn.cpp | 11 +++++------ wled00/colors.cpp | 44 +++++++++++++++++++------------------------- wled00/fcn_declare.h | 1 - 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index bea551860..69a43d7b8 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -175,7 +175,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint8_t _bri_t = currentBri(); if (_bri_t < 255) { - col = color_scale(col, _bri_t); + col = color_fade(col, _bri_t); } if (reverse ) x = virtualWidth() - x - 1; @@ -274,15 +274,13 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { } // blurRow: perform a blur on a row of a rectangular matrix -void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear) -{ +void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); - if (row >= rows) - return; + if (row >= rows) return; // blur one row uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; @@ -292,8 +290,8 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear) uint32_t curnew; for (unsigned x = 0; x < cols; x++) { uint32_t cur = getPixelColorXY(x, row); - uint32_t part = color_scale(cur, seep); - curnew = color_scale(cur, keep); + uint32_t part = color_fade(cur, seep); + curnew = color_fade(cur, keep); if (x > 0) { if (carryover) curnew = color_add(curnew, carryover, true); @@ -301,7 +299,7 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear) if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(x - 1, row, prev); } - else // first pixel or last pixel + else // first pixel setPixelColorXY(x, row, curnew); lastnew = curnew; last = cur; // save original value for comparison on next iteration @@ -326,8 +324,8 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { uint32_t curnew; for (unsigned y = 0; y < rows; y++) { uint32_t cur = getPixelColorXY(col, y); - uint32_t part = color_scale(cur, seep); - curnew = color_scale(cur, keep); + uint32_t part = color_fade(cur, seep); + curnew = color_fade(cur, keep); if (y > 0) { if (carryover) curnew = color_add(curnew, carryover, true); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 9a372ddf1..f3f74ba25 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -735,7 +735,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) uint16_t len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { - col = color_scale(col, _bri_t); + col = color_fade(col, _bri_t); } // expand pixel (taking into account start, grouping, spacing [and offset]) @@ -1003,7 +1003,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { return; } #endif - uint8_t keep = smear ? 250 : 255 - blur_amount; + uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; unsigned vlength = virtualLength(); uint32_t carryover = BLACK; @@ -1012,10 +1012,9 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint32_t curnew; for (unsigned i = 0; i < vlength; i++) { uint32_t cur = getPixelColor(i); - uint32_t part = color_scale(cur, seep); - curnew = color_scale(cur, keep); - if (i > 0) - { + uint32_t part = color_fade(cur, seep); + curnew = color_fade(cur, keep); + if (i > 0) { if (carryover) curnew = color_add(curnew, carryover, true); uint32_t prev = color_add(lastnew, part, true); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 188c67f41..30d2c069e 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -61,21 +61,6 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) } } -/* - * color scale function that replaces scale8 for 32bit colors - */ - -uint32_t color_scale(uint32_t c1, uint8_t scale) -{ - uint32_t fixedscale = 1 + scale; - uint32_t scaledcolor; //color order is: W R G B from MSB to LSB - scaledcolor = ((R(c1) * fixedscale) >> 8) << 16; - scaledcolor |= ((G(c1) * fixedscale) >> 8) << 8; - scaledcolor |= (B(c1) * fixedscale) >> 8; - scaledcolor |= ((W(c1) * fixedscale) >> 8) << 24; - return scaledcolor; -} - /* * fades color toward black * if using "video" method the resulting color will never become black unless it is already black @@ -83,17 +68,26 @@ uint32_t color_scale(uint32_t c1, uint8_t scale) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { - if (video) - { - uint8_t r = scale8_video(R(c1), amount); - uint8_t g = scale8_video(G(c1), amount); - uint8_t b = scale8_video(B(c1), amount); - uint8_t w = scale8_video(W(c1), amount); - return RGBW32(r, g, b, w); + uint32_t scaledcolor; // color order is: W R G B from MSB to LSB + uint32_t r = R(c1); + uint32_t g = G(c1); + uint32_t b = B(c1); + uint32_t w = W(c1); + if (video) { + uint32_t scale = amount; // 32bit for faster calculation + scaledcolor = (((r * scale) >> 8) << 16) + ((r && scale) ? 1 : 0); + scaledcolor |= (((g * scale) >> 8) << 8) + ((g && scale) ? 1 : 0); + scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); + scaledcolor |= (((w * scale) >> 8) << 24) + ((w && scale) ? 1 : 0); + return scaledcolor; } - else - { - return color_scale(c1, amount); + else { + uint32_t scale = 1 + amount; + scaledcolor = ((r * scale) >> 8) << 16; + scaledcolor |= ((g * scale) >> 8) << 8; + scaledcolor |= (b * scale) >> 8; + scaledcolor |= ((w * scale) >> 8) << 24; + return scaledcolor; } } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 0e1329267..2461ebb28 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -80,7 +80,6 @@ class NeoGammaWLEDMethod { #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_add(uint32_t,uint32_t, bool fast=false); -uint32_t color_scale(uint32_t c1, uint8_t scale); uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(void); From 6272969983c35e824622cf7d7515165eeff2e02f Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 16 Apr 2024 15:07:12 +0200 Subject: [PATCH 162/694] Set stale-pr-label & stale-issue-label to 'stale' in stale.yml Hopefully this fixes the error messages that were thrown the last time the action was executed --- .github/workflows/stale.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5d62571e1..1f2557160 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,6 +12,8 @@ jobs: with: days-before-stale: 120 days-before-close: 7 + stale-issue-label: 'stale' + stale-pr-label: 'stale' exempt-issue-labels: 'pinned,keep,enhancement,confirmed' exempt-pr-labels: 'pinned,keep,enhancement,confirmed' exempt-all-milestones: true From 1b75be5710615f8964cf1456c41e8e0568f2a1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 16 Apr 2024 17:37:48 +0200 Subject: [PATCH 163/694] Update FX_2Dfcn.cpp Undo indent --- wled00/FX_2Dfcn.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 69a43d7b8..5a73792b6 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -275,8 +275,7 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ - if (!isActive() || blur_amount == 0) - return; // not active + if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); From 3e20724058b4286de2b5e3a6ccd6a2578ec1c9a2 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 17 Apr 2024 18:52:35 +0200 Subject: [PATCH 164/694] ArduinoFFT update shadow variables --- platformio.ini | 5 ++--- usermods/multi_relay/usermod_multi_relay.h | 2 +- wled00/json.cpp | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index e334b7e35..0b11f2d24 100644 --- a/platformio.ini +++ b/platformio.ini @@ -174,7 +174,7 @@ lib_deps = # SHT85 ;robtillaart/SHT85@~0.3.3 # Audioreactive usermod - ;kosme/arduinoFFT @ 2.0.0 + ;kosme/arduinoFFT @ 2.0.1 extra_scripts = ${scripts_defaults.extra_scripts} @@ -225,7 +225,7 @@ lib_deps = ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE -AR_lib_deps = kosme/arduinoFFT @ 2.0.0 +AR_lib_deps = kosme/arduinoFFT @ 2.0.1 [esp32_idf_V4] ;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 @@ -238,7 +238,6 @@ platform_packages = build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one -DARDUINO_ARCH_ESP32 -DESP32 - #-DCONFIG_LITTLEFS_FOR_IDF_3_2 -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index cb1eec8e1..efb3c8ae1 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -667,7 +667,7 @@ void MultiRelay::addToJsonInfo(JsonObject &root) { for (int i=0; i()) { From 6d1410741d4c3f2d080b9ec12d7ffa7e0a2e4d8c Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 17 Apr 2024 19:00:16 +0200 Subject: [PATCH 165/694] Fix 8266 compile --- wled00/bus_wrapper.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 32a5c1aae..966a391c3 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -79,6 +79,11 @@ #define I_8266_U1_2805_5 90 #define I_8266_DM_2805_5 91 #define I_8266_BB_2805_5 92 +//TM1914 (RGB) +#define I_8266_U0_TM1914_3 99 +#define I_8266_U1_TM1914_3 100 +#define I_8266_DM_TM1914_3 101 +#define I_8266_BB_TM1914_3 102 /*** ESP32 Neopixel methods ***/ //RGB From 5c502b1fe48f8e12a51b0beabd56bca15e02a6a3 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 17 Apr 2024 22:49:33 +0200 Subject: [PATCH 166/694] Bump arduinoFFT, organise partitions --- platformio.ini | 22 ++++++++++++---------- tools/WLED_ESP32_4MB_700k_FS.csv | 6 ++++++ 2 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 tools/WLED_ESP32_4MB_700k_FS.csv diff --git a/platformio.ini b/platformio.ini index af0988a85..a5c21ba37 100644 --- a/platformio.ini +++ b/platformio.ini @@ -174,7 +174,7 @@ lib_deps = # SHT85 ;robtillaart/SHT85@~0.3.3 # Audioreactive usermod - ;kosme/arduinoFFT @ 2.0.0 + ;kosme/arduinoFFT @ 2.0.1 extra_scripts = ${scripts_defaults.extra_scripts} @@ -218,14 +218,18 @@ build_flags = -g #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 +tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv +large_partitions = tools/WLED_ESP32_8MB.csv +extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE -AR_lib_deps = kosme/arduinoFFT @ 2.0.0 +AR_lib_deps = kosme/arduinoFFT @ 2.0.1 [esp32_idf_V4] ;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 @@ -250,7 +254,6 @@ lib_deps = ;; generic definitions for all ESP32-S2 boards platform = espressif32@ ~6.3.2 platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) -default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 @@ -296,7 +299,6 @@ build_flags = -g -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT - lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} @@ -391,7 +393,7 @@ platform = ${esp32.platform} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio -board_build.partitions = tools/WLED_ESP32-wrover_4MB.csv +board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html @@ -404,7 +406,7 @@ platform = ${esp32c3.platform} platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 -board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.partitions = ${esp32.default_partitions} build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -428,7 +430,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} -board_build.partitions = tools/WLED_ESP32_8MB.csv +board_build.partitions = ${esp32.large_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio ; board_build.flash_mode = dio ;; try this if you have problems at startup @@ -450,7 +452,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} -board_build.partitions = tools/WLED_ESP32_8MB.csv +board_build.partitions = ${esp32.large_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder @@ -470,7 +472,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} -board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder @@ -479,7 +481,7 @@ monitor_filters = esp32_exception_decoder platform = ${esp32s2.platform} platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini -board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.partitions = ${esp32.default_partitions} ;board_build.flash_mode = qio ;board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} diff --git a/tools/WLED_ESP32_4MB_700k_FS.csv b/tools/WLED_ESP32_4MB_700k_FS.csv new file mode 100644 index 000000000..39c88e543 --- /dev/null +++ b/tools/WLED_ESP32_4MB_700k_FS.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1A0000, +app1, app, ota_1, 0x1B0000,0x1A0000, +spiffs, data, spiffs, 0x350000,0xB0000, From 57665e896400a43f8cf2a71d975db418739c2c63 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:16:04 +0200 Subject: [PATCH 167/694] audioreactive readme - removed UM_AUDIOREACTIVE_USE_NEW_FFT The customized library is not needed / supported any more in 0_15. --- usermods/audioreactive/readme.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index 47804b611..4668ca881 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -27,18 +27,11 @@ Currently ESP8266 is not supported, due to low speed and small RAM of this chip. There are however plans to create a lightweight audioreactive for the 8266, with reduced features. ## Installation -### using customised _arduinoFFT_ library for use with this usermod -Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment `build_flags`, as well as `https://github.com/blazoncek/arduinoFFT.git` to your `lib_deps`. -If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed. +### using latest _arduinoFFT_ library version 2.x +The latest arduinoFFT release version should be used for audioreactive. -Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git - -### using latest (develop) _arduinoFFT_ library -Alternatively, you can use the latest arduinoFFT development version. -ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM. - -* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT` -* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2` +* `build_flags` = `-D USERMOD_AUDIOREACTIVE` +* `lib_deps`= `kosme/arduinoFFT @ 2.0.1` ## Configuration From 6f3d7e76c93c039bd2dad518bf923310b66ecac3 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 19 Apr 2024 22:54:25 +0200 Subject: [PATCH 168/694] Fix for #3919 --- wled00/data/index.css | 2 +- wled00/data/index.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/wled00/data/index.css b/wled00/data/index.css index fa6e20077..6ac3f3a1f 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -358,7 +358,7 @@ button { #putil, #segutil, #segutil2 { min-height: 42px; - margin: 13px auto 0; + margin: 0 auto; } #segutil .segin { diff --git a/wled00/data/index.js b/wled00/data/index.js index 03ee276a8..5950b24ee 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -863,14 +863,11 @@ function populateSegments(s) gId("segcont").classList.remove("hide"); let noNewSegs = (lowestUnused >= maxSeg); resetUtil(noNewSegs); - if (gId('selall')) gId('selall').checked = true; for (var i = 0; i <= lSeg; i++) { if (!gId(`seg${i}`)) continue; updateLen(i); updateTrail(gId(`seg${i}bri`)); gId(`segr${i}`).classList.add("hide"); - //if (i2) d.querySelectorAll(".pop").forEach((e)=>{e.classList.remove("hide");}); - var cd = gId('csl').querySelectorAll("button"); for (let e = cd.length-1; e >= 0; e--) { cd[e].dataset.r = i.col[e][0]; @@ -1838,7 +1833,7 @@ function makeSeg() }); var cn = `
`+ `
`+ - ``+ + ``+ ``+ ``+ ``+ @@ -1864,13 +1859,19 @@ function makeSeg() function resetUtil(off=false) { - gId('segutil').innerHTML = `
` + gId('segutil').innerHTML = `
` + '' + `
Add segment
` + '
' + `` + '
' + '
'; + gId('selall').checked = true; + for (var i = 0; i <= lSeg; i++) { + if (!gId(`seg${i}`)) continue; + if (!gId(`seg${i}sel`).checked) gId('selall').checked = false; // uncheck if at least one is unselected. + } + if (lSeg>2) d.querySelectorAll("#Segments .pop").forEach((e)=>{e.classList.remove("hide");}); } function makePlSel(el, incPl=false) From fa32faf75971f68836fa4c1f9e740edaca5a7be2 Mon Sep 17 00:00:00 2001 From: Suxsem Date: Sun, 21 Apr 2024 13:37:07 +0200 Subject: [PATCH 169/694] feat(new): relay open drain output --- platformio_override.sample.ini | 1 + wled00/button.cpp | 4 ++-- wled00/cfg.cpp | 7 ++++++- wled00/data/settings_leds.htm | 5 +++-- wled00/set.cpp | 1 + wled00/wled.h | 6 ++++++ wled00/xml.cpp | 1 + 7 files changed, 20 insertions(+), 5 deletions(-) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index d7d41f3a6..406c6f23d 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -56,6 +56,7 @@ build_flags = ${common.build_flags_esp8266} ; -D IRPIN=4 ; -D RLYPIN=12 ; -D RLYMDE=1 +; -D RLYODRAIN=0 ; -D LED_BUILTIN=2 # GPIO of built-in LED ; ; Limit max buses diff --git a/wled00/button.cpp b/wled00/button.cpp index ce47a17ac..3b73df81d 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -379,7 +379,7 @@ void handleIO() esp32RMTInvertIdle(); #endif if (rlyPin>=0) { - pinMode(rlyPin, OUTPUT); + pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); digitalWrite(rlyPin, rlyMde); } offMode = false; @@ -400,7 +400,7 @@ void handleIO() esp32RMTInvertIdle(); #endif if (rlyPin>=0) { - pinMode(rlyPin, OUTPUT); + pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); digitalWrite(rlyPin, !rlyMde); } } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 530777ab5..9e6989a75 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -335,12 +335,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(irApplyToAllSelected, hw["ir"]["sel"]); JsonObject relay = hw[F("relay")]; + + if (relay.containsKey("odrain")) { + rlyOpenDrain = relay["odrain"]; + } int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { pinManager.deallocatePin(rlyPin, PinOwner::Relay); if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) { rlyPin = hw_relay_pin; - pinMode(rlyPin, OUTPUT); + pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); } else { rlyPin = -1; } @@ -868,6 +872,7 @@ void serializeConfig() { JsonObject hw_relay = hw.createNestedObject(F("relay")); hw_relay["pin"] = rlyPin; hw_relay["rev"] = !rlyMde; + hw_relay["odrain"] = rlyOpenDrain; hw[F("baud")] = serialBaud; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 4ad4cb16e..eecdf66f0 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -619,7 +619,8 @@ Swap: Apply IR change to main segment only:
IR info
- Relay GPIO: Invert  ✕
+ Relay GPIO: Invert Open drain  ✕

Defaults

Turn LEDs on after power up/reset:
diff --git a/wled00/set.cpp b/wled00/set.cpp index a2e884c81..d3382be18 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -243,6 +243,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) rlyPin = -1; } rlyMde = (bool)request->hasArg(F("RM")); + rlyOpenDrain = (bool)request->hasArg(F("RO")); disablePullUp = (bool)request->hasArg(F("IP")); touchThreshold = request->arg(F("TT")).toInt(); diff --git a/wled00/wled.h b/wled00/wled.h index b94f7790b..d8385e79e 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -288,6 +288,12 @@ WLED_GLOBAL bool rlyMde _INIT(true); #else WLED_GLOBAL bool rlyMde _INIT(RLYMDE); #endif +//Use open drain (floating pin) when relay should be off +#ifndef RLYODRAIN +WLED_GLOBAL bool rlyOpenDrain _INIT(false); +#else +WLED_GLOBAL bool rlyOpenDrain _INIT(RLYODRAIN); +#endif #ifndef IRPIN #define IRPIN -1 #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3915d9b0e..49fd522bb 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -458,6 +458,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('i',SET_F("PB"),strip.paletteBlend); sappend('v',SET_F("RL"),rlyPin); sappend('c',SET_F("RM"),rlyMde); + sappend('c',SET_F("RO"),rlyOpenDrain); for (uint8_t i=0; i Date: Sun, 21 Apr 2024 20:02:00 +0200 Subject: [PATCH 170/694] optimizations --- wled00/cfg.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 9e6989a75..22bfe577a 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -336,9 +336,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject relay = hw[F("relay")]; - if (relay.containsKey("odrain")) { - rlyOpenDrain = relay["odrain"]; - } + rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain; int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { pinManager.deallocatePin(rlyPin, PinOwner::Relay); @@ -872,7 +870,7 @@ void serializeConfig() { JsonObject hw_relay = hw.createNestedObject(F("relay")); hw_relay["pin"] = rlyPin; hw_relay["rev"] = !rlyMde; - hw_relay["odrain"] = rlyOpenDrain; + hw_relay[F("odrain")] = rlyOpenDrain; hw[F("baud")] = serialBaud; From 127ea7e3516231d4c12f1261d2132f65140401a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 23 Apr 2024 13:04:17 +0200 Subject: [PATCH 171/694] Fix for #3926 --- wled00/FX_fcn.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f3f74ba25..f94daca4a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1664,12 +1664,18 @@ bool WS2812FX::deserializeMap(uint8_t n) { return false; // if file does not load properly then exit } + JsonObject root = pDoc->as(); + // if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps) + if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { + Segment::maxWidth = min(max(root[F("width")].as(), 1), 128); + Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); + } + if (customMappingTable) delete[] customMappingTable; customMappingTable = new uint16_t[getLengthTotal()]; if (customMappingTable) { DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); - JsonObject root = pDoc->as(); JsonArray map = root[F("map")]; if (!map.isNull() && map.size()) { // not an empty map customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); From 8ffe1e65fd1bbb2f2e41aa93005e0c2d8487dcfe Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:07:08 +0200 Subject: [PATCH 172/694] audioreactive: arduino-esp32 up to 2.0.14 still has the swapped-channel-bug --- usermods/audioreactive/audio_source.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 18d00da3c..a7337eaf9 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -71,7 +71,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) @@ -770,4 +770,4 @@ class SPH0654 : public I2SSource { #endif } }; -#endif \ No newline at end of file +#endif From 0d3ea848c22d47d441942b9d76e477ec706272a0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:27:44 +0200 Subject: [PATCH 173/694] "big partition" added - 300KB FS and coredump support(from WLEDMM) --- platformio.ini | 1 + tools/WLED_ESP32_4MB_256KB_FS.csv | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tools/WLED_ESP32_4MB_256KB_FS.csv diff --git a/platformio.ini b/platformio.ini index a5c21ba37..8a40852a7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -221,6 +221,7 @@ build_flags = -g tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv +big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support large_partitions = tools/WLED_ESP32_8MB.csv extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv lib_deps = diff --git a/tools/WLED_ESP32_4MB_256KB_FS.csv b/tools/WLED_ESP32_4MB_256KB_FS.csv new file mode 100644 index 000000000..e54c22bbd --- /dev/null +++ b/tools/WLED_ESP32_4MB_256KB_FS.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1D0000, +app1, app, ota_1, 0x1E0000,0x1D0000, +spiffs, data, spiffs, 0x3B0000,0x40000, +coredump, data, coredump,,64K \ No newline at end of file From 11549058184a451a70e80c421eb40532804fbd79 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:49:39 +0200 Subject: [PATCH 174/694] upgrading WROVER to use new platform --- platformio.ini | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 8a40852a7..0dc03d630 100644 --- a/platformio.ini +++ b/platformio.ini @@ -390,16 +390,20 @@ lib_deps = ${esp32.lib_deps} board_build.partitions = ${esp32.default_partitions} [env:esp32_wrover] -platform = ${esp32.platform} +extends = esp32_idf_V4 +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags_esp32_V4} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D LEDPIN=25 -lib_deps = ${esp32.lib_deps} + ; ${esp32.AR_build_flags} +lib_deps = ${esp32_idf_V4.lib_deps} + ; ${esp32.AR_lib_deps} [env:esp32c3dev] extends = esp32c3 From 5d271d21bcfc34e02a7103980eb05e08081d94dc Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 23 Apr 2024 18:55:36 +0200 Subject: [PATCH 175/694] INI cleanup added 8M ESP32 and 16M ESP32-S3 --- platformio.ini | 74 +++++++++++++++++++--------------- platformio_override.sample.ini | 67 +++++++++++++++--------------- 2 files changed, 76 insertions(+), 65 deletions(-) diff --git a/platformio.ini b/platformio.ini index 14b5f7c1f..8d9109c02 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3_4M_PSRAM_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -86,7 +86,6 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG # This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m). # ------------------------------------------------------------------------------ build_flags = - -Wno-attributes -DMQTT_MAX_PACKET_SIZE=1024 -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL -DBEARSSL_SSL_BASIC @@ -104,10 +103,6 @@ build_flags = build_unflags = -build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} -build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} -build_flags_esp32_V4= ${common.build_flags} ${esp32_idf_V4.build_flags} - ldscript_1m128k = eagle.flash.1m128.ld ldscript_2m512k = eagle.flash.2m512.ld ldscript_2m1m = eagle.flash.2m1m.ld @@ -245,7 +240,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DESP32 -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 -default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} @@ -314,14 +308,14 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D [env:esp8266_2m] board = esp_wroom_02 @@ -329,13 +323,13 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 [env:esp01_1m_full] board = esp01_1m @@ -343,14 +337,14 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM [env:esp32dev] @@ -358,17 +352,30 @@ board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +[env:esp32dev_8M] +board = esp32dev +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET + ${esp32.AR_build_flags} +lib_deps = ${esp32_idf_V4.lib_deps} + ${esp32.AR_lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.large_partitions} +; board_build.f_flash = 80000000L + [env:esp32dev_audioreactive] board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} @@ -383,7 +390,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only lib_deps = ${esp32.lib_deps} board_build.partitions = ${esp32.default_partitions} @@ -395,9 +402,9 @@ platform_packages = ${esp32_idf_V4.platform_packages} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio -board_build.partitions = ${esp32.extended_partitions} +board_build.partitions = ${esp32.default_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32_V4} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D LEDPIN=25 ; ${esp32.AR_build_flags} @@ -420,27 +427,28 @@ upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} -[env:esp32s3dev_8MB] -;; ESP32-S3-DevKitC-1 development board, with 8MB FLASH, no PSRAM (flash_mode: qio) -board = esp32-s3-devkitc-1 +[env:esp32s3dev_16MB_opi] +;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) +board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support +board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} -upload_speed = 921600 ; or 460800 +upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 - -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip - ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} -board_build.partitions = ${esp32.large_partitions} +board_build.partitions = ${esp32.extreme_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio -; board_build.flash_mode = dio ;; try this if you have problems at startup monitor_filters = esp32_exception_decoder -[env:esp32s3dev_8MB_PSRAM_opi] +[env:esp32s3dev_8MB_opi] ;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB @@ -448,7 +456,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_PSRAM_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -461,7 +469,7 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder -[env:esp32s3_4M_PSRAM_qspi] +[env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB @@ -469,7 +477,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_PSRAM_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -D WLED_WATCHDOG_TIMEOUT=0 @@ -486,8 +494,8 @@ platform = ${esp32s2.platform} platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini board_build.partitions = ${esp32.default_partitions} -;board_build.flash_mode = qio -;board_build.f_flash = 80000000L +board_build.flash_mode = qio +board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index d7d41f3a6..e7bacb9bd 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -28,12 +28,15 @@ lib_deps = ${esp8266.lib_deps} ; robtillaart/SHT85@~0.3.3 ; gmag11/QuickESPNow ;@ 0.6.2 ; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library -; https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash -; build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +; https://github.com/kosme/arduinoFFT#develop @ 2.0.1 ;; used for USERMOD_AUDIOREACTIVE +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. ; +; Set a release name that may be used to distinguish required binary for flashing +; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS +; ; disable specific features ; -D WLED_DISABLE_OTA ; -D WLED_DISABLE_ALEXA @@ -179,7 +182,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} [env:d1_mini] @@ -189,7 +192,7 @@ platform_packages = ${common.platform_packages} upload_speed = 921600 board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder @@ -199,7 +202,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} [env:h803wf] @@ -208,7 +211,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:esp32dev_qio80] @@ -216,7 +219,7 @@ board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} @@ -231,7 +234,7 @@ board = esp32dev platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32_idf_V4.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32_idf_V4.default_partitions} @@ -240,14 +243,14 @@ board_build.flash_mode = dio [env:esp32s2_saola] board = esp32-s2-saola-1 -platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip -platform_packages = +platform = ${esp32s2.platform} +platform_packages = ${esp32s2.platform_packages} framework = arduino board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv board_build.flash_mode = qio upload_speed = 460800 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola +build_flags = ${common.build_flags} ${esp32s2.build_flags} ;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work -DARDUINO_USB_CDC_ON_BOOT=1 lib_deps = ${esp32s2.lib_deps} @@ -265,7 +268,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA lib_deps = ${esp8266.lib_deps} [env:esp8285_H801] @@ -274,7 +277,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA lib_deps = ${esp8266.lib_deps} [env:d1_mini_5CH_Shojo_PCB] @@ -283,7 +286,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_USE_SHOJO_PCB +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB lib_deps = ${esp8266.lib_deps} [env:d1_mini_debug] @@ -293,7 +296,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} ${common.debug_flags} +build_flags = ${common.build_flags} ${esp8266.build_flags} ${common.debug_flags} lib_deps = ${esp8266.lib_deps} [env:d1_mini_ota] @@ -305,7 +308,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} [env:anavi_miracle_controller] @@ -314,7 +317,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} [env:esp32c3dev_2MB] @@ -324,7 +327,7 @@ extends = esp32c3 platform = ${esp32c3.platform} platform_packages = ${esp32c3.platform_packages} board = esp32-c3-devkitm-1 -build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D WLED_DISABLE_OTA ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -341,7 +344,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 460800 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} +build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=16 -D RLYPIN=19 -D BTNPIN=17 @@ -361,7 +364,7 @@ board_build.partitions = ${esp32.default_partitions} [env:m5atom] board = esp32dev build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 +build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=27 -D BTNPIN=39 lib_deps = ${esp32.lib_deps} platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} @@ -371,14 +374,14 @@ board_build.partitions = ${esp32.default_partitions} board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=1 lib_deps = ${esp8266.lib_deps} [env:sp511e] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 lib_deps = ${esp8266.lib_deps} [env:Athom_RGBCW] ;7w and 5w(GU10) bulbs @@ -387,7 +390,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 lib_deps = ${esp8266.lib_deps} @@ -397,7 +400,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT lib_deps = ${esp8266.lib_deps} @@ -407,7 +410,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_4Pin_Controller] ; With clock and data interface @@ -416,7 +419,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_5Pin_Controller] ;Analog light strip controller @@ -425,7 +428,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:MY9291] @@ -434,7 +437,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D USERMOD_MY9291 lib_deps = ${esp8266.lib_deps} # ------------------------------------------------------------------------------ @@ -448,7 +451,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} [env:codm-controller-0_6-rev2] @@ -457,7 +460,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} # ------------------------------------------------------------------------------ @@ -468,7 +471,7 @@ board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 -build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED -D USERMOD_RTC -D USERMOD_ELEKSTUBE_IPS -D LEDPIN=12 From 674481f0d1d74c7e83de006a752e71cef48da451 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 23 Apr 2024 19:05:49 +0200 Subject: [PATCH 176/694] Multiple fixes - several compile warning fixes - multipin LED compile config - release info (update page, JSON "info") - WiFi scan fix if no networks found - UI glitch when no presets are found fix With multipin LED config it is now possible to assign GPIO to PWM RGB outputs. Achieved by having length of DATA_PINS be divisble by lengt of PIXEL_COUNTS. --- .../usermod_v2_four_line_display_ALT.h | 10 ++-- wled00/FX_2Dfcn.cpp | 10 ++-- wled00/FX_fcn.cpp | 23 +++---- wled00/data/index.js | 6 +- wled00/data/settings_wifi.htm | 60 ++++++++++--------- wled00/json.cpp | 1 + wled00/wled.h | 25 ++++---- wled00/xml.cpp | 2 + 8 files changed, 75 insertions(+), 62 deletions(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 24eb9794f..2cb1507ce 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -1135,10 +1135,12 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { return handled; } -#if CONFIG_FREERTOS_UNICORE -#define ARDUINO_RUNNING_CORE 0 -#else -#define ARDUINO_RUNNING_CORE 1 +#ifndef ARDUINO_RUNNING_CORE + #if CONFIG_FREERTOS_UNICORE + #define ARDUINO_RUNNING_CORE 0 + #else + #define ARDUINO_RUNNING_CORE 1 + #endif #endif void FourLineDisplayUsermod::onUpdateBegin(bool init) { #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 5a73792b6..e4007ed7e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -286,7 +286,7 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; - uint32_t curnew; + uint32_t curnew = BLACK; for (unsigned x = 0; x < cols; x++) { uint32_t cur = getPixelColorXY(x, row); uint32_t part = color_fade(cur, seep); @@ -297,8 +297,7 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ uint32_t prev = color_add(lastnew, part, true); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(x - 1, row, prev); - } - else // first pixel + } else // first pixel setPixelColorXY(x, row, curnew); lastnew = curnew; last = cur; // save original value for comparison on next iteration @@ -320,7 +319,7 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; - uint32_t curnew; + uint32_t curnew = BLACK; for (unsigned y = 0; y < rows; y++) { uint32_t cur = getPixelColorXY(col, y); uint32_t part = color_fade(cur, seep); @@ -331,8 +330,7 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { uint32_t prev = color_add(lastnew, part, true); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(col, y - 1, prev); - } - else // first pixel + } else // first pixel setPixelColorXY(col, y, curnew); lastnew = curnew; last = cur; //save original value for comparison on next iteration diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f94daca4a..5d031e8ce 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1009,7 +1009,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; - uint32_t curnew; + uint32_t curnew = BLACK; for (unsigned i = 0; i < vlength; i++) { uint32_t cur = getPixelColor(i); uint32_t part = color_fade(cur, seep); @@ -1099,21 +1099,24 @@ void WS2812FX::finalizeInit(void) { //if busses failed to load, add default (fresh install, FS issue, ...) if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); - const uint8_t defDataPins[] = {DATA_PINS}; - const uint16_t defCounts[] = {PIXEL_COUNTS}; - const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); - const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - uint16_t prevLen = 0; - for (int i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[] = {defDataPins[i]}; + const unsigned defDataPins[] = {DATA_PINS}; + const unsigned defCounts[] = {PIXEL_COUNTS}; + const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); + const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + const unsigned defNumBusses = defNumPins > defNumCounts && defNumCounts > 1 && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; + const unsigned pinsPerBus = defNumPins / defNumBusses; + unsigned prevLen = 0; + for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + uint8_t defPin[5]; // max 5 pins + for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc if (pinManager.isPinAllocated(defPin[0])) { defPin[0] = 1; // start with GPIO1 and work upwards while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; } - uint16_t start = prevLen; - uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + unsigned start = prevLen; + unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); if (BusManager::add(defCfg) == -1) break; diff --git a/wled00/data/index.js b/wled00/data/index.js index 5950b24ee..bbf6bd109 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -430,7 +430,7 @@ function presetError(empty) if (bckstr.length > 10) hasBackup = true; } catch (e) {} - var cn = `
`; + var cn = `
`; if (empty) cn += `You have no presets yet!`; else @@ -442,8 +442,8 @@ function presetError(empty) cn += `However, there is backup preset data of a previous installation available.
(Saving a preset will hide this and overwrite the backup)`; else cn += `Here is a backup of the last known good state:`; - cn += `
`; - cn += `
`; + cn += `
`; + cn += `
`; } cn += `
`; gId('pcont').innerHTML = cn; diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 3577e80d2..3c15d5a86 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -52,40 +52,42 @@ } scanLoops = 0; - let cs = d.querySelectorAll("#wifi_entries input[type=text]"); - for (let input of (cs||[])) { - let found = false; - let select = cE("select"); - select.id = input.id; - select.name = input.name; - select.setAttribute("onchange", "T(this)"); - preScanSSID = input.value; + if (networks.length > 0) { + let cs = d.querySelectorAll("#wifi_entries input[type=text]"); + for (let input of (cs||[])) { + let found = false; + let select = cE("select"); + select.id = input.id; + select.name = input.name; + select.setAttribute("onchange", "T(this)"); + preScanSSID = input.value; - for (let i = 0; i < select.children.length; i++) { - select.removeChild(select.children[i]); - } - - for (let i = 0; i < networks.length; i++) { - const option = cE("option"); - - option.setAttribute("value", networks[i].ssid); - option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; - - if (networks[i].ssid === input.value) { - option.setAttribute("selected", "selected"); - found = true; + for (let i = 0; i < select.children.length; i++) { + select.removeChild(select.children[i]); } + for (let i = 0; i < networks.length; i++) { + const option = cE("option"); + + option.setAttribute("value", networks[i].ssid); + option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; + + if (networks[i].ssid === input.value) { + option.setAttribute("selected", "selected"); + found = true; + } + + select.appendChild(option); + } + const option = cE("option"); + + option.setAttribute("value", "!Cs"); + option.textContent = "Other network..."; select.appendChild(option); + + if (input.value === "" || input.value === "Your_Network" || found) input.replaceWith(select); + else select.remove(); } - const option = cE("option"); - - option.setAttribute("value", "!Cs"); - option.textContent = "Other network..."; - select.appendChild(option); - - if (input.value === "" || input.value === "Your_Network" || found) input.replaceWith(select); - else select.remove(); } button.disabled = false; diff --git a/wled00/json.cpp b/wled00/json.cpp index 68973750e..ae8224ad3 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -636,6 +636,7 @@ void serializeInfo(JsonObject root) root[F("ver")] = versionString; root[F("vid")] = VERSION; root[F("cn")] = F(WLED_CODENAME); + root[F("release")] = FPSTR(releaseString); JsonObject leds = root.createNestedObject(F("leds")); leds[F("count")] = strip.getLengthTotal(); diff --git a/wled00/wled.h b/wled00/wled.h index b94f7790b..b6843b69d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -243,27 +243,32 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // int arr[]{0,1,2} becomes WLED_GLOBAL int arr[] _INIT_N(({0,1,2})); #ifndef WLED_DEFINE_GLOBAL_VARS -# define WLED_GLOBAL extern -# define _INIT(x) -# define _INIT_N(x) + #define WLED_GLOBAL extern + #define _INIT(x) + #define _INIT_N(x) + #define _INIT_PROGMEM(x) #else -# define WLED_GLOBAL -# define _INIT(x) = x - -//needed to ignore commas in array definitions -#define UNPACK( ... ) __VA_ARGS__ -# define _INIT_N(x) UNPACK x + #define WLED_GLOBAL + #define _INIT(x) = x + //needed to ignore commas in array definitions + #define UNPACK( ... ) __VA_ARGS__ + #define _INIT_N(x) UNPACK x + #define _INIT_PROGMEM(x) PROGMEM = x #endif #define STRINGIFY(X) #X #define TOSTRING(X) STRINGIFY(X) #ifndef WLED_VERSION - #define WLED_VERSION "dev" + #define WLED_VERSION dev +#endif +#ifndef WLED_RELEASE_NAME + #define WLED_RELEASE_NAME dev_release #endif // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); +WLED_GLOBAL char releaseString[] _INIT_PROGMEM(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3915d9b0e..0fe55b616 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -722,6 +722,8 @@ void getSettingsJS(byte subPage, char* dest) sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED ")); olen -= 2; //delete "; oappend(versionString); + oappend(SET_F("
")); + oappend((char*)FPSTR(releaseString)); oappend(SET_F("
(")); #if defined(ARDUINO_ARCH_ESP32) oappend(ESP.getChipModel()); From e83d3cb4a38e80d5c845d28a6d75853fcc27c3fd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 24 Apr 2024 16:04:54 +0200 Subject: [PATCH 177/694] Fix for #3878 --- usermods/BH1750_v2/usermod_bh1750.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index ede4aabc4..2a2bd4637 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -59,7 +59,7 @@ private: bool sensorFound = false; // Home Assistant and MQTT - String mqttLuminanceTopic = F(""); + String mqttLuminanceTopic; bool mqttInitialized = false; bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages From 24bd1db4fc51f4ff01236311a33bb9e6c1da7fff Mon Sep 17 00:00:00 2001 From: Suxsem Date: Fri, 26 Apr 2024 00:29:48 +0200 Subject: [PATCH 178/694] "X" span near to the number input field --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index eecdf66f0..b3e04076f 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -823,7 +823,7 @@ Swap:
IR info
- Relay GPIO: Invert Open drain  ✕
+ Relay GPIO:  ✕ Invert Open drain

Defaults

Turn LEDs on after power up/reset:
From 6276c2f1f5db493e2db47b85dfbeda018a1faa22 Mon Sep 17 00:00:00 2001 From: gaaat Date: Fri, 26 Apr 2024 16:39:32 +0200 Subject: [PATCH 179/694] improved brightness change via long button presses --- wled00/button.cpp | 26 +++++++++++++++++++++----- wled00/wled.h | 1 + 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 3b73df81d..296be8090 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -7,9 +7,10 @@ #define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing) #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press -#define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 +#define WLED_LONG_REPEATED_ACTION 400 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 #define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP #define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset +#define WLED_LONG_BRI_STEPS 16 // how long to wait before increasing/decreasing brightness on long press static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage @@ -39,7 +40,20 @@ void longPressAction(uint8_t b) if (!macroLongPress[b]) { switch (b) { case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break; - case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action + case 1: + // increase bri on true, decrease on false + if(buttonBriDirection) { + if (bri == 255) break; // avoid unnecessary updates to brightness + if (bri >= 255 - WLED_LONG_BRI_STEPS) bri = 255; + else bri += WLED_LONG_BRI_STEPS; + } else { + if (bri == 1) break; // avoid unnecessary updates to brightness + if (bri <= WLED_LONG_BRI_STEPS) bri = 1; + else bri -= WLED_LONG_BRI_STEPS; + } + stateUpdated(CALL_MODE_BUTTON); + buttonPressedTime[b] = millis(); + break; // repeatable action } } else { applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); @@ -284,10 +298,12 @@ void handleButton() buttonPressedBefore[b] = true; if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press - if (!buttonLongPressed[b]) longPressAction(b); - else if (b) { //repeatable action (~3 times per s) on button > 0 + if (!buttonLongPressed[b]) { + buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press longPressAction(b); - buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //333ms + } else if (b) { //repeatable action (~5 times per s) on button > 0 + longPressAction(b); + buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //200ms } buttonLongPressed[b] = true; } diff --git a/wled00/wled.h b/wled00/wled.h index 139c451f8..18eb17b7c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -594,6 +594,7 @@ WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false}); WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false}); WLED_GLOBAL unsigned long buttonPressedTime[WLED_MAX_BUTTONS] _INIT({0}); WLED_GLOBAL unsigned long buttonWaitTime[WLED_MAX_BUTTONS] _INIT({0}); +WLED_GLOBAL bool buttonBriDirection _INIT(false); WLED_GLOBAL bool disablePullUp _INIT(false); WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD); From bdd4d9f3ffd2db6d259d9ddb24cb7ad8b98ca1c3 Mon Sep 17 00:00:00 2001 From: gaaat98 <67930088+gaaat98@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:23:34 +0200 Subject: [PATCH 180/694] set buttonBriDirection as local static --- wled00/button.cpp | 2 +- wled00/wled.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 296be8090..1cd8245c8 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -13,6 +13,7 @@ #define WLED_LONG_BRI_STEPS 16 // how long to wait before increasing/decreasing brightness on long press static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage +static bool buttonBriDirection = false; // true: increase brightness, false: decrease brightness void shortPressAction(uint8_t b) { @@ -41,7 +42,6 @@ void longPressAction(uint8_t b) switch (b) { case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break; case 1: - // increase bri on true, decrease on false if(buttonBriDirection) { if (bri == 255) break; // avoid unnecessary updates to brightness if (bri >= 255 - WLED_LONG_BRI_STEPS) bri = 255; diff --git a/wled00/wled.h b/wled00/wled.h index 18eb17b7c..139c451f8 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -594,7 +594,6 @@ WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false}); WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false}); WLED_GLOBAL unsigned long buttonPressedTime[WLED_MAX_BUTTONS] _INIT({0}); WLED_GLOBAL unsigned long buttonWaitTime[WLED_MAX_BUTTONS] _INIT({0}); -WLED_GLOBAL bool buttonBriDirection _INIT(false); WLED_GLOBAL bool disablePullUp _INIT(false); WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD); From 886120fe9f661cf30a204f186dacd6329e4e62cb Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 26 Apr 2024 20:07:27 +0200 Subject: [PATCH 181/694] Bugfix - getPixelColor() for analog - RMT channel (#3922) --- wled00/bus_manager.cpp | 17 ++++++++++++++++- wled00/bus_wrapper.h | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 82e81a387..ac6923f40 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -466,7 +466,22 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { //does no index check uint32_t BusPwm::getPixelColor(uint16_t pix) { if (!_valid) return 0; - return RGBW32(_data[0], _data[1], _data[2], _data[3]); + // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) + switch (_type) { + case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation + return RGBW32(0, 0, 0, _data[0]); + case TYPE_ANALOG_2CH: //warm white + cold white + if (cctICused) return RGBW32(0, 0, 0, _data[0]); + else return RGBW32(0, 0, 0, _data[0] + _data[1]); + case TYPE_ANALOG_5CH: //RGB + warm white + cold white + if (cctICused) return RGBW32(_data[0], _data[1], _data[2], _data[3]); + else return RGBW32(_data[0], _data[1], _data[2], _data[3] + _data[4]); + case TYPE_ANALOG_4CH: //RGBW + return RGBW32(_data[0], _data[1], _data[2], _data[3]); + case TYPE_ANALOG_3CH: //standard dumb RGB + return RGBW32(_data[0], _data[1], _data[2], 0); + } + return RGBW32(_data[0], _data[0], _data[0], _data[0]); } #ifndef ESP8266 diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index ebbeca4ad..c48946eb8 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -496,6 +496,11 @@ class PolyBus { } static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { + #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) + // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation + // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation + if (channel > 1) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + #endif void* busPtr = nullptr; switch (busType) { case I_NONE: break; From d48bab02a1a30444b9bb026fe9dd24235385a8e1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 26 Apr 2024 20:11:46 +0200 Subject: [PATCH 182/694] Speed & size optimisations using native sized variables --- wled00/FX_2Dfcn.cpp | 110 ++++++++++++++++++++++---------------------- wled00/FX_fcn.cpp | 92 ++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index e4007ed7e..e14b68f4f 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -110,11 +110,11 @@ void WS2812FX::setUpMatrix() { releaseJSONBufferLock(); } - uint16_t x, y, pix=0; //pixel + unsigned x, y, pix=0; //pixel for (size_t pan = 0; pan < panel.size(); pan++) { Panel &p = panel[pan]; - uint16_t h = p.vertical ? p.height : p.width; - uint16_t v = p.vertical ? p.width : p.height; + unsigned h = p.vertical ? p.height : p.width; + unsigned v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ for(size_t i = 0; i < h; i++) { y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j; @@ -163,8 +163,8 @@ void WS2812FX::setUpMatrix() { // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) { - uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) - uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1) + unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; } @@ -180,7 +180,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; - if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed + if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels @@ -189,7 +189,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally - uint16_t xX = (x+g), yY = (y+j); + unsigned xX = (x+g), yY = (y+j); if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND @@ -221,16 +221,16 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); float fX = x * (cols-1); float fY = y * (rows-1); if (aa) { - uint16_t xL = roundf(fX-0.49f); - uint16_t xR = roundf(fX+0.49f); - uint16_t yT = roundf(fY-0.49f); - uint16_t yB = roundf(fY+0.49f); + unsigned xL = roundf(fX-0.49f); + unsigned xR = roundf(fX+0.49f); + unsigned yT = roundf(fY-0.49f); + unsigned yB = roundf(fY+0.49f); float dL = (fX - xL)*(fX - xL); float dR = (xR - fX)*(xR - fX); float dT = (fY - yT)*(fY - yT); @@ -266,7 +266,7 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; - if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed + if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels if (x >= width() || y >= height()) return 0; @@ -276,8 +276,8 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ if (!isActive() || blur_amount == 0) return; // not active - const uint_fast16_t cols = virtualWidth(); - const uint_fast16_t rows = virtualHeight(); + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); if (row >= rows) return; // blur one row @@ -309,8 +309,8 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ // blurCol: perform a blur on a column of a rectangular matrix void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active - const uint_fast16_t cols = virtualWidth(); - const uint_fast16_t rows = virtualHeight(); + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); if (col >= cols) return; // blur one column @@ -342,34 +342,34 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { if (!isActive() || blur_amount == 0) return; // not active - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); - const uint16_t dim1 = vertical ? rows : cols; - const uint16_t dim2 = vertical ? cols : rows; + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); + const unsigned dim1 = vertical ? rows : cols; + const unsigned dim2 = vertical ? cols : rows; if (i >= dim2) return; const float seep = blur_amount/255.f; const float keep = 3.f - 2.f*seep; // 1D box blur CRGB tmp[dim1]; - for (int j = 0; j < dim1; j++) { - uint16_t x = vertical ? i : j; - uint16_t y = vertical ? j : i; - int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow - int16_t yp = vertical ? y-1 : y; // "signed" to prevent underflow - uint16_t xn = vertical ? x : x+1; - uint16_t yn = vertical ? y+1 : y; + for (unsigned j = 0; j < dim1; j++) { + unsigned x = vertical ? i : j; + unsigned y = vertical ? j : i; + int xp = vertical ? x : x-1; // "signed" to prevent underflow + int yp = vertical ? y-1 : y; // "signed" to prevent underflow + unsigned xn = vertical ? x : x+1; + unsigned yn = vertical ? y+1 : y; CRGB curr = getPixelColorXY(x,y); CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp); CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn); - uint16_t r, g, b; + unsigned r, g, b; r = (curr.r*keep + (prev.r + next.r)*seep) / 3; g = (curr.g*keep + (prev.g + next.g)*seep) / 3; b = (curr.b*keep + (prev.b + next.b)*seep) / 3; tmp[j] = CRGB(r,g,b); } - for (int j = 0; j < dim1; j++) { - uint16_t x = vertical ? i : j; - uint16_t y = vertical ? j : i; + for (unsigned j = 0; j < dim1; j++) { + unsigned x = vertical ? i : j; + unsigned y = vertical ? j : i; setPixelColorXY(x, y, tmp[j]); } } @@ -389,14 +389,14 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { // it can be used to (slowly) clear the LEDs to black. void Segment::blur1d(fract8 blur_amount) { - const uint16_t rows = virtualHeight(); + const unsigned rows = virtualHeight(); for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount); } void Segment::moveX(int8_t delta, bool wrap) { if (!isActive()) return; // not active - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const int cols = virtualWidth(); + const int rows = virtualHeight(); if (!delta || abs(delta) >= cols) return; uint32_t newPxCol[cols]; for (int y = 0; y < rows; y++) { @@ -413,8 +413,8 @@ void Segment::moveX(int8_t delta, bool wrap) { void Segment::moveY(int8_t delta, bool wrap) { if (!isActive()) return; // not active - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const int cols = virtualWidth(); + const int rows = virtualHeight(); if (!delta || abs(delta) >= rows) return; uint32_t newPxCol[rows]; for (int x = 0; x < cols; x++) { @@ -474,13 +474,13 @@ void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { if (!isActive() || radius == 0) return; // not active - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); - for (int16_t y = -radius; y <= radius; y++) { - for (int16_t x = -radius; x <= radius; x++) { + const int cols = virtualWidth(); + const int rows = virtualHeight(); + for (int y = -radius; y <= radius; y++) { + for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius && - int16_t(cx)+x>=0 && int16_t(cy)+y>=0 && - int16_t(cx)+x=0 && int(cy)+y>=0 && + int(cx)+x= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; - const int16_t dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; + const int dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; for (;;) { setPixelColorXY(x0,y0,c); if (x0==x1 && y0==y1) break; @@ -525,8 +525,8 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const int cols = virtualWidth(); + const int rows = virtualHeight(); const int font = w*h; CRGB col = CRGB(color); @@ -565,7 +565,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu if (!isActive()) return; // not active // extract the fractional parts and derive their inverses - uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; + unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; // calculate the intensities for each affected pixel uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy), WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 5d031e8ce..ce510f16e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -327,7 +327,7 @@ void Segment::stopTransition() { } void Segment::handleTransition() { - uint16_t _progress = progress(); + unsigned _progress = progress(); if (_progress == 0xFFFFU) stopTransition(); } @@ -412,9 +412,9 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { #endif uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { - uint32_t prog = progress(); + unsigned prog = progress(); if (prog < 0xFFFFU) { - uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog; + unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); return curBri / 0xFFFFU; } @@ -423,7 +423,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND - uint16_t prog = progress(); + unsigned prog = progress(); if (modeBlending && prog < 0xFFFFU) return _t->_modeT; #endif return mode; @@ -440,13 +440,13 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); - uint16_t prog = progress(); + unsigned prog = progress(); if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms - uint16_t noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (int i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); targetPalette = _t->_palT; // copy transitioning/temporary palette } return targetPalette; @@ -576,7 +576,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { mode = fx; // load default values from effect string if (loadDefaults) { - int16_t sOpt; + int sOpt; sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; @@ -610,21 +610,21 @@ void Segment::setPalette(uint8_t pal) { // 2D matrix uint16_t IRAM_ATTR Segment::virtualWidth() const { - uint16_t groupLen = groupLength(); - uint16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; + unsigned groupLen = groupLength(); + unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } uint16_t IRAM_ATTR Segment::virtualHeight() const { - uint16_t groupLen = groupLength(); - uint16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; + unsigned groupLen = groupLength(); + unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } uint16_t IRAM_ATTR Segment::nrOfVStrips() const { - uint16_t vLen = 1; + unsigned vLen = 1; #ifndef WLED_DISABLE_2D if (is2D()) { switch (map1D2D) { @@ -641,9 +641,9 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const { uint16_t IRAM_ATTR Segment::virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { - uint16_t vW = virtualWidth(); - uint16_t vH = virtualHeight(); - uint16_t vLen = vW * vH; // use all pixels from segment + unsigned vW = virtualWidth(); + unsigned vH = virtualHeight(); + unsigned vLen = vW * vH; // use all pixels from segment switch (map1D2D) { case M12_pBar: vLen = vH; @@ -656,8 +656,8 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLen; } #endif - uint16_t groupLen = groupLength(); // is always >= 1 - uint16_t vLength = (length() + groupLen - 1) / groupLen; + unsigned groupLen = groupLength(); // is always >= 1 + unsigned vLength = (length() + groupLen - 1) / groupLen; if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED return vLength; } @@ -674,8 +674,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #ifndef WLED_DISABLE_2D if (is2D()) { - uint16_t vH = virtualHeight(); // segment height in logical pixels - uint16_t vW = virtualWidth(); + int vH = virtualHeight(); // segment height in logical pixels + int vW = virtualWidth(); switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip @@ -732,7 +732,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif - uint16_t len = length(); + unsigned len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); @@ -785,8 +785,8 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) float fC = i * (virtualLength()-1); if (aa) { - uint16_t iL = roundf(fC-0.49f); - uint16_t iR = roundf(fC+0.49f); + unsigned iL = roundf(fC-0.49f); + unsigned iR = roundf(fC+0.49f); float dL = (fC - iL)*(fC - iL); float dR = (iR - fC)*(iR - fC); uint32_t cIL = getPixelColor(iL | (vStrip<<16)); @@ -803,7 +803,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) setPixelColor(iL | (vStrip<<16), col); } } else { - setPixelColor(uint16_t(roundf(fC)) | (vStrip<<16), col); + setPixelColor(int(roundf(fC)) | (vStrip<<16), col); } } #endif @@ -818,8 +818,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #ifndef WLED_DISABLE_2D if (is2D()) { - uint16_t vH = virtualHeight(); // segment height in logical pixels - uint16_t vW = virtualWidth(); + unsigned vH = virtualHeight(); // segment height in logical pixels + unsigned vW = virtualWidth(); switch (map1D2D) { case M12_Pixels: return getPixelColorXY(i % vW, i / vW); @@ -875,9 +875,9 @@ uint8_t Segment::differs(Segment& b) const { } void Segment::refreshLightCapabilities() { - uint8_t capabilities = 0; - uint16_t segStartIdx = 0xFFFFU; - uint16_t segStopIdx = 0; + unsigned capabilities = 0; + unsigned segStartIdx = 0xFFFFU; + unsigned segStopIdx = 0; if (!isActive()) { _capabilities = 0; @@ -887,7 +887,7 @@ void Segment::refreshLightCapabilities() { if (start < Segment::maxWidth * Segment::maxHeight) { // we are withing 2D matrix (includes 1D segments) for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { - uint16_t index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical + unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical if (index < 0xFFFFU) { if (segStartIdx > index) segStartIdx = index; if (segStopIdx < index) segStopIdx = index; @@ -912,7 +912,7 @@ void Segment::refreshLightCapabilities() { if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) if (bus->hasWhite()) { - uint8_t aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); + unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; @@ -928,8 +928,8 @@ void Segment::refreshLightCapabilities() { */ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active - const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); - const uint16_t rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, c); else setPixelColor(x, c); @@ -941,8 +941,8 @@ void Segment::fill(uint32_t c) { */ void Segment::fade_out(uint8_t rate) { if (!isActive()) return; // not active - const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); - const uint16_t rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D rate = (255-rate) >> 1; float mappedRate = float(rate) +1.1f; @@ -979,8 +979,8 @@ void Segment::fade_out(uint8_t rate) { // fades all pixels to black using nscale8() void Segment::fadeToBlackBy(uint8_t fadeBy) { if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply - const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); - const uint16_t rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); @@ -1065,7 +1065,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); - uint8_t paletteIndex = i; + unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" @@ -1132,7 +1132,7 @@ void WS2812FX::finalizeInit(void) { _hasWhiteChannel |= bus->hasWhite(); //refresh is required to remain off if at least one of the strips requires the refresh. _isOffRefreshRequired |= bus->isOffRefreshRequired(); - uint16_t busEnd = bus->getStart() + bus->getLength(); + unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; #ifdef ESP8266 if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; @@ -1176,10 +1176,10 @@ void WS2812FX::service() { if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - uint16_t delay = FRAMETIME; + unsigned delay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen - int16_t oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) + int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) _virtualSegmentLength = seg.virtualLength(); //SEGLEN _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); @@ -1203,7 +1203,7 @@ void WS2812FX::service() { Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - uint16_t d2 = (*_mode[tmpMode])(); // run old mode + unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore @@ -1378,13 +1378,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) { } uint16_t WS2812FX::getLengthTotal(void) { - uint16_t len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D + unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D if (isMatrix && _length > len) len = _length; // for 2D with trailing strip return len; } uint16_t WS2812FX::getLengthPhysical(void) { - uint16_t len = 0; + unsigned len = 0; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses @@ -1461,8 +1461,8 @@ void WS2812FX::resetSegments() { void WS2812FX::makeAutoSegments(bool forceReset) { if (autoSegments) { //make one segment per bus - uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; - uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; + unsigned segStarts[MAX_NUM_SEGMENTS] = {0}; + unsigned segStops [MAX_NUM_SEGMENTS] = {0}; size_t s = 0; #ifndef WLED_DISABLE_2D From 25dd43b949176a99c19d43ca1bc7b53474754708 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 26 Apr 2024 23:49:34 +0200 Subject: [PATCH 183/694] Bugfix for bugfix - thanks @softhack007 --- wled00/bus_wrapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index c48946eb8..efaad7c42 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -499,7 +499,7 @@ class PolyBus { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - if (channel > 1) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 #endif void* busPtr = nullptr; switch (busType) { From d7e0b364d16b6474aed0d893d60e95f34a669fc0 Mon Sep 17 00:00:00 2001 From: gaaat98 <67930088+gaaat98@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:25:34 +0200 Subject: [PATCH 184/694] fixed wrong comment --- wled00/button.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 1cd8245c8..6d69f15f8 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -10,7 +10,7 @@ #define WLED_LONG_REPEATED_ACTION 400 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 #define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP #define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset -#define WLED_LONG_BRI_STEPS 16 // how long to wait before increasing/decreasing brightness on long press +#define WLED_LONG_BRI_STEPS 16 // how much to increase/decrease the brightness with each long press repetition static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage static bool buttonBriDirection = false; // true: increase brightness, false: decrease brightness From 1048bf993af9693c7b795f5e2ccb4db404636616 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 27 Apr 2024 23:34:35 +0200 Subject: [PATCH 185/694] use lolin_s3_mini for esp32-S3 4MB buildenv --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 8d9109c02..76c4c92d6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -471,8 +471,7 @@ monitor_filters = esp32_exception_decoder [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) -board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support -board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 From 9f99a1896df5e1439d328ab974d89f18bd5ac37e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:05:12 +0200 Subject: [PATCH 186/694] presets.json PSRAM caching: consider cacheInvalidate * trying to make the caching mechanism bulletproof. `cacheInvalidate` is changed when - autosave usermod updates presets - a file was upload * (coding style) fixed some unitialized variables --- wled00/file.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index eae50ff1d..814aa77e6 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -381,11 +381,15 @@ void updateFSInfo() { // original idea by @akaricchi (https://github.com/Akaricchi) // returns a pointer to the PSRAM buffer, updates size parameter static const uint8_t *getPresetCache(size_t &size) { - static unsigned long presetsCachedTime; - static uint8_t *presetsCached; - static size_t presetsCachedSize; + static unsigned long presetsCachedTime = 0; + static uint8_t *presetsCached = nullptr; + static size_t presetsCachedSize = 0; + static byte presetsCachedValidate = 0; - if (presetsModifiedTime != presetsCachedTime) { + //if (presetsModifiedTime != presetsCachedTime) DEBUG_PRINTLN(F("getPresetCache(): presetsModifiedTime changed.")); + //if (presetsCachedValidate != cacheInvalidate) DEBUG_PRINTLN(F("getPresetCache(): cacheInvalidate changed.")); + + if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) { if (presetsCached) { free(presetsCached); presetsCached = nullptr; @@ -396,6 +400,7 @@ static const uint8_t *getPresetCache(size_t &size) { File file = WLED_FS.open(FPSTR(getPresetsFileName()), "r"); if (file) { presetsCachedTime = presetsModifiedTime; + presetsCachedValidate = cacheInvalidate; presetsCachedSize = 0; presetsCached = (uint8_t*)ps_malloc(file.size() + 1); if (presetsCached) { From 74bc159a522a14f947e2b66288996a20a2e229e5 Mon Sep 17 00:00:00 2001 From: gaaat Date: Mon, 29 Apr 2024 20:19:10 +0200 Subject: [PATCH 187/694] enabled some audioreactive effects for single pixel strips/segments --- wled00/FX.cpp | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5592f7ba8..3110ab910 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6643,7 +6643,7 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall, // * JUGGLES // ////////////////////// uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + //if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6655,12 +6655,13 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); for (size_t i=0; i Date: Tue, 30 Apr 2024 14:09:12 +0200 Subject: [PATCH 188/694] using brightness in analog clock overlay --- wled00/overlay.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 19b26c224..92d8820e3 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -11,6 +11,7 @@ void _overlayAnalogClock() { _overlayAnalogCountdown(); return; } + uint8_t brightness = strip.getBrightness(); float hourP = ((float)(hour(localTime)%12))/12.0f; float minuteP = ((float)minute(localTime))/60.0f; hourP = hourP + minuteP/12.0f; @@ -25,11 +26,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, 0xFF0000); - strip.setRange(overlayMin, secondPixel, 0xFF0000); + strip.setRange(analogClock12pixel, overlayMax, (uint32_t)brightness<<16); + strip.setRange(overlayMin, secondPixel, (uint32_t)brightness<<16); } else { - strip.setRange(analogClock12pixel, secondPixel, 0xFF0000); + strip.setRange(analogClock12pixel, secondPixel, (uint32_t)brightness<<16); } } if (analogClock5MinuteMarks) @@ -38,12 +39,12 @@ void _overlayAnalogClock() { unsigned pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, 0x00FFAA); + strip.setPixelColor(pix, ((uint32_t)brightness<<8)|((uint32_t)brightness*2/3)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000); - strip.setPixelColor(minutePixel, 0x00FF00); - strip.setPixelColor(hourPixel, 0x0000FF); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, (uint32_t)brightness<<16); + strip.setPixelColor(minutePixel, (uint32_t)brightness<<8); + strip.setPixelColor(hourPixel, (uint32_t)brightness); } From bd69c24231153912e97ff31be399dcedd34567a5 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Tue, 30 Apr 2024 14:54:53 +0200 Subject: [PATCH 189/694] intermediate update --- usermods/Battery/battery_defaults.h | 1 + usermods/Battery/readme.md | 5 +++++ usermods/Battery/types/lion.h | 3 +-- usermods/Battery/types/lipo.h | 3 +-- usermods/Battery/types/unkown.h | 3 +-- usermods/Battery/usermod_v2_Battery.h | 6 ++---- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 6d0a95dc4..ea01e8620 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -111,6 +111,7 @@ #define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5 #endif +// battery types typedef enum { unknown=0, diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index 1ca229763..e82378084 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -86,6 +86,11 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3. ## 📝 Change Log +2024-04-30 + +- integrate factory pattern to make it easier to add other / custom battery types +- update readme + 2023-01-04 - basic support for LiPo rechargeable batteries ( `-D USERMOD_BATTERY_USE_LIPO`) diff --git a/usermods/Battery/types/lion.h b/usermods/Battery/types/lion.h index 0d2325386..b3641e263 100644 --- a/usermods/Battery/types/lion.h +++ b/usermods/Battery/types/lion.h @@ -13,8 +13,7 @@ class Lion : public Battery private: public: - Lion() - : Battery() + Lion() : Battery() { this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); diff --git a/usermods/Battery/types/lipo.h b/usermods/Battery/types/lipo.h index f65ab12c5..1deb6e7d3 100644 --- a/usermods/Battery/types/lipo.h +++ b/usermods/Battery/types/lipo.h @@ -13,8 +13,7 @@ class Lipo : public Battery private: public: - Lipo() - : Battery() + Lipo() : Battery() { this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); diff --git a/usermods/Battery/types/unkown.h b/usermods/Battery/types/unkown.h index edf220040..32a1bfe42 100644 --- a/usermods/Battery/types/unkown.h +++ b/usermods/Battery/types/unkown.h @@ -13,8 +13,7 @@ class Unkown : public Battery private: public: - Unkown() - : Battery() + Unkown() : Battery() { this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 9b980d557..a82a46667 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -9,9 +9,8 @@ /* * Usermod by Maximilian Mewes - * Mail: mewes.maximilian@gmx.de - * GitHub: itCarl - * Date: 25.12.2022 + * E-mail: mewes.maximilian@gmx.de + * Created at: 25.12.2022 * If you have any questions, please feel free to contact me. */ class UsermodBattery : public Usermod @@ -140,7 +139,6 @@ class UsermodBattery : public Usermod } #else //ESP8266 boards have only one analog input pin A0 pinMode(batteryPin, INPUT); - // voltage = readVoltage(); #endif // plug in the right battery type From a13f1a9bee52ad637cf6903546f1bac4ef3df3da Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Tue, 30 Apr 2024 15:24:02 +0200 Subject: [PATCH 190/694] bug fixes --- usermods/Battery/battery.h | 61 +++++++++++++++------------ usermods/Battery/types/lion.h | 8 ---- usermods/Battery/types/lipo.h | 8 ---- usermods/Battery/usermod_v2_Battery.h | 43 ++++++++++++++++++- 4 files changed, 75 insertions(+), 45 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index 4cdfb035f..084e6c0aa 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -32,7 +32,14 @@ class Battery this->setCalibration(USERMOD_BATTERY_CALIBRATION); } - virtual void update(batteryConfig cfg) = 0; + virtual void update(batteryConfig cfg) + { + if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); + if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); + if(cfg.level) this->setLevel(cfg.level); + if(cfg.calibration) this->setCalibration(cfg.calibration); + if(cfg.voltageMultiplier) this->setVoltageMultiplier(cfg.voltageMultiplier); + } /** * Corresponding battery curves @@ -49,10 +56,10 @@ class Battery /* - * - * Getter and Setter - * - */ + * + * Getter and Setter + * + */ /* * Get lowest configured battery voltage @@ -63,26 +70,26 @@ class Battery } /* - * Set lowest battery voltage - * can't be below 0 volt - */ + * Set lowest battery voltage + * can't be below 0 volt + */ virtual void setMinVoltage(float voltage) { this->minVoltage = max(0.0f, voltage); } /* - * Get highest configured battery voltage - */ + * Get highest configured battery voltage + */ virtual float getMaxVoltage() { return this->maxVoltage; } /* - * Set highest battery voltage - * can't be below minVoltage - */ + * Set highest battery voltage + * can't be below minVoltage + */ virtual void setMaxVoltage(float voltage) { this->maxVoltage = max(getMinVoltage()+.5f, voltage); @@ -110,43 +117,43 @@ class Battery void setLevel(float level) { - this->level = constrain(level, 0.0f, 110.0f);; + this->level = constrain(level, 0.0f, 110.0f); } /* - * Get the configured calibration value - * a offset value to fine-tune the calculated voltage. - */ + * Get the configured calibration value + * a offset value to fine-tune the calculated voltage. + */ virtual float getCalibration() { return calibration; } /* - * Set the voltage calibration offset value - * a offset value to fine-tune the calculated voltage. - */ + * Set the voltage calibration offset value + * a offset value to fine-tune the calculated voltage. + */ virtual void setCalibration(float offset) { calibration = offset; } /* - * Get the configured calibration value - * a value to set the voltage divider ratio - */ + * Get the configured calibration value + * a value to set the voltage divider ratio + */ virtual float getVoltageMultiplier() { return voltageMultiplier; } /* - * Set the voltage multiplier value - * a value to set the voltage divider ratio. - */ + * Set the voltage multiplier value + * a value to set the voltage divider ratio. + */ virtual void setVoltageMultiplier(float multiplier) { - voltageMultiplier = voltageMultiplier; + voltageMultiplier = multiplier; } }; diff --git a/usermods/Battery/types/lion.h b/usermods/Battery/types/lion.h index b3641e263..e77266164 100644 --- a/usermods/Battery/types/lion.h +++ b/usermods/Battery/types/lion.h @@ -19,14 +19,6 @@ class Lion : public Battery this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); } - void update(batteryConfig cfg) - { - if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); - if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); - if(cfg.level) this->setLevel(cfg.level); - if(cfg.calibration) this->setCalibration(cfg.calibration); - } - float mapVoltage(float v, float min, float max) override { return this->linearMapping(v, min, max); // basic mapping diff --git a/usermods/Battery/types/lipo.h b/usermods/Battery/types/lipo.h index 1deb6e7d3..d732cf4da 100644 --- a/usermods/Battery/types/lipo.h +++ b/usermods/Battery/types/lipo.h @@ -19,14 +19,6 @@ class Lipo : public Battery this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); } - void update(batteryConfig cfg) - { - if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); - if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); - if(cfg.level) this->setLevel(cfg.level); - if(cfg.calibration) this->setCalibration(cfg.calibration); - } - /** * LiPo batteries have a differnt discharge curve, see * https://blog.ampow.com/lipo-voltage-chart/ diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index a82a46667..b9631d6db 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -264,6 +264,7 @@ class UsermodBattery : public Usermod battery[F("min-voltage")] = bat->getMinVoltage(); battery[F("max-voltage")] = bat->getMaxVoltage(); battery[F("calibration")] = bat->getCalibration(); + battery[F("voltage-multiplier")] = bat->getVoltageMultiplier(); battery[FPSTR(_readInterval)] = readingInterval; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section @@ -283,6 +284,7 @@ class UsermodBattery : public Usermod getJsonValue(battery[F("min-voltage")], cfg.minVoltage); getJsonValue(battery[F("max-voltage")], cfg.maxVoltage); getJsonValue(battery[F("calibration")], cfg.calibration); + getJsonValue(battery[F("voltage-multiplier")], cfg.voltageMultiplier); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); @@ -459,6 +461,7 @@ class UsermodBattery : public Usermod setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); setCalibration(battery[F("calibration")] | calibration); + setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); getUsermodConfigFromJsonObject(battery); @@ -537,7 +540,25 @@ class UsermodBattery : public Usermod return USERMOD_ID_BATTERY; } + /** + * get currently active battery type + */ + batteryType getBatteryType() + { + return cfg.type; + } + /** + * Set currently active battery type + */ + batteryType setBatteryType(batteryType type) + { + cfg.type = type; + } + + /** + * + */ unsigned long getReadingInterval() { return readingInterval; @@ -561,7 +582,7 @@ class UsermodBattery : public Usermod /** * Set lowest battery voltage - * cant be below 0 volt + * can't be below 0 volt */ void setMinBatteryVoltage(float voltage) { @@ -622,6 +643,24 @@ class UsermodBattery : public Usermod bat->setCalibration(offset); } + /** + * Set the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + void setVoltageMultiplier(float multiplier) + { + bat->setVoltageMultiplier(multiplier); + } + + /* + * Get the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + float getVoltageMultiplier() + { + return bat->getVoltageMultiplier(); + } + /** * Get auto-off feature enabled status * is auto-off enabled, true/false @@ -727,7 +766,7 @@ class UsermodBattery : public Usermod } /** - * Get low-power-indicator status when the indication is done thsi returns true + * Get low-power-indicator status when the indication is done this returns true */ bool getLowPowerIndicatorDone() { From ff10130176ff06a81ed09113b6669703583a3c71 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 30 Apr 2024 16:53:47 +0200 Subject: [PATCH 191/694] Fix resizing bug The bug was that when resizing the window, it always jumped to the Colors tab instead of staying on the currently selected tab. --- wled00/data/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index bbf6bd109..9f8c579d0 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -304,7 +304,6 @@ function updateTablinks(tabI) { var tablinks = gEBCN("tablinks"); for (var i of tablinks) i.classList.remove('active'); - if (pcMode) return; tablinks[tabI].classList.add('active'); } @@ -3047,12 +3046,11 @@ function togglePcMode(fromB = false) if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); + openTab(0, true); } pcMode = (wW >= 1024) && pcModeA; if (cpick) cpick.resize(pcMode && wW>1023 && wW<1250 ? 230 : 260); // for tablet in landscape if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() - openTab(0, true); - updateTablinks(0); gId('buttonPcm').className = (pcMode) ? "active":""; gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); From 2245ee6fce23343cacfd6cd424a6339be0f1fada Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Tue, 30 Apr 2024 17:02:57 +0200 Subject: [PATCH 192/694] bugfixes --- usermods/Battery/battery.h | 9 +++++---- usermods/Battery/usermod_v2_Battery.h | 18 ++++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index 084e6c0aa..31e2e0755 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -27,7 +27,7 @@ class Battery public: Battery() { - this->setVoltage(this->getVoltage()); + this->setVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER); this->setCalibration(USERMOD_BATTERY_CALIBRATION); } @@ -105,9 +105,10 @@ class Battery */ void setVoltage(float voltage) { - this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) ) - ? -1.0f - : voltage; + // this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) ) + // ? -1.0f + // : voltage; + this->voltage = voltage; } float getLevel() diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index b9631d6db..31c31f066 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -28,8 +28,6 @@ class UsermodBattery : public Usermod unsigned long lastReadTime = 0; // between 0 and 1, to control strength of voltage smoothing filter float alpha = USERMOD_BATTERY_AVERAGING_ALPHA; - // offset or calibration value to fine tune the calculated voltage - float calibration = USERMOD_BATTERY_CALIBRATION; // auto shutdown/shutoff/master off feature bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED; @@ -45,6 +43,7 @@ class UsermodBattery : public Usermod unsigned long lowPowerActivationTime = 0; // used temporary during active time uint8_t lastPreset = 0; + // bool initDone = false; bool initializing = true; @@ -311,9 +310,8 @@ class UsermodBattery : public Usermod { JsonObject battery = root.createNestedObject(FPSTR(_name)); - if (battery.isNull()) { + if (battery.isNull()) battery = root.createNestedObject(FPSTR(_name)); - } addBatteryToJsonObject(battery, true); @@ -460,8 +458,8 @@ class UsermodBattery : public Usermod // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); - setCalibration(battery[F("calibration")] | calibration); - setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier); + setCalibration(battery[F("calibration")] | bat->getCalibration()); + setVoltageMultiplier(battery[F("voltage-multiplier")] | bat->getVoltageMultiplier()); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); getUsermodConfigFromJsonObject(battery); @@ -548,14 +546,6 @@ class UsermodBattery : public Usermod return cfg.type; } - /** - * Set currently active battery type - */ - batteryType setBatteryType(batteryType type) - { - cfg.type = type; - } - /** * */ From fd9570e7826b53a0c59440179bd340f195d0bb6a Mon Sep 17 00:00:00 2001 From: Pasquale Pizzuti Date: Tue, 30 Apr 2024 17:52:35 +0200 Subject: [PATCH 193/694] using color_fade --- wled00/overlay.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 92d8820e3..cd0c04c04 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -26,11 +26,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, (uint32_t)brightness<<16); - strip.setRange(overlayMin, secondPixel, (uint32_t)brightness<<16); + strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, brightness)); + strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, brightness)); } else { - strip.setRange(analogClock12pixel, secondPixel, (uint32_t)brightness<<16); + strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, brightness)); } } if (analogClock5MinuteMarks) @@ -39,12 +39,12 @@ void _overlayAnalogClock() { unsigned pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, ((uint32_t)brightness<<8)|((uint32_t)brightness*2/3)); + strip.setPixelColor(pix, color_fade(0x00FFAA, brightness)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, (uint32_t)brightness<<16); - strip.setPixelColor(minutePixel, (uint32_t)brightness<<8); - strip.setPixelColor(hourPixel, (uint32_t)brightness); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, brightness)); + strip.setPixelColor(minutePixel, color_fade(0x00FF00, brightness)); + strip.setPixelColor(hourPixel, color_fade(0x0000FF, brightness)); } From 05a8c692f29f2439a96027433ee6440f71a84c7b Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Tue, 30 Apr 2024 18:11:18 +0200 Subject: [PATCH 194/694] read initial voltage correctly --- usermods/Battery/battery.h | 1 - usermods/Battery/usermod_v2_Battery.h | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h index 31e2e0755..2ddd84149 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/battery.h @@ -27,7 +27,6 @@ class Battery public: Battery() { - this->setVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER); this->setCalibration(USERMOD_BATTERY_CALIBRATION); } diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 31c31f066..7b6b038a6 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -150,6 +150,8 @@ class UsermodBattery : public Usermod // update the choosen battery type with configured values bat->update(cfg); + bat->setVoltage(readVoltage()); + nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -389,7 +391,7 @@ class UsermodBattery : public Usermod addBatteryToJsonObject(battery, false); // read voltage in case calibration or voltage multiplier changed to see immediate effect - // voltage = readVoltage(); + bat->setVoltage(readVoltage()); DEBUG_PRINTLN(F("Battery config saved.")); } From d2984e9e160f649e0d900761d76240740201a4b6 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 30 Apr 2024 18:57:53 +0200 Subject: [PATCH 195/694] add Webpage shortcuts, resolves #2362 --- wled00/data/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wled00/data/index.js b/wled00/data/index.js index 9f8c579d0..8feec9789 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -272,6 +272,7 @@ function onLoad() selectSlot(0); updateTablinks(0); + handleLocationHash(); cpick.on("input:end", () => {setColor(1);}); cpick.on("color:change", () => {updatePSliders()}); pmtLS = localStorage.getItem('wledPmt'); @@ -314,6 +315,21 @@ function openTab(tabI, force = false) _C.classList.toggle('smooth', false); _C.style.setProperty('--i', iSlide); updateTablinks(tabI); + switch (tabI) { + case 0: window.location.hash = "Colors"; break; + case 1: window.location.hash = "Effects"; break; + case 2: window.location.hash = "Segments"; break; + case 3: window.location.hash = "Presets"; break; + } +} + +function handleLocationHash() { + switch (window.location.hash) { + case "#Colors": openTab(0); break; + case "#Effects": openTab(1); break; + case "#Segments": openTab(2); break; + case "#Presets": openTab(3); break; + } } var timeout; @@ -3212,6 +3228,7 @@ size(); _C.style.setProperty('--n', N); window.addEventListener('resize', size, true); +window.addEventListener('hashchange', handleLocationHash); _C.addEventListener('mousedown', lock, false); _C.addEventListener('touchstart', lock, false); From bed364d75e8ecc0f65c43bfb1db4d9a089072158 Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Tue, 30 Apr 2024 16:21:40 -0700 Subject: [PATCH 196/694] Update playlist.cpp Updated to allow a user to optionally skip to the next preset in the playlist anytime they desire. --- wled00/playlist.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 67c4f6049..0f6f5745b 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -123,11 +123,11 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { } -void handlePlaylist() { +void handlePlaylist(bool skipNext) { static unsigned long presetCycledTime = 0; if (currentPlaylist < 0 || playlistEntries == nullptr) return; - if (millis() - presetCycledTime > (100*playlistEntryDur)) { +if (millis() - presetCycledTime > (100 * playlistEntryDur) || skipNext) { presetCycledTime = millis(); if (bri == 0 || nightlightActive) return; From 071e0be445ed012ff9ab1f72347440684eed678f Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Tue, 30 Apr 2024 16:23:43 -0700 Subject: [PATCH 197/694] Update fcn_declare.h Updated to add the optional skipNext bool to handlePlaylist() which allows people to skip to the next preset when desired --- wled00/fcn_declare.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2461ebb28..d77bdd8f1 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -226,7 +226,7 @@ void _overlayAnalogClock(); void shufflePlaylist(); void unloadPlaylist(); int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); -void handlePlaylist(); +void handlePlaylist(bool skipNext=false); void serializePlaylist(JsonObject obj); //presets.cpp From 3b89814b6935261d65f95059690591f51a0eab5f Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Tue, 30 Apr 2024 16:33:30 -0700 Subject: [PATCH 198/694] Update set.cpp added new NP command to API to allow user to skip to next preset in a playlist. Example use is win&NP in a preset. --- wled00/set.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wled00/set.cpp b/wled00/set.cpp index d3382be18..8a00a9814 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -901,6 +901,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) applyPreset(presetCycCurr); } + pos = req.indexOf(F("NP")); //skips to next preset in a playlist + if (pos > 0) handlePlaylist(true); + //set brightness updateVal(req.c_str(), "&A=", &bri); From a1d6ffadad852449a825431ad0bb8bba2f7f448d Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Tue, 30 Apr 2024 16:49:52 -0700 Subject: [PATCH 199/694] Update json.cpp adds support for np boolean parameter to skip to next preset --- wled00/json.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index ae8224ad3..866fa968f 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -486,7 +486,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) strip.loadCustomPalettes(); } } - + + if (root.containsKey(F("np")) && root[F("np")].as()) { //skip to next preset in a playlist + handlePlaylist(true); + } + JsonObject wifi = root[F("wifi")]; if (!wifi.isNull()) { bool apMode = getBoolVal(wifi[F("ap")], apActive); From 25fb878e5434dd47b888bd3e5a46176c369ff8c4 Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:01:30 -0700 Subject: [PATCH 200/694] Update fcn_declare.h reworked approach based on feedback --- wled00/fcn_declare.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d77bdd8f1..2818ada30 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -226,7 +226,7 @@ void _overlayAnalogClock(); void shufflePlaylist(); void unloadPlaylist(); int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); -void handlePlaylist(bool skipNext=false); +void handlePlaylist(bool doAdvancePlaylist = false); void serializePlaylist(JsonObject obj); //presets.cpp From caa4fe1ec4f814f89fa2c77ef7405b0935b846ec Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:02:27 -0700 Subject: [PATCH 201/694] Update json.cpp reworked approach based on feedback --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 866fa968f..01cbeddb1 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -488,7 +488,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } if (root.containsKey(F("np")) && root[F("np")].as()) { //skip to next preset in a playlist - handlePlaylist(true); + doAdvancePlaylist = true; } JsonObject wifi = root[F("wifi")]; From a2b9aed40df5bb55eb4f53db72c6871c72c9b30d Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:03:16 -0700 Subject: [PATCH 202/694] Update playlist.cpp reworked approach based on feedback --- wled00/playlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 0f6f5745b..5665ef72f 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -123,7 +123,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { } -void handlePlaylist(bool skipNext) { +void handlePlaylist(bool doAdvancePlaylist) { static unsigned long presetCycledTime = 0; if (currentPlaylist < 0 || playlistEntries == nullptr) return; From e88c81ad0d6a1ff8c18667facd2b7b326ede2b74 Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:04:02 -0700 Subject: [PATCH 203/694] Update set.cpp reworked based on feedback --- wled00/set.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index 8a00a9814..0b4a0da3f 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -902,7 +902,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } pos = req.indexOf(F("NP")); //skips to next preset in a playlist - if (pos > 0) handlePlaylist(true); + if (pos > 0) doAdvancePlaylist = true; //set brightness updateVal(req.c_str(), "&A=", &bri); From 16086c09615d7e8c2e2050c650b9af00b875062c Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:05:26 -0700 Subject: [PATCH 204/694] Update wled.h reworked based on feedback from original PR --- wled00/wled.h | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/wled.h b/wled00/wled.h index 139c451f8..75f7c14c5 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -645,6 +645,7 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, WLED_GLOBAL byte timerMonth[] _INIT_N(({28,28,28,28,28,28,28,28})); WLED_GLOBAL byte timerDay[] _INIT_N(({1,1,1,1,1,1,1,1})); WLED_GLOBAL byte timerDayEnd[] _INIT_N(({31,31,31,31,31,31,31,31})); +WLED_GLOBAL bool doAdvancePlaylist _INIT(false); //improv WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning From 6daf7f6322eacdc0ff83756b4da735d02a269fa9 Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:07:52 -0700 Subject: [PATCH 205/694] Update wled.cpp reworked based on PR feedback --- wled00/wled.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eb7860851..25cc0442c 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -209,6 +209,12 @@ void WLED::loop() toki.resetTick(); +// Advance to next playlist preset if the flag is set to true + if (doAdvancePlaylist) { + handlePlaylist(true); + doAdvancePlaylist = false; // Reset flag to false + } + #if WLED_WATCHDOG_TIMEOUT > 0 // we finished our mainloop, reset the watchdog timer static unsigned long lastWDTFeed = 0; From db475b69988567f19c3969d1808c96918924f55e Mon Sep 17 00:00:00 2001 From: freakintoddles2 Date: Wed, 1 May 2024 10:09:17 -0700 Subject: [PATCH 206/694] Update playlist.cpp --- wled00/playlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 5665ef72f..fc39db42b 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -127,7 +127,7 @@ void handlePlaylist(bool doAdvancePlaylist) { static unsigned long presetCycledTime = 0; if (currentPlaylist < 0 || playlistEntries == nullptr) return; -if (millis() - presetCycledTime > (100 * playlistEntryDur) || skipNext) { +if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) { presetCycledTime = millis(); if (bri == 0 || nightlightActive) return; From 22f6128bc47c7ee7349b4f039758bf46962b0eba Mon Sep 17 00:00:00 2001 From: Pasquale Pizzuti Date: Thu, 2 May 2024 09:04:07 +0200 Subject: [PATCH 207/694] using global brightness --- wled00/overlay.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index cd0c04c04..d6d8ba52a 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -11,7 +11,6 @@ void _overlayAnalogClock() { _overlayAnalogCountdown(); return; } - uint8_t brightness = strip.getBrightness(); float hourP = ((float)(hour(localTime)%12))/12.0f; float minuteP = ((float)minute(localTime))/60.0f; hourP = hourP + minuteP/12.0f; @@ -26,11 +25,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, brightness)); - strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, brightness)); + strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, bri)); + strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, bri)); } else { - strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, brightness)); + strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, bri)); } } if (analogClock5MinuteMarks) @@ -39,12 +38,12 @@ void _overlayAnalogClock() { unsigned pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, color_fade(0x00FFAA, brightness)); + strip.setPixelColor(pix, color_fade(0x00FFAA, bri)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, brightness)); - strip.setPixelColor(minutePixel, color_fade(0x00FF00, brightness)); - strip.setPixelColor(hourPixel, color_fade(0x0000FF, brightness)); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, bri)); + strip.setPixelColor(minutePixel, color_fade(0x00FF00, bri)); + strip.setPixelColor(hourPixel, color_fade(0x0000FF, bri)); } From 736a8b1b80102d2f9b32b8cc88ce5f409eeca116 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 2 May 2024 10:31:50 +0200 Subject: [PATCH 208/694] Fix for rotating tablet into PC mode. --- wled00/data/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 8feec9789..d33fb63f7 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -3062,11 +3062,11 @@ function togglePcMode(fromB = false) if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); - openTab(0, true); } pcMode = (wW >= 1024) && pcModeA; if (cpick) cpick.resize(pcMode && wW>1023 && wW<1250 ? 230 : 260); // for tablet in landscape if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() + if (pcMode) openTab(0, true); gId('buttonPcm').className = (pcMode) ? "active":""; gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); From 4df936a437a2e643fca724885b336b6bb3034bbd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 2 May 2024 10:33:10 +0200 Subject: [PATCH 209/694] Fix for unfortunate prior CRLF coversion. --- wled00/mqtt.cpp | 394 ++++++++++++++++++++++++------------------------ 1 file changed, 197 insertions(+), 197 deletions(-) diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 810291094..2e2e4a6bd 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -1,197 +1,197 @@ -#include "wled.h" - -/* - * MQTT communication protocol for home automation - */ - -#ifdef WLED_ENABLE_MQTT -#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds - -void parseMQTTBriPayload(char* payload) -{ - if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} - else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);} - else { - uint8_t in = strtoul(payload, NULL, 10); - if (in == 0 && bri > 0) briLast = bri; - bri = in; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } -} - - -void onMqttConnect(bool sessionPresent) -{ - //(re)subscribe to required topics - char subuf[38]; - - if (mqttDeviceTopic[0] != 0) { - strlcpy(subuf, mqttDeviceTopic, 33); - mqtt->subscribe(subuf, 0); - strcat_P(subuf, PSTR("/col")); - mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/api")); - mqtt->subscribe(subuf, 0); - } - - if (mqttGroupTopic[0] != 0) { - strlcpy(subuf, mqttGroupTopic, 33); - mqtt->subscribe(subuf, 0); - strcat_P(subuf, PSTR("/col")); - mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttGroupTopic, 33); - strcat_P(subuf, PSTR("/api")); - mqtt->subscribe(subuf, 0); - } - - usermods.onMqttConnect(sessionPresent); - - DEBUG_PRINTLN(F("MQTT ready")); - publishMqtt(); -} - - -void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { - static char *payloadStr; - - DEBUG_PRINT(F("MQTT msg: ")); - DEBUG_PRINTLN(topic); - - // paranoia check to avoid npe if no payload - if (payload==nullptr) { - DEBUG_PRINTLN(F("no payload -> leave")); - return; - } - - if (index == 0) { // start (1st partial packet or the only packet) - if (payloadStr) delete[] payloadStr; // fail-safe: release buffer - payloadStr = new char[total+1]; // allocate new buffer - } - if (payloadStr == nullptr) return; // buffer not allocated - - // copy (partial) packet to buffer and 0-terminate it if it is last packet - char* buff = payloadStr + index; - memcpy(buff, payload, len); - if (index + len >= total) { // at end - payloadStr[total] = '\0'; // terminate c style string - } else { - DEBUG_PRINTLN(F("MQTT partial packet received.")); - return; // process next packet - } - DEBUG_PRINTLN(payloadStr); - - size_t topicPrefixLen = strlen(mqttDeviceTopic); - if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) { - topic += topicPrefixLen; - } else { - topicPrefixLen = strlen(mqttGroupTopic); - if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) { - topic += topicPrefixLen; - } else { - // Non-Wled Topic used here. Probably a usermod subscribed to this topic. - usermods.onMqttMessage(topic, payloadStr); - delete[] payloadStr; - payloadStr = nullptr; - return; - } - } - - //Prefix is stripped from the topic at this point - - if (strcmp_P(topic, PSTR("/col")) == 0) { - colorFromDecOrHexString(col, payloadStr); - colorUpdated(CALL_MODE_DIRECT_CHANGE); - } else if (strcmp_P(topic, PSTR("/api")) == 0) { - if (!requestJSONBufferLock(15)) { - delete[] payloadStr; - payloadStr = nullptr; - return; - } - if (payloadStr[0] == '{') { //JSON API - deserializeJson(*pDoc, payloadStr); - deserializeState(pDoc->as()); - } else { //HTTP API - String apireq = "win"; apireq += '&'; // reduce flash string usage - apireq += payloadStr; - handleSet(nullptr, apireq); - } - releaseJSONBufferLock(); - } else if (strlen(topic) != 0) { - // non standard topic, check with usermods - usermods.onMqttMessage(topic, payloadStr); - } else { - // topmost topic (just wled/MAC) - parseMQTTBriPayload(payloadStr); - } - delete[] payloadStr; - payloadStr = nullptr; -} - - -void publishMqtt() -{ - if (!WLED_MQTT_CONNECTED) return; - DEBUG_PRINTLN(F("Publish MQTT")); - - #ifndef USERMOD_SMARTNEST - char s[10]; - char subuf[48]; - - sprintf_P(s, PSTR("%u"), bri); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/g")); - mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - - sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/c")); - mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/status")); - mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT - - char apires[1024]; // allocating 1024 bytes from stack can be risky - XML_response(nullptr, apires); - strlcpy(subuf, mqttDeviceTopic, 33); - strcat_P(subuf, PSTR("/v")); - mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263) - #endif -} - - -//HA autodiscovery was removed in favor of the native integration in HA v0.102.0 - -bool initMqtt() -{ - if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; - - if (mqtt == nullptr) { - mqtt = new AsyncMqttClient(); - mqtt->onMessage(onMqttMessage); - mqtt->onConnect(onMqttConnect); - } - if (mqtt->connected()) return true; - - DEBUG_PRINTLN(F("Reconnecting MQTT")); - IPAddress mqttIP; - if (mqttIP.fromString(mqttServer)) //see if server is IP or domain - { - mqtt->setServer(mqttIP, mqttPort); - } else { - mqtt->setServer(mqttServer, mqttPort); - } - mqtt->setClientId(mqttClientID); - if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); - - #ifndef USERMOD_SMARTNEST - strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); - strcat_P(mqttStatusTopic, PSTR("/status")); - mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message - #endif - mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME); - mqtt->connect(); - return true; -} -#endif +#include "wled.h" + +/* + * MQTT communication protocol for home automation + */ + +#ifdef WLED_ENABLE_MQTT +#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds + +void parseMQTTBriPayload(char* payload) +{ + if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} + else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);} + else { + uint8_t in = strtoul(payload, NULL, 10); + if (in == 0 && bri > 0) briLast = bri; + bri = in; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } +} + + +void onMqttConnect(bool sessionPresent) +{ + //(re)subscribe to required topics + char subuf[38]; + + if (mqttDeviceTopic[0] != 0) { + strlcpy(subuf, mqttDeviceTopic, 33); + mqtt->subscribe(subuf, 0); + strcat_P(subuf, PSTR("/col")); + mqtt->subscribe(subuf, 0); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/api")); + mqtt->subscribe(subuf, 0); + } + + if (mqttGroupTopic[0] != 0) { + strlcpy(subuf, mqttGroupTopic, 33); + mqtt->subscribe(subuf, 0); + strcat_P(subuf, PSTR("/col")); + mqtt->subscribe(subuf, 0); + strlcpy(subuf, mqttGroupTopic, 33); + strcat_P(subuf, PSTR("/api")); + mqtt->subscribe(subuf, 0); + } + + usermods.onMqttConnect(sessionPresent); + + DEBUG_PRINTLN(F("MQTT ready")); + publishMqtt(); +} + + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + static char *payloadStr; + + DEBUG_PRINT(F("MQTT msg: ")); + DEBUG_PRINTLN(topic); + + // paranoia check to avoid npe if no payload + if (payload==nullptr) { + DEBUG_PRINTLN(F("no payload -> leave")); + return; + } + + if (index == 0) { // start (1st partial packet or the only packet) + if (payloadStr) delete[] payloadStr; // fail-safe: release buffer + payloadStr = new char[total+1]; // allocate new buffer + } + if (payloadStr == nullptr) return; // buffer not allocated + + // copy (partial) packet to buffer and 0-terminate it if it is last packet + char* buff = payloadStr + index; + memcpy(buff, payload, len); + if (index + len >= total) { // at end + payloadStr[total] = '\0'; // terminate c style string + } else { + DEBUG_PRINTLN(F("MQTT partial packet received.")); + return; // process next packet + } + DEBUG_PRINTLN(payloadStr); + + size_t topicPrefixLen = strlen(mqttDeviceTopic); + if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) { + topic += topicPrefixLen; + } else { + topicPrefixLen = strlen(mqttGroupTopic); + if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) { + topic += topicPrefixLen; + } else { + // Non-Wled Topic used here. Probably a usermod subscribed to this topic. + usermods.onMqttMessage(topic, payloadStr); + delete[] payloadStr; + payloadStr = nullptr; + return; + } + } + + //Prefix is stripped from the topic at this point + + if (strcmp_P(topic, PSTR("/col")) == 0) { + colorFromDecOrHexString(col, payloadStr); + colorUpdated(CALL_MODE_DIRECT_CHANGE); + } else if (strcmp_P(topic, PSTR("/api")) == 0) { + if (!requestJSONBufferLock(15)) { + delete[] payloadStr; + payloadStr = nullptr; + return; + } + if (payloadStr[0] == '{') { //JSON API + deserializeJson(*pDoc, payloadStr); + deserializeState(pDoc->as()); + } else { //HTTP API + String apireq = "win"; apireq += '&'; // reduce flash string usage + apireq += payloadStr; + handleSet(nullptr, apireq); + } + releaseJSONBufferLock(); + } else if (strlen(topic) != 0) { + // non standard topic, check with usermods + usermods.onMqttMessage(topic, payloadStr); + } else { + // topmost topic (just wled/MAC) + parseMQTTBriPayload(payloadStr); + } + delete[] payloadStr; + payloadStr = nullptr; +} + + +void publishMqtt() +{ + if (!WLED_MQTT_CONNECTED) return; + DEBUG_PRINTLN(F("Publish MQTT")); + + #ifndef USERMOD_SMARTNEST + char s[10]; + char subuf[48]; + + sprintf_P(s, PSTR("%u"), bri); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/g")); + mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) + + sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/c")); + mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) + + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/status")); + mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT + + char apires[1024]; // allocating 1024 bytes from stack can be risky + XML_response(nullptr, apires); + strlcpy(subuf, mqttDeviceTopic, 33); + strcat_P(subuf, PSTR("/v")); + mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263) + #endif +} + + +//HA autodiscovery was removed in favor of the native integration in HA v0.102.0 + +bool initMqtt() +{ + if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; + + if (mqtt == nullptr) { + mqtt = new AsyncMqttClient(); + mqtt->onMessage(onMqttMessage); + mqtt->onConnect(onMqttConnect); + } + if (mqtt->connected()) return true; + + DEBUG_PRINTLN(F("Reconnecting MQTT")); + IPAddress mqttIP; + if (mqttIP.fromString(mqttServer)) //see if server is IP or domain + { + mqtt->setServer(mqttIP, mqttPort); + } else { + mqtt->setServer(mqttServer, mqttPort); + } + mqtt->setClientId(mqttClientID); + if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); + + #ifndef USERMOD_SMARTNEST + strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); + strcat_P(mqttStatusTopic, PSTR("/status")); + mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message + #endif + mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME); + mqtt->connect(); + return true; +} +#endif From 5e38039c4dd630d7b4c6841deb5e0b04aa07f573 Mon Sep 17 00:00:00 2001 From: Todd Meyer Date: Thu, 2 May 2024 14:36:18 -0700 Subject: [PATCH 210/694] Updated based on more feedback --- wled00/fcn_declare.h | 2 +- wled00/json.cpp | 6 +++--- wled00/playlist.cpp | 3 ++- wled00/set.cpp | 4 ++-- wled00/wled.cpp | 6 ------ 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2818ada30..2461ebb28 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -226,7 +226,7 @@ void _overlayAnalogClock(); void shufflePlaylist(); void unloadPlaylist(); int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); -void handlePlaylist(bool doAdvancePlaylist = false); +void handlePlaylist(); void serializePlaylist(JsonObject obj); //presets.cpp diff --git a/wled00/json.cpp b/wled00/json.cpp index 01cbeddb1..d998a462b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -486,9 +486,9 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) strip.loadCustomPalettes(); } } - - if (root.containsKey(F("np")) && root[F("np")].as()) { //skip to next preset in a playlist - doAdvancePlaylist = true; + + if (root.containsKey(F("np"))) { + doAdvancePlaylist = root[F("np")].as(); //advances to next preset in playlist when true } JsonObject wifi = root[F("wifi")]; diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index fc39db42b..36235ab9e 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -123,7 +123,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) { } -void handlePlaylist(bool doAdvancePlaylist) { +void handlePlaylist() { static unsigned long presetCycledTime = 0; if (currentPlaylist < 0 || playlistEntries == nullptr) return; @@ -149,6 +149,7 @@ if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0); playlistEntryDur = playlistEntries[playlistIndex].dur; applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); + doAdvancePlaylist = false; } } diff --git a/wled00/set.cpp b/wled00/set.cpp index 0b4a0da3f..efbc7b18b 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -901,8 +901,8 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) applyPreset(presetCycCurr); } - pos = req.indexOf(F("NP")); //skips to next preset in a playlist - if (pos > 0) doAdvancePlaylist = true; + pos = req.indexOf(F("NP")); //advances to next preset in a playlist + if (pos > 0) doAdvancePlaylist = true; //set brightness updateVal(req.c_str(), "&A=", &bri); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 25cc0442c..eb7860851 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -209,12 +209,6 @@ void WLED::loop() toki.resetTick(); -// Advance to next playlist preset if the flag is set to true - if (doAdvancePlaylist) { - handlePlaylist(true); - doAdvancePlaylist = false; // Reset flag to false - } - #if WLED_WATCHDOG_TIMEOUT > 0 // we finished our mainloop, reset the watchdog timer static unsigned long lastWDTFeed = 0; From 2ff49cf657a322d17ec4fab854d9834ae10a5566 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 3 May 2024 15:45:15 +0200 Subject: [PATCH 211/694] Fix for #3952 - included IR optimisations & code rearrangement --- wled00/fcn_declare.h | 14 +-- wled00/ir.cpp | 254 ++++++++++++++++++++----------------------- wled00/set.cpp | 4 +- wled00/wled.cpp | 7 ++ 4 files changed, 130 insertions(+), 149 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2461ebb28..a6ff9d096 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -143,20 +143,8 @@ void handleImprovWifiScan(); void sendImprovIPRPCResult(ImprovRPCType type); //ir.cpp -void applyRepeatActions(); -byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); -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 decodeIRJson(uint32_t code); - void initIR(); +void deInitIR(); void handleIR(); //json.cpp diff --git a/wled00/ir.cpp b/wled00/ir.cpp index ba34aa526..e475198f6 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -1,20 +1,14 @@ #include "wled.h" +#ifndef WLED_DISABLE_INFRARED #include "ir_codes.h" /* - * Infrared sensor support for generic 24/40/44 key RGB remotes + * Infrared sensor support for several generic RGB remotes and custom JSON remote */ -#if defined(WLED_DISABLE_INFRARED) -void handleIR(){} -#else - IRrecv* irrecv; -//change pin in NpbWrapper.h - decode_results results; - unsigned long irCheckedTime = 0; uint32_t lastValidCode = 0; byte lastRepeatableAction = ACTION_NONE; @@ -35,16 +29,16 @@ uint8_t lastIR6ColourIdx = 0; // print("%d values: %s" % (len(result), result)) // // It would be hard to maintain repeatable steps if calculating this on the fly. -const byte brightnessSteps[] = { +const uint8_t brightnessSteps[] = { 5, 7, 9, 12, 16, 20, 26, 34, 43, 56, 72, 93, 119, 154, 198, 255 }; const size_t numBrightnessSteps = sizeof(brightnessSteps) / sizeof(uint8_t); // increment `bri` to the next `brightnessSteps` value -void incBrightness() +static void incBrightness() { // dumb incremental search is efficient enough for so few items - for (uint8_t index = 0; index < numBrightnessSteps; ++index) + for (unsigned index = 0; index < numBrightnessSteps; ++index) { if (brightnessSteps[index] > bri) { @@ -56,7 +50,7 @@ void incBrightness() } // decrement `bri` to the next `brightnessSteps` value -void decBrightness() +static void decBrightness() { // dumb incremental search is efficient enough for so few items for (int index = numBrightnessSteps - 1; index >= 0; --index) @@ -70,12 +64,12 @@ void decBrightness() } } -void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) +static void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) { applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID); } -byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte higherBoundary) +static byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF) { int16_t new_val = (int16_t) property + amount; if (lowerBoundary >= higherBoundary) return property; @@ -84,10 +78,10 @@ byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte highe return (byte)constrain(new_val, 0, 255); } -void changeEffect(uint8_t fx) +static void changeEffect(uint8_t fx) { if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; strip.setMode(i, fx); @@ -100,10 +94,10 @@ void changeEffect(uint8_t fx) stateChanged = true; } -void changePalette(uint8_t pal) +static void changePalette(uint8_t pal) { if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; seg.setPalette(pal); @@ -116,13 +110,13 @@ void changePalette(uint8_t pal) stateChanged = true; } -void changeEffectSpeed(int8_t amount) +static void changeEffectSpeed(int8_t amount) { if (effectCurrent != 0) { int16_t new_val = (int16_t) effectSpeed + amount; effectSpeed = (byte)constrain(new_val,0,255); if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; seg.speed = effectSpeed; @@ -134,10 +128,7 @@ void changeEffectSpeed(int8_t amount) } } else { // if Effect == "solid Color", change the hue of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); - CRGB fastled_col; - fastled_col.red = R(sseg.colors[0]); - fastled_col.green = G(sseg.colors[0]); - fastled_col.blue = B(sseg.colors[0]); + CRGB fastled_col = CRGB(sseg.colors[0]); CHSV prim_hsv = rgb2hsv_approximate(fastled_col); int16_t new_val = (int16_t)prim_hsv.h + amount; if (new_val > 255) new_val -= 255; // roll-over if bigger than 255 @@ -145,7 +136,7 @@ void changeEffectSpeed(int8_t amount) prim_hsv.h = (byte)new_val; hsv2rgb_rainbow(prim_hsv, fastled_col); if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); @@ -163,13 +154,13 @@ void changeEffectSpeed(int8_t amount) lastRepeatableValue = amount; } -void changeEffectIntensity(int8_t amount) +static void changeEffectIntensity(int8_t amount) { if (effectCurrent != 0) { int16_t new_val = (int16_t) effectIntensity + amount; effectIntensity = (byte)constrain(new_val,0,255); if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; seg.intensity = effectIntensity; @@ -181,16 +172,13 @@ void changeEffectIntensity(int8_t amount) } } else { // if Effect == "solid Color", change the saturation of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); - CRGB fastled_col; - fastled_col.red = R(sseg.colors[0]); - fastled_col.green = G(sseg.colors[0]); - fastled_col.blue = B(sseg.colors[0]); + CRGB fastled_col = CRGB(sseg.colors[0]); CHSV prim_hsv = rgb2hsv_approximate(fastled_col); int16_t new_val = (int16_t) prim_hsv.s + amount; prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 hsv2rgb_rainbow(prim_hsv, fastled_col); if (irApplyToAllSelected) { - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); @@ -208,11 +196,11 @@ void changeEffectIntensity(int8_t amount) lastRepeatableValue = amount; } -void changeColor(uint32_t c, int16_t cct=-1) +static void changeColor(uint32_t c, int16_t cct=-1) { if (irApplyToAllSelected) { // main segment may not be selected! - for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; byte capabilities = seg.getLightCapabilities(); @@ -249,7 +237,7 @@ void changeColor(uint32_t c, int16_t cct=-1) stateChanged = true; } -void changeWhite(int8_t amount, int16_t cct=-1) +static void changeWhite(int8_t amount, int16_t cct=-1) { Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); byte r = R(seg.colors[0]); @@ -259,72 +247,7 @@ void changeWhite(int8_t amount, int16_t cct=-1) changeColor(RGBW32(r, g, b, w), cct); } -void decodeIR(uint32_t code) -{ - if (code == 0xFFFFFFFF) { - //repeated code, continue brightness up/down - irTimesRepeated++; - applyRepeatActions(); - return; - } - lastValidCode = 0; irTimesRepeated = 0; - lastRepeatableAction = ACTION_NONE; - - if (irEnabled == 8) { // any remote configurable with ir.json file - decodeIRJson(code); - stateUpdated(CALL_MODE_BUTTON); - return; - } - if (code > 0xFFFFFF) return; //invalid code - - switch (irEnabled) { - case 1: - if (code > 0xF80000) decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values - else decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000 - break; - case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys - case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys - case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys - case 5: decodeIR21(code); break; // white 21-key remote - case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness, - // "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE" - // sets bright plain white - case 7: decodeIR9(code); break; - //case 8: return; // ir.json file, handled above switch statement - } - - if (nightlightActive && bri == 0) nightlightActive = false; - stateUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input -} - -void applyRepeatActions() -{ - if (irEnabled == 8) { - decodeIRJson(lastValidCode); - return; - } else switch (lastRepeatableAction) { - case ACTION_BRIGHT_UP : incBrightness(); stateUpdated(CALL_MODE_BUTTON); return; - case ACTION_BRIGHT_DOWN : decBrightness(); stateUpdated(CALL_MODE_BUTTON); return; - case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; - case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; - case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; - case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; - default: break; - } - if (lastValidCode == IR40_WPLUS) { - changeWhite(10); - stateUpdated(CALL_MODE_BUTTON); - } else if (lastValidCode == IR40_WMINUS) { - changeWhite(-10); - stateUpdated(CALL_MODE_BUTTON); - } else if ((lastValidCode == IR24_ON || lastValidCode == IR40_ON) && irTimesRepeated > 7 ) { - nightlightActive = true; - nightlightStartTime = millis(); - stateUpdated(CALL_MODE_BUTTON); - } -} - -void decodeIR24(uint32_t code) +static void decodeIR24(uint32_t code) { switch (code) { case IR24_BRIGHTER : incBrightness(); break; @@ -356,7 +279,7 @@ void decodeIR24(uint32_t code) lastValidCode = code; } -void decodeIR24OLD(uint32_t code) +static void decodeIR24OLD(uint32_t code) { switch (code) { case IR24_OLD_BRIGHTER : incBrightness(); break; @@ -388,7 +311,7 @@ void decodeIR24OLD(uint32_t code) lastValidCode = code; } -void decodeIR24CT(uint32_t code) +static void decodeIR24CT(uint32_t code) { switch (code) { case IR24_CT_BRIGHTER : incBrightness(); break; @@ -420,7 +343,7 @@ void decodeIR24CT(uint32_t code) lastValidCode = code; } -void decodeIR40(uint32_t code) +static void decodeIR40(uint32_t code) { Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); byte r = R(seg.colors[0]); @@ -473,7 +396,7 @@ void decodeIR40(uint32_t code) lastValidCode = code; } -void decodeIR44(uint32_t code) +static void decodeIR44(uint32_t code) { switch (code) { case IR44_BPLUS : incBrightness(); break; @@ -525,7 +448,7 @@ void decodeIR44(uint32_t code) lastValidCode = code; } -void decodeIR21(uint32_t code) +static void decodeIR21(uint32_t code) { switch (code) { case IR21_BRIGHTER: incBrightness(); break; @@ -554,7 +477,7 @@ void decodeIR21(uint32_t code) lastValidCode = code; } -void decodeIR6(uint32_t code) +static void decodeIR6(uint32_t code) { switch (code) { case IR6_POWER: toggleOnOff(); break; @@ -587,7 +510,7 @@ void decodeIR6(uint32_t code) lastValidCode = code; } -void decodeIR9(uint32_t code) +static void decodeIR9(uint32_t code) { switch (code) { case IR9_POWER : toggleOnOff(); break; @@ -628,7 +551,7 @@ Sample: "label": "Preset 1, fallback to Saw - Party if not found"}, } */ -void decodeIRJson(uint32_t code) +static void decodeIRJson(uint32_t code) { char objKey[10]; char fileName[16]; @@ -701,41 +624,102 @@ void decodeIRJson(uint32_t code) releaseJSONBufferLock(); } +static void applyRepeatActions() +{ + if (irEnabled == 8) { + decodeIRJson(lastValidCode); + return; + } else switch (lastRepeatableAction) { + case ACTION_BRIGHT_UP : incBrightness(); stateUpdated(CALL_MODE_BUTTON); return; + case ACTION_BRIGHT_DOWN : decBrightness(); stateUpdated(CALL_MODE_BUTTON); return; + case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; + case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; + case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; + case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return; + default: break; + } + if (lastValidCode == IR40_WPLUS) { + changeWhite(10); + stateUpdated(CALL_MODE_BUTTON); + } else if (lastValidCode == IR40_WMINUS) { + changeWhite(-10); + stateUpdated(CALL_MODE_BUTTON); + } else if ((lastValidCode == IR24_ON || lastValidCode == IR40_ON) && irTimesRepeated > 7 ) { + nightlightActive = true; + nightlightStartTime = millis(); + stateUpdated(CALL_MODE_BUTTON); + } +} + +static void decodeIR(uint32_t code) +{ + if (code == 0xFFFFFFFF) { + //repeated code, continue brightness up/down + irTimesRepeated++; + applyRepeatActions(); + return; + } + lastValidCode = 0; irTimesRepeated = 0; + lastRepeatableAction = ACTION_NONE; + + if (irEnabled == 8) { // any remote configurable with ir.json file + decodeIRJson(code); + stateUpdated(CALL_MODE_BUTTON); + return; + } + if (code > 0xFFFFFF) return; //invalid code + + switch (irEnabled) { + case 1: + if (code > 0xF80000) decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values + else decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000 + break; + case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys + case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys + case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys + case 5: decodeIR21(code); break; // white 21-key remote + case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness, + // "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE" + // sets bright plain white + case 7: decodeIR9(code); break; + //case 8: return; // ir.json file, handled above switch statement + } + + if (nightlightActive && bri == 0) nightlightActive = false; + stateUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input +} + void initIR() { - if (irEnabled > 0) - { + if (irEnabled > 0) { irrecv = new IRrecv(irPin); - irrecv->enableIRIn(); + if (irrecv) irrecv->enableIRIn(); + } else irrecv = nullptr; +} + +void deInitIR() +{ + if (irrecv) { + irrecv->disableIRIn(); + delete irrecv; } + irrecv = nullptr; } void handleIR() { - if (irEnabled > 0 && millis() - irCheckedTime > 120 && !strip.isUpdating()) - { - irCheckedTime = millis(); - if (irEnabled > 0) - { - if (irrecv == NULL) - { - initIR(); return; + unsigned long currentTime = millis(); + unsigned timeDiff = currentTime - irCheckedTime; + if (timeDiff > 120 && irEnabled > 0 && irrecv) { + if (strip.isUpdating() && timeDiff < 240) return; // be nice, but not too nice + irCheckedTime = currentTime; + if (irrecv->decode(&results)) { + if (results.value != 0) { // only print results if anything is received ( != 0 ) + if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266) + Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); } - - if (irrecv->decode(&results)) - { - if (results.value != 0) // only print results if anything is received ( != 0 ) - { - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266) - Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); - } - decodeIR(results.value); - irrecv->resume(); - } - } else if (irrecv != NULL) - { - irrecv->disableIRIn(); - delete irrecv; irrecv = NULL; + decodeIR(results.value); + irrecv->resume(); } } } diff --git a/wled00/set.cpp b/wled00/set.cpp index d3382be18..2e8ba69d0 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -104,7 +104,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } #ifndef WLED_DISABLE_INFRARED if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) { - pinManager.deallocatePin(irPin, PinOwner::IR); + deInitIR(); + pinManager.deallocatePin(irPin, PinOwner::IR); } #endif for (uint8_t s=0; sarg(F("IT")).toInt(); + initIR(); #endif irApplyToAllSelected = !request->hasArg(F("MSO")); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eb7860851..6251735c3 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -505,6 +505,13 @@ void WLED::setup() initServer(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); +#ifndef WLED_DISABLE_INFRARED + // init IR + DEBUG_PRINTLN(F("initIR")); + initIR(); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); +#endif + // Seed FastLED random functions with an esp random value, which already works properly at this point. #if defined(ARDUINO_ARCH_ESP32) const uint32_t seed32 = esp_random(); From 6504fb68b6ba3fc49d18594df78812c6e153e9bc Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 3 May 2024 15:46:16 +0200 Subject: [PATCH 212/694] Minor MQTT optimisation. --- wled00/mqtt.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 2e2e4a6bd..5599824ef 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -103,20 +103,17 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties colorFromDecOrHexString(col, payloadStr); colorUpdated(CALL_MODE_DIRECT_CHANGE); } else if (strcmp_P(topic, PSTR("/api")) == 0) { - if (!requestJSONBufferLock(15)) { - delete[] payloadStr; - payloadStr = nullptr; - return; + if (requestJSONBufferLock(15)) { + if (payloadStr[0] == '{') { //JSON API + deserializeJson(*pDoc, payloadStr); + deserializeState(pDoc->as()); + } else { //HTTP API + String apireq = "win"; apireq += '&'; // reduce flash string usage + apireq += payloadStr; + handleSet(nullptr, apireq); + } + releaseJSONBufferLock(); } - if (payloadStr[0] == '{') { //JSON API - deserializeJson(*pDoc, payloadStr); - deserializeState(pDoc->as()); - } else { //HTTP API - String apireq = "win"; apireq += '&'; // reduce flash string usage - apireq += payloadStr; - handleSet(nullptr, apireq); - } - releaseJSONBufferLock(); } else if (strlen(topic) != 0) { // non standard topic, check with usermods usermods.onMqttMessage(topic, payloadStr); From fa76431dd673e5c1a24c75fe7d9348a93e3f22f5 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 3 May 2024 16:08:20 +0200 Subject: [PATCH 213/694] Changelog update --- CHANGELOG.md | 10 ++++++++++ wled00/wled.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c58dfa3..e37b08b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ ## WLED changelog +#### Build 240503 +- Using brightness in analog clock overlay (#3944 by @paspiz85) +- Add Webpage shortcuts (#3945 by @w00000dy) +- ArtNet Poll reply (#3892 by @askask) +- Improved brightness change via long button presses (#3933 by @gaaat98) +- Relay open drain output (#3920 by @Suxsem) +- NEW JSON API: release info (update page, `info.release`) +- update esp32 platform to arduino-esp32 v2.0.9 (#3902) +- various optimisations and bugfixes (#3952, #3922, #3878, #3926, #3919, #3904 @DedeHai) + #### Build 2404120 - v0.15.0-b3 - fix for #3896 & WS2815 current saving diff --git a/wled00/wled.h b/wled00/wled.h index 139c451f8..c2efa1612 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2404120 +#define VERSION 2405030 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 6df3b417a94899fd382708208ced9ab08845826c Mon Sep 17 00:00:00 2001 From: Todd Meyer Date: Fri, 3 May 2024 08:30:37 -0700 Subject: [PATCH 214/694] Updated based on more feedback --- wled00/json.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index d998a462b..82952f4c4 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -487,9 +487,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } - if (root.containsKey(F("np"))) { - doAdvancePlaylist = root[F("np")].as(); //advances to next preset in playlist when true - } + doAdvancePlaylist = root["np"].as() || doAdvancePlaylist; //advances to next preset in playlist when true JsonObject wifi = root[F("wifi")]; if (!wifi.isNull()) { From dd19aa63d0e15693f0666ea1e33a370677b88450 Mon Sep 17 00:00:00 2001 From: Todd Meyer Date: Fri, 3 May 2024 08:47:14 -0700 Subject: [PATCH 215/694] Forgot F[], added it --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 82952f4c4..9342dc53c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -487,7 +487,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } - doAdvancePlaylist = root["np"].as() || doAdvancePlaylist; //advances to next preset in playlist when true + doAdvancePlaylist = root[F("np")].as() || doAdvancePlaylist; //advances to next preset in playlist when true JsonObject wifi = root[F("wifi")]; if (!wifi.isNull()) { From 379f1813620a56bb0b3136315feb647fb0c3d45d Mon Sep 17 00:00:00 2001 From: Todd Meyer Date: Fri, 3 May 2024 11:51:47 -0700 Subject: [PATCH 216/694] Further simplification --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 9342dc53c..f306eb323 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -487,7 +487,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } - doAdvancePlaylist = root[F("np")].as() || doAdvancePlaylist; //advances to next preset in playlist when true + doAdvancePlaylist = root[F("np")] | doAdvancePlaylist; //advances to next preset in playlist when true JsonObject wifi = root[F("wifi")]; if (!wifi.isNull()) { From cd5494fdd2040ba8d6858532b7348b082c345ebb Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 May 2024 13:36:56 +0200 Subject: [PATCH 217/694] AR pin config: SCK == 1 --> PDM microphone --- usermods/audioreactive/audio_reactive.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 61915170c..746617a35 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1121,6 +1121,11 @@ class AudioReactive : public Usermod { delay(100); // Give that poor microphone some time to setup. useBandPassFilter = false; + + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone + #endif + switch (dmType) { #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) // stub cases for not-yet-supported I2S modes on other ESP32 chips From 3f9a6cae53889898486dae727bbacebc680d6ee0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 May 2024 14:34:23 +0200 Subject: [PATCH 218/694] AR: fix for arduinoFFT 2.x API in contrast to previous 'dev' versions, the storage for windowWeighingFactors is now managed internally by the arduinoFFT object. --- usermods/audioreactive/audio_reactive.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 746617a35..442a651ea 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -183,7 +183,6 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul // These are the input and output vectors. Input vectors receive computed results from FFT. static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins static float vImag[samplesFFT] = {0.0f}; // imaginary parts -static float windowWeighingFactors[samplesFFT] = {0.0f}; // Create FFT object // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 @@ -196,7 +195,8 @@ static float windowWeighingFactors[samplesFFT] = {0.0f}; #include -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); +/* Create FFT object with weighing factor storage */ +static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); // Helper functions From a6e536189c5bc0d3e18b026ff6fcd1682d1cdb8b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 5 May 2024 21:56:01 +0200 Subject: [PATCH 219/694] output_bin.py : fix for mapfile copy The build script was not looking into the right place, so there was never a .map file dropped into build_output/map/ Builds with the newer arduino-esp32 v2.0.x framework actually generate a .map file that is placed directly next to firmware.bin --- pio-scripts/output_bins.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index c0e85dcbb..633654008 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -36,6 +36,8 @@ def create_release(source): def bin_rename_copy(source, target, env): _create_dirs() variant = env["PIOENV"] + builddir = os.path.join(env["PROJECT_BUILD_DIR"], variant) + source_map = os.path.join(builddir, env["PROGNAME"] + ".map") # create string with location and file names based on variant map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) @@ -44,7 +46,11 @@ def bin_rename_copy(source, target, env): # copy firmware.map to map/.map if os.path.isfile("firmware.map"): - shutil.move("firmware.map", map_file) + print("Found linker mapfile firmware.map") + shutil.copy("firmware.map", map_file) + if os.path.isfile(source_map): + print(f"Found linker mapfile {source_map}") + shutil.copy(source_map, map_file) def bin_gzip(source, target): # only create gzip for esp8266 From 2607c44fbb577cc549a23a5e0dbaf858c1fd2d80 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 6 May 2024 11:00:41 +0200 Subject: [PATCH 220/694] make objdump work Script update based on latest version from Tasmota * add support for all esp32 variants * add "-C" : Decode (demangle) low-level symbol names into user-level C++ names. --- pio-scripts/obj-dump.py | 19 +++++++++++++++++-- platformio.ini | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pio-scripts/obj-dump.py b/pio-scripts/obj-dump.py index 91bc3de58..174df509c 100644 --- a/pio-scripts/obj-dump.py +++ b/pio-scripts/obj-dump.py @@ -1,9 +1,24 @@ # Little convenience script to get an object dump +# You may add "-S" to the objdump commandline (i.e. replace "-D -C " with "-d -S -C ") +# to get source code intermixed with disassembly (SLOW !) Import('env') def obj_dump_after_elf(source, target, env): + platform = env.PioPlatform() + board = env.BoardConfig() + mcu = board.get("build.mcu", "esp32") + print("Create firmware.asm") - env.Execute("xtensa-lx106-elf-objdump "+ "-D " + str(target[0]) + " > "+ "${PROGNAME}.asm") - + if mcu == "esp8266": + env.Execute("xtensa-lx106-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm") + if mcu == "esp32": + env.Execute("xtensa-esp32-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm") + if mcu == "esp32s2": + env.Execute("xtensa-esp32s2-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm") + if mcu == "esp32s3": + env.Execute("xtensa-esp32s3-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm") + if mcu == "esp32c3": + env.Execute("riscv32-esp-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm") + env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [obj_dump_after_elf]) diff --git a/platformio.ini b/platformio.ini index 76c4c92d6..fe8b3a278 100644 --- a/platformio.ini +++ b/platformio.ini @@ -115,6 +115,7 @@ extra_scripts = post:pio-scripts/strip-floats.py pre:pio-scripts/user_config_copy.py pre:pio-scripts/build_ui.py + ; post:pio-scripts/obj-dump.py ;; convenience script to create a disassembly dump of the firmware (hardcore debugging) # ------------------------------------------------------------------------------ # COMMON SETTINGS: From 18e9f9c304482e3506e8982805b5df33a2bbf6c4 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Mon, 6 May 2024 17:39:40 +0200 Subject: [PATCH 221/694] Rename Battery classes --- usermods/Battery/{battery.h => UMBattery.h} | 4 ++-- usermods/Battery/battery_defaults.h | 1 - usermods/Battery/types/{lion.h => LionUMBattery.h} | 8 ++++---- usermods/Battery/types/{lipo.h => LipoUMBattery.h} | 8 ++++---- .../Battery/types/{unkown.h => UnkownUMBattery.h} | 8 ++++---- usermods/Battery/usermod_v2_Battery.h | 14 +++++++------- 6 files changed, 21 insertions(+), 22 deletions(-) rename usermods/Battery/{battery.h => UMBattery.h} (99%) rename usermods/Battery/types/{lion.h => LionUMBattery.h} (86%) rename usermods/Battery/types/{lipo.h => LipoUMBattery.h} (92%) rename usermods/Battery/types/{unkown.h => UnkownUMBattery.h} (87%) diff --git a/usermods/Battery/battery.h b/usermods/Battery/UMBattery.h similarity index 99% rename from usermods/Battery/battery.h rename to usermods/Battery/UMBattery.h index 2ddd84149..8a8ad891e 100644 --- a/usermods/Battery/battery.h +++ b/usermods/Battery/UMBattery.h @@ -7,7 +7,7 @@ * Battery base class * all other battery classes should inherit from this */ -class Battery +class UMBattery { private: @@ -25,7 +25,7 @@ class Battery } public: - Battery() + UMBattery() { this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER); this->setCalibration(USERMOD_BATTERY_CALIBRATION); diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index ea01e8620..8b56c6014 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -131,5 +131,4 @@ typedef struct bconfig_t float voltageMultiplier; } batteryConfig; - #endif \ No newline at end of file diff --git a/usermods/Battery/types/lion.h b/usermods/Battery/types/LionUMBattery.h similarity index 86% rename from usermods/Battery/types/lion.h rename to usermods/Battery/types/LionUMBattery.h index e77266164..801faee7c 100644 --- a/usermods/Battery/types/lion.h +++ b/usermods/Battery/types/LionUMBattery.h @@ -2,18 +2,18 @@ #define UMBLion_h #include "../battery_defaults.h" -#include "../battery.h" +#include "../UMBattery.h" /** - * Lion Battery + * LiOn Battery * */ -class Lion : public Battery +class LionUMBattery : public UMBattery { private: public: - Lion() : Battery() + LionUMBattery() : UMBattery() { this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE); diff --git a/usermods/Battery/types/lipo.h b/usermods/Battery/types/LipoUMBattery.h similarity index 92% rename from usermods/Battery/types/lipo.h rename to usermods/Battery/types/LipoUMBattery.h index d732cf4da..bb6a6ef94 100644 --- a/usermods/Battery/types/lipo.h +++ b/usermods/Battery/types/LipoUMBattery.h @@ -2,18 +2,18 @@ #define UMBLipo_h #include "../battery_defaults.h" -#include "../battery.h" +#include "../UMBattery.h" /** - * Lipo Battery + * LiPo Battery * */ -class Lipo : public Battery +class LipoUMBattery : public UMBattery { private: public: - Lipo() : Battery() + LipoUMBattery() : UMBattery() { this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE); diff --git a/usermods/Battery/types/unkown.h b/usermods/Battery/types/UnkownUMBattery.h similarity index 87% rename from usermods/Battery/types/unkown.h rename to usermods/Battery/types/UnkownUMBattery.h index 32a1bfe42..ede5ffd88 100644 --- a/usermods/Battery/types/unkown.h +++ b/usermods/Battery/types/UnkownUMBattery.h @@ -2,18 +2,18 @@ #define UMBUnkown_h #include "../battery_defaults.h" -#include "../battery.h" +#include "../UMBattery.h" /** - * Lion Battery + * Unkown / Default Battery * */ -class Unkown : public Battery +class UnkownUMBattery : public UMBattery { private: public: - Unkown() : Battery() + UnkownUMBattery() : UMBattery() { this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE); this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE); diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 7b6b038a6..c70babf53 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -2,10 +2,10 @@ #include "wled.h" #include "battery_defaults.h" -#include "battery.h" -#include "types/unkown.h" -#include "types/lion.h" -#include "types/lipo.h" +#include "UMBattery.h" +#include "types/UnkownUMBattery.h" +#include "types/LionUMBattery.h" +#include "types/LiPoUMBattery.h" /* * Usermod by Maximilian Mewes @@ -19,7 +19,7 @@ class UsermodBattery : public Usermod // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; - Battery* bat = new Unkown(); + UMBattery* bat = new UnkownUMBattery(); batteryConfig cfg; // how often to read the battery voltage @@ -142,9 +142,9 @@ class UsermodBattery : public Usermod // plug in the right battery type if(cfg.type == (batteryType)lipo) { - bat = new Lipo(); + bat = new LipoUMBattery(); } else if(cfg.type == (batteryType)lion) { - bat = new Lion(); + bat = new LipoUMBattery(); } // update the choosen battery type with configured values From d33651c25bfdc3ab4457c6e95496914fb924bad1 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Mon, 6 May 2024 17:45:02 +0200 Subject: [PATCH 222/694] Update setup method --- usermods/Battery/usermod_v2_Battery.h | 41 +++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index c70babf53..09be3ccc6 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -120,26 +120,6 @@ class UsermodBattery : public Usermod */ void setup() { - #ifdef ARDUINO_ARCH_ESP32 - bool success = false; - DEBUG_PRINTLN(F("Allocating battery pin...")); - if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) - if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { - DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); - success = true; - voltage = readVoltage(); - } - - if (!success) { - DEBUG_PRINTLN(F("Battery pin allocation failed.")); - batteryPin = -1; // allocation failed - } else { - pinMode(batteryPin, INPUT); - } - #else //ESP8266 boards have only one analog input pin A0 - pinMode(batteryPin, INPUT); - #endif - // plug in the right battery type if(cfg.type == (batteryType)lipo) { bat = new LipoUMBattery(); @@ -150,7 +130,26 @@ class UsermodBattery : public Usermod // update the choosen battery type with configured values bat->update(cfg); - bat->setVoltage(readVoltage()); + #ifdef ARDUINO_ARCH_ESP32 + bool success = false; + DEBUG_PRINTLN(F("Allocating battery pin...")); + if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) + if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { + DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); + success = true; + bat->setVoltage(readVoltage()); + } + + if (!success) { + DEBUG_PRINTLN(F("Battery pin allocation failed.")); + batteryPin = -1; // allocation failed + } else { + pinMode(batteryPin, INPUT); + } + #else //ESP8266 boards have only one analog input pin A0 + pinMode(batteryPin, INPUT); + bat->setVoltage(readVoltage()); + #endif nextReadTime = millis() + readingInterval; lastReadTime = millis(); From 52020cbe269e39dae3493e387bacbd35eefdc92b Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Mon, 6 May 2024 17:46:26 +0200 Subject: [PATCH 223/694] CP fix --- usermods/Battery/usermod_v2_Battery.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 09be3ccc6..88a879b72 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -124,7 +124,7 @@ class UsermodBattery : public Usermod if(cfg.type == (batteryType)lipo) { bat = new LipoUMBattery(); } else if(cfg.type == (batteryType)lion) { - bat = new LipoUMBattery(); + bat = new LionUMBattery(); } // update the choosen battery type with configured values From 5bccb5fc422f4f680b6d3720db47c6bac2f11c1e Mon Sep 17 00:00:00 2001 From: gaaat98 <67930088+gaaat98@users.noreply.github.com> Date: Tue, 7 May 2024 00:31:37 +0200 Subject: [PATCH 224/694] removed commented checks --- wled00/FX.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3110ab910..3c499edad 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6643,7 +6643,6 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall, // * JUGGLES // ////////////////////// uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. - //if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7027,7 +7026,6 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color // ** DJLight // ///////////////////////// uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. - //if (SEGLEN == 1) return mode_static(); // No need to prevent from executing on single led strips, only mid will be set (mid = 0) const int mid = SEGLEN / 2; @@ -7100,7 +7098,6 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting // ** Freqmatrix // /////////////////////// uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. - //if (SEGLEN == 1) return mode_static(); // No need to prevent from executing on single led strips, we simply change pixel 0 each time and avoid the shift um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -7207,7 +7204,6 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. // Depending on the music stream you have you might find it useful to change the frequency mapping. uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. - //if (SEGLEN == 1) return mode_static(); // As before, this effect can also work on single pixels, we just lose the shifting effect um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -7318,7 +7314,6 @@ static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sens // ** Noisemove // ////////////////////// uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline - //if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7346,7 +7341,6 @@ static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin // ** Rocktaves // ////////////////////// uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline - //if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7388,9 +7382,8 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12= /////////////////////// // Combines peak detection with FFT_MajorPeak and FFT_Magnitude. uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline - //if (SEGLEN == 1) return mode_static(); // effect can work on single pixels, we just lose the shifting effect - + um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio From 88372cd516f959db0033644479621f5221aef3b1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 7 May 2024 16:34:15 +0200 Subject: [PATCH 225/694] Brighter peek (ignore strip brightness) --- wled00/ws.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 307a0959e..cf09d592e 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -206,9 +206,12 @@ bool sendLiveLedsWs(uint32_t wsClient) uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - buffer[pos++] = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map - buffer[pos++] = scale8(qadd8(w, g), strip.getBrightness()); //G - buffer[pos++] = scale8(qadd8(w, b), strip.getBrightness()); //B + //buffer[pos++] = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map + //buffer[pos++] = scale8(qadd8(w, g), strip.getBrightness()); //G + //buffer[pos++] = scale8(qadd8(w, b), strip.getBrightness()); //B + buffer[pos++] = qadd8(w, r); //R, add white channel to RGB channels as a simple RGBW -> RGB map + buffer[pos++] = qadd8(w, g); //G + buffer[pos++] = qadd8(w, b); //B } wsc->binary(std::move(wsBuf)); From b88c300d0467bd65dd9e58b3ae99977434f70fab Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:57:29 +0200 Subject: [PATCH 226/694] audioreactive: workaround for ArduinoFFT bug 93 This fix works around a problem that was solved in upstream ArduinoFFT 2.0.2 --- usermods/audioreactive/audio_reactive.h | 1 + 1 file changed, 1 insertion(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 442a651ea..8741eb14c 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -282,6 +282,7 @@ void FFTcode(void * parameter) //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes + vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects From da79b93387cb80873d5dc65cd864be866a4b70bf Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Tue, 7 May 2024 18:04:29 -0400 Subject: [PATCH 227/694] Added Pinwheel Expand 1D Effect --- wled00/FX.h | 3 ++- wled00/FX_fcn.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++- wled00/data/index.js | 1 + 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 106a6712c..b1e6823ff 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -320,7 +320,8 @@ typedef enum mapping1D2D { M12_Pixels = 0, M12_pBar = 1, M12_pArc = 2, - M12_pCorner = 3 + M12_pCorner = 3, + M12_sPinwheel = 4 } mapping1D2D_t; // segment, 80 bytes diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index ce510f16e..7f8c1319a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -637,6 +637,14 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const { return vLen; } +// Constants for mapping mode "Pinwheel" +constexpr int Pinwheel_Steps_Medium = 208; // no holes up to 32x32; 60fps +constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Steps_Big = 360; // no holes expected up to 58x58; 40fps +constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...208 to Radians +constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...360 to Radians + + // 1D strip uint16_t IRAM_ATTR Segment::virtualLength() const { #ifndef WLED_DISABLE_2D @@ -652,6 +660,12 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { case M12_pArc: vLen = max(vW,vH); // get the longest dimension break; + case M12_sPinwheel: + if (max(vW,vH) <= Pinwheel_Size_Medium) + vLen = Pinwheel_Steps_Medium; + else + vLen = Pinwheel_Steps_Big; + break; } return vLen; } @@ -718,6 +732,38 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); break; + case M12_sPinwheel: { + // i = angle --> 0 through 359 (Big), OR 0 through 208 (Medium) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); + // int maxDistance = sqrt(centerX * centerX + centerY * centerY) + 1; + float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + float cosVal = cosf(angleRad); + float sinVal = sinf(angleRad); + + // draw line at angle, starting at center and ending at the segment edge + // we use fixed point math for better speed. Starting distance is 0.5 for better rounding + constexpr int_fast32_t Fixed_Scale = 512; // fixpoint scaling factor + int_fast32_t posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point + int_fast32_t posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point + int_fast16_t inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) + int_fast16_t inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) + + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + // draw until we hit any edge + while ((posx > 0) && (posy > 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + int x = posx / Fixed_Scale; + int y = posy / Fixed_Scale; + // set pixel + setPixelColorXY(x, y, col); + // advance to next position + posx += inc_x; + posy += inc_y; + } + break; + } } return; } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { @@ -833,7 +879,17 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) // use longest dimension return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); break; - } + case M12_sPinwheel: + // not 100% accurate, returns outer edge of circle + float distance = max(1.0f, min(vH-1, vW-1) / 2.0f); + float centerX = (vW - 1) / 2.0f; + float centerY = (vH - 1) / 2.0f; + float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + int x = roundf(centerX + distance * cosf(angleRad)); + int y = roundf(centerY + distance * sinf(angleRad)); + return getPixelColorXY(x, y); + break; + } return 0; } #endif diff --git a/wled00/data/index.js b/wled00/data/index.js index d33fb63f7..26d78b284 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -801,6 +801,7 @@ function populateSegments(s) ``+ ``+ ``+ + ``+ `
`+ `
`; let sndSim = `
Sound sim
`+ From 0ac53d83532b048f12d8475f8680b63eaab3afb2 Mon Sep 17 00:00:00 2001 From: gaaat Date: Wed, 8 May 2024 15:33:51 +0200 Subject: [PATCH 228/694] initial port of MoonModules/WLED/pull/60 and related commit --- platformio.ini | 1 + usermods/audioreactive/audio_reactive.h | 229 ++++++++++++++++++------ 2 files changed, 179 insertions(+), 51 deletions(-) diff --git a/platformio.ini b/platformio.ini index 76c4c92d6..d77650bee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -195,6 +195,7 @@ build_flags = ; decrease code cache size and increase IRAM to fit all pixel functions -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown + -D USERMOD_AUDIOREACTIVE lib_deps = #https://github.com/lorol/LITTLEFS.git diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 442a651ea..2b43de1b2 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1,6 +1,9 @@ #pragma once #include "wled.h" + +#ifdef ARDUINO_ARCH_ESP32 + #include #include @@ -8,11 +11,9 @@ #error This audio reactive usermod is not compatible with DMX Out. #endif -#ifndef ARDUINO_ARCH_ESP32 - #error This audio reactive usermod does not support the ESP8266. #endif -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) #include #endif @@ -57,6 +58,50 @@ #define MAX_PALETTES 3 +static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. +static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group + +#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! + +// audioreactive variables +#ifdef ARDUINO_ARCH_ESP32 +static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) +static float sampleAgc = 0.0f; // Smoothed AGC sample +static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) +#endif +//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample +static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency +static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData +static unsigned long timeOfPeak = 0; // time of last sample peak detection. +static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects + +// TODO: probably best not used by receive nodes +//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 + +// user settable parameters for limitSoundDynamics() +#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF +static bool limiterOn = false; // bool: enable / disable dynamics limiter +#else +static bool limiterOn = true; +#endif +static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec +static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec + +// peak detection +#ifdef ARDUINO_ARCH_ESP32 +static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) - no used for 8266 receive-only mode +#endif +static void autoResetPeak(void); // peak auto-reset function +static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) + +#ifdef ARDUINO_ARCH_ESP32 + // use audio source class (ESP32 specific) #include "audio_source.h" constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) @@ -74,18 +119,10 @@ static uint8_t inputLevel = 128; // UI slider value #else uint8_t sampleGain = SR_GAIN; // sample gain (config value) #endif -static uint8_t soundAgc = 1; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) -static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) -static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group +//static uint8_t soundAgc = 1; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) +//static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +//static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group -// user settable parameters for limitSoundDynamics() -#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF -static bool limiterOn = false; // bool: enable / disable dynamics limiter -#else -static bool limiterOn = true; -#endif -static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec -static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec // user settable options for FFTResult scaling static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root @@ -109,23 +146,23 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // // AGC presets end static AudioSource *audioSource = nullptr; -static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. +//static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. // audioreactive variables shared with FFT task -static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point -static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier -static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) -static float sampleAgc = 0.0f; // Smoothed AGC sample +// static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +// static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +// static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) +// static float sampleAgc = 0.0f; // Smoothed AGC sample // peak detection -static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() -static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) -static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) -static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData -static unsigned long timeOfPeak = 0; // time of last sample peak detection. -static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) -static void autoResetPeak(void); // peak auto-reset function +// static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +// static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +// static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) +// static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData +// static unsigned long timeOfPeak = 0; // time of last sample peak detection. +// static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) +// static void autoResetPeak(void); // peak auto-reset function //////////////////// @@ -139,7 +176,7 @@ void FFTcode(void * parameter); // audio processing task: read samples, run static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels -#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! +//#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! static TaskHandle_t FFT_Task = nullptr; @@ -147,9 +184,9 @@ static TaskHandle_t FFT_Task = nullptr; static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; // globals and FFT Output variables shared with animations -static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency -static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects +//static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency +//static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +//static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects #if defined(WLED_DEBUG) || defined(SR_DEBUG) static uint64_t fftTime = 0; static uint64_t sampleTime = 0; @@ -521,6 +558,8 @@ static void detectSamplePeak(void) { } } +#endif + static void autoResetPeak(void) { uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. @@ -538,6 +577,8 @@ static void autoResetPeak(void) { class AudioReactive : public Usermod { private: +#ifdef ARDUINO_ARCH_ESP32 + #ifndef AUDIOPIN int8_t audioPin = -1; #else @@ -569,6 +610,7 @@ class AudioReactive : public Usermod { #else int8_t mclkPin = MCLK_PIN; #endif +#endif // new "V2" audiosync struct - 40 Bytes struct audioSyncPacket { @@ -612,10 +654,14 @@ class AudioReactive : public Usermod { const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED uint16_t audioSyncPort= 11988;// default port for UDP sound sync + bool updateIsRunning = false; // true during OTA. + +#ifdef ARDUINO_ARCH_ESP32 // used for AGC int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error + // variables used by getSample() and agcAvg() int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller. @@ -624,6 +670,7 @@ class AudioReactive : public Usermod { float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) int16_t rawSampleAgc = 0; // not smoothed AGC sample +#endif // variables used in effects float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample @@ -644,7 +691,9 @@ class AudioReactive : public Usermod { static const char _dynamics[]; static const char _frequency[]; static const char _inputLvl[]; +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) static const char _analogmic[]; +#endif static const char _digitalmic[]; static const char _addPalettes[]; static const char UDP_SYNC_HEADER[]; @@ -671,11 +720,13 @@ class AudioReactive : public Usermod { //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); + #ifdef ARDUINO_ARCH_ESP32 //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); //PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t"); //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); + #endif PLOT_PRINTLN(); #endif @@ -731,6 +782,7 @@ class AudioReactive : public Usermod { } // logAudio() +#ifdef ARDUINO_ARCH_ESP32 ////////////////////// // Audio Processing // ////////////////////// @@ -901,6 +953,7 @@ class AudioReactive : public Usermod { sampleAvg = fabsf(sampleAvg); // make sure we have a positive value } // getSample() +#endif /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) @@ -947,12 +1000,14 @@ class AudioReactive : public Usermod { if (udpSyncConnected) return; // already connected if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds + if (updateIsRunning) return; // if we arrive here, we need a UDP connection but don't have one last_connection_attempt = millis(); connected(); // try to start UDP } +#ifdef ARDUINO_ARCH_ESP32 void transmitAudioData() { if (!udpSyncConnected) return; @@ -983,11 +1038,13 @@ class AudioReactive : public Usermod { return; } // transmitAudioData() +#endif + static bool isValidUdpSyncVersion(const char *header) { - return strncmp_P(header, PSTR(UDP_SYNC_HEADER), 6) == 0; + return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; } static bool isValidUdpSyncVersion_v1(const char *header) { - return strncmp_P(header, PSTR(UDP_SYNC_HEADER_v1), 6) == 0; + return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; } void decodeAudioData(int packetSize, uint8_t *fftBuff) { @@ -995,12 +1052,14 @@ class AudioReactive : public Usermod { // update samples for effects volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); +#ifdef ARDUINO_ARCH_ESP32 // update internal samples sampleRaw = volumeRaw; sampleAvg = volumeSmth; rawSampleAgc = volumeRaw; sampleAgc = volumeSmth; multAgc = 1.0f; +#endif // Only change samplePeak IF it's currently false. // If it's true already, then the animation still needs to respond. autoResetPeak(); @@ -1009,7 +1068,7 @@ class AudioReactive : public Usermod { if (samplePeak) timeOfPeak = millis(); //userVar1 = samplePeak; } - //These values are only available on the ESP32 + //These values are only computed by ESP32 for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; @@ -1021,12 +1080,14 @@ class AudioReactive : public Usermod { // update samples for effects volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample +#ifdef ARDUINO_ARCH_ESP32 // update internal samples sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; sampleAgc = volumeSmth; rawSampleAgc = volumeRaw; - multAgc = 1.0f; + multAgc = 1.0f; +#endif // Only change samplePeak IF it's currently false. // If it's true already, then the animation still needs to respond. autoResetPeak(); @@ -1112,6 +1173,9 @@ class AudioReactive : public Usermod { um_data->u_type[7] = UMT_BYTE; } + +#ifdef ARDUINO_ARCH_ESP32 + // Reset I2S peripheral for good measure i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed #if !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -1189,10 +1253,12 @@ class AudioReactive : public Usermod { delay(250); // give microphone enough time to initialise if (!audioSource) enabled = false; // audio failed to initialise - if (enabled) onUpdateBegin(false); // create FFT task - if (FFT_Task == nullptr) enabled = false; // FFT task creation failed - if (enabled) disableSoundProcessing = false; // all good - enable audio processing +#endif + if (enabled) onUpdateBegin(false); // create FFT task, and initailize network + +#ifdef ARDUINO_ARCH_ESP32 + if (FFT_Task == nullptr) enabled = false; // FFT task creation failed if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync #ifdef WLED_DEBUG DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); @@ -1201,7 +1267,8 @@ class AudioReactive : public Usermod { #endif disableSoundProcessing = true; } - +#endif + if (enabled) disableSoundProcessing = false; // all good - enable audio processing if (enabled) connectUDPSoundSync(); if (enabled && addPalettes) createAudioPalettes(); initDone = true; @@ -1220,7 +1287,7 @@ class AudioReactive : public Usermod { } if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { - #ifndef ESP8266 + #ifdef ARDUINO_ARCH_ESP32 udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); #else udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); @@ -1259,7 +1326,7 @@ class AudioReactive : public Usermod { ||(realtimeMode == REALTIME_MODE_ADALIGHT) ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed { - #ifdef WLED_DEBUG + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); @@ -1267,7 +1334,7 @@ class AudioReactive : public Usermod { #endif disableSoundProcessing = true; } else { - #ifdef WLED_DEBUG + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); @@ -1279,6 +1346,7 @@ class AudioReactive : public Usermod { if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode +#ifdef ARDUINO_ARCH_ESP32 if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source @@ -1318,6 +1386,7 @@ class AudioReactive : public Usermod { limitSampleDynamics(); } // if (!disableSoundProcessing) +#endif autoResetPeak(); // auto-reset sample peak after strip minShowDelay if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected @@ -1351,6 +1420,7 @@ class AudioReactive : public Usermod { #endif // Info Page: keep max sample from last 5 seconds +#ifdef ARDUINO_ARCH_ESP32 if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { sampleMaxTimer = millis(); maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing @@ -1358,13 +1428,25 @@ class AudioReactive : public Usermod { } else { if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume } +#else // similar functionality for 8266 receive only - use VolumeSmth instead of raw sample data + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15 * maxSample5sec) + 0.85 * volumeSmth; // reset, and start with some smoothing + if (volumeSmth < 1.0f) maxSample5sec = 0; // noise gate + if (maxSample5sec < 0.0f) maxSample5sec = 0; // avoid negative values + } else { + if (volumeSmth >= 1.0f) maxSample5sec = fmaxf(maxSample5sec, volumeRaw); // follow maximum volume + } +#endif +#ifdef ARDUINO_ARCH_ESP32 //UDP Microphone Sync - transmit mode if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { // Only run the transmit code IF we're in Transmit mode transmitAudioData(); lastTime = millis(); } +#endif fillAudioPalettes(); } @@ -1377,7 +1459,7 @@ class AudioReactive : public Usermod { return true; } - +#ifdef ARDUINO_ARCH_ESP32 void onUpdateBegin(bool init) override { #ifdef WLED_DEBUG @@ -1426,9 +1508,32 @@ class AudioReactive : public Usermod { } micDataReal = 0.0f; // just to be sure if (enabled) disableSoundProcessing = false; + updateIsRunning = init; } +#else // reduced function for 8266 + void onUpdateBegin(bool init) + { + // gracefully suspend audio (if running) + disableSoundProcessing = true; + // reset sound data + volumeRaw = 0; volumeSmth = 0; + for(int i=(init?0:1); i don't process audio + updateIsRunning = init; + } +#endif +#ifdef ARDUINO_ARCH_ESP32 /** * handleButton() can be used to override default button behaviour. Returning true * will prevent button working in a default way. @@ -1446,7 +1551,7 @@ class AudioReactive : public Usermod { return false; } - +#endif //////////////////////////// // Settings and Info Page // //////////////////////////// @@ -1458,7 +1563,9 @@ class AudioReactive : public Usermod { */ void addToJsonInfo(JsonObject& root) override { - char myStringBuffer[16]; // buffer for snprintf() +#ifdef ARDUINO_ARCH_ESP32 + char myStringBuffer[16]; // buffer for snprintf() - not used yet on 8266 +#endif JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -1476,6 +1583,7 @@ class AudioReactive : public Usermod { infoArr.add(uiDomString); if (enabled) { +#ifdef ARDUINO_ARCH_ESP32 // Input Level Slider if (disableSoundProcessing == false) { // only show slider when audio processing is running if (soundAgc > 0) { @@ -1492,7 +1600,7 @@ class AudioReactive : public Usermod { uiDomString += F(" />
"); // infoArr.add(uiDomString); } - +#endif // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG // current Audio input @@ -1508,6 +1616,11 @@ class AudioReactive : public Usermod { } else { infoArr.add(F(" - no connection")); } +#ifndef ARDUINO_ARCH_ESP32 // substitute for 8266 + } else { + infoArr.add(F("sound sync Off")); + } +#else // ESP32 only } else { // Analog or I2S digital input if (audioSource && (audioSource->isInitialized())) { @@ -1552,7 +1665,7 @@ class AudioReactive : public Usermod { infoArr.add(roundf(multAgc*100.0f) / 100.0f); infoArr.add("x"); } - +#endif // UDP Sound Sync status infoArr = user.createNestedArray(F("UDP Sound Sync")); if (audioSyncEnabled) { @@ -1571,6 +1684,7 @@ class AudioReactive : public Usermod { } #if defined(WLED_DEBUG) || defined(SR_DEBUG) + #ifdef ARDUINO_ARCH_ESP32 infoArr = user.createNestedArray(F("Sampling time")); infoArr.add(float(sampleTime)/100.0f); infoArr.add(" ms"); @@ -1587,6 +1701,7 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); #endif + #endif } } @@ -1625,9 +1740,11 @@ class AudioReactive : public Usermod { if (!prevEnabled && enabled) createAudioPalettes(); } } +#ifdef ARDUINO_ARCH_ESP32 if (usermod[FPSTR(_inputLvl)].is()) { inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); } +#endif } if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { // handle removal of custom palettes from JSON call so we don't break things @@ -1683,6 +1800,7 @@ class AudioReactive : public Usermod { top[FPSTR(_enabled)] = enabled; top[FPSTR(_addPalettes)] = addPalettes; +#ifdef ARDUINO_ARCH_ESP32 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); amic["pin"] = audioPin; @@ -1701,13 +1819,15 @@ class AudioReactive : public Usermod { cfg[F("gain")] = sampleGain; cfg[F("AGC")] = soundAgc; + JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); + freqScale[F("scale")] = FFTScalingMode; +#endif + JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); dynLim[F("limiter")] = limiterOn; dynLim[F("rise")] = attackTime; dynLim[F("fall")] = decayTime; - JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); - freqScale[F("scale")] = FFTScalingMode; JsonObject sync = top.createNestedObject("sync"); sync["port"] = audioSyncPort; @@ -1740,6 +1860,7 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); +#ifdef ARDUINO_ARCH_ESP32 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); #else @@ -1763,12 +1884,12 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); + configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); - - configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); - +#endif configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); @@ -1783,6 +1904,7 @@ class AudioReactive : public Usermod { void appendConfigData() override { +#ifdef ARDUINO_ARCH_ESP32 oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) oappend(SET_F("addOption(dd,'Generic Analog',0);")); @@ -1814,11 +1936,15 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); +#endif oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); oappend(SET_F("addOption(dd,'Off',0);")); +#ifdef ARDUINO_ARCH_ESP32 oappend(SET_F("addOption(dd,'Send',1);")); +#endif oappend(SET_F("addOption(dd,'Receive',2);")); +#ifdef ARDUINO_ARCH_ESP32 oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); @@ -1828,6 +1954,7 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); #endif +#endif } From cec72419863a0ce8f19a02aa05bf9073b1302dcd Mon Sep 17 00:00:00 2001 From: gaaat Date: Wed, 8 May 2024 15:42:41 +0200 Subject: [PATCH 229/694] removed commented variables --- usermods/audioreactive/audio_reactive.h | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index a0a657d68..a718011fe 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -119,10 +119,6 @@ static uint8_t inputLevel = 128; // UI slider value #else uint8_t sampleGain = SR_GAIN; // sample gain (config value) #endif -//static uint8_t soundAgc = 1; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) -//static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) -//static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group - // user settable options for FFTResult scaling static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root @@ -146,25 +142,8 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // // AGC presets end static AudioSource *audioSource = nullptr; -//static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. -// audioreactive variables shared with FFT task -// static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point -// static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier -// static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) -// static float sampleAgc = 0.0f; // Smoothed AGC sample - -// peak detection -// static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() -// static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) -// static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) -// static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData -// static unsigned long timeOfPeak = 0; // time of last sample peak detection. -// static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) -// static void autoResetPeak(void); // peak auto-reset function - - //////////////////// // Begin FFT Code // //////////////////// @@ -176,17 +155,12 @@ void FFTcode(void * parameter); // audio processing task: read samples, run static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels -//#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! - static TaskHandle_t FFT_Task = nullptr; // Table of multiplication factors so that we can even out the frequency response. static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; // globals and FFT Output variables shared with animations -//static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency -//static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -//static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects #if defined(WLED_DEBUG) || defined(SR_DEBUG) static uint64_t fftTime = 0; static uint64_t sampleTime = 0; From a320f164045c53b4b8454c9b25e792f64fae57ca Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 9 May 2024 23:58:58 +0200 Subject: [PATCH 230/694] Real math fix --- wled00/fcn_declare.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a6ff9d096..1b25c8926 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -409,14 +409,14 @@ void clearEEPROM(); float fmod_t(float num, float denom); #else #include - #define sin_t sin - #define cos_t cos - #define tan_t tan - #define asin_t asin - #define acos_t acos - #define atan_t atan - #define fmod_t fmod - #define floor_t floor + #define sin_t sinf + #define cos_t cosf + #define tan_t tanf + #define asin_t asinf + #define acos_t acosf + #define atan_t atanf + #define fmod_t fmodf + #define floor_t floorf #endif //wled_serial.cpp From 4dbe9a701596d130ff34157c111abf93f0c29846 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 10 May 2024 00:02:28 +0200 Subject: [PATCH 231/694] Antialiased line & circle --- wled00/FX.cpp | 4 +- wled00/FX.h | 27 ++++-- wled00/FX_2Dfcn.cpp | 211 ++++++++++++++++++++++++++------------------ 3 files changed, 147 insertions(+), 95 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3c499edad..d4566976d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2506,7 +2506,7 @@ uint16_t ripple_base() uint16_t cx = rippleorigin >> 8; uint16_t cy = rippleorigin & 0xFF; uint8_t mag = scale8(sin8((propF>>2)), amp); - if (propI > 0) SEGMENT.draw_circle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag)); + if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif { @@ -6056,7 +6056,7 @@ uint16_t mode_2Dfloatingblobs(void) { } } uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); - if (blob->r[i] > 1.f) SEGMENT.fill_circle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); + if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c); // move x if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f)); diff --git a/wled00/FX.h b/wled00/FX.h index 106a6712c..6e458fcea 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -106,6 +106,10 @@ #define PURPLE (uint32_t)0x400080 #define ORANGE (uint32_t)0xFF3000 #define PINK (uint32_t)0xFF1493 +#define GREY (uint32_t)0x808080 +#define GRAY GREY +#define DARKGREY (uint32_t)0x333333 +#define DARKGRAY DARKGREY #define ULTRAWHITE (uint32_t)0xFFFFFFFF #define DARKSLATEGRAY (uint32_t)0x2F4F4F #define DARKSLATEGREY DARKSLATEGRAY @@ -605,6 +609,7 @@ typedef struct Segment { inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } @@ -624,24 +629,25 @@ typedef struct Segment { void moveX(int8_t delta, bool wrap = false); void moveY(int8_t delta, bool wrap = false); void move(uint8_t dir, uint8_t delta, bool wrap = false); - void draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); - void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0); inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); - void blur1d(fract8 blur_amount); // blur all rows in 1 dimension inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } - void nscale8(uint8_t scale); #else inline uint16_t XY(uint16_t x, uint16_t y) { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } @@ -660,9 +666,12 @@ typedef struct Segment { inline void moveX(int8_t delta, bool wrap = false) {} inline void moveY(int8_t delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} - inline void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index e14b68f4f..b262c157d 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -342,55 +342,36 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - const unsigned dim1 = vertical ? rows : cols; - const unsigned dim2 = vertical ? cols : rows; + const int cols = virtualWidth(); + const int rows = virtualHeight(); + const int dim1 = vertical ? rows : cols; + const int dim2 = vertical ? cols : rows; if (i >= dim2) return; const float seep = blur_amount/255.f; const float keep = 3.f - 2.f*seep; // 1D box blur - CRGB tmp[dim1]; - for (unsigned j = 0; j < dim1; j++) { - unsigned x = vertical ? i : j; - unsigned y = vertical ? j : i; - int xp = vertical ? x : x-1; // "signed" to prevent underflow - int yp = vertical ? y-1 : y; // "signed" to prevent underflow - unsigned xn = vertical ? x : x+1; - unsigned yn = vertical ? y+1 : y; - CRGB curr = getPixelColorXY(x,y); - CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp); - CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn); - unsigned r, g, b; - r = (curr.r*keep + (prev.r + next.r)*seep) / 3; - g = (curr.g*keep + (prev.g + next.g)*seep) / 3; - b = (curr.b*keep + (prev.b + next.b)*seep) / 3; - tmp[j] = CRGB(r,g,b); + uint32_t out[dim1], in[dim1]; + for (int j = 0; j < dim1; j++) { + int x = vertical ? i : j; + int y = vertical ? j : i; + in[j] = getPixelColorXY(x, y); } - for (unsigned j = 0; j < dim1; j++) { - unsigned x = vertical ? i : j; - unsigned y = vertical ? j : i; - setPixelColorXY(x, y, tmp[j]); + for (int j = 0; j < dim1; j++) { + uint32_t curr = in[j]; + uint32_t prev = j > 0 ? in[j-1] : BLACK; + uint32_t next = j < dim1-1 ? in[j+1] : BLACK; + uint8_t r, g, b, w; + r = (R(curr)*keep + (R(prev) + R(next))*seep) / 3; + g = (G(curr)*keep + (G(prev) + G(next))*seep) / 3; + b = (B(curr)*keep + (B(prev) + B(next))*seep) / 3; + w = (W(curr)*keep + (W(prev) + W(next))*seep) / 3; + out[j] = RGBW32(r,g,b,w); + } + for (int j = 0; j < dim1; j++) { + int x = vertical ? i : j; + int y = vertical ? j : i; + setPixelColorXY(x, y, out[j]); } -} - -// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors. -// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors. -// -// 0 = no spread at all -// 64 = moderate spreading -// 172 = maximum smooth, even spreading -// -// 173..255 = wider spreading, but increasing flicker -// -// Total light is NOT entirely conserved, so many repeated -// calls to 'blur' will also result in the light fading, -// eventually all the way to black; this is by design so that -// it can be used to (slowly) clear the LEDs to black. - -void Segment::blur1d(fract8 blur_amount) { - const unsigned rows = virtualHeight(); - for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount); } void Segment::moveX(int8_t delta, bool wrap) { @@ -447,33 +428,67 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { } } -void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { +void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { if (!isActive() || radius == 0) return; // not active - // Bresenham’s Algorithm - int d = 3 - (2*radius); - int y = radius, x = 0; - while (y >= x) { - setPixelColorXY(cx+x, cy+y, col); - setPixelColorXY(cx-x, cy+y, col); - setPixelColorXY(cx+x, cy-y, col); - setPixelColorXY(cx-x, cy-y, col); - setPixelColorXY(cx+y, cy+x, col); - setPixelColorXY(cx-y, cy+x, col); - setPixelColorXY(cx+y, cy-x, col); - setPixelColorXY(cx-y, cy-x, col); - x++; - if (d > 0) { - y--; - d += 4 * (x - y) + 10; - } else { - d += 4 * x + 6; + if (soft) { + // Xiaolin Wu’s algorithm + int rsq = radius*radius; + int x = 0; + int y = radius; + unsigned oldFade = 0; + while (x < y) { + float yf = sqrtf(float(rsq - x*x)); // needs to be floating point + unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep + if (oldFade > fade) y--; + oldFade = fade; + setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); + setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); + setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); + setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); + setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); + setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); + setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); + setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); + setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); + setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); + setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); + setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); + setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); + setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); + setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); + setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); + x++; + } + } else { + // Bresenham’s Algorithm + int d = 3 - (2*radius); + int y = radius, x = 0; + while (y >= x) { + setPixelColorXY(cx+x, cy+y, col); + setPixelColorXY(cx-x, cy+y, col); + setPixelColorXY(cx+x, cy-y, col); + setPixelColorXY(cx-x, cy-y, col); + setPixelColorXY(cx+y, cy+x, col); + setPixelColorXY(cx-y, cy+x, col); + setPixelColorXY(cx+y, cy-x, col); + setPixelColorXY(cx-y, cy-x, col); + x++; + if (d > 0) { + y--; + d += 4 * (x - y) + 10; + } else { + d += 4 * x + 6; + } } } } // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs -void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { +void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { if (!isActive() || radius == 0) return; // not active + // draw soft bounding circle + if (soft) drawCircle(cx, cy, radius, col, soft); + // fill it const int cols = virtualWidth(); const int rows = virtualHeight(); for (int y = -radius; y <= radius; y++) { @@ -486,30 +501,58 @@ void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { } } -void Segment::nscale8(uint8_t scale) { - if (!isActive()) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { - setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale)); - } -} - //line function -void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) { +void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { if (!isActive()) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const int cols = virtualWidth(); + const int rows = virtualHeight(); if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; - const int dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; - for (;;) { - setPixelColorXY(x0,y0,c); - if (x0==x1 && y0==y1) break; - e2 = err; - if (e2 >-dx) { err -= dy; x0 += sx; } - if (e2 < dy) { err += dx; y0 += sy; } + + const int dx = abs(x1-x0), sx = x0 dx; + if (steep) { + // we need to go along longest dimension + std::swap(x0,y0); + std::swap(x1,y1); + } + if (x0 > x1) { + // we need to go in increasing fashion + std::swap(x0,x1); + std::swap(y0,y1); + } + float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); + float intersectY = y0; + for (int x = x0; x <= x1; x++) { + unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep + unsigned seep = 0xFFFF - keep; // how much background to keep + int y = int(intersectY); + if (steep) std::swap(x,y); // temporaryly swap if steep + // pixel coverage is determined by fractional part of y co-ordinate + setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); + setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); + intersectY += gradient; + if (steep) std::swap(x,y); // restore if steep + } + } else { + // Bresenham's algorithm + int err = (dx>dy ? dx : -dy)/2; // error direction + for (;;) { + setPixelColorXY(x0, y0, c); + if (x0==x1 && y0==y1) break; + int e2 = err; + if (e2 >-dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } } } From bc5aadff7d73931f971b3d1ee534b7a2d4dc7f0a Mon Sep 17 00:00:00 2001 From: Adam Matthews Date: Thu, 9 May 2024 23:09:45 +0100 Subject: [PATCH 232/694] Update Usermod: Battery Issue: When taking the initial voltage reading after first powering on, voltage hasn't had chance to stabilize so the reading can be inaccurate, which in turn may incorrectly trigger the low-power preset. (Manifests when the user has selected a low read interval and/or is using a capacitor). Resolution: A non-blocking, fixed 10 second delay has been added to the initial voltage reading to give the voltage time to stabilize. This is a reworked version of the (now closed) PR here: https://github.com/Aircoookie/WLED/pull/3959 - Rebased the update for 0_15. - Added a constant so the delay can be modified via my_config.h. - Small adjustments to make the PR compatible again after the recent restructuring in this PR: (https://github.com/Aircoookie/WLED/pull/3003). Thankyou! --- usermods/Battery/battery_defaults.h | 6 ++++++ usermods/Battery/readme.md | 5 +++++ usermods/Battery/usermod_v2_Battery.h | 28 ++++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 8b56c6014..ddbd114e4 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -14,6 +14,12 @@ #endif #endif +// The initial delay before the first battery voltage reading after power-on. +// This allows the voltage to stabilize before readings are taken, improving accuracy of initial reading. +#ifndef USERMOD_BATTERY_INITIAL_DELAY + #define USERMOD_BATTERY_INITIAL_DELAY 10000 // (milliseconds) +#endif + // the frequency to check the battery, 30 sec #ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL #define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000 diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index b3607482a..efe25cc24 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -37,6 +37,7 @@ define `USERMOD_BATTERY` in `wled00/my_config.h` | ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- | | `USERMOD_BATTERY` | | define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | | `USERMOD_BATTERY_MEASUREMENT_PIN` | | defaults to A0 on ESP8266 and GPIO35 on ESP32 | +| `USERMOD_BATTERY_INITIAL_DELAY` | ms | delay before initial reading. defaults to 10 seconds to allow voltage stabilization | `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds | | `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | | `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | @@ -88,6 +89,10 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3. 2024-04-30 +- improved initial reading accuracy by delaying initial measurement to allow voltage to stabilize at power-on + +2024-04-30 + - integrate factory pattern to make it easier to add other / custom battery types - update readme diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 88a879b72..35da337e1 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -22,6 +22,10 @@ class UsermodBattery : public Usermod UMBattery* bat = new UnkownUMBattery(); batteryConfig cfg; + // Initial delay before first reading to allow voltage stabilization + unsigned long initialDelay = USERMOD_BATTERY_INITIAL_DELAY; + bool initialDelayComplete = false; + bool isFirstVoltageReading = true; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; @@ -137,7 +141,6 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; - bat->setVoltage(readVoltage()); } if (!success) { @@ -148,10 +151,10 @@ class UsermodBattery : public Usermod } #else //ESP8266 boards have only one analog input pin A0 pinMode(batteryPin, INPUT); - bat->setVoltage(readVoltage()); #endif - nextReadTime = millis() + readingInterval; + // First voltage reading is delayed to allow voltage stabilization after powering up + nextReadTime = millis() + initialDelay; lastReadTime = millis(); initDone = true; @@ -178,6 +181,25 @@ class UsermodBattery : public Usermod lowPowerIndicator(); + // Handling the initial delay + if (!initialDelayComplete && millis() < nextReadTime) + return; // Continue to return until the initial delay is over + + // Once the initial delay is over, set it as complete + if (!initialDelayComplete) + { + initialDelayComplete = true; + // Set the regular interval after initial delay + nextReadTime = millis() + readingInterval; + } + + // Make the first voltage reading after the initial delay has elapsed + if (isFirstVoltageReading) + { + bat->setVoltage(readVoltage()); + isFirstVoltageReading = false; + } + // check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms) if (millis() < nextReadTime) return; From 6a18ce078e3b2f31404d3309b39985638d53fcf4 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Thu, 9 May 2024 20:38:41 -0400 Subject: [PATCH 233/694] Pinwheel Expand1D changes cosf/sinf changed to cos_t/sin_t. int_fast32_t and int_fast_16_t changed to int. --- wled00/FX_fcn.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7f8c1319a..88ec78f3e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -738,16 +738,17 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) float centerY = roundf((vH-1) / 2.0f); // int maxDistance = sqrt(centerX * centerX + centerY * centerY) + 1; float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians - float cosVal = cosf(angleRad); - float sinVal = sinf(angleRad); + float cosVal = cos_t(angleRad); + float sinVal = sin_t(angleRad); // draw line at angle, starting at center and ending at the segment edge // we use fixed point math for better speed. Starting distance is 0.5 for better rounding - constexpr int_fast32_t Fixed_Scale = 512; // fixpoint scaling factor - int_fast32_t posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point - int_fast32_t posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point - int_fast16_t inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) - int_fast16_t inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) + // int_fast16_t and int_fast32_t types changed to int, minimum bits commented + constexpr int Fixed_Scale = 512; // fixpoint scaling factor 18 bit + int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit + int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit + int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit + int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint @@ -885,8 +886,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) float centerX = (vW - 1) / 2.0f; float centerY = (vH - 1) / 2.0f; float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians - int x = roundf(centerX + distance * cosf(angleRad)); - int y = roundf(centerY + distance * sinf(angleRad)); + int x = roundf(centerX + distance * cos_t(angleRad)); + int y = roundf(centerY + distance * sin_t(angleRad)); return getPixelColorXY(x, y); break; } From 4afed48f58569394f1a71a74d2df26a0b8be7acd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 10 May 2024 15:59:11 +0200 Subject: [PATCH 234/694] Use libc trigonometric functions on ESP32 by default - use custom (space saving) functions on ESP8266 --- platformio.ini | 4 ++-- wled00/fcn_declare.h | 2 +- wled00/ntp.cpp | 36 ++++++++++++------------------------ 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/platformio.ini b/platformio.ini index fe8b3a278..504a1f3f7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -339,14 +339,14 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA - ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM + ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA - ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM + ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM [env:esp32dev] board = esp32dev diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1b25c8926..2a9d0bfcd 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -398,7 +398,7 @@ void clearEEPROM(); #endif //wled_math.cpp -#ifndef WLED_USE_REAL_MATH +#if defined(ESP8266) && !defined(WLED_USE_REAL_MATH) template T atan_t(T x); float cos_t(float phi); float sin_t(float x); diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index d473186ed..e2c99045a 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -2,20 +2,8 @@ #include "wled.h" #include "fcn_declare.h" -// on esp8266, building with `-D WLED_USE_UNREAL_MATH` saves around 7Kb flash and 1KB RAM -// warning: causes errors in sunset calculations, see #3400 -#if defined(WLED_USE_UNREAL_MATH) -#define sinf sin_t -#define asinf asin_t -#define cosf cos_t -#define acosf acos_t -#define tanf tan_t -#define atanf atan_t -#define fmodf fmod_t -#define floorf floor_t -#else -#include -#endif +// WARNING: may cause errors in sunset calculations on ESP8266, see #3400 +// building with `-D WLED_USE_REAL_MATH` will prevent those errors at the expense of flash and RAM /* * Acquires time from NTP server @@ -439,7 +427,7 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo //1. first calculate the day of the year float N1 = 275 * month / 9; float N2 = (month + 9) / 12; - float N3 = (1.0f + floorf((year - 4 * floorf(year / 4) + 2.0f) / 3.0f)); + float N3 = (1.0f + floor_t((year - 4 * floor_t(year / 4) + 2.0f) / 3.0f)); float N = N1 - (N2 * N3) + day - 30.0f; //2. convert the longitude to hour value and calculate an approximate time @@ -450,37 +438,37 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo float M = (0.9856f * t) - 3.289f; //4. calculate the Sun's true longitude - float L = fmodf(M + (1.916f * sinf(DEG_TO_RAD*M)) + (0.02f * sinf(2*DEG_TO_RAD*M)) + 282.634f, 360.0f); + float L = fmod_t(M + (1.916f * sin_t(DEG_TO_RAD*M)) + (0.02f * sin_t(2*DEG_TO_RAD*M)) + 282.634f, 360.0f); //5a. calculate the Sun's right ascension - float RA = fmodf(RAD_TO_DEG*atanf(0.91764f * tanf(DEG_TO_RAD*L)), 360.0f); + float RA = fmod_t(RAD_TO_DEG*atan_t(0.91764f * tan_t(DEG_TO_RAD*L)), 360.0f); //5b. right ascension value needs to be in the same quadrant as L - float Lquadrant = floorf( L/90) * 90; - float RAquadrant = floorf(RA/90) * 90; + float Lquadrant = floor_t( L/90) * 90; + float RAquadrant = floor_t(RA/90) * 90; RA = RA + (Lquadrant - RAquadrant); //5c. right ascension value needs to be converted into hours RA /= 15.0f; //6. calculate the Sun's declination - float sinDec = 0.39782f * sinf(DEG_TO_RAD*L); - float cosDec = cosf(asinf(sinDec)); + float sinDec = 0.39782f * sin_t(DEG_TO_RAD*L); + float cosDec = cos_t(asin_t(sinDec)); //7a. calculate the Sun's local hour angle - float cosH = (sinf(DEG_TO_RAD*ZENITH) - (sinDec * sinf(DEG_TO_RAD*lat))) / (cosDec * cosf(DEG_TO_RAD*lat)); + float cosH = (sin_t(DEG_TO_RAD*ZENITH) - (sinDec * sin_t(DEG_TO_RAD*lat))) / (cosDec * cos_t(DEG_TO_RAD*lat)); if ((cosH > 1.0f) && !sunset) return INT16_MAX; // the sun never rises on this location (on the specified date) if ((cosH < -1.0f) && sunset) return INT16_MAX; // the sun never sets on this location (on the specified date) //7b. finish calculating H and convert into hours - float H = sunset ? RAD_TO_DEG*acosf(cosH) : 360 - RAD_TO_DEG*acosf(cosH); + float H = sunset ? RAD_TO_DEG*acos_t(cosH) : 360 - RAD_TO_DEG*acos_t(cosH); H /= 15.0f; //8. calculate local mean time of rising/setting float T = H + RA - (0.06571f * t) - 6.622f; //9. adjust back to UTC - float UT = fmodf(T - lngHour, 24.0f); + float UT = fmod_t(T - lngHour, 24.0f); // return in minutes from midnight return UT*60; From b209b1e481715716825d6ad2b34f5abb924dee05 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 10 May 2024 16:01:47 +0200 Subject: [PATCH 235/694] Peek on/off fix --- wled00/ws.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wled00/ws.cpp b/wled00/ws.cpp index cf09d592e..d0bac144d 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -206,12 +206,9 @@ bool sendLiveLedsWs(uint32_t wsClient) uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - //buffer[pos++] = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map - //buffer[pos++] = scale8(qadd8(w, g), strip.getBrightness()); //G - //buffer[pos++] = scale8(qadd8(w, b), strip.getBrightness()); //B - buffer[pos++] = qadd8(w, r); //R, add white channel to RGB channels as a simple RGBW -> RGB map - buffer[pos++] = qadd8(w, g); //G - buffer[pos++] = qadd8(w, b); //B + buffer[pos++] = bri ? qadd8(w, r) : 0; //R, add white channel to RGB channels as a simple RGBW -> RGB map + buffer[pos++] = bri ? qadd8(w, g) : 0; //G + buffer[pos++] = bri ? qadd8(w, b) : 0; //B } wsc->binary(std::move(wsBuf)); From d3492b5b6c38e6a8cb2136bc8d7654a4594b59df Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 10 May 2024 16:06:37 -0400 Subject: [PATCH 236/694] Pinwheel Expand 1D Bug Fix Removed holes on 31x31 and 32x32 grids. --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 88ec78f3e..17a504ea0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -639,7 +639,7 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const { // Constants for mapping mode "Pinwheel" constexpr int Pinwheel_Steps_Medium = 208; // no holes up to 32x32; 60fps -constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Size_Medium = 30; // larger than this -> use "Big" constexpr int Pinwheel_Steps_Big = 360; // no holes expected up to 58x58; 40fps constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...208 to Radians constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...360 to Radians From b9ca2cfe90f18b0d5ebfaa1361d281fb98d7f0dc Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Fri, 10 May 2024 20:55:10 +0200 Subject: [PATCH 237/694] Fix missing conversions of bme280 values The BME280 usermod uses a multiply-round-divide approach to cap the temperature/humidity/pressure values to some number of decimals. But the divide-part was missing in a few instances. --- usermods/BME280_v2/usermod_bme280.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 38930da5a..ae6eba89d 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -368,9 +368,9 @@ public: JsonArray temperature_json = user.createNestedArray(F("Temperature")); JsonArray pressure_json = user.createNestedArray(F("Pressure")); - temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals))); + temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals)); temperature_json.add(tempScale); - pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals))); + pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals)); pressure_json.add(F("hPa")); } else if (sensorType==1) //BME280 @@ -382,9 +382,9 @@ public: JsonArray dewpoint_json = user.createNestedArray(F("Dew Point")); temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals)); temperature_json.add(tempScale); - humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals))); + humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals)); humidity_json.add(F("%")); - pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals))); + pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals)); pressure_json.add(F("hPa")); heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals)); heatindex_json.add(tempScale); From 43d024fe429c5f519fb50f80fe75c8d184c64aea Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Fri, 10 May 2024 22:43:55 +0200 Subject: [PATCH 238/694] Make BME280 usermod i2c address changeable --- usermods/BME280_v2/README.md | 1 + usermods/BME280_v2/usermod_bme280.h | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/usermods/BME280_v2/README.md b/usermods/BME280_v2/README.md index 0a4afbf1f..51e93336d 100644 --- a/usermods/BME280_v2/README.md +++ b/usermods/BME280_v2/README.md @@ -7,6 +7,7 @@ This Usermod is designed to read a `BME280` or `BMP280` sensor and output the fo - Dew Point (`BME280` only) Configuration is performed via the Usermod menu. There are no parameters to set in code! The following settings can be configured in the Usermod Menu: +- The i2c address in decimal. Set it to either 118 (0x76, the default) or 119 (0x77). **Requires reboot**. - Temperature Decimals (number of decimal places to output) - Humidity Decimals - Pressure Decimals diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index ae6eba89d..0040c5efa 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -28,6 +28,7 @@ private: bool UseCelsius = true; // Use Celsius for Reporting bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information bool enabled = true; + BME280I2C::I2CAddr i2cAddress = BME280I2C::I2CAddr_0x76; // Default i2c address for BME280 // set the default pins based on the architecture, these get overridden by Usermod menu settings #ifdef ESP8266 @@ -48,7 +49,7 @@ private: BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76 }; - BME280I2C bme{settings}; + BME280I2C bme; uint8_t sensorType; @@ -186,6 +187,9 @@ public: { if (i2c_scl<0 || i2c_sda<0) { enabled = false; sensorType = 0; return; } + settings.bme280Addr = i2cAddress; + bme = BME280I2C(settings); + if (!bme.begin()) { sensorType = 0; @@ -399,6 +403,7 @@ public: { JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; + top[F("I2CAddress")] = i2cAddress; top[F("TemperatureDecimals")] = TemperatureDecimals; top[F("HumidityDecimals")] = HumidityDecimals; top[F("PressureDecimals")] = PressureDecimals; @@ -426,6 +431,10 @@ public: configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing + uint8_t tmpI2cAddress; + configComplete &= getJsonValue(top[F("I2CAddress")], tmpI2cAddress, 0x76); + i2cAddress = static_cast(tmpI2cAddress); + configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1); configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0); configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0); From 6b8d8bf735c3c4e3a04a2302c89c1f1895ea7a0b Mon Sep 17 00:00:00 2001 From: Adam Matthews Date: Sat, 11 May 2024 13:34:35 +0100 Subject: [PATCH 239/694] Update Battery usermod documentation Improved wiring, installation and calibration instructions. Example screenshots added. Minor grammar improvements. Heading visual consistency improved. Improved vertical separation between sections (separator lines added). Thankyou! --- .../battery_connection_schematic_esp32.png | Bin 0 -> 131927 bytes .../battery_connection_schematic_esp32_v2.png | Bin 0 -> 65817 bytes .../assets/installation_my_config_h.png | Bin 0 -> 50412 bytes .../installation_platformio_override_ini.png | Bin 0 -> 55011 bytes usermods/Battery/readme.md | 152 ++++++++++++------ 5 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 usermods/Battery/assets/battery_connection_schematic_esp32.png create mode 100644 usermods/Battery/assets/battery_connection_schematic_esp32_v2.png create mode 100644 usermods/Battery/assets/installation_my_config_h.png create mode 100644 usermods/Battery/assets/installation_platformio_override_ini.png diff --git a/usermods/Battery/assets/battery_connection_schematic_esp32.png b/usermods/Battery/assets/battery_connection_schematic_esp32.png new file mode 100644 index 0000000000000000000000000000000000000000..3890f477edf3809c419c837616f95497519af657 GIT binary patch literal 131927 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

lls+tC92^|djZug zu}W)@ief`D@mD7QwFcYTt9lV%;`+<`%!1%~4$2~YL!!{0 zK{Y1hUOio(adfqay7bZL^>tiQynRilj6g=Iu|y&??vwKZgY>-pfGT5!iWjji7G zF$Pj(vY0W}Gc3Tv(Jaz+MuCaiULqUv$w^#<`=$7zelSEfXGY#fL~hMT2dC1wfIg*| z%;TLER2fOi;W#j2e}Om&gGM2Pu8HIV6X;>_Z~VSt9nz@damQBElsKa;{G7su2Y;eR zo~7QaKWBzsE0kKab*MQQW>7Kl{Sa;Tw4evv(9&v|wU>h_2Xn5lFq7(RBpnN&qS{I!Col~Ie8IMaC{;c#0Ci&BeLZooozsg zLht=E5>pTlVY9IgD#z5ZK_K=3y+3ikY@p~x^7AKutTeN?yKMu^6Sww@<>(E&-9jcm z!4_dh=_{xw7WVNW9;=)triBI8w61uByyf5tIxu?^kEX%>;ruCodd}%&sI3tnM&XRo zmMd^tP^Hp;^Z-1TXJN)5wq71kqIc>Sr4ww1$nekoup!C;PNHl00Zf-4gNt7NHIww)${5W+KA>Go`{a zW~>YtA!C3^9=N7e#7IuXvm)5X_r1eEoTLFI67eU&djwodjew^rnAWZeacO|>{pKOBR^s?#zcv>!1Hd6n!ANq zZy}Do;%kb;fAC%j=92NMA0deZe6ki|_PBuOygjPgbkqR_K$>baGFQd6cB%e$Q5vmW z^Qvmwxz`n2ofB$4UgrUlS5j6h3%%I|eCuybChHEvB_Icxm~0`)kbbnI7meRKa$hr8 z0`HTEk8Huy?VDpAtY5cHq-4L6a@X{&FM3)s{GupGf>6PLC&4Kn>M!?MoZe8g>ya&= zuddh6pP2OJUE0;*+Mfp8h#z4gi;QQOIG|n6C#<1<_*jV)J8Jgiq*?%NM8-y_eDJ5o z;Q=Wp;=PzoLo~!}f`MThsG#+fJOR;TQTe1$sNpt%T>P2xoM0gbCQq|+l}l-u%XRpl z4<$SlK2vvG^ZQ)9Z?{6`|=IlYFQkb)~nBsX%PQD^->h zeh_80{nT^wy0&Umep|^}*Mk1QTg_CCO<31ReBB-qkMPh)&pW`=Mr&fS**DXPTMYCT zV)klU06Ce0h*{qKyw_)0cz?1rFW>1y3yE`(yA+^%r?ahhU-+`TUcJ>=st?86UPD*B zkFg-!g;b}?OqJ#Q@Iy6U1O#{(kuew}L8ufcR&_1+KLg-ewYr_wi z!@Zm%NLZXt8@cRY9ps?_?+>wPVElz6UXu}NR6Q8lx>B@gTZVN6lb)y+B4wRqb`J2{ zTiRR_hrpn&b0uL*_0wv~^HcoY5QpDHsOpG}FGQ1MzJ*Hm4bdItQbQ7#5TqyxYA}i! zyxKQ4P7XJa1GJ%eq9y9f6dnu4OcGEOIzG90mFsRwaL@8ua5q$*Cmtjy#$HKucTdvn zMPtdWyWW-!K-W&x`hiEhahT{oc(J+V$9dz18Qa?=CC zzRy#H3dZ5PUWCHX>#JBu))q#bz=^6sP(B;{LK_$+4TS@@m~*=}<{^(9hm5iQ&RmyI z6!b$<5-6(4aY7(B!LoCE4^p@;-%`i-R_J7h6(dXGCjQL>Ou z*Fw)f;rwtVgt?ps1;7Bq=sJsn^^_Vn7;6<<_X{O0%dP5{RHrN(=e&uI{P6X`Lo zoH6Fof2gM~N!zhuq9R#OZ(caXA&Y>FPraK6Q&M~Xd-+c_IvVn{Cinedan-3QQUQig z(!c%dZi7|?>#HNX>Do3e9xz#aBD@cOaCg5?mJnA@bh_FSA`H(YP@1^vd{}_;?Ikz zz>&Ib`9G`eanU8WA`^eRP^`b6UZzkV{JwS*aU`zGY_WHm=fK2RDlX>MM zpsZ|QrPYUl@;&@kn~I_08_rET-62DJ_9SJ~cM#e&e3A zp`+(27SV_YF~)?176%B}<3zl*ggm=vCLVBa*ENYJ@6zayLZlrR0nL)~x)ra94P7xl1&gw?gIXIOTz@zQ1$%5zt0urQnb6eM>^=Vs zKN_32+0`xJwFDuWjTt)Ldoz164d>zP35 zks8H@CjieRZtyZx!Aa=-ncxq`2Wi09;k2!0n7s0`XG4NFoq>&pPQT+@8--k`)p#nE z7GF1(j=W<7?b&VIe*rv*52K`L&{?Y$8hw<)uqDbvF+(zKvXVn?k8L2;_LqX&bX>!| zepN}H{QF%n`L~ML&O*V5*Ljv&eTl_SF}^Ri`C^5a2e^m#L43>SPur(JR-brjRmPx5 zl`*3Ir0|n~qI%(p3VP$wfEBBir61-;3*x*Mv+E_>>5Z98%0I{Iv0|R|snA=_2{y^H z5nG0QLb)D!jM3Vls{CZop57XzzW*ZiFBryBu@QpCCLWbsmZSsA-(AOP3+BAXeIr%wLPT$FLIzP{E0`#ou_6F?e zEY5Z*ta+^sVR~)#{)kF4v(p$+XYfg+uQ<0Pxo)+^6c0cBynkNN*_wJR5LxePg3Xa2 zcTVqfuc6K9il;BXV=*aT8N420tT?+pxx_yV{;@Gq4!5&BQ9dGaZV_s~3sMCf0}?IK z)8-dasO{0`X z=O(;SV`ouVXBL>gY2D-~bITwR#G?!+5Nd}CT53oU&L5#9JHgl(z=|wR^n;mQmrNtQ zCSNOAjs`=5+-(YbH$CUB%;0fWtDa!oFWC2js{=@x>uw{O; zk21tTLqQFOl71pZGhG-GO(S#BHqFe+YvfpRnG*;%9v>e^p-mE0NCE@r9}*Z95ChP` z=|+?QoqkVm&ymg5JMFO-0|X^(bpQie0?2-&eF$a~Ab^ziRD$E4*yV%!Booje>=ktu(+RQd}jjd+!I@!zCUdcJ?|kjV~b7b(0M@>R^Ghh^L2tNzE_%_D~X4 zCyKdMqD&iZ)o>y0g5ag;zrFO5x&Fl)Dl!hU8n=tXj5*T1ZhneMozS{Bsod;rnJ0)E z=ZpddiLN{1|C#bq{jG}Z!ZG`LJ`o4eEr3MpOZw+o5iLGle;tE(;$LErlS7!HCmd|J z)M|o}Z6@^Z4b9H^rF`AHTN$B2);R*Nm$W)Hqh znS(nmN@*a62fo@T)J#8#EcTT;I{qeaw^aP{emZz~qXAjeg%xS}9o`Av@Q<#F$?FIM zLKZvW^L#sUQw%Bi*VECOMt5PvQzcH{5wlQ$XkRX;8O&4V$C z9%Z@5Vda;%H}wY-UAux=xGku8iytg1R54>wY<2VSM7?UYz;4^QuCSK9k0C1?jl9lV z7`_3wV%J%d?&inztrfAZ5_8iRC{ssA_@O(MBC^@qk)%%*o`fs-Pw%iQ>4MCO(BO!Z zBr%_2#!uT2RSF7g9xmP(CJ9oNfuCvT$BVYn0#|r7NgzdJIO%SLLd41|2jg3HVaua# z#2sOgC!>!>tV)d+D*1xiOxs?Lxz$fWeeu;3Ifni8@(j*wfLO8#nTF86j{SXbsc76u zPf}goP47f}C>q7#ziGwfdk&t@l8wUaabYDN^Bj$})@sNRDt;t9BN0T~i!BxFJI43z z?;X6_cl^`0Tr^p9)r?0GYZ_aRc=GtS@mKXyTFmx3{1p%_X3Iod94sFjEKWP0#T!OG z+5Ac|q{WZ;TWcBvpb3~L8CTkJJDVLiCws@L>X@WH|9%loc^ri;T1Z1p zKhP~we);_VW=Rx6^~s6cV>gaAH6}&?ugr7=+h@e&aS^Mip~1TIos<^#s=G76_vNJP zoI49tAQ*$tv=j&591q+Cbck5fyP~zzGTMj&74L}5`~@=c*o1T6cu_M#;JL`8j?ia7 z_uvfayZg4l$lc@JT7Qw~zkjwfb*CmRXTR6`1-!Ft8Bo>lwiCNQ_J=UoCof9)9e#`- zA;cY79th}VORR+v#+rxt6Xr^IA0O_H`($=J!eDXv^X(d51~7cnGAqpbF%0E?bwtR2 zPpA;^tkC+X|2$S67$ii(zo7Zi97PMtA9*9jmfMJ93<9Bm08Z_FwuJ=yRp{yR!PlLE7E zh56rm{!+fKMff{G|6Ae6Cs?#HVF9CxDL7Kv68<5n15t zMOx=j@>P}<14ic{kFUNq=uDQcL^ql%$^IJe?`iB%`Q2ZGM3Dk{6Q=D%pc=?+Tp!0Z^reW2eLffl@PZ_+l}+KcZ!mF5 z7Kj{vIg9s8JCPx-tffy_$%^-tKR;S$pWzGgY4es!!WYF9z!v>?o2^iF&RW|V2KZrt z)808COIWuiHmj7T5$QbTQl|4uC@MJ4E+8XZEac{4Yj|VaT99HJ4h-=HB58RLH@=WU zWiY)Z&N&Sc1u{6SV`2Rv2I1XQYcIp4%_3r(wt0!AgeoF1wBirc^W@bJfK~^vDAM5V zlOh>@9#xue@-+bY*gxoYdj2=&96kZJGBzVwpOiZab^=)(vbwM^zl8B(WAJ znWi)(d*u8JDut3TRd^=>oQMf4BU>063`S;8Db$=tBu{5cI9`D7CQEvLzF&!^`L6peLqzP~gesFw3PwVMwlk5~6LI(z)j}kp~r9May+j21kS|p1g-<~4?nu$wXLJiE@T4-^DK*%E++ni?Ck_)9^ z&M@NMhm0Aq=*{Cyz1bAMJ=S)w>V+pR>1CpI*J%Ihohh&&#r4{2sZyS?;*o~i#XG!< zJ}|9qo&BG>0*;Al_@w$Lrk$swT{v`Oe97||k^}{F5#RfQ>rZPhjOt3}WGA74A(YcZ z#}-zsoq7&d;sh=*PA)NmW&WI48*}bG+smz{M0s0&2gn=QW1O%1_TNsion3yX!=6)o zp$;9ewe$DbXG*UVz)i32-ttrBc=m=W@W2OdhAuMXI)v{LePK3tTW=~hUF?(R3y`Ks zgeJ$ta}Y8uA8oWN9+lD0Xg=>iCuXjg)SpgIH&~u7(A=>w-{pWP$_BYIH6{bQO_t7; ztwQHsOE0JU8pfb1DTQs$;yN=e+B(&sO}b!&jlG!SdPWXC?YR^rBsX~WFt@Zc3|gqs zXt6VBBCJnEmi&kJ$ne)b@A*%h?~`K86+WpRgXt*9<+H6OYHCvm=jpt}t|cc%Jb_P> z>1+#I3`oxV3MT%Bc-=HP?pB<4mN}p`XPtOR^{_Uas@CGi=Bg_reK)hXDzw%sG5`g2 zToN?o#SqC}sHZ$SPqyrT+6}!FF zF*AUl@#ViEu?hpphc-^oM0lF9>Ic$&q$UiG4_~p`>pwz@%SU0@NXSM4|9-4QZ37uE zS6`|52ZI0UY?@?W`5#uXi)tT5bi48VevDEMtlXI)suPdsL)u+=rW#s?0%9UEegT*G z89m39PILAggfsjAkg8Zr%?M7y^`e+rP57Zq;am8jZQgz-T=(LTiXmyNRVB5#3%arUe?7+yMes|Wd_Ld(o|H)- zJaf;tf?n1+JX~TfG)ZJli=rrIn^y9KlhWqqlpj!t|HaytGvYhsC-C98ov^6cAhFdQ zms-mmUFCNkF2+LA?}?3?lXdVaEE44-JbElIJH)PY{-_+ppfgS(e5=1{MJ{Y>UufQv-z;=hgBrSPuImVHOgXCQNdKF$`d9oQo26X& zkCQQL{bU}!WMu^-UFmnHdSdWgA@!i;Ltev>1^4N$PV*K79A>~;v_L$?@smP_74oJ; z=y93AKqejn<$j+mW%xC{>cse7>xY@VVs^MFu}S3>Q^ImM)}=XJbEh}UL{2QZARFB~ z0cJhAf76vj1Dj|WLhB4Bxcnd36|rY#;YSB83`~o5qoOd7(06epB+tL zGL0d2#WX8!DuQH)RK9#kn?751o%Bc+UV_>ztIcyyI}8oZt2vspx^FbEtS{hwgfPZ& zP45fS)$0}NeLI}=+r30*)Bw8EFpELBCyPGdWLH2}TiAEvsW{69Mufb?j-Qg`?gZxn zl>h6Y_md4)5@Ns3)`r}UTOkQHS0zdKIxYPUMp`}&s;PfN2gBY#$V@NyIXZlA=?LbW z+sCR+UkjW*^4Zu@RVGs?VZ@5G=c1yg5=sBO$ZeD{ zm0G}|qJwy{?Ao{xuh99G=eN_#>OkgrHdR59rmW2U02DrS$Osa|UpzUTENJo~P*Ll* zWbT>NYznY(=C3OC=T8-9&a~g8t*b&zL7tD7{Qj@btkOR*J577^eqv)inqrRZA7py@ zggWkyq@g%eUxSrWo4b3=;4xOV@2i7pA0B7Wr++NbnDp$|jib6cJgs8iAGARg=xFKE zqma#-U{E$$uT(fgyvdtaQJGE{ufI0{J(c!+PENYIF&oC(0+<@90s^He& z$LY8d(?NX9@Q#^w#_VOg%fW4Zr11oT5Wg3tpwlaOAjI zrpg-pt$C;-;QjFeS|Pyib@vlp#PO>m{SXMrTsJXCqaSxSno|;O>|caQ@LOQyZ@zEp zrS5vlJ9m_E%zk+p^W?m2mtbLG?}ZdG%Xih}EN&j3RZZ8Ku*!d0h7}7lriSKw)KbRF zV5b}k*&q0wAKdGW0fVORUYAlLgazmax!MO^wR`KVaAg%sSR<(Fav7ImdBt!{k5I6L zP3SP`6hhY@zFV|IP9BhynTOqvvMr8`;43iMGEKgD6{2FiSm5IR1d7`*vO0gvo!naQ z-7OJY(xcSD?m1YemULA+ZE_pF;Yt<)9Ol^j$YUie)7Im|6XnRVxvLxL+V!+oKZNO^ zUtl0QSRP46Z$@32zPr(zmj_JmrO$%>pqfCKIQAp|H*~;-(F*2v4VCuf^%Z&@^(A2D zWsgfF$uAUB1=!J7X5g#$2Hd()#fc;;`|-u))_(qU?LNx_8KC_S12*s@XK;rsHN1j}&FDUdh>9hYu`_aa=XGEK3g9YW>v z69{2j&E2=%Px?pp&wv$lArDBSDACEt@@eE;bDQ&jb#fzhAu@MF6=TM`kpabJlE476 zH%eqZ1Fijo%$ziP8%1DJk|Ou;cf5-S)PLj=BJ2>K78E=vqXSqkUGLOgBEm`duz`p| z)qJ23MdlU8D%_x$1F5P?2eIY$fPVHX|JY1~o8p-*{UsVHu>Mj9GfwyNkxPlA=#Mjh zDqwRW+K54l^(JRYok_SG5VR}7ij~8Ujm}S@Oj*)bwdt(7S5N=P6T8IJAlw4bPpB#@ zz2mG!i(;DuK@+z-EUHjSP*cEvFrntar zkrIM}{e#*Oube8)x16MZgwe$#PCqFCb)F*r8!>(6v+90vJPp4P_pH49KV|q>-A}Xf zlSjgTi_Hk~JDOGMsrr^i=dyfHyVQ~Wlzl0M^lR~PChjOVNyg|x6fWl{4Vsd${|&`@ z;wkjFo2;^4$hl2~B0p2PP399G>Lp(0i;bX}F&7<_u|_=E{WlUpw#SKisaxO7f`m)>XKSfu&j;W+Ya;L~eF0IGXEt8ow#N}P`S@;! z!e_)k*OX%YaWzDs79D&74zBh3M}s~XhIGRMYDAj_ z16wm>iPt|2ZQbw^B)r^yBU-VSeA2wW#Qk)1Xn$}LW8b84A@3jOI$Q62%3QGDT{%jJB`^3FlcsnK13yP1V-(cH4$^cb25{A8;LR zda+38y7`~x5v#Bny+~Ur6t>5VVi&R$|E3Avz7rZWA&&0t7d$UX0{ucNRGjK4@owGXkslFV~SQN)^4xTo=(} z)L3lKL0aN}AT0avTxf{NnISI0M_5Y?TL4+1MgKfBam$t2^c}}ld#H}QPlDLl67#Zv zaD(kPVm!I~#(K9CqEB+43457yeL!e=(bH{DL3JGOa4?fUI1nCXpUx`(#ymjGvMR5+ zBI9_55t#3gUy>co!JT?1lCX|9_wNFd; zP33S58tt`!e{7GzAbO$p|g>jHpQVoVAO^e_ware^XBfkcjf$SV$|3d zcNn2UI*td*9Cj;;99l!$isdU&mh42(MH0Yc11?*NVCI$f_L&k?M`cJ%v$vG>Wnn&7t>E%(qZo? z_X_Rfg)SD7MiYdt|K)MrgzO=CwvXIg>{p!$Oy%(jqVJ!kDrGiXWHy? z@(U&}7;ug@!!2K4RbjN>DPtEHEa4z5VP@$4tRnI0aAf~6JHEtLK)Gu&pgRDyWZJh8 zzaCAx8BM-lKUWO`0x8l}TZ?VN0tD0DRtH(4542g#{9G*|5omB{>!pl^3?5*1oY03z33Knp&lcWY`^hM90b!quz@XtrROePZ^s~hMqO{+IDASV%`mX#? zP|4TvNF9;YU&mY?p(U}bcFaLLCrnLnV{|pX@(ns9mx|YK$^b2Mp;{ZC`@BNt)ivWAq&5?!9ni9)WP9NV@QBid=9ev#GYoQU( z+-i@o`>oHN4PS;jcYlWPK~C4;$oP{dfCF{GBf?u%lAcBv>SppGT{6ixn#;w{NWyo@ zyy3ErfP@%kKihV8?M)eP$*O$(c4vfqb;h_iYaCKtjYss%E^doMWd>UnG`)ZDr{zi2 z0vh|C>&*XlZ{hfAz8B@iJH06w31HZC31X@U+Pl@OGqgZ`;tYMcl&P0WKdRQ3^pik^ z2D7jmf!45e^L9Stv+9uC6ygeLauBHvYe4uys@%rH|3J))a`ozpu@Ak<}&L9 z8F0Xbhg}Xwd9oC^0|zkzw4tTe2ZDl(0{BRFI8kCo-s^)!b2)={jOB&?Z#1lA^NeYi z>Q>3p7ut2tN!VeHeOA>HfuLqfk}V1He69h6Zb)c8cO!iQ9wOjwD}K z6fs=#w{{lqIdOvG!ay=(F>y#T4e?Lk^DI|4yoJ^;0gx5KpM?w)jbzViea2d9NW&f} zKLu0=sdi#al<#C5t^V&+9C_=4uxuP36oTdYN+xBK-CllM&1{>Sv#@g7nzj?n~_f}>8r6!dJ z{q#9~x({~x)!p&j<@sbrhU;|pOYT$W)l&+&nzqE81jEylLlqV}k&P9?*uLaQo&j3d zAUJ#HZ*xpZ+SZF-+EaRL9dT=jf0Y`hBP zYgCYf4ZfrE@Mn^d-T+;D3}$1J7^yf{Zs8x={xdwh6Ija*A^ECXR)0N|%*-X_$M%cl zG>o~Xx)I!B`d!<;Ur;|Jk>+7k$%cU6dp^%70zQ`IB@f=SJpbHem1!V()BmTH5Q0G) zU}J`#d)gjkTUalwPqgjjfX*(zgj@3P4_jK=#4ZgXCF(h=yh$EUO=UR+FNzploXoUp zD62UsPv);>64M+U8#o(gZl>x@pgs`3HsOd3whXWvo^ZXG_0(9jgF~TlNG8{pb#+dpk%8319yc~5e7hknM{+ZHD8s>l zP}5=4=*=veX&Y+ z!;fq|f_DMcfRr~o8_SZ;E(1OjYJB5cYl-|^2o6F|q|L{fU_wkF`%&23jnss>Qj7oa z1}zj6NC1y7q?^wXl^Q#y{%_fdi0&ax`C5FI!(A@(zE0Q~1djj;ydSK8`LC&VW^byZ z*KyrcSRS(IDhVe}-Lu-BZd&4PUZ8TZTj+JSCGGn7Poqdb@RaM z*4_R)*SbW%v#l=qs3m3IPs%%oKhdqUS^&IK#+ow;qPr6MEt~Lp1x2IvHPRd1+f*!x zhJS8=Qm)+AF28d?X0icK6$R)2Dvrwp3oUumr*T`J!n3cJ4S<9 zKczslp}@1Q$7-bXmBMY=->?>SW{qz&ndvea7x5>{Q`uMcUNJ?Uuq1}u=T`XD@%v+` zzn~aw>yOV<$L@FRy&uNbyGgw2^K>)SCC+CjR`|MwWWO8hbHDlOgNFZ+20N!mudX8R zzu;mavSVqVF5hZ@u5p;b%Bk)i1hQWbZ;`_BTj<;>g1RU?nmF@m^0QDGyB4R1!}K4<2?E#p`6Gcsuj-d)Q>UYIR$)VNF$79!$HWI)b)I{^b%K zYi3n3jx5wzrgI^Cri$0)<%P`g_CVbDHpK@XEDJYUOZ|+}K`Q1?&zM%$w0jvP3{$Ki zUs5V=mJ;^Pc+%5OV;Kx3o52Q0=Ay4(D_gMaVYAY|xFi;{=0E66Je)DxDS%uRZ$E$b z3ljFTwK{D**+|Cm+&KKDOKNld97b7@bEDA)BNOEWERcYm)kG8~U4JfaM1UgtIai6G zq^Xy=(5L#Ufux7tI1`*?&=+-DXi}m-Aw$cp9I;j*ys{vR#e>}*nR*6?3VMa6>exaD zoN0uFir0&XUycu7LYWPI6Ar%72*vQn2noph)r%M#UT4V=v~&V;ZTTl9D3}_ayMM}l zS5QP{Uw%z+##-pwuJ!yqVk^I z8X%obl1)0db489DieLA*|h*SxODs?QCE=T=iuRfl1hTxzHmLA)MFlsfn zPj~QFC}eQkhMxjL)Zy@K<(Q~b?fmdG2e07!y~nuAYYDHy?7FwzfY;LE3)OY87GHNT zs$q z9gFpO3;v(EC!FC@-irp_;$}3Tk-OFVPWW2DbpPLRN z#%VAkaod=;ysF6>Q&3wxGzTd@rmfLfto|`$DXvQkI37+jQDb_4tT-*@>~?bwVIfUe zFxr@1^r`dnDpvi~nSh*nXu&~T2A=N#;>ENCz6N<|Dv8ImJpgfA)VzscGpkOxu!%b-uWmuQ5jA?1H4 z-{G7?{d4t_9MRoVezApuqNI8Q5!9C_2D>JT$WvrM%m%o#KX+Ev`ESa5@~78fv4sM` zb|g(!c~qo4^UaznNPirsX)%v;qcu?Z|Fa>V5WxkENGM#aLm+Js>G(v#+V*#1hx2=;$9Ok6CgNyKY#(@J(SmwaA;|o z+Ru1+xo!N3_I5kw=C&hLOcgb0WS zjl>P_o6<#T@Ie z51~$r%8xha^8GvaDKMU^MCy1&9GV8YESf`t&E2ns8F)e(PDuGQwzKKUR)K!Oj{(i&vcnP@51i+!ooJ8F`^FYNALiKhj5-voVX9Y z-@k-nB=fevJ5H)RFD`$D*}V3uPPLR4Bh_4RR7>>~^!*9yI$szLLJ6mJ+(@NMVxG72 zjAlNOD;pyk&;o`JnsqbOyV1~sI-VP!X|aHNn_|Duy3+DA&*_YGLd9g2L{d=M?$=-* z=z{`JLO`?Q1D?Q1!cwmGB2W}7{NBdA@5Ri)or9>^TylQVPwll?zrG=Td#-ErVCw3G zmglJNaH*ivNN7hG-iO9X%A;`Y;C;sl)KLdJL4JaZgbb)d)4cQocUOGu!4^+9BZUP1 zvK~ZQ+a|`f>ofMuAL(Ez#uAE$cH+v0Q8Vj$OSmg$50-ox*zlXCc&~*-RXUcxy-P7| z^FHC|j6*MIjiuy_rO5Vk#9TphpU0JY;x5ytNM%CGn1sKSnJ1efr%(q^Amd0feMI9m z#)Z+gXmy|pipXsf{EloGv|W)Tm+6#lwwp;RMWMjliVC&%6q?yX zD5+s&RpwK%WLA<+phl4v(VW;v6&9n(QL%8y%cd1o{QV=LbdI7n6_+amlp9uJ$D1(p z%khsT=zP~eQ>CqZel3=Th0=K$$Rjk-jn0x1*$G{|x{i%j96DQ~a{7L}=>grA_Xc|mA~h{zrak0LPSoE#uP%#i331aL~9BAJEmsCZdDjwoGjVc z6f?gI9@A{;6xju1hFcO%#XuuwabuIuGY8&q!74807+ z#DVCpXSwzRn++M{#yy!Od!Zb0wZjx{zHmmD{`z#;IhZte{rW&Af16u~S@+8dpTv=P`IQG5EzRoTZeOC*!<8%52^6yEq>b4t z#GKoPc29{)BudE?!yWkw5jNk+`13B6p+J2quVID)F^Ae$O8I1T!t4b=HCQoh()7GL zSq`V#%Xa(ch2^9h>4bu<(f^$VF5^cxg>-)Z!4dI2Ytnx%D-sIb2J1ihRUm|H?;Jvq zpFdu_AOelE^-F9D#B#SIktw1IZ>~Tr@rf}hY;4@`q}K;W*G;7-K|Urx`XVM+YRP^lkGEcW`D;0h{AGJ6n~EHZO_-EBNyyX^B&>M(6LA zxxO^qpCYHagybnI)-npFOJ%9VG1xhH+r717tABZNp(xeS&Vf$fZ6Zp|a8=4!N_^k~ z>IhtyqyIJ^ARm@}xU1dE=+^Z)e*}20asbYZV?=y9D zyV<~_A4DWo#VC>>(^Nx+8FAvav#RV5i^X$p&!(!Bd3Z*Im&+2E@m=}yGWEtbM^gJd$$Vtt4i}2kb z7HT2NN^hONye{~g9!D^$;JFP!XHpiC%50@IKpBw6{#$2o8H9Z?>CB{D-+W4wn-;bj z;-??KtEr1B-ZRyRNRs{{Rlx=Z^E-awT8>q4QEn)d+w*6l7^ww7n+Xm* zwOp2T)no+PUG&+@I`?T)CTKDpmQ!hLysb!l-X}5Mj+E)k;j8vXx&PtovfhMCIUyi`P27YlbyL`QklL*;q`u_b-mZ+SvM?*$pllGLigJ!{e)g?;%VWxH z&JEghAs6wU;(g<59`wp!0Qy=OV$l>4zgT2kF}CapJ6*e*p2WHz4x(>3>!g-vh{JSA z=E3|iLOneG6ibNxi|4*!e-K#w@dNYbkI{dO#J9nst`L=%rt{Mo0ZcgXTgygIIdFt4 z!hBsJhgZei*`AqC{%=i9?$>{#2(vS%Ed|&Su~~om5x};KN`t&dD4%aF4;woQVn+<5 z4QKKU)<)7&r;fTumKp+ldB4dMbWLXS1bw}1%5a?9?X~@J9|a0=0zU7brkWlAZ6|%2 zZRh9KlQ|r5XIBBKV&nTAJzg2M3<8P8iTlt`b$pKq#E?SF&D6LJlj<|+5SIHsMDhFzWCamLv^*C~fnU z0AmD!TCQdQaiqJ9A=Pwlb8vxpGA57Nzh0*r(0< z2M1yy;Un1DlrTzWj%kTRdJ(C`*A$iyBkL#H<{SE;% z<55%$qhupT!^SPj8Y&T$HaJ*0dOh*)d{|ostkKUB?$8p&Il1(!XbBhcp&JcjvYwih ziB{;M%L~O?y^VO}_@QV{>XSqoS?U-RR1XN#R%IBM5-7|H6_u8n6UD-14&Vvok@I>< z;4?z~_L-6bJ6THTJN?>IAZoydhm5RcBU1uSl?9r7!H}muksK`x76g_z1)`2xFf0Ht z^jiNDjRcyJCExV1)}-nw^Ezg>f6`_3dRxmpz>co^ti4BWTu(8Ag+H|@46SK(I1^!I zH-eMz6MV2#b(S{TZFvrJ?tW=k z6C}nfcahuZUTag6Y#_GOro1w$)S9Xxgj%i;ELe?3 zLAf@ooN$_^kzF~q07;E@);(w4p#vdrIjQ*((dc9#Ez`9<+KJmmYyZ`9>Cu0)ZR2qb zga(IdI?RGt|KQ;@T0$wA5-}pdAKd~E z+8Z-qJg=HK1qy-+w+F?TS+*F6X~RAa3!(Mg z24qqC2if2O+K0D4qE^~Jy4h~TjWWOQCTJv>LrG(wqGgn->4X1# zOS8J*4mBnQZsyeSv44ok>S;mm{kmg6Tjd1p_D+Seg0ar~Ck0igqb~a60LmWQ9VI*!aFtu-?LUB>!!-7!de2 zC4X*fSHyWqj2-7Z9cg-ejbX+>fnI|_?( z!XzYGl-^`6Gr6mtqb2>E5Z-q8uAIa3%@r(Uq1$8F&5LV&)i2H#w(H?TL8Noq(xA#w zKA#ct7{ZGo3s0n#GS&nDCZ3P@?4c8C>;t^WY4~>Hwmplsn+&(>VTz@NM=7$HDeULF z44DWUlk>S14Gkp{7e}I8ykdtB5L6Kgt{Un@@dykDGj)>PRlyJgF<^&`cg@40G5_taBvK{aip+_*; zO{tU`AB)y)j1BS;*?I7~dFdRfu5}v_QvkK$a%#)|pL1lw#g(t=L-%%Z zbFYUFyA>Z-SBS6QR+O%m`{2Kx%Rv$A@xb3fboCFXZX#$i*CBYz$ZPb&Wy1sRoe=cn zW6Chu%*pk2K5@;B!D9vs8+rIT#dQJax%FHF4<E*f4W-0q>E9Y^=U|QeB^1(w$6O1 zT07&)@NI4RdGjh@ltftN| z{oH=gyy+0+$zZca?GvKZzlg;Si(@SN8P;yk#(7cbat`luCj$zp*gn~2q?tGn{0>+( zT@?bW4d=`T%c|*NK~uTIp0hm)}&GbKHmIN9hqBw8czE#) zk1D;Zl!4--&2V!1;-he3ZuxaMFvi9H&9Pi6O)2bIxQ*j_di5BVlhTD%*!r$yvqBn) z01QB=)o=qNNrKezNi!8akz&w{f~(>@F34oH{;5z5KuvqW5( zzu7`0Np1H#yh3(hzb~Pxx^`c@V>j$iqpJY8Xj*H>8Zzu-xuoS}5no$^iyHSqQ)6Hx zOubeF{|y&IOPW(TUe=T}DWR}ByHCgZe24}R2D(GKWVe++AT!Iue0p_rPK*vy1c-rq z)@I`Ou|sN*>|2QFrk?kz z_x|Cb#q{09SF6wH+a+vR+u5gJk+lbKZ186Y&PV4m2h|QjD47`P99S_HHXGNK)j&mt zp0fzq!i0~Pt0D7OIej5{zETR&UUr4O7%B~|t*q6tvZUBgc5xv?ro!btiA1Q{tDk{RBUkpFhOF^$qoD?DNUYUTrOQdQWf<6@B{j5MuXT_jwjF2 z8s$YNGD24KJy9Oop3 z{8l^s%^dj9Id1M|y4NaQIUi!WQeypG=jGbbbhPN`@xwd!vx{3&EGVKt44-zL z;}e@P8qime;KI%}1ac0Qs1Ftj^YA_;?J@R)3M<7@b$k20y6N|oNy`LJAPxMULVUL> zPwIe`D15qh_AtA-8C+P7Jq*o=Hb&_ENUz|pI~FW*vyobepbt&G*9 zin83@Rm;4C)2g~@tzW#nyf1U{0fRLyTjQ~uTj|;cPhNa_?u+Uh0dE&LzzxvpW1Zm} z00oEyKx8Z)r^~kg(@k2>dnlemE7(ua1E00FrgPc4hgh|&4LS^7m~>gOO2t{(4XhcQ zuAG4%%UMM0zA1=QMleC8Hg>DmGzGn*)#hFNHLd(@UjE){YVxAWY$kKx_&4eAJ^om< zkqj30Rh!B$Q}PxjXgt5G4!`gIT-qSmWgxo0%wStnwwk`S?cH zjnRhU@#jcMKfxhbyNHMSL0p+UY1u>Lwk^ZAVibYZ(hbRdQ+&XqdlsG5^k(r@W&EVI z-h9ju`=o_W=( zGDfU6CEXMDGgfnj1MaylOq^lV?ux!m}N#FP)!GgY&UlfBx%2i zTf65jL^bt^(N2K*C8l(xG{y3}0k@h&RJ^Bmv zC80b7%cXaMvCiNLdy~USxE8(O&n`NMVGG|FarHLvuxyxzvGEAHFD^^kmd;O#SG#@C zL}m-6^4Wo(FMpSpRrU1b+}+uOg#JZLtLy6i=mlLb{^Kj?Cgku2Jw5M(lnti=guj3P zGCA*x5&E7pcRvp?5;hhiV$x|nV*`Z&AT>>b6KM7h4*cDocU_qEqxW;Ew5;WaZ{giacpFDu#}hDk>AR>6C0C(TVX!D?N%?H9YXbr!qb}dh!c`r}qu7d0|ci zWaSLmzLwP>Rat1Wj}#1fBuJ_Qrwx-AV zimUehS)r@^I(80b zXqeT3-=FopLXevlp^3DryfQX7SA*eWzJ=R;j;~a%wzSbzFw8igYK^4R+v(Nqx9A2&y-za5{3!30W! z%6}ObGpV8=^Nf$I{4~S1ph)SRTavuAbIfei;PS~_nrK>RaO^v=sx}(tG zBLEz2nGSv_U<_SZ3vHByfiYAj$Gyx}P|3YfK3d0s|GgI@szlz)Hh#!+r3d9pHxt2a zMep^QYyUF`M-ybcx9`=-_3yHQfwE!G*}r?_)>1L_FojaT2<*F2lC}gY$AqQPP(=C@ zS#iIogbDzn^9l>`_Z6Cq_d3!=Or9=z__p+t!Cs$kebEfZ8lFMkh*X7bZ5g1%C{B%L zsA0x0_5J-GIC{DHaf0x4xj8YOLfaTeOHFM&lgpo8nvroqaJ^nrQ**GNr1L&+G8XEKa1wow3R4G)BYe$qd$tN*+JL8``=~;0!1;=lgu##?g1xuK0hp7Ab@J`iEVZDP|k<3m*nPDcAHma^^ycClSD$CF#^ll z%*XQ|Z^8}%I8rPk*j(9HHDCtJ7B*|hq>!X{mXEBJF>_)P>EVi1mY!bNs{E{*Oa6do zDrUpUm?6)9Gf}(wvsVBr35~RNZaXoB!?_b(D&OsY()9GA7W)^Q9=F z=w|obPL0Gd)eb`E)W}?sqUb;hkd+>O{_uF+c0uQ)uuPW{+jy~vPkRqm%>!uny#Rusejkz zVD;`+37gDi;M^HZq+d*ZM%}o{h%JT>O(toy!{z&J0f&bh*6fv8dVD8{A({1L;@L_~ zT}AkruBPRD|6Bo3=CRpTzT!mv_$&@aGvt-7ftN@ZBq>31?CkMUK%-Q9bUOQ?3QKO* z?K!Man(`2H&L1qNM?*>Btt^fmi(CnluqOph))I#&;c)^JRLyhpL`anR3}Oow!CssoWN?iUZ=XgPBT!t+ypPd;5Sg#4N0vZ+ey>f*%sqRQw7J}2b= zVMh+T6(LYSt?Rsn%gM>m96KQ9tLsQrY~zVy$ZloHuAm(Q0YR?BJp5M({yi$8RFtxnhD>gu2g z7NBr5boCTi%rm^zKsBuIT3U-y`gm3^r|`UO2Uo1HlyQ@J&(+d3 zR1N<^?J@o~?O#8H@`8S;FZILWo&>&l$Eq_v7Y0ym^=!;^8lw};oo=q2D!k+r@d3?Y zR9Lt-ko>C8YK9^Sgak54mAZLc#lxT7+diQ)Iew~`_#hB_bs6I}&UE6}adPIMYRQzv zr_QTj3lWi^Lvt06!hy5NL-41NmW*V~y(wxeUgA2Z93K`8>WBc<>|e;e$-mYGl^2ji{g4t&qn|*bORC=M z#srhm;63*Z10CJz>*EP&B>=R$z#W1N*xPiw@XzfqWH^|FT8v>J60hrylrXMccE)vVi`gx_$FCwUv2&HeYzEuV!p2V53~}gb3D6JbUy+jvI91*(5Vye+7_k zg66M~mShFLJM6M}{9|kUled1*7?Gq<4KP-55Xh(xsCA?%tZ&Q|{QwPJqQH<)#z0d? zDF+fWjtos||7bG`K&Z*#pTri#NaKZ8Z+x1Vco^%J#@XfV^dQjGp8hr7qGTq_Q#aO| zGN?~Lm*_!xmwtILBw^i!|BpXYK@*O`YOkOaG?`}Eu0c8J6kA%j&i(c3_^-Pm0%`Z=KC zzg@N4`!dH0$kGw0*T7|4jVE8Kwxjnwp*pWuz%IUn9cTDj3`r zRxS!ObHkT6;^xziWD~dY9CwS(Bw5P}!%Yl%jKv%$ryaMkgB=j%{hH4&iyf{gZe0{b z7X>!{PL^83r$^vBCd^xdcycjdFpH@+e9S2ek_y@H$mweGz$_ah#CZvrb=uWUs0Z=O&jLXdi1?m^kAZNo-6PHl#y-*T{QK(*-fnsgG z+hpC|`*?j-1Fbl11+4yeDRD!EF^me!zVo|Xm^U8) zV-V7*)~f#ns;1d1ZFj}Pz2xw?pGL&NhM2bEbFeMyR5o4T{It#-+zsZHi7GR09};Dh+YEnL^bG2oCX#e(*;ZM(kvPZ<`hA*%W)6Ge!6!+ ziI=?L=&BOz!S|8w(BG{+EEpI82BSS!0GE`dM=&4z;JEeHAifjTdNb9RB!=Exn%5>J{XzyN~q=Lb0C=x&m!x~vo z_C@oti5;jbUR?t=mzGR+u{2xf=+`?1MuH5ggU6GoFk{lcqA#Xk^ga%3`LKMkU`{4J zOWOM{J?7YNOYz(#jLKd)u6GyeC1!?@rIH#J3I;}_!Qgj7sgPj_$QEm90{U^km==~%B1@{l{(@4`Bl3G?Vqg-5JZ@si zUr^FLLTT`|OwoR-X^c$`&oAz%c738KU*@*4I+?|v)|~ok%DMzA9LE@+D~1z`658X4 zu-7mF2&?y_p``&QCs?jsVFD?YtJ4-~bBn5~!ufq|thQQmqySQh+rnJ{o^!&bO24CSta)JZ>kpmR#FNQdlS8R9<7ny)oJT#3s^Ci(Q@X9| z++wHTS-3ew5al!`=GeFK8LA4)M?(xGGr{8^w+;y$Z-vioO4iQJf5^=zhQOx(y_y+; z#~akKmGDzcRQid3^>?yZ1^E4Y37C{b!tQRQ&|~8=rb~hxPHWJXlFy$4Ro*mJ#q8*b z6ES6g*-P)B_%IuSN_(VnvD68VcY7b|7h$tNLv0TsTC}#;u81EYV-#uGCSV@|=&^c< zH{X|px?tp~8qfX{^jwVo4*2i^u|0&JAf9-BWhG+!>S&U6X1})M(quB7S(MNd6#H-A z?`Du*-ar)o00QSm?~%Y0clX=o6{1W-8oloD^UYq{Nh*KxMQ+MqHaA&Ho0q`J$R~CTZX~ z@;|OomDe2S5pY@ERO$`Wp%&H>`79^44B7J zJ(|iInJ+05wc`b%?B?;BX>>K<4q(4$k|n}W;VM@io71!dp+~92s5o=a>lVZ)8(6$& zSw4@R7_Uoa81|bD>%RR}1faQ8=AhJDl-%U17$|$LH=n=+)%2_qBIYyxR|{Zk zyVw18xfh1#fXzhJ{qeZo3E~vP5sdzWG9yHS&o^D#+kyA7Nl1ldBQeYct7>xONX3uF zM9uZg@!C{gQ6BV-t0K?5-A!E7jXavWjg@siPz)gxduYPunkSIlDBtr1Hns0wZ zp;hqmf*=P6Xv9RkWp{WH>}P$!WyCsTMOLT&f*Hau(2Hf{JVu`8v~J(B#RVX} zdzbgsxv2UdPVYmdfCPc_<=VIiLq}Vt72TFB*BOCu9C>-W?e)o84!oi}MU!(_j;)Fd zi94%vILgFbYugk@!zh9`dq~o@aC%)=bY!~#97h|=Og~)rkf%?~w)Y9igIo-_t0U^iUIukZ~7`3VDpfOr7{4o)|fSag<`s>+q%_}Jd z#*<107RYdgJ~T~k4Dbfuc(X4Aq(&rbwtGDBa`*IP?G_nkKEZ1BD_IG)UmS0;8dsXCV#dz9K@jU8>#z;lu;bF&%Jd zas6*W-!57j{a$XX?_fy0PAkf|4w6-=7#YhNIqe=W|5Kh?ozx2kOZWfYVeTj6S#Q74 zuYLD8A~`gwjC2wT1Wu(g*Z)X%0OW#K`bLRtvH77-6Az&`DG)fP9u zE247K8xj9>h_0lf5o?U=*33yf0r$DFTWzN|@*^s0WPW`?#B`J6+yhIY1*wiPaPVL< zhdr8A$40BACiHUUnr=wGOMx(;12}QCh!(THvXwdExP>iht&n`CP(?n2CdmmP)E3PB z2x4R2-aI*o?Gi6GY&o&BT=Bo)Kmx>PY_x4KBnMTr|9~20 zjvH#D#S|&JJp9{AmScAY2!nS&{;30F@_%)sP{;ji#1 zXfW1;B7u_KbvmX43{=c>gm*Iyl8ZCx#gMO6Erd9DanK_x2_Po;F}4gHSa0CA10caL zKlTRlonN_#aEfPypXfcIeA=f1$N zU!G5Y8U?vJHDB-8%|Y*M>Pu|}i!Tqq-rfGW@RQ?@wN(^>&@t1A>$eRrJq|W?iBvhZ zF)?Mi-5aVoDQwmBbkd9HZpUo_hxePh=RG0?31xPkwXAYmE1cW*{vD>Cmxa5@*RhoH z$}o$k#63omI;|qJbCF_!Hk5GvqV5u3Z1=xk$HkjG#ofs({whv|+l~58UMZ;%=F; zgQ}|2C&?K(mW+%^;7Nk?*33vSbVkNyW$mk)d(YYW_G`e=x3>;%J(n?Z9vQLXxA1=` zGK&sN7wLD#|7@$HN?uwTIZ&8-nf&GaSaPL@>!mE|fpJrpOW@6aF>VXAePLD|M*a>{ z#Mvge%V(k*=lV28tu=qtL};RRCMH?2E1Wh@j}NqhN32p`eyv_?Ue+I<9a5f5 zpQRT)@fv=CFK_%#Zk9`3`uk6J+c)dmo)F5q=~?rI-DY#wh_{o|fvR?v5tG!>6F6w$ zc4WVf?);8}EpHn0w$~SNH;y>G2{}J^d$Xvv3GW}(hUWd=V=j_0_QQx-uX!nIt~)G< z9{tHUYdHUTCpgn@ttd|uEomkmV_!|N_@S3I-nFn-FL35XkezfJ1_7#I976Gg(W&`Nkmlau1j5=QH%%&~F-NS;CaG{}Quf{l0fW z@{jXcu}6ahO1ikw%5!?F*3V65)Zp+d-&TRv-1rn{>L~8b(!Y;E2{}XXVb{S$#OT9J z^ziG$Y;y231NVBwvVaugjl;c{HdF7uK?;*Sfx*GKht3B5kuzlszoZH|gJ306cE17d zNyFBNmmgSQ*H?xwEX(?y1ZCMA@zSam_jtz|bcWNQx_9qW_m?Q&q(hx8NP0Gz$)EK` zfg97G*5_SWuf5o1cSs}=HjPOr*;II6 z`mKw*!kHf-aqj!Bym6&>Cgtm^-oh)(_l1`vS7f7>hT$9Gpvs%9(x+f01TIp0`W=f8h|9 z#?$BXU#_x02)f-KzguXqFJ%6|&b~S-%B_8y5K%&u?h-*jx?4gJr39qA8|e;dNdajA zK|-Z#=%G7Ca_ELZ1{j(l=X*HkyuWX~Yn|U8-#2SLJZpyi#LoNP`@XJg;~j0!DT-fM zVP(YcaV8Xf%DI+(GsJ@TtI9kld3ClPVWMy7wDu6fC+u?jepq5t;s(OJeKibWvOPkq zoecvqHtefU9XoDl<;FSZz(SM-C@VH+X*QbSi-)^=3t6{ z%Twf8fcWLYQCZ>LDx#`EfJa3_<3`fk#?w^is#QR$0=XK=t(+Xxvi;Q8nS_EN;xZz0 z7v@6&TYPBRm{A`aPvwn$Yig0TVll4MjOgxtCi^V8gr&wUK&%bb^@dR8%bHG@nkOAg zLWIo)vAR6vybaSN&+V_VzArt_Q%q>&#%pUnYEY}HqN3n|=AFpexE?I|`oPxgy4ThM z>r*c+YJkxxfSb68d;9Y!*so^k)$Z=Ct$(EJ0>MheX^@ectR;WXoFU0q{_;<6RTn$$ z#oo-0eTm^mCkn}v0Xh0)SJpg%xV3wU-5$H5`Y<6qeLV+;2O6~kPDOc^gxH=|IEb5v z9u+s}PlYM*O5jqRvO+;IMs?u^`F$(Fk%b8{SZCBeq8`{d~Hol=;LIPD7p0X z=_)zZz{{s9#42%;K7UJ$CYmEI{Lsg;&h#L$8)<98L@m)p2#rAt`S2)M=+M$$W z@Am}pVZasaDcCv0_W!ZvCFNmsitkPXSnqF5qIjJ^bicna&EMjsHY&8NXUyG*x`pN< zNAl7}j4LAF4Rul%|N1$qPuiH{HF$OXLf2%a&#!$4sRkhAn02A6`2@oohsB`E+p=tf z>y9Cv+hMjG!$VT`+Y>y$X|66&`dz)P+X{zfj`@BxX2%QzVy=XyUEA83Ikd_ z^Mon?yo(XE?gkA|^F)}gTshU>XiE&{my5`no6QjP(oK$Fme_{*_VPNo*PZb3ufXTg1mdnntcm z-tlgxgK_uulL(5A+7NQB;G@Hyui!0Pt@2r^(StomPP<=`z=w`ckn>ADrAIlde(HU6 zrSYPtOQ!|gggaVd0Y=n~keDZj`>qg)^A8uBX+%Bw5FtcXbH+qsPQB*m;xdfb$)5|k?o8HO`7 zsD3PtVc-P{bI$MIKG|rTT{tk$+zWWwXJTz^vwY5j7y3DSUgWT!T&XfHmM0>iaRMAyT;c%4Y#tYvz0$~H)7H^(p7S$~Py0B3PHChKkqZ(Y(ia71z;80@Tthj=Ft!r%-;BlGS$-Mu!ju3{W#Gp4sk_Jo*p6fBWf}A(Px)x zhXwrzgosWadB=y;SA5AxV%NdiMqoV3Qu@jtTGN{`*?O#R)?;bG$1r}#TV39flZvsH zvCr|o2tB2TRh&y+920*4P;E?G3&9&uGI>2e1Av68KV7y8@bAyR;@65#LZF#BE9A@} z&)DYXGs%r5k}dMXzi9C9!Ur#{xDIR2Q-@y`938~_RiGp*+H?gHba{s99v~-#%52yFzy1+KMkf%NYL5!|sh#tr#CG8kPz~ z8VF9^p9R&IA_!c&tbJb*(dnAa&uZ?^Eq)H%EB3kRS)dhpq|d^6v4r{Y#g%H$ zM!Sjxwh{4$p1tx!d(9xj{M&=NK~~OLl!!4odlySg+TLtHWdrw1dK20MWR=6L;6t$* z5Q?3s4#|dm)YAyd83K-Z#QHukEu?L`x`eh#uAC}m@P=d4dcRZ%kD?ZJrTz%vg<#L0 zR4UQAeP+KNPsrI{f-GTkakC0J{oGui<-~eL3$py|5ujCW{}g^kC%p9@%2&|@i*tZ` zrs4Z2lzUrBkmX1?zYGxDY&W!ahx^S>F35HAU<(8szLdC9c?NMV?~gm)%|J@ta|~7d z?q!;Bf1O?Cy5eHfP^6)(3}8rkp>co>VXx`h>hUm_kE7(wbXL)7&w~zkd^n5hP#B}! zBjr?kcwO5r{*)c+{U@MFgLk6({_kQcLMo!7@LnJzFeFoL@kEiJp5~zUSwrB)pH_xm zs8|PGA)(4PQ=}ZmVzcIk%qV*D8|3|m-!lc*nf!OlE1F~{*L>q-mqwSxk$6B3Dxi`* zu2I3>Y5~f;*B+`6Cr3+YP4LID9YB|4*LkK_8QrGY! zh}~d*OmfG}{e1yDe+-a~{{B=HK~1gscqUsx3w=CKl*q$hW~@%a)|lw?ef<7LC9Eue z$dM7!`h#mJWhDH}4tSWPs!kR%wjrJ0hQ365zQG-5qRFC(;|WCR3Ha%q2leb-JwEe% z%;O0(%L8R4-e85^Y@Ut1h3aB}->r<4wK-HSCJK|m5~UWy(3V{o9;#NQg$}0_BX(%_ zM2ikaMk2PnM&dy~^U}SH5(w!!A9%rXgCw;L`IDz~4%+7-1T-2_rO%?8+(60J4ebYD~c_LypE`KXIiH8Y- z7G|iKJ^IyCPyg9+t@|fZ>&sry=%#0`jY!4;yOu^16;IjpF}!5$(XlacN@Tz` z#KnETBV0T-6WUM?}W4xnaPPaO62e(9nhn}M);{s;yZT%M?v0ybCV zj6k^cv@9C%TZ@4#z;ZMB0VCBi@7G_qAMax0#a<;at>%DOk zfROf=HwiD>TI1haWD6H%H2b_Dbrtc_l8Cpj`c)KrsTH%dulN?U_iW|^7duKc%iAyP zi2Fm-7_}h*NQjnO|2_TC!|(GACzEBhg&_;5L5p(X06uHw<&jJpz9?(F5RfYW_7g~Y zq{OTJtyWe+r^SvRgAYUF{E#`3rkQ=K(lf0Gdw;G4_;rd@e=lQa+z_P{#IdQz{@FfS zF}qa6x#6hY<)Ha(EpOZbJ;rDJ+4h?_H`)=q18oALzU{oww*T8f$zHLox%V65A(N6+ zfSQ^|X>$Xfc3U8)}OXc-csE6Mqw*Y9v$=!-r)| z!IS3&_yMJcV(k^BTQak|yebkfO<+53@AVEL6DscDK%)rLgRJH22mn3ueeRGE`zu+* zs(Df;KfXNKv&PLXIBQ(NK!c$6==^YyxI|=~pYNH=$Zkv8V_I7%(vU|;yOajo zl%f(Duj3W(%;6~FM8i-pZQJK_uq@c+x8IU(^pCvBFPFu*$U!`c^rvMDXt#^fWf0- zGJudES#u7{e`&5c*koY&VU}euq)*b1kFS^F-Yy37=*-Ypul=qeU;cVfm6IFar^&Ch z@P+x*=FXGWQBvbmU=z-iC#JB`Y)pbynqKXMVtTEY9;KOeTYBR6&qxvwSK^1=L^xnZ zY^-Iz;z`~-{vrE1$+WN4F>6UK778~+D!ufJNU|+uFWu&1n49cUc|3ukc`$tEwk8~M z+4^qn4_Y`wWT%>w`ZpTocNn0qsTiSZF50{*k!~LrCzjQj1(e?V`gipA;W<*JtH9AA zSY3)d)xPiIVnSQ25CzGZ(+-Tlv}FF^Y->cp;SfLN20kE@_iz_w!usBgkK ze*G5O=ej!3T&bKpRwewN=Dc=AcY!7{1HT(pPRppSjU7I_Ohv%~s4aKEth&dycDDL>k>5l$fVW z^ z1#CWhEuI^Cx@S6<5n(BCbUjaUz5W#{R>XbZKR0c8;N$^#{*1(RcF}r`>M`y`EXSxB zWg}}Ok1O3!FCd?uBJf(2Sx8d+JlF&i|L{3eV53B$riNDbFroDm!P+HlIZRzgNBSGE|GDEx zygF|NlbcH0bFYHSK&OTE1p*q5mcaTTznp~if{e>yNW=P1`k$rJ9=g$dq&a;B6fxYf zqt4{9<+$aeu*<-GVNYJgokaz2j{q77c$L>nvbMX%w@dTRH;~`(5w0nPn->VaGtznO z#yiZV#Zkbh8R;mx8J5C=JPtN8ul=zrEP3LOH_^$f zY&IriHn!APA_0>qKK{fNm|b9=E~8jb*>YQ&Z|O>Ca2L;_hcEE zN%o3tmz`2u`Ll#PUx_jY4H(|1V%B`cciomyx)s(*7Bel0U+&giUmn(<$QP}!GrB4jj9*uD25a_O>6^1o znd23WK3_{vb=dMdZw>5+z3)-~8e|rrofZ{qpXm8QZ(Pt%|LG*Zyp2-&y%z~R$ypTo zUzrF`MD@n;45WnK9|EJ7mGQ!^FGr-^m@*Bc4VSKX%2D;W5eno5(R#r?!`Hwv&&pOd z?lPTVk)IvWWVjfgar90iGUD2pPNzFGK}zzdiQ18t)j^)TCyme@a@KYH?j zly_?bqQ0y)bh;eyc@bZcGXUjXegg&eQaZp0ypVxVA#fqMFlH6~AuHK(q)z>fsk8Jia#T+~Ol z>u;042B~x6?Zsdt&lOY(S#8ad;Nai0N$>{dprtQxHWPhdU8xxf!c1K6^YgNo|Df!qS%npQL*CdeOfMEC~!5jt<30{a=nD@D8>HygT}C3 zyolALnfgbe8JJflQmf4c@`cyTXlP`=73HKg$5{!*1b=^>8&0pLCt{a1X$Q>_*C{uG zYt08`Vk;h~YY|&GSn|YS9kc!vjROd;<;qf>ns678V49n&2N5pwbk)hH#z_#qKeCeR zb_iG&*1&!z`6xj;0$TN*JwwEiFzzrrQew;1d1+y~+tL_Z0f(*WlAU;WfMFqsHNV*f z^nDnoYb@p?->ye)s6j`cMXywVuf;r~d`ceCadH85?a*KDsP$KxU^3Zjdr}F0R)w1K zSrKt@Xhv`f9)9VHao!z6_a&YXOTt5BRpq5pCYt)V3F4oD!pJ3l;S^t+$BL}3IXzkZ zj7JnSzbAgxX{SP&T*Npj?>8hlM@jV@w>QIZle>8^k7-7wr7*Efpica%-tfz_{l0}u zfip+Hq@SDkIwaN_0fR8p;o|4H7m6ww!UtNV(P~NTlCDNeXMmX)5Y3Q8?fW>@C*F@| z{^K4}nk>0to|$ELNFefJW@XvF?JQiChi&VyJT}yn98i2WjpPmuPw#kc^*ov++SKz# zJIoa8_*Pxq_4~)k=_!{hu3RQ9@l4!NfW?zyt2EJBdxpq!w+qT$ZEJS0xhJRu5e ziewCZ@uxJ-GT1W2@$#O-1Hgy`F_f$?7pdmk59%kpTo2XRt4MUzRkeW%v6i-BLTuOR zAQJY$C0-Y+Be^26v6Dgkxc6inW|4Ne>#=@Lf*W#UEE65*e9?I_07!)jC)p^pmi#A zJPqSicI2Q-S)8EVk35OrD%JFgY+v&`Y)q);(Pmoxgs6m^fZ&?F^(Q?3Yg2A(naton z>t3-_@JmeG7mc?564YKRj(+9N)Rj&N*wEN#+lbzS8uEr(eg_6ELED!@z6izd83CP> zU2|t2$+abFAfFwuWfZ&u2zVH~ZV$}}d>r|h$ZorIW7+E-QuobrtWzL!6xgz<%+Gu2 zBxIShO|Cojt1zW`b<@0d+-|%f5>;_QIc+fs95W-uqYD^T=n)jZslJ3%P;(jiAHGU` zq1y=%U0yT08T(DE$x=Di>N+-v8B~9IQ8zjMhFzJTC-B;PYc#tgn}zoQEpu`&G2y3s z&CIqoR4Xskz_3+{(CZJ9^vI%W2BUd_c!ed&P=#s8%96I#ZwIc8xcV!)Fkjz;mK> z8ou6Vcwj6pMsOCGIl%lI*aVoGh3*-#KagwqzldO-=r{36$ zhbMbHoY69ZCPFsFbki*6M0V_5I(L&V{!}-kzqSq_+;ikl=it{FsRb`aRCTs<9oYs2 zB{8>UOBi7m(J|)?e;&24wN?E^D#~@F9+L--A5|oZAXYgR{uqu3wI}|35K2zNr_@@N z2U>>h**;viFzqR_+nK|VyPqDUuP!JR5Gb1Wt%Krt)a1-Pp-4%L42-x5W9+HzHVmxY z%?M0$`y;|)u6GuEX1{!em}%2)KMx@M1R$RxC7U-pG%&3VonKxBtc6v5>6ZDzM3YI> zXM6>nuX{rHNsChNEQnU3*^V6%bVxE9h+y;-+HgV(SotL7Hk(w#THcT**An;3=1F|= zD#$NKY!~`@P?$+Q13lpW_FwU{-Pg}wN(-ZC>W&*9vB1?IKicw$WLD2;ah3(2Pkfln zr>8C`cs^G)#b9=imAkXSh?Hx4b(R)=KL7WFC#O$|A2SixKAf4ZbjY>KJ6=;ukkCqx z2+y9Cf8mLtT((!LhY}CE)JJiYH|sNYPtX=~wWJHdwZDu2{v|%x|fTZB(YPc40f1ipm2Uh$+)lMEsRe zQ4uI3==MNHrh`7K-eGksS3*|ValZM8;u|Dp^`fN`6O;nQ-!t$8LtU6aPjiQ)2;u$O z9P%29B8?T)8s_TcAn8V3`EESE%s1nEikXz>V@pf&`u4a04(eC55r(JR~Ps%UO)`dg+PjivebdQj>EAyw#n_?G> zN7=ITkC$N)B@n8~K&3jmrCYbf7XtdzNdp`5irZg%g$O_KTK4`?@Idk&%vL#ah=IyR zZXD*qy28V7^J8g(WKcEZ7l8>`G>${m2e-Kr=DbxlU!u>LJoZR|&2xM%%ZxA&&rz@` zr#3&Eo_X=wwLMegAYtJHd4&KYJcOvbeLgklFt#||%uJal^NinAkGb%Ry0URuzSdQmMYEGhLyI-FKUp<6| z^Ef6C+*(8rEK;i(8L9aAG;4`4Tku13Aw&yo+`)uj(Qp?*K%5!c2oyP2|as~mQ-16$kwM|9_slK zZyCD*%uYn{0^%VynwwMR#oD)Hb131fYZQ%GyiQ>NTfgL;y^+rYrQQct#}BU(PmR)1 z^6(GB5pXLTr_yJ{toO*r!^plXq-dzCN5BQf4N52m3 zAA0TPb0QG*`mMu=!26z zGI*~7h!vi8Rkxu0y6P6reu{T_UIl7*$!lXO^i}z8f{d!_N&m!!MYkS7LbA7MQo`zAVuB#{p>4LxCJzQ7E$DGWn! zW&MJNs2?d0^@M?^cn&1!Viz`^X)tTU@ijZN<^A{fyt@wG4OLLnS&e=j8qT;0>Iv%Z z)KU!W(qc@Z098IyNJz>jhX!6((~>c?SGhdNKbCp)O%lwzi?^802z&rv;sxC9P+)S6 z{qJwoYB4}4BI-GEkZ0t4a{NEPR+cW{<3!k+P>R;d0iO=-cQAM@&kOWBt1r`kDm{}|5|ang6Z>z@n3+B~c9j-#2xBeO6npj0kCOsJ z@$w#zpFDYjWh|2FlanU)Ly1P~pIdR)0lsZi6kt}#{^$J+{^$K%q&a{|!C%h|2k=;E zzZVutvl;%*g~Mg95V%9%xfQEDN5T&Ye96lO{==w1;%EGpngzb)h&AATKUj_}z22j? zSA$8N0t1v%@4*l1KGu7{0#8yBdkP}uYCTa;$fe4he~+6@_AA^jPwxjQaB`WP?BwL) zED)v~n8#HtaK&PO0PJ-FjOp~$!}c9$Y;W>?ICOy0Kdcj0zj|Bu+vlI_?oOd);Z36b zm3K^Sf{Pj;Z~e{69oi8+9Q6w!mYe0o97AiV`c7jiKzZC1h&(}2yy-B1{NFWlR~A6U z{Of5ju{dk~pPIo2!)#gZ(gFq7^$~iGj&IHUcd^83tdRVDXHItz((~5%pDS$bF8==g z?zzB&DgI1ON&j6#4Zml5qz!5QC*k3LCHzl%X@5RwPX~UZMZZntKTp;d7oYBiu3XTf z>9P+QU$Bqx+r$3(mzZaDaz)@)$!B2vt)IQV?vt{nflnUYwKWC&`GF>3T_I=o?K?99 zI2?4JJlr{h3Z^>yUTuGzTH*s7*1zL3+eDclh2~6%Jef6{ioHW$@%L)MT?`GU^{3Bj=2=t zkZ&&t&GATxiQaLz8~xQ@{;K|-W+Pmx#4b%`0kWVyGup3L^a6;;A0O+*m~XbA9W~J? zthK0dXyQ)E)B&fM)n7eP&W9rwybC^k1b}YFEuE9#5DhKXe01jt2E4k^>qA|v z!E`|m6sR-j+;xC01~6mMf&p$afC5x-0$*4eu(-{@RSS-qbHA3xSZ0vl_c4SRdfj{x z3q%I29U>2mg5~(S+1D)$35IwdYR<{Ayy6iz*MZgm%=uAYl&(+3E6Ie}r!GeBP72b( z>9EwOZ~qyl^7LZU^pX5UQI! z$mb(K0|k~-XXvRyUshzO`k1%Rnwd@-n<&SNeQ zJvM-D?l&X0GOTm*04k}5ib%@b+*}KyhRAAkW62LOGE}|ZJxk{Nln_VFD$OJ@m*_`c zqhVSp&RKX2iYsAU5Dp5^g(Fcf!9ddppx=(D?ma62@cY!sHw@Vgl~x&wE_Ay0G7j2CqkF# z?#0*XkpQ?BfweNglCyoexX|dNcyQHKYV^HGrPc zZRhnx<0nEk`8W&{APa^GE{65+t@@612|q!5Xn7$R44y}5{G4oVk_uRcMYET;^DEP0GWJyZQji=N4=sLAXC8ddtOLzqg zP#yV;)&(d!ml{bR6Upy%oOmx_B?X2 zZv6uwQ*sRCQr)@;C=k5nCsJYV|Q#CC4{3=0j6;_AclOWBnAjjTwfgf02sRr0eez3fLyRe)`r9qT68B$GxBBC zC|21l%5qw}faMnN2haWnq&+D3td!16a3?HwCVv7tWfbV)%)d5J8Xbl zKrXU-D)txkh`w{VpEeiM(s9oD!5W}DW?#YZ_`!!ig=ss(#6XiUwdSvXcD-i91b7Bt z(?oJ>JZfuMnysi63)PWeRVLnA$2W!Q_b@(|D{_en@Yid+BLupq`p?PFX+f3V;wnsp z9jE?4;2z=0ArAQt1_$<+fy)kw6V*)mYue<#zsn!nW}utUjq!J>-hcNlW>uW)=+&Kd z_}fUxt8>Wv#~?aH71a8H*m)N@=r^wW2nNh||6LVIX`D9zE|GLBG_K7j- z)cA#A(@A9-K!S0#v$Ly}1uXYQl)5%HMP+q$wW;o3Q@y(irgpJAOhLcuSCH(ylW&W_ zr@Hi4V%vNCKu5<40oCjpA0x=6~-0yy&67-z#K@ih~{q-`i~{Wonln2t9+V=c87!|@|1+G zS}eAlvn$^j_6UCj%YP+~S#KouYzG}R_4poc`T#P7)AU18tWJoKkXe2%o>4w zf#Q2py^U~JEew-v75;Ba`l~_pYjEryt;s}xPi$%ZEXSr@8S>#4hqRL8FZUAfTRu}J uG~}5&;E?{;bwJ^&X=DFOVf0t6p|Gt<|Ck;Ol5wE{AH`QHa^*56A^!(Nh|h}v literal 0 HcmV?d00001 diff --git a/usermods/Battery/assets/battery_connection_schematic_esp32_v2.png b/usermods/Battery/assets/battery_connection_schematic_esp32_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..2dff54f8866dab07c0257faa9715a25b45c87ce5 GIT binary patch literal 65817 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 z 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 literal 0 HcmV?d00001 diff --git a/usermods/Battery/assets/installation_my_config_h.png b/usermods/Battery/assets/installation_my_config_h.png new file mode 100644 index 0000000000000000000000000000000000000000..06235a224d34062889caf31417df28f9abce54fb GIT binary patch literal 50412 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 literal 0 HcmV?d00001 diff --git a/usermods/Battery/assets/installation_platformio_override_ini.png b/usermods/Battery/assets/installation_platformio_override_ini.png new file mode 100644 index 0000000000000000000000000000000000000000..72cca7dc15585894b920345b06ed58bfc01c9500 GIT binary patch literal 55011 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�?1*RrW`e2$7Yd=H_xI9t+j@PVS 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 literal 0 HcmV?d00001 diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index b3607482a..b6918c596 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -6,53 +6,78 @@ Enables battery level monitoring of your project. -For this to work, the positive side of the (18650) battery must be connected to pin `A0` of the d1 mini/esp8266 with a 100k Ohm resistor (see [Useful Links](#useful-links)). - -If you have an ESP32 board, connect the positive side of the battery to ADC1 (GPIO32 - GPIO39) - -

- +

+

+
+ ## ⚙️ Features -- 💯 Displays current battery voltage +- 💯 Displays current battery voltage - 🚥 Displays battery level -- 🚫 Auto-off with configurable Threshold +- 🚫 Auto-off with configurable threshold - 🚨 Low power indicator with many configuration possibilities +

+ ## 🎈 Installation -define `USERMOD_BATTERY` in `wled00/my_config.h` +| **Option 1** | **Option 2** | +|--------------|--------------| +| In `wled00/my_config.h`
Add the line: `#define USERMOD_BATTERY`

[Example: my_config.h](assets/installation_my_config_h.png) | In `platformio_override.ini` (or `platformio.ini`)
Under: `build_flags =`, add the line: `-D USERMOD_BATTERY`

[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) | -### Example wiring +

-

- -

+## 🔌 Example wiring -### Define Your Options +- (see [Useful Links](#useful-links)). + +
${isM?'Start X':'Start LED'}
+ + + + + + +
+ +

ESP8266
+ With a 100k Ohm resistor, connect the positive
+ side of the battery to pin `A0`.

+
+ +

ESP32 (+S2, S3, C3 etc...)
+ Use a voltage divider (two resistors of equal value).
+ Connect to ADC1 (GPIO32 - GPIO39). GPIO35 is Default.

+
+ +

+ +## Define Your Options | Name | Unit | Description | | ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- | -| `USERMOD_BATTERY` | | define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | -| `USERMOD_BATTERY_MEASUREMENT_PIN` | | defaults to A0 on ESP8266 and GPIO35 on ESP32 | -| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds | -| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | -| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | -| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up | -| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller | +| `USERMOD_BATTERY` | | Define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | +| `USERMOD_BATTERY_MEASUREMENT_PIN` | | Defaults to A0 on ESP8266 and GPIO35 on ESP32 | +| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | Battery check interval. defaults to 30 seconds | +| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | Minimum battery voltage. default is 2.6 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | Maximum battery voltage. default is 4.2 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | The capacity of all cells in parallel summed up | +| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | Offset / calibration number, fine tune the measured voltage by the microcontroller | | Auto-Off | --- | --- | -| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off | -| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | when this threshold is reached master power turns off | +| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | Enables auto-off | +| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | When this threshold is reached master power turns off | | Low-Power-Indicator | --- | --- | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | enables low power indication | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | when low power is detected then use this preset to indicate low power | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | when this threshold is reached low power gets indicated | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | for this long the configured preset is played | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | Enables low power indication | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | When low power is detected then use this preset to indicate low power | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | When this threshold is reached low power gets indicated | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | For this long the configured preset is played | All parameters can be configured at runtime via the Usermods settings page. +
+ **NOTICE:** Each Battery type can be pre-configured individualy (in `my_config.h`) | Name | Alias | `my_config.h` example | @@ -60,62 +85,85 @@ All parameters can be configured at runtime via the Usermods settings page. | Lithium Polymer | lipo (Li-Po) | `USERMOD_BATTERY_lipo_MIN_VOLTAGE` | | Lithium Ionen | lion (Li-Ion) | `USERMOD_BATTERY_lion_TOTAL_CAPACITY` | +

+ +## 🔧 Calibration + +The calibration number is a value that is added to the final computed voltage after it has been scaled by the voltage multiplier. + +It fine-tunes the voltage reading so that it more closely matches the actual battery voltage, compensating for inaccuracies inherent in the voltage divider resistors or the ESP's ADC measurements. + +Set calibration either in the Usermods settings page or at compile time in `my_config.h` or `platformio_override.ini`. + +It can be either a positive or negative number. + +

+ ## ⚠️ Important -- Make sure you know your battery specifications! All batteries are **NOT** the same! -- Example: +Make sure you know your battery specifications! All batteries are **NOT** the same! -| Your battery specification table | | Options you can define | -| :-------------------------------- |:--------------- | :---------------------------- | -| Capacity | 3500mAh 12,5 Wh | | -| Minimum capacity | 3350mAh 11,9 Wh | | +Example: + +| Your battery specification table | | Options you can define | +| --------------------------------- | --------------- | ----------------------------- | +| Capacity | 3500mAh 12.5Wh | | +| Minimum capacity | 3350mAh 11.9Wh | | | Rated voltage | 3.6V - 3.7V | | -| **Charging end voltage** | **4,2V ± 0,05** | `USERMOD_BATTERY_MAX_VOLTAGE` | -| **Discharge voltage** | **2,5V** | `USERMOD_BATTERY_MIN_VOLTAGE` | +| **Charging end voltage** | **4.2V ± 0.05** | `USERMOD_BATTERY_MAX_VOLTAGE` | +| **Discharge voltage** | **2.5V** | `USERMOD_BATTERY_MIN_VOLTAGE` | | Max. discharge current (constant) | 10A (10000mA) | | | max. charging current | 1.7A (1700mA) | | | ... | ... | ... | | .. | .. | .. | -Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833) +Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833) + +

## 🌐 Useful Links - https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start - https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/ +

+ ## 📝 Change Log +2024-05-11 + +- Documentation updated + 2024-04-30 -- integrate factory pattern to make it easier to add other / custom battery types -- update readme +- Integrate factory pattern to make it easier to add other / custom battery types +- Update readme 2023-01-04 -- basic support for LiPo rechargeable batteries ( `-D USERMOD_BATTERY_USE_LIPO`) -- improved support for esp32 (read calibrated voltage) -- corrected config saving (measurement pin, and battery min/max were lost) -- various bugfixes +- Basic support for LiPo rechargeable batteries (`-D USERMOD_BATTERY_USE_LIPO`) +- Improved support for ESP32 (read calibrated voltage) +- Corrected config saving (measurement pin, and battery min/max were lost) +- Various bugfixes 2022-12-25 -- added "auto-off" feature -- added "low-power-indication" feature -- added "calibration/offset" field to configuration page -- added getter and setter, so that user usermods could interact with this one -- update readme (added new options, made it markdownlint compliant) +- Added "auto-off" feature +- Added "low-power-indication" feature +- Added "calibration/offset" field to configuration page +- Added getter and setter, so that user usermods could interact with this one +- Update readme (added new options, made it markdownlint compliant) 2021-09-02 -- added "Battery voltage" to info -- added circuit diagram to readme -- added MQTT support, sending battery voltage -- minor fixes +- Added "Battery voltage" to info +- Added circuit diagram to readme +- Added MQTT support, sending battery voltage +- Minor fixes 2021-08-15 -- changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries +- Changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries - Updated readme, added specification table 2021-08-10 From ecc9443677e1c69291138e8bf8f54d524614a144 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 11 May 2024 14:45:42 +0200 Subject: [PATCH 240/694] (0_14 branch only) adding compatibility for building with upstream arduinoFFT 2.xx support compilation with new arduinoFFT versions 2.x --- usermods/audioreactive/audio_reactive.h | 18 +++++++++++++----- usermods/audioreactive/readme.md | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index fec0525ec..716953957 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -177,9 +177,6 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul // These are the input and output vectors. Input vectors receive computed results from FFT. static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins static float vImag[samplesFFT] = {0.0f}; // imaginary parts -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT -static float windowWeighingFactors[samplesFFT] = {0.0f}; -#endif // Create FFT object #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT @@ -198,9 +195,15 @@ static float windowWeighingFactors[samplesFFT] = {0.0f}; #include #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); +#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 + // arduinoFFT 2.x has a slightly different API + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); #else -static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); + static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); +#endif +#else + static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); #endif // Helper functions @@ -300,7 +303,12 @@ void FFTcode(void * parameter) #endif #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT + #if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 + // arduinoFFT 2.x has a slightly different API + FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant + #else FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant + #endif #else FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index 47804b611..8959021ba 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -38,7 +38,7 @@ Alternatively, you can use the latest arduinoFFT development version. ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM. * `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT` -* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2` +* `lib_deps`= `https://github.com/kosme/arduinoFFT#419d7b0` ## Configuration From 9e468bd059510c66bbb99e41ed4855193a9cc05c Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sat, 11 May 2024 13:57:21 -0400 Subject: [PATCH 241/694] Pinwheel Expand 1D Optimizations Added small pinwheel size. Adjusts medium and large values. --- wled00/FX_fcn.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 17a504ea0..ec0e087bc 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -638,9 +638,12 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const { } // Constants for mapping mode "Pinwheel" -constexpr int Pinwheel_Steps_Medium = 208; // no holes up to 32x32; 60fps -constexpr int Pinwheel_Size_Medium = 30; // larger than this -> use "Big" -constexpr int Pinwheel_Steps_Big = 360; // no holes expected up to 58x58; 40fps +constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16; +constexpr int Pinwheel_Size_Small = 16; +constexpr int Pinwheel_Steps_Medium = 200; // no holes up to 32x32; 60fps +constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Steps_Big = 296; // no holes expected up to 58x58; 40fps +constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...208 to Radians constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...208 to Radians constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...360 to Radians @@ -661,7 +664,9 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { vLen = max(vW,vH); // get the longest dimension break; case M12_sPinwheel: - if (max(vW,vH) <= Pinwheel_Size_Medium) + if (max(vW,vH) <= Pinwheel_Size_Small) + vLen = Pinwheel_Steps_Small; + else if (max(vW,vH) <= Pinwheel_Size_Medium) vLen = Pinwheel_Steps_Medium; else vLen = Pinwheel_Steps_Big; @@ -736,8 +741,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) // i = angle --> 0 through 359 (Big), OR 0 through 208 (Medium) float centerX = roundf((vW-1) / 2.0f); float centerY = roundf((vH-1) / 2.0f); - // int maxDistance = sqrt(centerX * centerX + centerY * centerY) + 1; - float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + float angleRad = (max(vW, vH) > Pinwheel_Size_Small ? (max(vW, vH) > Pinwheel_Size_Medium ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med) : float(i) * Int_to_Rad_Small); // angle in radians float cosVal = cos_t(angleRad); float sinVal = sin_t(angleRad); @@ -885,7 +889,7 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) float distance = max(1.0f, min(vH-1, vW-1) / 2.0f); float centerX = (vW - 1) / 2.0f; float centerY = (vH - 1) / 2.0f; - float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + float angleRad = (max(vW, vH) > Pinwheel_Size_Small ? (max(vW, vH) > Pinwheel_Size_Medium ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med) : float(i) * Int_to_Rad_Small); // angle in radians int x = roundf(centerX + distance * cos_t(angleRad)); int y = roundf(centerY + distance * sin_t(angleRad)); return getPixelColorXY(x, y); From 1ff5cb0596d4a355a96570a296b5c7f58ba7cf92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 12 May 2024 11:12:13 +0200 Subject: [PATCH 242/694] Experimental parallel I2S support for ESP32 - increased outputs to 17 - increased max possible color order overrides - use WLED_USE_PARALLEL_I2S during compile WARNING: Do not set up more than 256 LEDs per output when using parallel I2S with NeoPixelBus less than 2.9.0 --- wled00/bus_wrapper.h | 65 +++++++++++++++++++++----- wled00/cfg.cpp | 14 +++--- wled00/const.h | 6 ++- wled00/data/settings_leds.htm | 86 ++++++++++++++++++----------------- wled00/set.cpp | 51 +++++++++++---------- wled00/xml.cpp | 36 ++++++++------- 6 files changed, 156 insertions(+), 102 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 99ae4c5ef..57e98467e 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -224,8 +224,11 @@ //#define B_32_I0_NEO_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_NEO_3 NeoPixelBusLg -//#define B_32_I1_NEO_3 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_NEO_3 NeoPixelBusLg // parallel I2S + #endif #endif //RGBW #define B_32_RN_NEO_4 NeoPixelBusLg @@ -234,8 +237,11 @@ //#define B_32_I0_NEO_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_NEO_4 NeoPixelBusLg -//#define B_32_I1_NEO_4 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_NEO_4 NeoPixelBusLg // parallel I2S + #endif #endif //400Kbps #define B_32_RN_400_3 NeoPixelBusLg @@ -244,8 +250,11 @@ //#define B_32_I0_400_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_400_3 NeoPixelBusLg -//#define B_32_I1_400_3 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_400_3 NeoPixelBusLg // parallel I2S + #endif #endif //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg @@ -254,8 +263,11 @@ //#define B_32_I0_TM1_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM1_4 NeoPixelBusLg -//#define B_32_I1_TM1_4 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_TM1_4 NeoPixelBusLg // parallel I2S + #endif #endif //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg @@ -264,8 +276,11 @@ //#define B_32_I0_TM2_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM2_3 NeoPixelBusLg -//#define B_32_I1_TM2_3 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_TM2_3 NeoPixelBusLg // parallel I2S + #endif #endif //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg @@ -274,8 +289,11 @@ //#define B_32_I0_UCS_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_UCS_3 NeoPixelBusLg -//#define B_32_I1_UCS_3 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_UCS_3 NeoPixelBusLg // parallel I2S + #endif #endif //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg @@ -284,8 +302,11 @@ //#define B_32_I0_UCS_4 NeoPixelBusLg// parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_UCS_4 NeoPixelBusLg -//#define B_32_I1_UCS_4 NeoPixelBusLg// parallel I2S + #else +#define B_32_I1_UCS_4 NeoPixelBusLg// parallel I2S + #endif #endif #define B_32_RN_APA106_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS @@ -293,8 +314,11 @@ //#define B_32_I0_APA106_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_APA106_3 NeoPixelBusLg -//#define B_32_I1_APA106_3 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_APA106_3 NeoPixelBusLg // parallel I2S + #endif #endif //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg @@ -303,8 +327,11 @@ //#define B_32_I0_FW6_5 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_FW6_5 NeoPixelBusLg -//#define B_32_I1_FW6_5 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_FW6_5 NeoPixelBusLg // parallel I2S + #endif #endif //WS2805 RGBWC #define B_32_RN_2805_5 NeoPixelBusLg @@ -313,8 +340,11 @@ //#define B_32_I0_2805_5 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_2805_5 NeoPixelBusLg -//#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S + #else +#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S + #endif #endif //TM1914 (RGB) #define B_32_RN_TM1914_3 NeoPixelBusLg @@ -323,8 +353,11 @@ //#define B_32_I0_TM1914_3 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS + #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM1914_3 NeoPixelBusLg -//#define B_32_I1_TM1914_3 NeoPixelBusLg + #else +#define B_32_I1_TM1914_3 NeoPixelBusLg + #endif #endif #endif @@ -541,7 +574,11 @@ class PolyBus { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation + #ifdef WLED_USE_PARALLEL_I2S + if (channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32 + #else if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + #endif #endif void* busPtr = nullptr; switch (busType) { @@ -1619,9 +1656,15 @@ class PolyBus { //if (num > 3) offset = num -4; // I2S not supported yet #else // standard ESP32 has 8 RMT and 2 I2S channels + #ifdef WLED_USE_PARALLEL_I2S + if (num > 16) return I_NONE; + if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels + if (num == 16) offset = 1; + #else if (num > 9) return I_NONE; if (num > 8) offset = 1; if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + #endif #endif switch (busType) { case TYPE_WS2812_1CH_X3: diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 22bfe577a..addd3fc5e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -124,7 +124,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.panels, matrix[F("mpc")]); strip.panel.clear(); JsonArray panels = matrix[F("panels")]; - uint8_t s = 0; + int s = 0; if (!panels.isNull()) { strip.panel.reserve(max(1U,min((size_t)strip.panels,(size_t)WLED_MAX_PANELS))); // pre-allocate memory for panels for (JsonObject pnl : panels) { @@ -156,7 +156,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray ins = hw_led["ins"]; if (fromFS || !ins.isNull()) { - uint8_t s = 0; // bus iterator + int s = 0; // bus iterator if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback uint32_t mem = 0, globalBufMem = 0; uint16_t maxlen = 0; @@ -790,7 +790,7 @@ void serializeConfig() { JsonObject matrix = hw_led.createNestedObject(F("matrix")); matrix[F("mpc")] = strip.panels; JsonArray panels = matrix.createNestedArray(F("panels")); - for (uint8_t i=0; igetLength()==0) break; JsonObject ins = hw_led_ins.createNestedObject(); @@ -815,7 +815,7 @@ void serializeConfig() { JsonArray ins_pin = ins.createNestedArray("pin"); uint8_t pins[5]; uint8_t nPins = bus->getPins(pins); - for (uint8_t i = 0; i < nPins; i++) ins_pin.add(pins[i]); + for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]); ins[F("order")] = bus->getColorOrder(); ins["rev"] = bus->isReversed(); ins[F("skip")] = bus->skippedLeds(); @@ -829,7 +829,7 @@ void serializeConfig() { JsonArray hw_com = hw.createNestedArray(F("com")); const ColorOrderMap& com = BusManager::getColorOrderMap(); - for (uint8_t s = 0; s < com.count(); s++) { + for (int s = 0; s < com.count(); s++) { const ColorOrderMapEntry *entry = com.get(s); if (!entry) break; @@ -846,7 +846,7 @@ void serializeConfig() { JsonArray hw_btn_ins = hw_btn.createNestedArray("ins"); // configuration for all buttons - for (uint8_t i=0; i LED Settings ', options).replace(/<[\/]*script>/g, ''); + let js = await minifyHtml('', options); + return js.replace(/<[\/]*script>/g, ''); } else if (type == "html-minify") { return await minifyHtml(str, options); } @@ -252,6 +253,12 @@ writeChunks( str .replace("%%", "%") }, + { + file: "common.js", + name: "JS_common", + method: "gzip", + filter: "js-minify", + }, { file: "settings.htm", name: "PAGE_settings", diff --git a/wled00/data/common.js b/wled00/data/common.js new file mode 100644 index 000000000..9378ef07a --- /dev/null +++ b/wled00/data/common.js @@ -0,0 +1,118 @@ +var d=document; +var loc = false, locip, locproto = "http:"; + +function H(pg="") { window.open("https://kno.wled.ge/"+pg); } +function GH() { window.open("https://github.com/Aircoookie/WLED"); } +function gId(c) { return d.getElementById(c); } // getElementById +function cE(e) { return d.createElement(e); } // createElement +function gEBCN(c) { return d.getElementsByClassName(c); } // getElementsByClassName +function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName +function isE(o) { return Object.keys(o).length === 0; } // isEmpty +function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } // isObject +function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber +// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer +function isF(n) { return n === +n && n !== (n|0); } // isFloat +function isI(n) { return n === +n && n === (n|0); } // isInteger +function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); } +function tooltip(cont=null) { + d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{ + element.addEventListener("mouseover", ()=>{ + // save title + element.setAttribute("data-title", element.getAttribute("title")); + const tooltip = d.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = element.getAttribute("title"); + + // prevent default title popup + element.removeAttribute("title"); + + let { top, left, width } = element.getBoundingClientRect(); + + d.body.appendChild(tooltip); + + const { offsetHeight, offsetWidth } = tooltip; + + const offset = element.classList.contains("sliderwrap") ? 4 : 10; + top -= offsetHeight + offset; + left += (width - offsetWidth) / 2; + + tooltip.style.top = top + "px"; + tooltip.style.left = left + "px"; + tooltip.classList.add("visible"); + }); + + element.addEventListener("mouseout", ()=>{ + d.querySelectorAll('.tooltip').forEach((tooltip)=>{ + tooltip.classList.remove("visible"); + d.body.removeChild(tooltip); + }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); + }); + }); +}; +// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript +function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) { + let scE = d.createElement("script"); + scE.setAttribute("src", FILE_URL); + scE.setAttribute("type", "text/javascript"); + scE.setAttribute("async", async); + d.body.appendChild(scE); + // success event + scE.addEventListener("load", () => { + //console.log("File loaded"); + if (preGetV) preGetV(); + GetV(); + if (postGetV) postGetV(); + }); + // error event + scE.addEventListener("error", (ev) => { + console.log("Error on loading file", ev); + alert("Loading of configuration script failed.\nIncomplete page data!"); + }); +} +function getLoc() { + let l = window.location; + if (l.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } else { + // detect reverse proxy + let path = l.pathname; + let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/"); + if (paths.length > 1) paths.pop(); // remove subpage (or "settings") + if (paths.length > 0 && paths[paths.length-1]=="settings") paths.pop(); // remove "settings" + if (paths.length > 1) { + locproto = l.protocol; + loc = true; + locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths.join('/'); + } + } +} +function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } +function B() { window.open(getURL("/settings"),"_self"); } +var timeout; +function showToast(text, error = false) { + var x = gId("toast"); + if (!x) return; + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); +} +function uploadFile(fileObj, name) { + var req = new XMLHttpRequest(); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); + req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.open("POST", "/upload"); + var formData = new FormData(); + formData.append("data", fileObj.files[0], name); + req.send(formData); + fileObj.value = ''; + return false; +} diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index a4b913592..b58c0987a 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -608,8 +608,8 @@ } function generatePaletteDivs() { - const palettesDiv = d.getElementById("palettes"); - const staticPalettesDiv = d.getElementById("staticPalettes"); + const palettesDiv = gId("palettes"); + const staticPalettesDiv = gId("staticPalettes"); const paletteDivs = Array.from(palettesDiv.children).filter((child) => { return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit }); @@ -620,25 +620,25 @@ for (let i = 0; i < paletteArray.length; i++) { const palette = paletteArray[i]; - const paletteDiv = d.createElement("div"); + const paletteDiv = cE("div"); paletteDiv.id = `palette${i}`; paletteDiv.classList.add("palette"); const thisKey = Object.keys(palette)[0]; paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]); - const gradientDiv = d.createElement("div"); + const gradientDiv = cE("div"); gradientDiv.id = `paletteGradient${i}` - const buttonsDiv = d.createElement("div"); + const buttonsDiv = cE("div"); buttonsDiv.id = `buttonsDiv${i}`; buttonsDiv.classList.add("buttonsDiv") - const sendSpan = d.createElement("span"); + const sendSpan = cE("span"); sendSpan.id = `sendSpan${i}`; sendSpan.onclick = function() {initiateUpload(i)}; sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send? sendSpan.innerHTML = svgSave; sendSpan.classList.add("sendSpan") - const editSpan = d.createElement("span"); + const editSpan = cE("span"); editSpan.id = `editSpan${i}`; editSpan.onclick = function() {loadForEdit(i)}; editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); diff --git a/wled00/data/pxmagic/pxmagic.htm b/wled00/data/pxmagic/pxmagic.htm index d59f924cf..8ec11f454 100644 --- a/wled00/data/pxmagic/pxmagic.htm +++ b/wled00/data/pxmagic/pxmagic.htm @@ -882,10 +882,8 @@ hostnameLabel(); })(); - function gId(id) { - return d.getElementById(id); - } - + function gId(e) {return d.getElementById(e);} + function cE(e) {return d.createElement(e);} function hostnameLabel() { const link = gId("wledEdit"); link.href = WLED_URL + "/edit"; @@ -1675,7 +1673,7 @@ } function createCanvas(width, height) { - const canvas = d.createElement("canvas"); + const canvas = cE("canvas"); canvas.width = width; canvas.height = height; @@ -1719,7 +1717,7 @@ const blob = new Blob([text], { type: mimeType }); const url = URL.createObjectURL(blob); - const anchorElement = d.createElement("a"); + const anchorElement = cE("a"); anchorElement.href = url; anchorElement.download = `${filename}.${fileExtension}`; @@ -1790,7 +1788,7 @@ hideElement = "preview" ) { const hide = gId(hideElement); - const toast = d.createElement("div"); + const toast = cE("div"); const wait = 100; toast.style.animation = "fadeIn"; @@ -1799,14 +1797,14 @@ toast.classList.add("toast", type); - const body = d.createElement("span"); + const body = cE("span"); body.classList.add("toast-body"); body.textContent = message; toast.appendChild(body); - const progress = d.createElement("div"); + const progress = cE("div"); progress.classList.add("toast-progress"); progress.style.animation = "progress"; @@ -1831,7 +1829,7 @@ function carousel(id, images, delay = 3000) { let index = 0; - const carousel = d.createElement("div"); + const carousel = cE("div"); carousel.classList.add("carousel"); images.forEach((canvas, i) => { @@ -1959,7 +1957,7 @@ let errorElement = parent.querySelector(".error-message"); if (!errorElement) { - errorElement = d.createElement("div"); + errorElement = cE("div"); errorElement.classList.add("error-message"); parent.appendChild(errorElement); } diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index 52b64006b..82c778214 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -4,53 +4,12 @@ WLED Settings +
-
+

Imma firin ma lazer (if it has DMX support)

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 206d4a8c7..54ba9d8ba 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -4,20 +4,12 @@ LED Settings +
-
+

LED & Hardware setup

@@ -861,7 +800,7 @@ Swap:  ✕
Apply IR change to main segment only:
- +
IR info

Relay GPIO:  ✕
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index ff8231ccb..ce9bd8aa3 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -4,55 +4,9 @@ Misc Settings +
-
+


diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index e2fbd5eb7..c2f0ffbf2 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -4,75 +4,55 @@ Usermod Settings +
-
+

WiFi setup

diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 291f6f5fc..9d4e4c85b 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -18,6 +18,7 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; +static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? static bool isIp(String str) { @@ -237,6 +238,10 @@ void initServer() handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_liveview, PAGE_liveview_length); }); + server.on(_common_js, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + }); + //settings page server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); @@ -511,6 +516,10 @@ void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t erro void serveSettingsJS(AsyncWebServerRequest* request) { + if (request->url().indexOf(FPSTR(_common_js)) > 0) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + return; + } char buf[SETTINGS_STACK_BUF_SIZE+37]; buf[0] = 0; byte subPage = request->arg(F("p")).toInt(); From 88fb8605681a6417e27b7a09a40ae846c8f4d909 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 17 Sep 2024 16:34:38 +0200 Subject: [PATCH 473/694] SAVE_RAM bugfix introduced by #4137 --- wled00/wled.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/wled00/wled.h b/wled00/wled.h index 33dea8b03..31a612858 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -641,17 +641,19 @@ typedef class Receive { bool SegmentOptions : 1; bool SegmentBounds : 1; bool Direct : 1; - uint8_t reserved : 2; + bool Palette : 1; + uint8_t reserved : 1; }; }; Receive(int i) { Options = i; } - Receive(bool b, bool c, bool e, bool sO, bool sB) { - Brightness = b; - Color = c; - Effects = e; - SegmentOptions = sO; - SegmentBounds = sB; - }; + Receive(bool b, bool c, bool e, bool sO, bool sB, bool p) + : Brightness(b) + , Color(c) + , Effects(e) + , SegmentOptions(sO) + , SegmentBounds(sB) + , Palette(p) + {}; } __attribute__ ((aligned(1), packed)) receive_notification_t; typedef class Send { public: @@ -673,7 +675,7 @@ typedef class Send { Hue = h; } } __attribute__ ((aligned(1), packed)) send_notification_t; -WLED_GLOBAL receive_notification_t receiveN _INIT(0b00100111); +WLED_GLOBAL receive_notification_t receiveN _INIT(0b01100111); WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); #define receiveNotificationBrightness receiveN.Brightness #define receiveNotificationColor receiveN.Color From 72455ccde1f35c390e86a1038d2c2fd63d5e86fb Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 17 Sep 2024 19:47:24 +0200 Subject: [PATCH 474/694] Missing "not" --- wled00/data/settings_sync.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index bf5ce3979..34b9fc6cd 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -206,7 +206,7 @@ Hue status: Disabled in this build

Serial

- This firmware build does support Serial interface.
+ This firmware build does not support Serial interface.
Baud rate: From 696290527abfd01fcff3d4bd5fbfdc9c53050787 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 18 Sep 2024 22:10:27 +0200 Subject: [PATCH 475/694] cleanup and improved color_add() - optimized color_add() again: now it is as fast with preserved ratio scaling than the "fast" variant was before (if no scaling is needed, it is even faster). plus it saves 250 bytes of flash - bugfix in `color_fade()` - removed a lot of whitespaces --- wled00/FX.h | 24 +++++++-------- wled00/FX_2Dfcn.cpp | 26 ++++++++-------- wled00/FX_fcn.cpp | 16 +++++----- wled00/colors.cpp | 70 +++++++++++++++++++++----------------------- wled00/fcn_declare.h | 2 +- 5 files changed, 67 insertions(+), 71 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3c28274d6..bea4dbcb8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -595,9 +595,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } - inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } - inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } + inline void addPixelColor(int n, uint32_t color) { setPixelColor(n, color_add(getPixelColor(n), color)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } + inline void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; @@ -605,11 +605,11 @@ typedef struct Segment { // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns const unsigned cols = virtualWidth(); - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); } inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows const unsigned rows = virtualHeight(); - for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); + for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); } // 2D matrix @@ -632,10 +632,10 @@ typedef struct Segment { // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } - inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } - inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } + inline void addPixelColorXY(int x, int y, uint32_t color) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); @@ -670,9 +670,9 @@ typedef struct Segment { inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } - inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } + inline void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } + inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} inline void blur2D(uint8_t blur_amount, bool smear = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 57ee2e5e3..10b85a82e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -173,7 +173,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint8_t _bri_t = currentBri(); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } @@ -185,11 +185,11 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) y *= groupLength(); // expand to physical pixels int W = width(); int H = height(); - + int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically if(yY >= H) break; - int xX = x; + int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally if (xX >= W) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND @@ -293,8 +293,8 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ curnew = color_fade(cur, keep); if (x > 0) { if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(x - 1, row, prev); } else // first pixel @@ -326,15 +326,15 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { curnew = color_fade(cur, keep); if (y > 0) { if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(col, y - 1, prev); } else // first pixel setPixelColorXY(col, y, curnew); lastnew = curnew; last = cur; //save original value for comparison on next iteration - carryover = part; + carryover = part; } setPixelColorXY(col, rows - 1, curnew); } @@ -356,8 +356,8 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(x - 1, row, prev); } else setPixelColorXY(x, row, curnew); // first pixel @@ -375,14 +375,14 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(col, y - 1, prev); } else setPixelColorXY(col, y, curnew); // first pixel lastnew = curnew; last = cur; //save original value for comparison on next iteration - carryover = part; + carryover = part; } setPixelColorXY(col, rows - 1, curnew); } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 236f7ad4a..66aeaab63 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -712,12 +712,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D - int vStrip; + int vStrip; #endif if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call { #ifndef WLED_DISABLE_2D - vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) #endif i &= 0xFFFF; //truncate vstrip index if (i >= virtualLength() || i<0) return; // if pixel would still fall out of segment just exit @@ -735,7 +735,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) case M12_pBar: // expand 1D effect vertically or have it play on virtual strips if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); break; case M12_pArc: // expand in circular fashion from center @@ -796,7 +796,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) // Odd rays start further from center if prevRay started at center. static int prevRay = INT_MIN; // previous ray number if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel + int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel posx += inc_x * jump; posy += inc_y * jump; } @@ -1145,8 +1145,8 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (i > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColor(i - 1, prev); } else // first pixel @@ -1188,7 +1188,7 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * @returns Single color from palette */ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { - + uint32_t color = currentColor(mcol); // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { @@ -1196,7 +1196,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ return (pbri == 255) ? color : color_fade(color, pbri, true); } - uint8_t paletteIndex = i; + unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 104d25e60..54469ebe0 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -33,33 +33,32 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) /* * color add function that preserves ratio - * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * heavily optimized for speed by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) +uint32_t color_add(uint32_t c1, uint32_t c2) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); - uint32_t r = rb >> 16; - uint32_t b = rb & 0xFFFF; - uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once + uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + uint32_t r = rb >> 16; // extract single color values + uint32_t b = rb & 0xFFFF; uint32_t w = wg >> 16; - uint32_t g = wg & 0xFFFF; + uint32_t g = wg & 0xFFFF; - if (fast) { - r = r > 255 ? 255 : r; - g = g > 255 ? 255 : g; - b = b > 255 ? 255 : b; - w = w > 255 ? 255 : w; - return RGBW32(r,g,b,w); - } else { - unsigned max = r; - max = g > max ? g : max; - max = b > max ? b : max; - max = w > max ? w : max; - if (max < 256) return RGBW32(r, g, b, w); - else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + unsigned max = r; // check for overflow note: not checking and just topping out at 255 (formerly 'fast') is not any faster (but even slower if not overflowing) + max = g > max ? g : max; + max = b > max ? b : max; + max = w > max ? w : max; + + if (max > 255) { + uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & 0x00FF00FF; // + wg = (wg * scale) & 0xFF00FF00; } + else wg = wg << 8; //shift white and green back to correct position + return rb | wg; } /* @@ -70,52 +69,49 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (c1 == BLACK || amount == 0) return BLACK; - if (amount == 255) return c1; + if (amount == 255) return c1; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; - if (!video) amount++; // add one for correct scaling using bitshifts + if (!video) scale++; // add one for correct scaling using bitshifts else { // video scaling: make sure colors do not dim to zero if they started non-zero - addRemains = R(c1) ? 0x00010000 : 0; + addRemains = R(c1) ? 0x00010000 : 0; addRemains |= G(c1) ? 0x00000100 : 0; addRemains |= B(c1) ? 0x00000001 : 0; addRemains |= W(c1) ? 0x01000000 : 0; } uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green - scaledcolor = (rb | wg) + addRemains; + scaledcolor = (rb | wg) + addRemains; return scaledcolor; } // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { - if ( blendType == LINEARBLEND_NOWRAP) { - //index = map8(index, 0, 239); + if (blendType == LINEARBLEND_NOWRAP) { index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping } unsigned hi4 = byte(index) >> 4; - // We then add that to a base array pointer. const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); unsigned red1 = entry->r; unsigned green1 = entry->g; - unsigned blue1 = entry->b; + unsigned blue1 = entry->b; if(blendType != NOBLEND) { if(hi4 == 15) entry = &(pal[0]); else ++entry; - // unsigned red2 = entry->red; unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max - red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; - green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; + green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } if( brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - uint32_t scale = brightness + 1; // adjust for rounding (bitshift) + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) red1 = (red1 * scale) >> 8; green1 = (green1 * scale) >> 8; blue1 = (blue1 * scale) >> 8; - } + } return CRGB((uint8_t)red1, (uint8_t)green1, (uint8_t)blue1); } @@ -176,7 +172,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) harmonics[1] = basehue + 205 + random8(10); harmonics[2] = basehue - 5 + random8(10); break; - + case 3: // square harmonics[0] = basehue + 85 + random8(10); harmonics[1] = basehue + 175 + random8(10); @@ -213,9 +209,9 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) //apply saturation & gamma correction CRGB RGBpalettecolors[4]; for (int i = 0; i < 4; i++) { - if (makepastelpalette && palettecolors[i].saturation > 180) { + if (makepastelpalette && palettecolors[i].saturation > 180) { palettecolors[i].saturation -= 160; //desaturate all four colors - } + } RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ac941dc97..be7ed4462 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -80,7 +80,7 @@ class NeoGammaWLEDMethod { #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) [[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); -[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); +[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); From d4268ba070bc33d52193aafa7f7e1efe904d39dd Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 7 Sep 2024 19:51:23 -0400 Subject: [PATCH 476/694] handleFileRead: Skip duplicate FS check Since we validate the file existence ourselves, no need to have AsyncWebServer do it again. --- wled00/file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 69e1e692c..bc3467202 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -433,7 +433,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ } #endif if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) { - request->send(WLED_FS, path, String(), request->hasArg(F("download"))); + request->send(request->beginResponse(WLED_FS, path, {}, request->hasArg(F("download")), {})); return true; } return false; From 1346eb4f76805fc6210e31949e33a06fa44d813a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 8 Sep 2024 15:55:50 -0400 Subject: [PATCH 477/694] tools: Add all_xml fetch script Useful for checking that I haven't broken anything. --- tools/all_xml.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/all_xml.sh diff --git a/tools/all_xml.sh b/tools/all_xml.sh new file mode 100644 index 000000000..68ed07bbd --- /dev/null +++ b/tools/all_xml.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Pull all settings pages for comparison +HOST=$1 +TGT_PATH=$2 +CURL_ARGS="--compressed" + +# Replicate one target many times +function replicate() { + for i in {0..10} + do + echo -n " http://${HOST}/settings.js?p=$i -o ${TGT_PATH}/$i.xml" + done +} +read -a TARGETS <<< $(replicate) + +mkdir -p ${TGT_PATH} +curl ${CURL_ARGS} ${TARGETS[@]} From 32f9616b6e078e53da033f1054e747e04e8b30c3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 5 Sep 2024 21:13:55 -0400 Subject: [PATCH 478/694] Remove oappend Remove the large stack buffer as we're just going to copy it in to a heap buffer anyways. Later we can refine the length estimation or use a rope-style dynamic data structure like DynamicBufferList. --- wled00/fcn_declare.h | 14 +- wled00/mqtt.cpp | 34 +- wled00/set.cpp | 6 +- wled00/um_manager.cpp | 2 +- wled00/util.cpp | 69 +--- wled00/wled.h | 4 - wled00/wled_server.cpp | 18 +- wled00/xml.cpp | 772 ++++++++++++++++++++--------------------- 8 files changed, 442 insertions(+), 477 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a95064a2a..6ce30facf 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -302,7 +302,7 @@ class Usermod { virtual bool handleButton(uint8_t b) { return false; } // button overrides are possible here virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; // usermod data exchange [see examples for audio effects] virtual void connected() {} // called when WiFi is (re)connected - virtual void appendConfigData() {} // helper function called from usermod settings page to add metadata for entry fields + virtual void appendConfigData(Print&) {} // helper function called from usermod settings page to add metadata for entry fields virtual void addToJsonState(JsonObject& obj) {} // add JSON objects for WLED state virtual void addToJsonInfo(JsonObject& obj) {} // add JSON objects for UI Info page virtual void readFromJsonState(JsonObject& obj) {} // process JSON messages received from web server @@ -328,7 +328,7 @@ class UsermodManager { bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods void setup(); void connected(); - void appendConfigData(); + void appendConfigData(Print&); void addToJsonState(JsonObject& obj); void addToJsonInfo(JsonObject& obj); void readFromJsonState(JsonObject& obj); @@ -362,10 +362,8 @@ void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); bool getBoolVal(JsonVariant elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -bool oappend(const char* txt); // append new c string to temp buffer efficiently -bool oappendi(int i); // append new number to temp buffer efficiently -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); +void sappend(Print& dest, char stype, const char* key, int val); +void sappends(Print& dest, char stype, const char* key, char* val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); @@ -444,7 +442,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp void sendDataWs(AsyncWebSocketClient * client = nullptr); //xml.cpp -void XML_response(AsyncWebServerRequest *request, char* dest = nullptr); -void getSettingsJS(byte subPage, char* dest); +void XML_response(Print& dest); +void getSettingsJS(byte subPage, Print& dest); #endif diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 833e6eb7d..6694be07d 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -124,6 +124,32 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp payloadStr = nullptr; } +// Print adapter for flat buffers +namespace { +class bufferPrint : public Print { + char* _buf; + size_t _size, _offset; + public: + + bufferPrint(char* buf, size_t size) : _buf(buf), _size(size), _offset(0) {}; + + size_t write(const uint8_t *buffer, size_t size) { + size = std::min(size, _size - _offset); + memcpy(_buf + _offset, buffer, size); + _offset += size; + return size; + } + + size_t write(uint8_t c) { + return this->write(&c, 1); + } + + char* data() const { return _buf; } + size_t size() const { return _offset; } + size_t capacity() const { return _size; } +}; +}; // anonymous namespace + void publishMqtt() { @@ -148,11 +174,13 @@ void publishMqtt() strcat_P(subuf, PSTR("/status")); mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT - char apires[1024]; // allocating 1024 bytes from stack can be risky - XML_response(nullptr, apires); + // TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API. + DynamicBuffer buf(1024); + bufferPrint pbuf(buf.data(), buf.size()); + XML_response(pbuf); strlcpy(subuf, mqttDeviceTopic, 33); strcat_P(subuf, PSTR("/v")); - mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263) + mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263) #endif } diff --git a/wled00/set.cpp b/wled00/set.cpp index 812bcc52f..05b5b3181 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1191,7 +1191,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) // internal call, does not send XML response pos = req.indexOf(F("IN")); - if (pos < 1) XML_response(request); + if (pos < 1) { + auto response = request->beginResponseStream("text/xml"); + XML_response(*response); + request->send(response); + } return true; } diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 2db29c3cd..3970e7af4 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -8,7 +8,7 @@ void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++ void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } void UsermodManager::loop() { for (unsigned i = 0; i < numMods; i++) ums[i]->loop(); } void UsermodManager::handleOverlayDraw() { for (unsigned i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); } -void UsermodManager::appendConfigData() { for (unsigned i = 0; i < numMods; i++) ums[i]->appendConfigData(); } +void UsermodManager::appendConfigData(Print& dest) { for (unsigned i = 0; i < numMods; i++) ums[i]->appendConfigData(dest); } bool UsermodManager::handleButton(uint8_t b) { bool overrideIO = false; for (unsigned i = 0; i < numMods; i++) { diff --git a/wled00/util.cpp b/wled00/util.cpp index 99a75bdd3..00506ea97 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -89,88 +89,43 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv //append a numeric setting to string buffer -void sappend(char stype, const char* key, int val) +void sappend(Print& dest, char stype, const char* key, int val) { - char ds[] = "d.Sf."; - + const __FlashStringHelper* type_str; switch(stype) { case 'c': //checkbox - oappend(ds); - oappend(key); - oappend(".checked="); - oappendi(val); - oappend(";"); + type_str = F(".checked="); break; case 'v': //numeric - oappend(ds); - oappend(key); - oappend(".value="); - oappendi(val); - oappend(";"); + type_str = F(".value="); break; case 'i': //selectedIndex - oappend(ds); - oappend(key); - oappend(SET_F(".selectedIndex=")); - oappendi(val); - oappend(";"); + type_str = F(".selectedIndex="); break; + default: + return; //??? } + + dest.printf_P(PSTR("d.Sf.%s%s%d;"), key, type_str, val); } //append a string setting to buffer -void sappends(char stype, const char* key, char* val) +void sappends(Print& dest, char stype, const char* key, char* val) { switch(stype) { case 's': {//string (we can interpret val as char*) - String buf = val; - //convert "%" to "%%" to make EspAsyncWebServer happy - //buf.replace("%","%%"); - oappend("d.Sf."); - oappend(key); - oappend(".value=\""); - oappend(buf.c_str()); - oappend("\";"); + dest.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); break;} case 'm': //message - oappend(SET_F("d.getElementsByClassName")); - oappend(key); - oappend(SET_F(".innerHTML=\"")); - oappend(val); - oappend("\";"); + dest.printf_P(PSTR("d.getElementsByClassName%s.innerHTML=\"%s\";"), key, val); break; } } -bool oappendi(int i) -{ - char s[12]; // 32bit signed number can have 10 digits plus - sign - sprintf(s, "%d", i); - return oappend(s); -} - - -bool oappend(const char* txt) -{ - unsigned len = strlen(txt); - if ((obuf == nullptr) || (olen + len >= SETTINGS_STACK_BUF_SIZE)) { // sanity checks -#ifdef WLED_DEBUG - DEBUG_PRINT(F("oappend() buffer overflow. Cannot append ")); - DEBUG_PRINT(len); DEBUG_PRINT(F(" bytes \t\"")); - DEBUG_PRINT(txt); DEBUG_PRINTLN(F("\"")); -#endif - return false; // buffer full - } - strcpy(obuf + olen, txt); - olen += len; - return true; -} - - void prepareHostname(char* hostname) { sprintf_P(hostname, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); diff --git a/wled00/wled.h b/wled00/wled.h index 31a612858..052f29b29 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -839,10 +839,6 @@ WLED_GLOBAL time_t sunrise _INIT(0); WLED_GLOBAL time_t sunset _INIT(0); WLED_GLOBAL Toki toki _INIT(Toki()); -// Temp buffer -WLED_GLOBAL char* obuf; -WLED_GLOBAL uint16_t olen _INIT(0); - // General filesystem WLED_GLOBAL size_t fsBytesUsed _INIT(0); WLED_GLOBAL size_t fsBytesTotal _INIT(0); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 9d4e4c85b..8fdcb1ebe 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -520,27 +520,23 @@ void serveSettingsJS(AsyncWebServerRequest* request) handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); return; } - char buf[SETTINGS_STACK_BUF_SIZE+37]; - buf[0] = 0; byte subPage = request->arg(F("p")).toInt(); if (subPage > 10) { - strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');")); - request->send(501, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); + request->send_P(501, FPSTR(CONTENT_TYPE_JAVASCRIPT), PSTR("alert('Settings for this request are not implemented.');")); return; } if (subPage > 0 && !correctPIN && strlen(settingsPIN)>0) { - strcpy_P(buf, PSTR("alert('PIN incorrect.');")); - request->send(401, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); + request->send_P(401, FPSTR(CONTENT_TYPE_JAVASCRIPT), PSTR("alert('PIN incorrect.');")); return; } - strcat_P(buf,PSTR("function GetV(){var d=document;")); - getSettingsJS(subPage, buf+strlen(buf)); // this may overflow by 35bytes!!! - strcat_P(buf,PSTR("}")); - AsyncWebServerResponse *response; - response = request->beginResponse(200, FPSTR(CONTENT_TYPE_JAVASCRIPT), buf); + AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT)); response->addHeader(F("Cache-Control"), F("no-store")); response->addHeader(F("Expires"), F("0")); + + response->print(F("function GetV(){var d=document;")); + getSettingsJS(subPage, *response); + response->print(F("}")); request->send(response); } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 71d66d002..2d63d61f3 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -6,85 +6,80 @@ */ //build XML response to HTTP /win API request -void XML_response(AsyncWebServerRequest *request, char* dest) +void XML_response(Print& dest) { - char sbuf[(dest == nullptr)?1024:1]; //allocate local buffer if none passed - obuf = (dest == nullptr)? sbuf:dest; - - olen = 0; - oappend(SET_F("")); - oappendi((nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); - oappend(SET_F("")); + dest.print(F("")); + dest.print((nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); + dest.print(F("")); for (int i = 0; i < 3; i++) { - oappend(""); - oappendi(col[i]); - oappend(""); + dest.print(""); + dest.print(col[i]); + dest.print(""); } for (int i = 0; i < 3; i++) { - oappend(""); - oappendi(colSec[i]); - oappend(""); + dest.print(""); + dest.print(colSec[i]); + dest.print(""); } - oappend(SET_F("")); - oappendi(notifyDirect); - oappend(SET_F("")); - oappendi(receiveGroups!=0); - oappend(SET_F("")); - oappendi(nightlightActive); - oappend(SET_F("")); - oappendi(nightlightMode > NL_MODE_SET); - oappend(SET_F("")); - oappendi(nightlightDelayMins); - oappend(SET_F("")); - oappendi(nightlightTargetBri); - oappend(SET_F("")); - oappendi(effectCurrent); - oappend(SET_F("")); - oappendi(effectSpeed); - oappend(SET_F("")); - oappendi(effectIntensity); - oappend(SET_F("")); - oappendi(effectPalette); - oappend(SET_F("")); + dest.print(F("")); + dest.print(notifyDirect); + dest.print(F("")); + dest.print(receiveGroups!=0); + dest.print(F("")); + dest.print(nightlightActive); + dest.print(F("")); + dest.print(nightlightMode > NL_MODE_SET); + dest.print(F("")); + dest.print(nightlightDelayMins); + dest.print(F("")); + dest.print(nightlightTargetBri); + dest.print(F("")); + dest.print(effectCurrent); + dest.print(F("")); + dest.print(effectSpeed); + dest.print(F("")); + dest.print(effectIntensity); + dest.print(F("")); + dest.print(effectPalette); + dest.print(F("")); if (strip.hasWhiteChannel()) { - oappendi(col[3]); + dest.print(col[3]); } else { - oappend("-1"); + dest.print("-1"); } - oappend(SET_F("")); - oappendi(colSec[3]); - oappend(SET_F("")); - oappendi(currentPreset); - oappend(SET_F("")); - oappendi(currentPlaylist >= 0); - oappend(SET_F("")); - oappend(serverDescription); + dest.print(F("")); + dest.print(colSec[3]); + dest.print(F("")); + dest.print(currentPreset); + dest.print(F("")); + dest.print(currentPlaylist >= 0); + dest.print(F("")); + dest.print(serverDescription); if (realtimeMode) { - oappend(SET_F(" (live)")); + dest.print(F(" (live)")); } - oappend(SET_F("")); - oappendi(strip.getFirstSelectedSegId()); - oappend(SET_F("")); - if (request != nullptr) request->send(200, "text/xml", obuf); + dest.print(F("")); + dest.print(strip.getFirstSelectedSegId()); + dest.print(F("")); } -void extractPin(JsonObject &obj, const char *key) { +static void extractPin(Print& dest, JsonObject &obj, const char *key) { if (obj[key].is()) { JsonArray pins = obj[key].as(); for (JsonVariant pv : pins) { - if (pv.as() > -1) { oappend(","); oappendi(pv.as()); } + if (pv.as() > -1) { dest.print(","); dest.print(pv.as()); } } } else { - if (obj[key].as() > -1) { oappend(","); oappendi(obj[key].as()); } + if (obj[key].as() > -1) { dest.print(","); dest.print(obj[key].as()); } } } -// oappend used pins by scanning JsonObject (1 level deep) -void fillUMPins(JsonObject &mods) +// dest.print used pins by scanning JsonObject (1 level deep) +void fillUMPins(Print& dest, JsonObject &mods) { for (JsonPair kv : mods) { // kv.key() is usermod name or subobject key @@ -93,7 +88,7 @@ void fillUMPins(JsonObject &mods) if (!obj.isNull()) { // element is an JsonObject if (!obj["pin"].isNull()) { - extractPin(obj, "pin"); + extractPin(dest, obj, "pin"); } else { // scan keys (just one level deep as is possible with usermods) for (JsonPair so : obj) { @@ -102,7 +97,7 @@ void fillUMPins(JsonObject &mods) // we found a key containing "pin" substring if (strlen(strstr(key, "pin")) == 3) { // and it is at the end, we found another pin - extractPin(obj, key); + extractPin(dest, obj, key); continue; } } @@ -110,7 +105,7 @@ void fillUMPins(JsonObject &mods) JsonObject subObj = obj[so.key()]; if (!subObj["pin"].isNull()) { // get pins from subobject - extractPin(subObj, "pin"); + extractPin(dest, subObj, "pin"); } } } @@ -118,101 +113,99 @@ void fillUMPins(JsonObject &mods) } } -void appendGPIOinfo() { +void appendGPIOinfo(Print& dest) { char nS[8]; // add usermod pins as d.um_p array - oappend(SET_F("d.um_p=[-1")); // has to have 1 element + dest.print(F("d.um_p=[-1")); // has to have 1 element if (i2c_sda > -1 && i2c_scl > -1) { - oappend(","); oappend(itoa(i2c_sda,nS,10)); - oappend(","); oappend(itoa(i2c_scl,nS,10)); + dest.print(","); dest.print(itoa(i2c_sda,nS,10)); + dest.print(","); dest.print(itoa(i2c_scl,nS,10)); } if (spi_mosi > -1 && spi_sclk > -1) { - oappend(","); oappend(itoa(spi_mosi,nS,10)); - oappend(","); oappend(itoa(spi_sclk,nS,10)); + dest.print(","); dest.print(itoa(spi_mosi,nS,10)); + dest.print(","); dest.print(itoa(spi_sclk,nS,10)); } // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins JsonObject mods = pDoc->createNestedObject(F("um")); usermods.addToConfig(mods); - if (!mods.isNull()) fillUMPins(mods); + if (!mods.isNull()) fillUMPins(dest, mods); releaseJSONBufferLock(); } - oappend(SET_F("];")); + dest.print(F("];")); // add reserved (unusable) pins - oappend(SET_F("d.rsvd=[")); + dest.print(SET_F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (!pinManager.isPinOk(i, false)) { // include readonly pins - oappendi(i); oappend(","); + dest.print(i); dest.print(","); } } #ifdef WLED_ENABLE_DMX - oappend(SET_F("2,")); // DMX hardcoded pin + dest.print(SET_F("2,")); // DMX hardcoded pin #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - oappend(itoa(hardwareTX,nS,10)); oappend(","); // debug output (TX) pin + dest.print(itoa(hardwareTX,nS,10)); dest.print(","); // debug output (TX) pin #endif //Note: Using pin 3 (RX) disables Adalight / Serial JSON #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { oappend(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); oappend(","); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { oappend(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); oappend(","); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { oappend(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); oappend(","); } + for (unsigned p=0; p=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); dest.print(","); } + if (ethernetBoards[ethernetType].eth_mdc>=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); dest.print(","); } + if (ethernetBoards[ethernetType].eth_mdio>=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); dest.print(","); } switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: - oappend(SET_F("0")); + dest.print(SET_F("0")); break; case ETH_CLOCK_GPIO16_OUT: - oappend(SET_F("16")); + dest.print(SET_F("16")); break; case ETH_CLOCK_GPIO17_OUT: - oappend(SET_F("17")); + dest.print(SET_F("17")); break; } } #endif - oappend(SET_F("];")); // rsvd + dest.print(SET_F("];")); // rsvd // add info for read-only GPIO - oappend(SET_F("d.ro_gpio=[")); + dest.print(SET_F("d.ro_gpio=[")); bool firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (pinManager.isReadOnlyPin(i)) { // No comma before the first pin - if (!firstPin) oappend(SET_F(",")); - oappendi(i); + if (!firstPin) dest.print(SET_F(",")); + dest.print(i); firstPin = false; } } - oappend(SET_F("];")); + dest.print(SET_F("];")); // add info about max. # of pins - oappend(SET_F("d.max_gpio=")); - oappendi(WLED_NUM_PINS); - oappend(SET_F(";")); + dest.print(SET_F("d.max_gpio=")); + dest.print(WLED_NUM_PINS); + dest.print(SET_F(";")); } //get values for settings form in javascript -void getSettingsJS(byte subPage, char* dest) +void getSettingsJS(byte subPage, Print& dest) { //0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); - obuf = dest; - olen = 0; if (subPage <0 || subPage >10) return; if (subPage == SUBPAGE_MENU) { #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in - oappend(PSTR("gId('2dbtn').style.display='none';")); + dest.print(F("gId('2dbtn').style.display='none';")); #endif #ifdef WLED_ENABLE_DMX // include only if DMX is enabled - oappend(PSTR("gId('dmxbtn').style.display='';")); + dest.print(F("gId('dmxbtn').style.display='';")); #endif } @@ -220,65 +213,65 @@ void getSettingsJS(byte subPage, char* dest) { char nS[10]; size_t l; - oappend(SET_F("resetWiFi(")); - oappend(itoa(WLED_MAX_WIFI_COUNT,nS,10)); - oappend(SET_F(");")); + dest.print(F("resetWiFi(")); + dest.print(WLED_MAX_WIFI_COUNT); + dest.print(F(");")); for (size_t n = 0; n < multiWiFi.size(); n++) { l = strlen(multiWiFi[n].clientPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - oappend(SET_F("addWiFi(\"")); - oappend(multiWiFi[n].clientSSID); - oappend(SET_F("\",\"")); - oappend(fpass); - oappend(SET_F("\",0x")); - oappend(itoa(multiWiFi[n].staticIP,nS,16)); - oappend(SET_F(",0x")); - oappend(itoa(multiWiFi[n].staticGW,nS,16)); - oappend(SET_F(",0x")); - oappend(itoa(multiWiFi[n].staticSN,nS,16)); - oappend(SET_F(");")); + dest.print(F("addWiFi(\"")); + dest.print(multiWiFi[n].clientSSID); + dest.print(F("\",\"")); + dest.print(fpass); + dest.print(F("\",0x")); + dest.print(itoa(multiWiFi[n].staticIP,nS,16)); + dest.print(F(",0x")); + dest.print(itoa(multiWiFi[n].staticGW,nS,16)); + dest.print(F(",0x")); + dest.print(itoa(multiWiFi[n].staticSN,nS,16)); + dest.print(F(");")); } - sappend('v',SET_F("D0"),dnsAddress[0]); - sappend('v',SET_F("D1"),dnsAddress[1]); - sappend('v',SET_F("D2"),dnsAddress[2]); - sappend('v',SET_F("D3"),dnsAddress[3]); + sappend(dest,'v',SET_F("D0"),dnsAddress[0]); + sappend(dest,'v',SET_F("D1"),dnsAddress[1]); + sappend(dest,'v',SET_F("D2"),dnsAddress[2]); + sappend(dest,'v',SET_F("D3"),dnsAddress[3]); - sappends('s',SET_F("CM"),cmDNS); - sappend('i',SET_F("AB"),apBehavior); - sappends('s',SET_F("AS"),apSSID); - sappend('c',SET_F("AH"),apHide); + sappends(dest,'s',SET_F("CM"),cmDNS); + sappend(dest,'i',SET_F("AB"),apBehavior); + sappends(dest,'s',SET_F("AS"),apSSID); + sappend(dest,'c',SET_F("AH"),apHide); l = strlen(apPass); char fapass[l+1]; //fill password field with *** fapass[l] = 0; memset(fapass,'*',l); - sappends('s',SET_F("AP"),fapass); + sappends(dest,'s',SET_F("AP"),fapass); - sappend('v',SET_F("AC"),apChannel); + sappend(dest,'v',SET_F("AC"),apChannel); #ifdef ARDUINO_ARCH_ESP32 - sappend('v',SET_F("TX"),txPower); + sappend(dest,'v',SET_F("TX"),txPower); #else - oappend(SET_F("gId('tx').style.display='none';")); + dest.print(F("gId('tx').style.display='none';")); #endif - sappend('c',SET_F("FG"),force802_3g); - sappend('c',SET_F("WS"),noWifiSleep); + sappend(dest,'c',SET_F("FG"),force802_3g); + sappend(dest,'c',SET_F("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW - sappend('c',SET_F("RE"),enableESPNow); - sappends('s',SET_F("RMAC"),linked_remote); + sappend(dest,'c',SET_F("RE"),enableESPNow); + sappends(dest,'s',SET_F("RMAC"),linked_remote); #else //hide remote settings if not compiled - oappend(SET_F("toggle('ESPNOW');")); // hide ESP-NOW setting + dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif #ifdef WLED_USE_ETHERNET - sappend('v',SET_F("ETH"),ethernetType); + sappend(dest,'v',SET_F("ETH"),ethernetType); #else //hide ethernet setting if not compiled in - oappend(SET_F("gId('ethd').style.display='none';")); + dest.print(F("gId('ethd').style.display='none';")); #endif if (Network.isConnected()) //is connected @@ -290,10 +283,10 @@ void getSettingsJS(byte subPage, char* dest) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); #endif - sappends('m',SET_F("(\"sip\")[0]"),s); + sappends(dest,'m',SET_F("(\"sip\")[0]"),s); } else { - sappends('m',SET_F("(\"sip\")[0]"),(char*)F("Not connected")); + sappends(dest,'m',SET_F("(\"sip\")[0]"),(char*)F("Not connected")); } if (WiFi.softAPIP()[0] != 0) //is active @@ -301,19 +294,19 @@ void getSettingsJS(byte subPage, char* dest) char s[16]; IPAddress apIP = WiFi.softAPIP(); sprintf(s, "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); - sappends('m',SET_F("(\"sip\")[1]"),s); + sappends(dest,'m',SET_F("(\"sip\")[1]"),s); } else { - sappends('m',SET_F("(\"sip\")[1]"),(char*)F("Not active")); + sappends(dest,'m',SET_F("(\"sip\")[1]"),(char*)F("Not active")); } #ifndef WLED_DISABLE_ESPNOW if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote - sappends('m',SET_F("(\"rlid\")[0]"),last_signal_src); + sappends(dest,'m',SET_F("(\"rlid\")[0]"),last_signal_src); } else if (!enableESPNow) { - sappends('m',SET_F("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); + sappends(dest,'m',SET_F("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); } else { - sappends('m',SET_F("(\"rlid\")[0]"),(char*)F("None")); + sappends(dest,'m',SET_F("(\"rlid\")[0]"),(char*)F("None")); } #endif } @@ -322,30 +315,30 @@ void getSettingsJS(byte subPage, char* dest) { char nS[32]; - appendGPIOinfo(); + appendGPIOinfo(dest); - oappend(SET_F("d.ledTypes=")); oappend(BusManager::getLEDTypesJSONString().c_str()); oappend(";"); + dest.print(SET_F("d.ledTypes=")); dest.print(BusManager::getLEDTypesJSONString().c_str()); dest.print(";"); // set limits - oappend(SET_F("bLimits(")); - oappend(itoa(WLED_MAX_BUSSES,nS,10)); oappend(","); - oappend(itoa(WLED_MIN_VIRTUAL_BUSSES,nS,10)); oappend(","); - oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); oappend(","); - oappend(itoa(MAX_LED_MEMORY,nS,10)); oappend(","); - oappend(itoa(MAX_LEDS,nS,10)); oappend(","); - oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); oappend(","); - oappend(itoa(WLED_MAX_DIGITAL_CHANNELS,nS,10)); oappend(","); - oappend(itoa(WLED_MAX_ANALOG_CHANNELS,nS,10)); - oappend(SET_F(");")); + dest.print(F("bLimits(")); + dest.print(itoa(WLED_MAX_BUSSES,nS,10)); dest.print(","); + dest.print(itoa(WLED_MIN_VIRTUAL_BUSSES,nS,10)); dest.print(","); + dest.print(itoa(MAX_LEDS_PER_BUS,nS,10)); dest.print(","); + dest.print(itoa(MAX_LED_MEMORY,nS,10)); dest.print(","); + dest.print(itoa(MAX_LEDS,nS,10)); dest.print(","); + dest.print(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); dest.print(","); + dest.print(itoa(WLED_MAX_DIGITAL_CHANNELS,nS,10)); dest.print(","); + dest.print(itoa(WLED_MAX_ANALOG_CHANNELS,nS,10)); + dest.print(F(");")); - sappend('c',SET_F("MS"),strip.autoSegments); - sappend('c',SET_F("CCT"),strip.correctWB); - sappend('c',SET_F("IC"),cctICused); - sappend('c',SET_F("CR"),strip.cctFromRgb); - sappend('v',SET_F("CB"),strip.cctBlending); - sappend('v',SET_F("FR"),strip.getTargetFps()); - sappend('v',SET_F("AW"),Bus::getGlobalAWMode()); - sappend('c',SET_F("LD"),useGlobalLedBuffer); + sappend(dest,'c',SET_F("MS"),strip.autoSegments); + sappend(dest,'c',SET_F("CCT"),strip.correctWB); + sappend(dest,'c',SET_F("IC"),cctICused); + sappend(dest,'c',SET_F("CR"),strip.cctFromRgb); + sappend(dest,'v',SET_F("CB"),strip.cctBlending); + sappend(dest,'v',SET_F("FR"),strip.getTargetFps()); + sappend(dest,'v',SET_F("AW"),Bus::getGlobalAWMode()); + sappend(dest,'c',SET_F("LD"),useGlobalLedBuffer); unsigned sumMa = 0; for (int s = 0; s < BusManager::getNumBusses(); s++) { @@ -365,22 +358,22 @@ void getSettingsJS(byte subPage, char* dest) char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current - oappend(SET_F("addLEDs(1);")); + dest.print(F("addLEDs(1);")); uint8_t pins[5]; int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); + if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend(dest,'v',lp,pins[i]); } - sappend('v',lc,bus->getLength()); - sappend('v',lt,bus->getType()); - sappend('v',co,bus->getColorOrder() & 0x0F); - sappend('v',ls,bus->getStart()); - sappend('c',cv,bus->isReversed()); - sappend('v',sl,bus->skippedLeds()); - sappend('c',rf,bus->isOffRefreshRequired()); - sappend('v',aw,bus->getAutoWhiteMode()); - sappend('v',wo,bus->getColorOrder() >> 4); + sappend(dest,'v',lc,bus->getLength()); + sappend(dest,'v',lt,bus->getType()); + sappend(dest,'v',co,bus->getColorOrder() & 0x0F); + sappend(dest,'v',ls,bus->getStart()); + sappend(dest,'c',cv,bus->isReversed()); + sappend(dest,'v',sl,bus->skippedLeds()); + sappend(dest,'c',rf,bus->isOffRefreshRequired()); + sappend(dest,'v',aw,bus->getAutoWhiteMode()); + sappend(dest,'v',wo,bus->getColorOrder() >> 4); unsigned speed = bus->getFrequency(); if (bus->isPWM()) { switch (speed) { @@ -401,158 +394,158 @@ void getSettingsJS(byte subPage, char* dest) case 20000 : speed = 4; break; } } - sappend('v',sp,speed); - sappend('v',la,bus->getLEDCurrent()); - sappend('v',ma,bus->getMaxCurrent()); + sappend(dest,'v',sp,speed); + sappend(dest,'v',la,bus->getLEDCurrent()); + sappend(dest,'v',ma,bus->getMaxCurrent()); sumMa += bus->getMaxCurrent(); } - sappend('v',SET_F("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); - sappend('c',SET_F("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); - sappend('c',SET_F("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); + sappend(dest,'v',SET_F("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); + sappend(dest,'c',SET_F("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); + sappend(dest,'c',SET_F("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); - oappend(SET_F("resetCOM(")); - oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); - oappend(SET_F(");")); + dest.print(F("resetCOM(")); + dest.print(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); + dest.print(F(");")); const ColorOrderMap& com = BusManager::getColorOrderMap(); for (int s = 0; s < com.count(); s++) { const ColorOrderMapEntry* entry = com.get(s); if (entry == nullptr) break; - oappend(SET_F("addCOM(")); - oappend(itoa(entry->start,nS,10)); oappend(","); - oappend(itoa(entry->len,nS,10)); oappend(","); - oappend(itoa(entry->colorOrder,nS,10)); oappend(");"); + dest.print(F("addCOM(")); + dest.print(itoa(entry->start,nS,10)); dest.print(","); + dest.print(itoa(entry->len,nS,10)); dest.print(","); + dest.print(itoa(entry->colorOrder,nS,10)); dest.print(");"); } - sappend('v',SET_F("CA"),briS); + sappend(dest,'v',SET_F("CA"),briS); - sappend('c',SET_F("BO"),turnOnAtBoot); - sappend('v',SET_F("BP"),bootPreset); + sappend(dest,'c',SET_F("BO"),turnOnAtBoot); + sappend(dest,'v',SET_F("BP"),bootPreset); - sappend('c',SET_F("GB"),gammaCorrectBri); - sappend('c',SET_F("GC"),gammaCorrectCol); - dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS); - sappend('c',SET_F("TF"),fadeTransition); - sappend('c',SET_F("EB"),modeBlending); - sappend('v',SET_F("TD"),transitionDelayDefault); - sappend('c',SET_F("PF"),strip.paletteFade); - sappend('v',SET_F("TP"),randomPaletteChangeTime); - sappend('c',SET_F("TH"),useHarmonicRandomPalette); - sappend('v',SET_F("BF"),briMultiplier); - sappend('v',SET_F("TB"),nightlightTargetBri); - sappend('v',SET_F("TL"),nightlightDelayMinsDefault); - sappend('v',SET_F("TW"),nightlightMode); - sappend('i',SET_F("PB"),strip.paletteBlend); - sappend('v',SET_F("RL"),rlyPin); - sappend('c',SET_F("RM"),rlyMde); - sappend('c',SET_F("RO"),rlyOpenDrain); + sappend(dest,'c',SET_F("GB"),gammaCorrectBri); + sappend(dest,'c',SET_F("GC"),gammaCorrectCol); + dtostrf(gammaCorrectVal,3,1,nS); sappends(dest,'s',SET_F("GV"),nS); + sappend(dest,'c',SET_F("TF"),fadeTransition); + sappend(dest,'c',SET_F("EB"),modeBlending); + sappend(dest,'v',SET_F("TD"),transitionDelayDefault); + sappend(dest,'c',SET_F("PF"),strip.paletteFade); + sappend(dest,'v',SET_F("TP"),randomPaletteChangeTime); + sappend(dest,'c',SET_F("TH"),useHarmonicRandomPalette); + sappend(dest,'v',SET_F("BF"),briMultiplier); + sappend(dest,'v',SET_F("TB"),nightlightTargetBri); + sappend(dest,'v',SET_F("TL"),nightlightDelayMinsDefault); + sappend(dest,'v',SET_F("TW"),nightlightMode); + sappend(dest,'i',SET_F("PB"),strip.paletteBlend); + sappend(dest,'v',SET_F("RL"),rlyPin); + sappend(dest,'c',SET_F("RM"),rlyMde); + sappend(dest,'c',SET_F("RO"),rlyOpenDrain); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { - oappend(SET_F("addBtn(")); - oappend(itoa(i,nS,10)); oappend(","); - oappend(itoa(btnPin[i],nS,10)); oappend(","); - oappend(itoa(buttonType[i],nS,10)); - oappend(SET_F(");")); + dest.print(F("addBtn(")); + dest.print(itoa(i,nS,10)); dest.print(","); + dest.print(itoa(btnPin[i],nS,10)); dest.print(","); + dest.print(itoa(buttonType[i],nS,10)); + dest.print(F(");")); } - sappend('c',SET_F("IP"),disablePullUp); - sappend('v',SET_F("TT"),touchThreshold); + sappend(dest,'c',SET_F("IP"),disablePullUp); + sappend(dest,'v',SET_F("TT"),touchThreshold); #ifndef WLED_DISABLE_INFRARED - sappend('v',SET_F("IR"),irPin); - sappend('v',SET_F("IT"),irEnabled); + sappend(dest,'v',SET_F("IR"),irPin); + sappend(dest,'v',SET_F("IT"),irEnabled); #endif - sappend('c',SET_F("MSO"),!irApplyToAllSelected); + sappend(dest,'c',SET_F("MSO"),!irApplyToAllSelected); } if (subPage == SUBPAGE_UI) { - sappends('s',SET_F("DS"),serverDescription); - sappend('c',SET_F("SU"),simplifiedUI); + sappends(dest,'s',SET_F("DS"),serverDescription); + sappend(dest,'c',SET_F("SU"),simplifiedUI); } if (subPage == SUBPAGE_SYNC) { [[maybe_unused]] char nS[32]; - sappend('v',SET_F("UP"),udpPort); - sappend('v',SET_F("U2"),udpPort2); + sappend(dest,'v',SET_F("UP"),udpPort); + sappend(dest,'v',SET_F("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW - if (enableESPNow) sappend('c',SET_F("EN"),useESPNowSync); - else oappend(SET_F("toggle('ESPNOW');")); // hide ESP-NOW setting + if (enableESPNow) sappend(dest,'c',SET_F("EN"),useESPNowSync); + else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #else - oappend(SET_F("toggle('ESPNOW');")); // hide ESP-NOW setting + dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif - sappend('v',SET_F("GS"),syncGroups); - sappend('v',SET_F("GR"),receiveGroups); + sappend(dest,'v',SET_F("GS"),syncGroups); + sappend(dest,'v',SET_F("GR"),receiveGroups); - sappend('c',SET_F("RB"),receiveNotificationBrightness); - sappend('c',SET_F("RC"),receiveNotificationColor); - sappend('c',SET_F("RX"),receiveNotificationEffects); - sappend('c',SET_F("RP"),receiveNotificationPalette); - sappend('c',SET_F("SO"),receiveSegmentOptions); - sappend('c',SET_F("SG"),receiveSegmentBounds); - sappend('c',SET_F("SS"),sendNotifications); - sappend('c',SET_F("SD"),notifyDirect); - sappend('c',SET_F("SB"),notifyButton); - sappend('c',SET_F("SH"),notifyHue); - sappend('v',SET_F("UR"),udpNumRetries); + sappend(dest,'c',SET_F("RB"),receiveNotificationBrightness); + sappend(dest,'c',SET_F("RC"),receiveNotificationColor); + sappend(dest,'c',SET_F("RX"),receiveNotificationEffects); + sappend(dest,'c',SET_F("RP"),receiveNotificationPalette); + sappend(dest,'c',SET_F("SO"),receiveSegmentOptions); + sappend(dest,'c',SET_F("SG"),receiveSegmentBounds); + sappend(dest,'c',SET_F("SS"),sendNotifications); + sappend(dest,'c',SET_F("SD"),notifyDirect); + sappend(dest,'c',SET_F("SB"),notifyButton); + sappend(dest,'c',SET_F("SH"),notifyHue); + sappend(dest,'v',SET_F("UR"),udpNumRetries); - sappend('c',SET_F("NL"),nodeListEnabled); - sappend('c',SET_F("NB"),nodeBroadcastEnabled); + sappend(dest,'c',SET_F("NL"),nodeListEnabled); + sappend(dest,'c',SET_F("NB"),nodeBroadcastEnabled); - sappend('c',SET_F("RD"),receiveDirect); - sappend('c',SET_F("MO"),useMainSegmentOnly); - sappend('c',SET_F("RLM"),realtimeRespectLedMaps); - sappend('v',SET_F("EP"),e131Port); - sappend('c',SET_F("ES"),e131SkipOutOfSequence); - sappend('c',SET_F("EM"),e131Multicast); - sappend('v',SET_F("EU"),e131Universe); - sappend('v',SET_F("DA"),DMXAddress); - sappend('v',SET_F("XX"),DMXSegmentSpacing); - sappend('v',SET_F("PY"),e131Priority); - sappend('v',SET_F("DM"),DMXMode); - sappend('v',SET_F("ET"),realtimeTimeoutMs); - sappend('c',SET_F("FB"),arlsForceMaxBri); - sappend('c',SET_F("RG"),arlsDisableGammaCorrection); - sappend('v',SET_F("WO"),arlsOffset); + sappend(dest,'c',SET_F("RD"),receiveDirect); + sappend(dest,'c',SET_F("MO"),useMainSegmentOnly); + sappend(dest,'c',SET_F("RLM"),realtimeRespectLedMaps); + sappend(dest,'v',SET_F("EP"),e131Port); + sappend(dest,'c',SET_F("ES"),e131SkipOutOfSequence); + sappend(dest,'c',SET_F("EM"),e131Multicast); + sappend(dest,'v',SET_F("EU"),e131Universe); + sappend(dest,'v',SET_F("DA"),DMXAddress); + sappend(dest,'v',SET_F("XX"),DMXSegmentSpacing); + sappend(dest,'v',SET_F("PY"),e131Priority); + sappend(dest,'v',SET_F("DM"),DMXMode); + sappend(dest,'v',SET_F("ET"),realtimeTimeoutMs); + sappend(dest,'c',SET_F("FB"),arlsForceMaxBri); + sappend(dest,'c',SET_F("RG"),arlsDisableGammaCorrection); + sappend(dest,'v',SET_F("WO"),arlsOffset); #ifndef WLED_DISABLE_ALEXA - sappend('c',SET_F("AL"),alexaEnabled); - sappends('s',SET_F("AI"),alexaInvocationName); - sappend('c',SET_F("SA"),notifyAlexa); - sappend('v',SET_F("AP"),alexaNumPresets); + sappend(dest,'c',SET_F("AL"),alexaEnabled); + sappends(dest,'s',SET_F("AI"),alexaInvocationName); + sappend(dest,'c',SET_F("SA"),notifyAlexa); + sappend(dest,'v',SET_F("AP"),alexaNumPresets); #else - oappend(SET_F("toggle('Alexa');")); // hide Alexa settings + dest.print(F("toggle('Alexa');")); // hide Alexa settings #endif #ifndef WLED_DISABLE_MQTT - sappend('c',SET_F("MQ"),mqttEnabled); - sappends('s',SET_F("MS"),mqttServer); - sappend('v',SET_F("MQPORT"),mqttPort); - sappends('s',SET_F("MQUSER"),mqttUser); + sappend(dest,'c',SET_F("MQ"),mqttEnabled); + sappends(dest,'s',SET_F("MS"),mqttServer); + sappend(dest,'v',SET_F("MQPORT"),mqttPort); + sappends(dest,'s',SET_F("MQUSER"),mqttUser); byte l = strlen(mqttPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - sappends('s',SET_F("MQPASS"),fpass); - sappends('s',SET_F("MQCID"),mqttClientID); - sappends('s',"MD",mqttDeviceTopic); - sappends('s',SET_F("MG"),mqttGroupTopic); - sappend('c',SET_F("BM"),buttonPublishMqtt); - sappend('c',SET_F("RT"),retainMqttMsg); - oappend(SET_F("d.Sf.MD.maxlength=")); oappend(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); oappend(SET_F(";")); - oappend(SET_F("d.Sf.MG.maxlength=")); oappend(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); oappend(SET_F(";")); - oappend(SET_F("d.Sf.MS.maxlength=")); oappend(itoa(MQTT_MAX_SERVER_LEN,nS,10)); oappend(SET_F(";")); + sappends(dest,'s',SET_F("MQPASS"),fpass); + sappends(dest,'s',SET_F("MQCID"),mqttClientID); + sappends(dest,'s',"MD",mqttDeviceTopic); + sappends(dest,'s',SET_F("MG"),mqttGroupTopic); + sappend(dest,'c',SET_F("BM"),buttonPublishMqtt); + sappend(dest,'c',SET_F("RT"),retainMqttMsg); + dest.print(F("d.Sf.MD.maxlength=")); dest.print(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); dest.print(F(";")); + dest.print(F("d.Sf.MG.maxlength=")); dest.print(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); dest.print(F(";")); + dest.print(F("d.Sf.MS.maxlength=")); dest.print(itoa(MQTT_MAX_SERVER_LEN,nS,10)); dest.print(F(";")); #else - oappend(SET_F("toggle('MQTT');")); // hide MQTT settings + dest.print(F("toggle('MQTT');")); // hide MQTT settings #endif #ifndef WLED_DISABLE_HUESYNC - sappend('v',SET_F("H0"),hueIP[0]); - sappend('v',SET_F("H1"),hueIP[1]); - sappend('v',SET_F("H2"),hueIP[2]); - sappend('v',SET_F("H3"),hueIP[3]); - sappend('v',SET_F("HL"),huePollLightId); - sappend('v',SET_F("HI"),huePollIntervalMs); - sappend('c',SET_F("HP"),huePollingEnabled); - sappend('c',SET_F("HO"),hueApplyOnOff); - sappend('c',SET_F("HB"),hueApplyBri); - sappend('c',SET_F("HC"),hueApplyColor); + sappend(dest,'v',SET_F("H0"),hueIP[0]); + sappend(dest,'v',SET_F("H1"),hueIP[1]); + sappend(dest,'v',SET_F("H2"),hueIP[2]); + sappend(dest,'v',SET_F("H3"),hueIP[3]); + sappend(dest,'v',SET_F("HL"),huePollLightId); + sappend(dest,'v',SET_F("HI"),huePollIntervalMs); + sappend(dest,'c',SET_F("HP"),huePollingEnabled); + sappend(dest,'c',SET_F("HO"),hueApplyOnOff); + sappend(dest,'c',SET_F("HB"),hueApplyBri); + sappend(dest,'c',SET_F("HC"),hueApplyColor); char hueErrorString[25]; switch (hueError) { @@ -566,61 +559,61 @@ void getSettingsJS(byte subPage, char* dest) default: sprintf_P(hueErrorString,PSTR("Bridge Error %i"),hueError); } - sappends('m',SET_F("(\"sip\")[0]"),hueErrorString); + sappends(dest,'m',SET_F("(\"sip\")[0]"),hueErrorString); #else - oappend(SET_F("toggle('Hue');")); // hide Hue Sync settings + dest.print(F("toggle('Hue');")); // hide Hue Sync settings #endif - sappend('v',SET_F("BD"),serialBaud); + sappend(dest,'v',SET_F("BD"),serialBaud); #ifndef WLED_ENABLE_ADALIGHT - oappend(SET_F("toggle('Serial);")); + dest.print(SET_F("toggle('Serial);")); #endif } if (subPage == SUBPAGE_TIME) { - sappend('c',SET_F("NT"),ntpEnabled); - sappends('s',SET_F("NS"),ntpServerName); - sappend('c',SET_F("CF"),!useAMPM); - sappend('i',SET_F("TZ"),currentTimezone); - sappend('v',SET_F("UO"),utcOffsetSecs); + sappend(dest,'c',SET_F("NT"),ntpEnabled); + sappends(dest,'s',SET_F("NS"),ntpServerName); + sappend(dest,'c',SET_F("CF"),!useAMPM); + sappend(dest,'i',SET_F("TZ"),currentTimezone); + sappend(dest,'v',SET_F("UO"),utcOffsetSecs); char tm[32]; dtostrf(longitude,4,2,tm); - sappends('s',SET_F("LN"),tm); + sappends(dest,'s',SET_F("LN"),tm); dtostrf(latitude,4,2,tm); - sappends('s',SET_F("LT"),tm); + sappends(dest,'s',SET_F("LT"),tm); getTimeString(tm); - sappends('m',SET_F("(\"times\")[0]"),tm); + sappends(dest,'m',SET_F("(\"times\")[0]"),tm); if ((int)(longitude*10.0f) || (int)(latitude*10.0f)) { sprintf_P(tm, PSTR("Sunrise: %02d:%02d Sunset: %02d:%02d"), hour(sunrise), minute(sunrise), hour(sunset), minute(sunset)); - sappends('m',SET_F("(\"times\")[1]"),tm); + sappends(dest,'m',SET_F("(\"times\")[1]"),tm); } - sappend('c',SET_F("OL"),overlayCurrent); - sappend('v',SET_F("O1"),overlayMin); - sappend('v',SET_F("O2"),overlayMax); - sappend('v',SET_F("OM"),analogClock12pixel); - sappend('c',SET_F("OS"),analogClockSecondsTrail); - sappend('c',SET_F("O5"),analogClock5MinuteMarks); - sappend('c',SET_F("OB"),analogClockSolidBlack); + sappend(dest,'c',SET_F("OL"),overlayCurrent); + sappend(dest,'v',SET_F("O1"),overlayMin); + sappend(dest,'v',SET_F("O2"),overlayMax); + sappend(dest,'v',SET_F("OM"),analogClock12pixel); + sappend(dest,'c',SET_F("OS"),analogClockSecondsTrail); + sappend(dest,'c',SET_F("O5"),analogClock5MinuteMarks); + sappend(dest,'c',SET_F("OB"),analogClockSolidBlack); - sappend('c',SET_F("CE"),countdownMode); - sappend('v',SET_F("CY"),countdownYear); - sappend('v',SET_F("CI"),countdownMonth); - sappend('v',SET_F("CD"),countdownDay); - sappend('v',SET_F("CH"),countdownHour); - sappend('v',SET_F("CM"),countdownMin); - sappend('v',SET_F("CS"),countdownSec); + sappend(dest,'c',SET_F("CE"),countdownMode); + sappend(dest,'v',SET_F("CY"),countdownYear); + sappend(dest,'v',SET_F("CI"),countdownMonth); + sappend(dest,'v',SET_F("CD"),countdownDay); + sappend(dest,'v',SET_F("CH"),countdownHour); + sappend(dest,'v',SET_F("CM"),countdownMin); + sappend(dest,'v',SET_F("CS"),countdownSec); - sappend('v',SET_F("A0"),macroAlexaOn); - sappend('v',SET_F("A1"),macroAlexaOff); - sappend('v',SET_F("MC"),macroCountdown); - sappend('v',SET_F("MN"),macroNl); + sappend(dest,'v',SET_F("A0"),macroAlexaOn); + sappend(dest,'v',SET_F("A1"),macroAlexaOff); + sappend(dest,'v',SET_F("MC"),macroCountdown); + sappend(dest,'v',SET_F("MN"),macroNl); for (unsigned i=0; i> 4) & 0x0F); - k[0] = 'P'; sappend('v',k,timerMonth[i] & 0x0F); - k[0] = 'D'; sappend('v',k,timerDay[i]); - k[0] = 'E'; sappend('v',k,timerDayEnd[i]); + k[0] = 'M'; sappend(dest,'v',k,(timerMonth[i] >> 4) & 0x0F); + k[0] = 'P'; sappend(dest,'v',k,timerMonth[i] & 0x0F); + k[0] = 'D'; sappend(dest,'v',k,timerDay[i]); + k[0] = 'E'; sappend(dest,'v',k,timerDayEnd[i]); } } } @@ -647,121 +640,116 @@ void getSettingsJS(byte subPage, char* dest) char fpass[l+1]; //fill PIN field with 0000 fpass[l] = 0; memset(fpass,'0',l); - sappends('s',SET_F("PIN"),fpass); - sappend('c',SET_F("NO"),otaLock); - sappend('c',SET_F("OW"),wifiLock); - sappend('c',SET_F("AO"),aOtaEnabled); - sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED ")); - olen -= 2; //delete "; - oappend(versionString); - oappend(SET_F(" (build ")); - oappendi(VERSION); - oappend(SET_F(")\";")); - oappend(SET_F("sd=\"")); - oappend(serverDescription); - oappend(SET_F("\";")); + sappends(dest,'s',SET_F("PIN"),fpass); + sappend(dest,'c',SET_F("NO"),otaLock); + sappend(dest,'c',SET_F("OW"),wifiLock); + sappend(dest,'c',SET_F("AO"),aOtaEnabled); + char tmp_buf[128]; + snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION); + sappends(dest,'m',SET_F("(\"sip\")[0]"),tmp_buf); + dest.print(F("sd=\"")); + dest.print(serverDescription); + dest.print(F("\";")); } #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { - sappend('v',SET_F("PU"),e131ProxyUniverse); + sappend(dest,'v',SET_F("PU"),e131ProxyUniverse); - sappend('v',SET_F("CN"),DMXChannels); - sappend('v',SET_F("CG"),DMXGap); - sappend('v',SET_F("CS"),DMXStart); - sappend('v',SET_F("SL"),DMXStartLED); + sappend(dest,'v',SET_F("CN"),DMXChannels); + sappend(dest,'v',SET_F("CG"),DMXGap); + sappend(dest,'v',SET_F("CS"),DMXStart); + sappend(dest,'v',SET_F("SL"),DMXStartLED); - sappend('i',SET_F("CH1"),DMXFixtureMap[0]); - sappend('i',SET_F("CH2"),DMXFixtureMap[1]); - sappend('i',SET_F("CH3"),DMXFixtureMap[2]); - sappend('i',SET_F("CH4"),DMXFixtureMap[3]); - sappend('i',SET_F("CH5"),DMXFixtureMap[4]); - sappend('i',SET_F("CH6"),DMXFixtureMap[5]); - sappend('i',SET_F("CH7"),DMXFixtureMap[6]); - sappend('i',SET_F("CH8"),DMXFixtureMap[7]); - sappend('i',SET_F("CH9"),DMXFixtureMap[8]); - sappend('i',SET_F("CH10"),DMXFixtureMap[9]); - sappend('i',SET_F("CH11"),DMXFixtureMap[10]); - sappend('i',SET_F("CH12"),DMXFixtureMap[11]); - sappend('i',SET_F("CH13"),DMXFixtureMap[12]); - sappend('i',SET_F("CH14"),DMXFixtureMap[13]); - sappend('i',SET_F("CH15"),DMXFixtureMap[14]); + sappend(dest,'i',SET_F("CH1"),DMXFixtureMap[0]); + sappend(dest,'i',SET_F("CH2"),DMXFixtureMap[1]); + sappend(dest,'i',SET_F("CH3"),DMXFixtureMap[2]); + sappend(dest,'i',SET_F("CH4"),DMXFixtureMap[3]); + sappend(dest,'i',SET_F("CH5"),DMXFixtureMap[4]); + sappend(dest,'i',SET_F("CH6"),DMXFixtureMap[5]); + sappend(dest,'i',SET_F("CH7"),DMXFixtureMap[6]); + sappend(dest,'i',SET_F("CH8"),DMXFixtureMap[7]); + sappend(dest,'i',SET_F("CH9"),DMXFixtureMap[8]); + sappend(dest,'i',SET_F("CH10"),DMXFixtureMap[9]); + sappend(dest,'i',SET_F("CH11"),DMXFixtureMap[10]); + sappend(dest,'i',SET_F("CH12"),DMXFixtureMap[11]); + sappend(dest,'i',SET_F("CH13"),DMXFixtureMap[12]); + sappend(dest,'i',SET_F("CH14"),DMXFixtureMap[13]); + sappend(dest,'i',SET_F("CH15"),DMXFixtureMap[14]); } #endif if (subPage == SUBPAGE_UM) //usermods { - appendGPIOinfo(); - oappend(SET_F("numM=")); - oappendi(usermods.getModCount()); - oappend(";"); - sappend('v',SET_F("SDA"),i2c_sda); - sappend('v',SET_F("SCL"),i2c_scl); - sappend('v',SET_F("MOSI"),spi_mosi); - sappend('v',SET_F("MISO"),spi_miso); - sappend('v',SET_F("SCLK"),spi_sclk); - oappend(SET_F("addInfo('SDA','")); oappendi(HW_PIN_SDA); oappend(SET_F("');")); - oappend(SET_F("addInfo('SCL','")); oappendi(HW_PIN_SCL); oappend(SET_F("');")); - oappend(SET_F("addInfo('MOSI','")); oappendi(HW_PIN_DATASPI); oappend(SET_F("');")); - oappend(SET_F("addInfo('MISO','")); oappendi(HW_PIN_MISOSPI); oappend(SET_F("');")); - oappend(SET_F("addInfo('SCLK','")); oappendi(HW_PIN_CLOCKSPI); oappend(SET_F("');")); - usermods.appendConfigData(); + appendGPIOinfo(dest); + dest.print(F("numM=")); + dest.print(usermods.getModCount()); + dest.print(";"); + sappend(dest,'v',SET_F("SDA"),i2c_sda); + sappend(dest,'v',SET_F("SCL"),i2c_scl); + sappend(dest,'v',SET_F("MOSI"),spi_mosi); + sappend(dest,'v',SET_F("MISO"),spi_miso); + sappend(dest,'v',SET_F("SCLK"),spi_sclk); + dest.print(F("addInfo('SDA','")); dest.print(HW_PIN_SDA); dest.print(F("');")); + dest.print(F("addInfo('SCL','")); dest.print(HW_PIN_SCL); dest.print(F("');")); + dest.print(F("addInfo('MOSI','")); dest.print(HW_PIN_DATASPI); dest.print(F("');")); + dest.print(F("addInfo('MISO','")); dest.print(HW_PIN_MISOSPI); dest.print(F("');")); + dest.print(F("addInfo('SCLK','")); dest.print(HW_PIN_CLOCKSPI); dest.print(F("');")); + usermods.appendConfigData(dest); } if (subPage == SUBPAGE_UPDATE) // update { - sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED ")); - olen -= 2; //delete "; - oappend(versionString); - oappend(SET_F("
")); - oappend(releaseString); - oappend(SET_F("
(")); + char tmp_buf[128]; + snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s
%s
(%s build %d)"), + versionString, + releaseString, #if defined(ARDUINO_ARCH_ESP32) - oappend(ESP.getChipModel()); + ESP.getChipModel(), #else - oappend("esp8266"); + F("esp8266"), #endif - oappend(SET_F(" build ")); - oappendi(VERSION); - oappend(SET_F(")\";")); + VERSION); + + sappends(dest,'m',SET_F("(\"sip\")[0]"),tmp_buf); } if (subPage == SUBPAGE_2D) // 2D matrices { - sappend('v',SET_F("SOMP"),strip.isMatrix); + sappend(dest,'v',SET_F("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D - oappend(SET_F("maxPanels=")); oappendi(WLED_MAX_PANELS); oappend(SET_F(";")); - oappend(SET_F("resetPanels();")); + dest.print(F("maxPanels=")); dest.print(WLED_MAX_PANELS); dest.print(F(";")); + dest.print(F("resetPanels();")); if (strip.isMatrix) { if(strip.panels>0){ - sappend('v',SET_F("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience - sappend('v',SET_F("PH"),strip.panel[0].height); + sappend(dest,'v',SET_F("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience + sappend(dest,'v',SET_F("PH"),strip.panel[0].height); } - sappend('v',SET_F("MPC"),strip.panels); + sappend(dest,'v',SET_F("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Tue, 17 Sep 2024 18:26:46 -0400 Subject: [PATCH 479/694] Usermod: Implement shim for oappend Use a static Print* to transform old oappend calls to print calls. --- wled00/fcn_declare.h | 12 +++++++++++- wled00/um_manager.cpp | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 6ce30facf..8c90e2be1 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -302,7 +302,7 @@ class Usermod { virtual bool handleButton(uint8_t b) { return false; } // button overrides are possible here virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; // usermod data exchange [see examples for audio effects] virtual void connected() {} // called when WiFi is (re)connected - virtual void appendConfigData(Print&) {} // helper function called from usermod settings page to add metadata for entry fields + virtual void appendConfigData(Print&); // helper function called from usermod settings page to add metadata for entry fields virtual void addToJsonState(JsonObject& obj) {} // add JSON objects for WLED state virtual void addToJsonInfo(JsonObject& obj) {} // add JSON objects for UI Info page virtual void readFromJsonState(JsonObject& obj) {} // process JSON messages received from web server @@ -314,6 +314,16 @@ class Usermod { virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} + + // API shims + private: + static Print* oappend_shim; + // old form of appendConfigData; called by default appendConfigData(Print&) with oappend_shim set up + // private so it is not accidentally invoked except via Usermod::appendConfigData(Print&) + virtual void appendConfigData() {} + protected: + // Shim for oappend(), which used to exist in utils.cpp + template static inline void oappend(const T& t) { oappend_shim->print(t); }; }; class UsermodManager { diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 3970e7af4..5307d26f6 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -68,3 +68,14 @@ bool UsermodManager::add(Usermod* um) ums[numMods++] = um; return true; } + + +/* Usermod v2 interface shim for oappend */ +Print* Usermod::oappend_shim = nullptr; + +void Usermod::appendConfigData(Print& p) { + assert(!oappend_shim); + oappend_shim = &p; + this->appendConfigData(); + oappend_shim = nullptr; +} From 4ef583c8445e74eea3f6c0bc5805563728351f17 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 7 Sep 2024 19:52:30 -0400 Subject: [PATCH 480/694] xml: Print optimization Reduce the total number of calls by using printf_P and skipping atoi(). --- wled00/xml.cpp | 171 +++++++++++++++---------------------------------- 1 file changed, 50 insertions(+), 121 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 2d63d61f3..5ed1109c9 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -8,63 +8,22 @@ //build XML response to HTTP /win API request void XML_response(Print& dest) { - dest.print(F("")); - dest.print((nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); - dest.print(F("")); - + dest.printf_P(PSTR("%d"), (nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); for (int i = 0; i < 3; i++) { - dest.print(""); - dest.print(col[i]); - dest.print(""); + dest.printf_P(PSTR("%d"), col[i]); } for (int i = 0; i < 3; i++) { - dest.print(""); - dest.print(colSec[i]); - dest.print(""); + dest.printf_P(PSTR("%d"), colSec[i]); } - dest.print(F("")); - dest.print(notifyDirect); - dest.print(F("")); - dest.print(receiveGroups!=0); - dest.print(F("")); - dest.print(nightlightActive); - dest.print(F("")); - dest.print(nightlightMode > NL_MODE_SET); - dest.print(F("")); - dest.print(nightlightDelayMins); - dest.print(F("")); - dest.print(nightlightTargetBri); - dest.print(F("")); - dest.print(effectCurrent); - dest.print(F("")); - dest.print(effectSpeed); - dest.print(F("")); - dest.print(effectIntensity); - dest.print(F("")); - dest.print(effectPalette); - dest.print(F("")); - if (strip.hasWhiteChannel()) { - dest.print(col[3]); - } else { - dest.print("-1"); - } - dest.print(F("")); - dest.print(colSec[3]); - dest.print(F("")); - dest.print(currentPreset); - dest.print(F("")); - dest.print(currentPlaylist >= 0); - dest.print(F("")); - dest.print(serverDescription); - if (realtimeMode) - { - dest.print(F(" (live)")); - } - dest.print(F("")); - dest.print(strip.getFirstSelectedSegId()); - dest.print(F("")); + dest.printf_P(PSTR("%d%d%d%d%d%d%d%d%d%d%d%d%d%d%s%s%d"), + notifyDirect, receiveGroups!=0, nightlightActive, nightlightMode > NL_MODE_SET, nightlightDelayMins, + nightlightTargetBri, effectCurrent, effectSpeed, effectIntensity, effectPalette, + strip.hasWhiteChannel() ? col[3] : -1, colSec[3], currentPreset, currentPlaylist >= 0, + serverDescription, realtimeMode ? PSTR(" (live)") : "", + strip.getFirstSelectedSegId() + ); } static void extractPin(Print& dest, JsonObject &obj, const char *key) { @@ -114,17 +73,12 @@ void fillUMPins(Print& dest, JsonObject &mods) } void appendGPIOinfo(Print& dest) { - char nS[8]; - - // add usermod pins as d.um_p array dest.print(F("d.um_p=[-1")); // has to have 1 element if (i2c_sda > -1 && i2c_scl > -1) { - dest.print(","); dest.print(itoa(i2c_sda,nS,10)); - dest.print(","); dest.print(itoa(i2c_scl,nS,10)); + dest.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl); } if (spi_mosi > -1 && spi_sclk > -1) { - dest.print(","); dest.print(itoa(spi_mosi,nS,10)); - dest.print(","); dest.print(itoa(spi_sclk,nS,10)); + dest.printf_P(PSTR(",%d,%d"), spi_mosi, spi_sclk); } // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly if (requestJSONBufferLock(6)) { @@ -147,16 +101,16 @@ void appendGPIOinfo(Print& dest) { dest.print(SET_F("2,")); // DMX hardcoded pin #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - dest.print(itoa(hardwareTX,nS,10)); dest.print(","); // debug output (TX) pin + dest.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin #endif //Note: Using pin 3 (RX) disables Adalight / Serial JSON #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); dest.print(","); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); dest.print(","); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { dest.print(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); dest.print(","); } - switch (ethernetBoards[ethernetType].eth_clk_mode) { + for (unsigned p=0; p=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_power); } + if (ethernetBoards[ethernetType].eth_mdc>=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } + if (ethernetBoards[ethernetType].eth_mdio>=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } + switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: dest.print(SET_F("0")); @@ -211,27 +165,19 @@ void getSettingsJS(byte subPage, Print& dest) if (subPage == SUBPAGE_WIFI) { - char nS[10]; size_t l; - dest.print(F("resetWiFi(")); - dest.print(WLED_MAX_WIFI_COUNT); - dest.print(F(");")); + dest.printf_P(PSTR("resetWiFi(%d);"), WLED_MAX_WIFI_COUNT); for (size_t n = 0; n < multiWiFi.size(); n++) { l = strlen(multiWiFi[n].clientPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - dest.print(F("addWiFi(\"")); - dest.print(multiWiFi[n].clientSSID); - dest.print(F("\",\"")); - dest.print(fpass); - dest.print(F("\",0x")); - dest.print(itoa(multiWiFi[n].staticIP,nS,16)); - dest.print(F(",0x")); - dest.print(itoa(multiWiFi[n].staticGW,nS,16)); - dest.print(F(",0x")); - dest.print(itoa(multiWiFi[n].staticSN,nS,16)); - dest.print(F(");")); + dest.printf_P(PSTR("addWiFi(\"%s\",\",%s\",0x%X,0x%X,0x%X);"), + multiWiFi[n].clientSSID, + fpass, + (uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct + (uint32_t) multiWiFi[n].staticGW, + (uint32_t) multiWiFi[n].staticSN); } sappend(dest,'v',SET_F("D0"),dnsAddress[0]); @@ -320,16 +266,16 @@ void getSettingsJS(byte subPage, Print& dest) dest.print(SET_F("d.ledTypes=")); dest.print(BusManager::getLEDTypesJSONString().c_str()); dest.print(";"); // set limits - dest.print(F("bLimits(")); - dest.print(itoa(WLED_MAX_BUSSES,nS,10)); dest.print(","); - dest.print(itoa(WLED_MIN_VIRTUAL_BUSSES,nS,10)); dest.print(","); - dest.print(itoa(MAX_LEDS_PER_BUS,nS,10)); dest.print(","); - dest.print(itoa(MAX_LED_MEMORY,nS,10)); dest.print(","); - dest.print(itoa(MAX_LEDS,nS,10)); dest.print(","); - dest.print(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); dest.print(","); - dest.print(itoa(WLED_MAX_DIGITAL_CHANNELS,nS,10)); dest.print(","); - dest.print(itoa(WLED_MAX_ANALOG_CHANNELS,nS,10)); - dest.print(F(");")); + dest.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), + WLED_MAX_BUSSES, + WLED_MIN_VIRTUAL_BUSSES, + MAX_LEDS_PER_BUS, + MAX_LED_MEMORY, + MAX_LEDS, + WLED_MAX_COLOR_ORDER_MAPPINGS, + WLED_MAX_DIGITAL_CHANNELS, + WLED_MAX_ANALOG_CHANNELS + ); sappend(dest,'c',SET_F("MS"),strip.autoSegments); sappend(dest,'c',SET_F("CCT"),strip.correctWB); @@ -403,17 +349,12 @@ void getSettingsJS(byte subPage, Print& dest) sappend(dest,'c',SET_F("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); sappend(dest,'c',SET_F("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); - dest.print(F("resetCOM(")); - dest.print(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); - dest.print(F(");")); + dest.printf_P(PSTR("resetCOM(%d);"), WLED_MAX_COLOR_ORDER_MAPPINGS); const ColorOrderMap& com = BusManager::getColorOrderMap(); for (int s = 0; s < com.count(); s++) { const ColorOrderMapEntry* entry = com.get(s); if (entry == nullptr) break; - dest.print(F("addCOM(")); - dest.print(itoa(entry->start,nS,10)); dest.print(","); - dest.print(itoa(entry->len,nS,10)); dest.print(","); - dest.print(itoa(entry->colorOrder,nS,10)); dest.print(");"); + dest.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder); } sappend(dest,'v',SET_F("CA"),briS); @@ -439,11 +380,7 @@ void getSettingsJS(byte subPage, Print& dest) sappend(dest,'c',SET_F("RM"),rlyMde); sappend(dest,'c',SET_F("RO"),rlyOpenDrain); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { - dest.print(F("addBtn(")); - dest.print(itoa(i,nS,10)); dest.print(","); - dest.print(itoa(btnPin[i],nS,10)); dest.print(","); - dest.print(itoa(buttonType[i],nS,10)); - dest.print(F(");")); + dest.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]); } sappend(dest,'c',SET_F("IP"),disablePullUp); sappend(dest,'v',SET_F("TT"),touchThreshold); @@ -528,9 +465,8 @@ void getSettingsJS(byte subPage, Print& dest) sappends(dest,'s',SET_F("MG"),mqttGroupTopic); sappend(dest,'c',SET_F("BM"),buttonPublishMqtt); sappend(dest,'c',SET_F("RT"),retainMqttMsg); - dest.print(F("d.Sf.MD.maxlength=")); dest.print(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); dest.print(F(";")); - dest.print(F("d.Sf.MG.maxlength=")); dest.print(itoa(MQTT_MAX_TOPIC_LEN,nS,10)); dest.print(F(";")); - dest.print(F("d.Sf.MS.maxlength=")); dest.print(itoa(MQTT_MAX_SERVER_LEN,nS,10)); dest.print(F(";")); + dest.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else dest.print(F("toggle('MQTT');")); // hide MQTT settings #endif @@ -608,12 +544,7 @@ void getSettingsJS(byte subPage, Print& dest) sappend(dest,'v',SET_F("MC"),macroCountdown); sappend(dest,'v',SET_F("MN"),macroNl); for (unsigned i=0; i Date: Mon, 9 Sep 2024 20:00:23 -0400 Subject: [PATCH 481/694] Replace sappend and sappends Use named functions to describe what's being printed. --- wled00/fcn_declare.h | 7 +- wled00/util.cpp | 53 ++---- wled00/xml.cpp | 422 +++++++++++++++++++++---------------------- 3 files changed, 235 insertions(+), 247 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 8c90e2be1..a36f2dc26 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -372,8 +372,11 @@ void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); bool getBoolVal(JsonVariant elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -void sappend(Print& dest, char stype, const char* key, int val); -void sappends(Print& dest, char stype, const char* key, char* val); +size_t printSetCheckbox(Print& dest, const char* key, int val); +size_t printSetValue(Print& dest, const char* key, int val); +size_t printSetValue(Print& dest, const char* key, const char* val); +size_t printSetIndex(Print& dest, const char* key, int index); +size_t printSetMessage(Print& dest, const char* key, const char* val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 00506ea97..660877d18 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -87,43 +87,28 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv return true; } - -//append a numeric setting to string buffer -void sappend(Print& dest, char stype, const char* key, int val) -{ - const __FlashStringHelper* type_str; - switch(stype) - { - case 'c': //checkbox - type_str = F(".checked="); - break; - case 'v': //numeric - type_str = F(".value="); - break; - case 'i': //selectedIndex - type_str = F(".selectedIndex="); - break; - default: - return; //??? - } - - dest.printf_P(PSTR("d.Sf.%s%s%d;"), key, type_str, val); +static size_t printSetInt(Print& dest, const char* key, const char* selector, int value) { + return dest.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value); } - -//append a string setting to buffer -void sappends(Print& dest, char stype, const char* key, char* val) -{ - switch(stype) - { - case 's': {//string (we can interpret val as char*) - dest.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); - break;} - case 'm': //message - dest.printf_P(PSTR("d.getElementsByClassName%s.innerHTML=\"%s\";"), key, val); - break; - } +size_t printSetCheckbox(Print& dest, const char* key, int val) { + return printSetInt(dest, key, PSTR("checked"), val); } +size_t printSetValue(Print& dest, const char* key, int val) { + return printSetInt(dest, key, PSTR("value"), val); +} +size_t printSetIndex(Print& dest, const char* key, int index) { + return printSetInt(dest, key, PSTR("selectedIndex"), index); +} + +size_t printSetValue(Print& dest, const char* key, const char* val) { + return dest.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); +} + +size_t printSetMessage(Print& dest, const char* key, const char* val) { + return dest.printf_P(PSTR("d.getElementsByClassName%s.innerHTML=\"%s\";"), key, val); +} + void prepareHostname(char* hostname) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 5ed1109c9..e8858066c 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -180,41 +180,41 @@ void getSettingsJS(byte subPage, Print& dest) (uint32_t) multiWiFi[n].staticSN); } - sappend(dest,'v',SET_F("D0"),dnsAddress[0]); - sappend(dest,'v',SET_F("D1"),dnsAddress[1]); - sappend(dest,'v',SET_F("D2"),dnsAddress[2]); - sappend(dest,'v',SET_F("D3"),dnsAddress[3]); + printSetValue(dest,PSTR("D0"),dnsAddress[0]); + printSetValue(dest,PSTR("D1"),dnsAddress[1]); + printSetValue(dest,PSTR("D2"),dnsAddress[2]); + printSetValue(dest,PSTR("D3"),dnsAddress[3]); - sappends(dest,'s',SET_F("CM"),cmDNS); - sappend(dest,'i',SET_F("AB"),apBehavior); - sappends(dest,'s',SET_F("AS"),apSSID); - sappend(dest,'c',SET_F("AH"),apHide); + printSetValue(dest,PSTR("CM"),cmDNS); + printSetIndex(dest,PSTR("AB"),apBehavior); + printSetValue(dest,PSTR("AS"),apSSID); + printSetCheckbox(dest,PSTR("AH"),apHide); l = strlen(apPass); char fapass[l+1]; //fill password field with *** fapass[l] = 0; memset(fapass,'*',l); - sappends(dest,'s',SET_F("AP"),fapass); + printSetValue(dest,PSTR("AP"),fapass); - sappend(dest,'v',SET_F("AC"),apChannel); + printSetValue(dest,PSTR("AC"),apChannel); #ifdef ARDUINO_ARCH_ESP32 - sappend(dest,'v',SET_F("TX"),txPower); + printSetValue(dest,PSTR("TX"),txPower); #else dest.print(F("gId('tx').style.display='none';")); #endif - sappend(dest,'c',SET_F("FG"),force802_3g); - sappend(dest,'c',SET_F("WS"),noWifiSleep); + printSetCheckbox(dest,PSTR("FG"),force802_3g); + printSetCheckbox(dest,PSTR("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW - sappend(dest,'c',SET_F("RE"),enableESPNow); - sappends(dest,'s',SET_F("RMAC"),linked_remote); + printSetCheckbox(dest,PSTR("RE"),enableESPNow); + printSetValue(dest,PSTR("RMAC"),linked_remote); #else //hide remote settings if not compiled dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif #ifdef WLED_USE_ETHERNET - sappend(dest,'v',SET_F("ETH"),ethernetType); + printSetValue(dest,PSTR("ETH"),ethernetType); #else //hide ethernet setting if not compiled in dest.print(F("gId('ethd').style.display='none';")); @@ -229,10 +229,10 @@ void getSettingsJS(byte subPage, Print& dest) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); #endif - sappends(dest,'m',SET_F("(\"sip\")[0]"),s); + printSetMessage(dest,PSTR("(\"sip\")[0]"),s); } else { - sappends(dest,'m',SET_F("(\"sip\")[0]"),(char*)F("Not connected")); + printSetMessage(dest,PSTR("(\"sip\")[0]"),(char*)F("Not connected")); } if (WiFi.softAPIP()[0] != 0) //is active @@ -240,19 +240,19 @@ void getSettingsJS(byte subPage, Print& dest) char s[16]; IPAddress apIP = WiFi.softAPIP(); sprintf(s, "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); - sappends(dest,'m',SET_F("(\"sip\")[1]"),s); + printSetMessage(dest,PSTR("(\"sip\")[1]"),s); } else { - sappends(dest,'m',SET_F("(\"sip\")[1]"),(char*)F("Not active")); + printSetMessage(dest,PSTR("(\"sip\")[1]"),(char*)F("Not active")); } #ifndef WLED_DISABLE_ESPNOW if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote - sappends(dest,'m',SET_F("(\"rlid\")[0]"),last_signal_src); + printSetMessage(dest,PSTR("(\"rlid\")[0]"),last_signal_src); } else if (!enableESPNow) { - sappends(dest,'m',SET_F("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); + printSetMessage(dest,PSTR("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); } else { - sappends(dest,'m',SET_F("(\"rlid\")[0]"),(char*)F("None")); + printSetMessage(dest,PSTR("(\"rlid\")[0]"),(char*)F("None")); } #endif } @@ -277,14 +277,14 @@ void getSettingsJS(byte subPage, Print& dest) WLED_MAX_ANALOG_CHANNELS ); - sappend(dest,'c',SET_F("MS"),strip.autoSegments); - sappend(dest,'c',SET_F("CCT"),strip.correctWB); - sappend(dest,'c',SET_F("IC"),cctICused); - sappend(dest,'c',SET_F("CR"),strip.cctFromRgb); - sappend(dest,'v',SET_F("CB"),strip.cctBlending); - sappend(dest,'v',SET_F("FR"),strip.getTargetFps()); - sappend(dest,'v',SET_F("AW"),Bus::getGlobalAWMode()); - sappend(dest,'c',SET_F("LD"),useGlobalLedBuffer); + printSetCheckbox(dest,PSTR("MS"),strip.autoSegments); + printSetCheckbox(dest,PSTR("CCT"),strip.correctWB); + printSetCheckbox(dest,PSTR("IC"),cctICused); + printSetCheckbox(dest,PSTR("CR"),strip.cctFromRgb); + printSetValue(dest,PSTR("CB"),strip.cctBlending); + printSetValue(dest,PSTR("FR"),strip.getTargetFps()); + printSetValue(dest,PSTR("AW"),Bus::getGlobalAWMode()); + printSetCheckbox(dest,PSTR("LD"),useGlobalLedBuffer); unsigned sumMa = 0; for (int s = 0; s < BusManager::getNumBusses(); s++) { @@ -309,17 +309,17 @@ void getSettingsJS(byte subPage, Print& dest) int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend(dest,'v',lp,pins[i]); + if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) printSetValue(dest,lp,pins[i]); } - sappend(dest,'v',lc,bus->getLength()); - sappend(dest,'v',lt,bus->getType()); - sappend(dest,'v',co,bus->getColorOrder() & 0x0F); - sappend(dest,'v',ls,bus->getStart()); - sappend(dest,'c',cv,bus->isReversed()); - sappend(dest,'v',sl,bus->skippedLeds()); - sappend(dest,'c',rf,bus->isOffRefreshRequired()); - sappend(dest,'v',aw,bus->getAutoWhiteMode()); - sappend(dest,'v',wo,bus->getColorOrder() >> 4); + printSetValue(dest,lc,bus->getLength()); + printSetValue(dest,lt,bus->getType()); + printSetValue(dest,co,bus->getColorOrder() & 0x0F); + printSetValue(dest,ls,bus->getStart()); + printSetCheckbox(dest,cv,bus->isReversed()); + printSetValue(dest,sl,bus->skippedLeds()); + printSetCheckbox(dest,rf,bus->isOffRefreshRequired()); + printSetValue(dest,aw,bus->getAutoWhiteMode()); + printSetValue(dest,wo,bus->getColorOrder() >> 4); unsigned speed = bus->getFrequency(); if (bus->isPWM()) { switch (speed) { @@ -340,14 +340,14 @@ void getSettingsJS(byte subPage, Print& dest) case 20000 : speed = 4; break; } } - sappend(dest,'v',sp,speed); - sappend(dest,'v',la,bus->getLEDCurrent()); - sappend(dest,'v',ma,bus->getMaxCurrent()); + printSetValue(dest,sp,speed); + printSetValue(dest,la,bus->getLEDCurrent()); + printSetValue(dest,ma,bus->getMaxCurrent()); sumMa += bus->getMaxCurrent(); } - sappend(dest,'v',SET_F("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); - sappend(dest,'c',SET_F("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); - sappend(dest,'c',SET_F("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); + printSetValue(dest,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); + printSetCheckbox(dest,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); + printSetCheckbox(dest,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); dest.printf_P(PSTR("resetCOM(%d);"), WLED_MAX_COLOR_ORDER_MAPPINGS); const ColorOrderMap& com = BusManager::getColorOrderMap(); @@ -357,114 +357,114 @@ void getSettingsJS(byte subPage, Print& dest) dest.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder); } - sappend(dest,'v',SET_F("CA"),briS); + printSetValue(dest,PSTR("CA"),briS); - sappend(dest,'c',SET_F("BO"),turnOnAtBoot); - sappend(dest,'v',SET_F("BP"),bootPreset); + printSetCheckbox(dest,PSTR("BO"),turnOnAtBoot); + printSetValue(dest,PSTR("BP"),bootPreset); - sappend(dest,'c',SET_F("GB"),gammaCorrectBri); - sappend(dest,'c',SET_F("GC"),gammaCorrectCol); - dtostrf(gammaCorrectVal,3,1,nS); sappends(dest,'s',SET_F("GV"),nS); - sappend(dest,'c',SET_F("TF"),fadeTransition); - sappend(dest,'c',SET_F("EB"),modeBlending); - sappend(dest,'v',SET_F("TD"),transitionDelayDefault); - sappend(dest,'c',SET_F("PF"),strip.paletteFade); - sappend(dest,'v',SET_F("TP"),randomPaletteChangeTime); - sappend(dest,'c',SET_F("TH"),useHarmonicRandomPalette); - sappend(dest,'v',SET_F("BF"),briMultiplier); - sappend(dest,'v',SET_F("TB"),nightlightTargetBri); - sappend(dest,'v',SET_F("TL"),nightlightDelayMinsDefault); - sappend(dest,'v',SET_F("TW"),nightlightMode); - sappend(dest,'i',SET_F("PB"),strip.paletteBlend); - sappend(dest,'v',SET_F("RL"),rlyPin); - sappend(dest,'c',SET_F("RM"),rlyMde); - sappend(dest,'c',SET_F("RO"),rlyOpenDrain); + printSetCheckbox(dest,PSTR("GB"),gammaCorrectBri); + printSetCheckbox(dest,PSTR("GC"),gammaCorrectCol); + dtostrf(gammaCorrectVal,3,1,nS); printSetValue(dest,PSTR("GV"),nS); + printSetCheckbox(dest,PSTR("TF"),fadeTransition); + printSetCheckbox(dest,PSTR("EB"),modeBlending); + printSetValue(dest,PSTR("TD"),transitionDelayDefault); + printSetCheckbox(dest,PSTR("PF"),strip.paletteFade); + printSetValue(dest,PSTR("TP"),randomPaletteChangeTime); + printSetCheckbox(dest,PSTR("TH"),useHarmonicRandomPalette); + printSetValue(dest,PSTR("BF"),briMultiplier); + printSetValue(dest,PSTR("TB"),nightlightTargetBri); + printSetValue(dest,PSTR("TL"),nightlightDelayMinsDefault); + printSetValue(dest,PSTR("TW"),nightlightMode); + printSetIndex(dest,PSTR("PB"),strip.paletteBlend); + printSetValue(dest,PSTR("RL"),rlyPin); + printSetCheckbox(dest,PSTR("RM"),rlyMde); + printSetCheckbox(dest,PSTR("RO"),rlyOpenDrain); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { dest.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]); } - sappend(dest,'c',SET_F("IP"),disablePullUp); - sappend(dest,'v',SET_F("TT"),touchThreshold); + printSetCheckbox(dest,PSTR("IP"),disablePullUp); + printSetValue(dest,PSTR("TT"),touchThreshold); #ifndef WLED_DISABLE_INFRARED - sappend(dest,'v',SET_F("IR"),irPin); - sappend(dest,'v',SET_F("IT"),irEnabled); + printSetValue(dest,PSTR("IR"),irPin); + printSetValue(dest,PSTR("IT"),irEnabled); #endif - sappend(dest,'c',SET_F("MSO"),!irApplyToAllSelected); + printSetCheckbox(dest,PSTR("MSO"),!irApplyToAllSelected); } if (subPage == SUBPAGE_UI) { - sappends(dest,'s',SET_F("DS"),serverDescription); - sappend(dest,'c',SET_F("SU"),simplifiedUI); + printSetValue(dest,PSTR("DS"),serverDescription); + printSetCheckbox(dest,PSTR("SU"),simplifiedUI); } if (subPage == SUBPAGE_SYNC) { [[maybe_unused]] char nS[32]; - sappend(dest,'v',SET_F("UP"),udpPort); - sappend(dest,'v',SET_F("U2"),udpPort2); + printSetValue(dest,PSTR("UP"),udpPort); + printSetValue(dest,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW - if (enableESPNow) sappend(dest,'c',SET_F("EN"),useESPNowSync); + if (enableESPNow) printSetCheckbox(dest,PSTR("EN"),useESPNowSync); else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif - sappend(dest,'v',SET_F("GS"),syncGroups); - sappend(dest,'v',SET_F("GR"),receiveGroups); + printSetValue(dest,PSTR("GS"),syncGroups); + printSetValue(dest,PSTR("GR"),receiveGroups); - sappend(dest,'c',SET_F("RB"),receiveNotificationBrightness); - sappend(dest,'c',SET_F("RC"),receiveNotificationColor); - sappend(dest,'c',SET_F("RX"),receiveNotificationEffects); - sappend(dest,'c',SET_F("RP"),receiveNotificationPalette); - sappend(dest,'c',SET_F("SO"),receiveSegmentOptions); - sappend(dest,'c',SET_F("SG"),receiveSegmentBounds); - sappend(dest,'c',SET_F("SS"),sendNotifications); - sappend(dest,'c',SET_F("SD"),notifyDirect); - sappend(dest,'c',SET_F("SB"),notifyButton); - sappend(dest,'c',SET_F("SH"),notifyHue); - sappend(dest,'v',SET_F("UR"),udpNumRetries); + printSetCheckbox(dest,PSTR("RB"),receiveNotificationBrightness); + printSetCheckbox(dest,PSTR("RC"),receiveNotificationColor); + printSetCheckbox(dest,PSTR("RX"),receiveNotificationEffects); + printSetCheckbox(dest,PSTR("RP"),receiveNotificationPalette); + printSetCheckbox(dest,PSTR("SO"),receiveSegmentOptions); + printSetCheckbox(dest,PSTR("SG"),receiveSegmentBounds); + printSetCheckbox(dest,PSTR("SS"),sendNotifications); + printSetCheckbox(dest,PSTR("SD"),notifyDirect); + printSetCheckbox(dest,PSTR("SB"),notifyButton); + printSetCheckbox(dest,PSTR("SH"),notifyHue); + printSetValue(dest,PSTR("UR"),udpNumRetries); - sappend(dest,'c',SET_F("NL"),nodeListEnabled); - sappend(dest,'c',SET_F("NB"),nodeBroadcastEnabled); + printSetCheckbox(dest,PSTR("NL"),nodeListEnabled); + printSetCheckbox(dest,PSTR("NB"),nodeBroadcastEnabled); - sappend(dest,'c',SET_F("RD"),receiveDirect); - sappend(dest,'c',SET_F("MO"),useMainSegmentOnly); - sappend(dest,'c',SET_F("RLM"),realtimeRespectLedMaps); - sappend(dest,'v',SET_F("EP"),e131Port); - sappend(dest,'c',SET_F("ES"),e131SkipOutOfSequence); - sappend(dest,'c',SET_F("EM"),e131Multicast); - sappend(dest,'v',SET_F("EU"),e131Universe); - sappend(dest,'v',SET_F("DA"),DMXAddress); - sappend(dest,'v',SET_F("XX"),DMXSegmentSpacing); - sappend(dest,'v',SET_F("PY"),e131Priority); - sappend(dest,'v',SET_F("DM"),DMXMode); - sappend(dest,'v',SET_F("ET"),realtimeTimeoutMs); - sappend(dest,'c',SET_F("FB"),arlsForceMaxBri); - sappend(dest,'c',SET_F("RG"),arlsDisableGammaCorrection); - sappend(dest,'v',SET_F("WO"),arlsOffset); + printSetCheckbox(dest,PSTR("RD"),receiveDirect); + printSetCheckbox(dest,PSTR("MO"),useMainSegmentOnly); + printSetCheckbox(dest,PSTR("RLM"),realtimeRespectLedMaps); + printSetValue(dest,PSTR("EP"),e131Port); + printSetCheckbox(dest,PSTR("ES"),e131SkipOutOfSequence); + printSetCheckbox(dest,PSTR("EM"),e131Multicast); + printSetValue(dest,PSTR("EU"),e131Universe); + printSetValue(dest,PSTR("DA"),DMXAddress); + printSetValue(dest,PSTR("XX"),DMXSegmentSpacing); + printSetValue(dest,PSTR("PY"),e131Priority); + printSetValue(dest,PSTR("DM"),DMXMode); + printSetValue(dest,PSTR("ET"),realtimeTimeoutMs); + printSetCheckbox(dest,PSTR("FB"),arlsForceMaxBri); + printSetCheckbox(dest,PSTR("RG"),arlsDisableGammaCorrection); + printSetValue(dest,PSTR("WO"),arlsOffset); #ifndef WLED_DISABLE_ALEXA - sappend(dest,'c',SET_F("AL"),alexaEnabled); - sappends(dest,'s',SET_F("AI"),alexaInvocationName); - sappend(dest,'c',SET_F("SA"),notifyAlexa); - sappend(dest,'v',SET_F("AP"),alexaNumPresets); + printSetCheckbox(dest,PSTR("AL"),alexaEnabled); + printSetValue(dest,PSTR("AI"),alexaInvocationName); + printSetCheckbox(dest,PSTR("SA"),notifyAlexa); + printSetValue(dest,PSTR("AP"),alexaNumPresets); #else dest.print(F("toggle('Alexa');")); // hide Alexa settings #endif #ifndef WLED_DISABLE_MQTT - sappend(dest,'c',SET_F("MQ"),mqttEnabled); - sappends(dest,'s',SET_F("MS"),mqttServer); - sappend(dest,'v',SET_F("MQPORT"),mqttPort); - sappends(dest,'s',SET_F("MQUSER"),mqttUser); + printSetCheckbox(dest,PSTR("MQ"),mqttEnabled); + printSetValue(dest,PSTR("MS"),mqttServer); + printSetValue(dest,PSTR("MQPORT"),mqttPort); + printSetValue(dest,PSTR("MQUSER"),mqttUser); byte l = strlen(mqttPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - sappends(dest,'s',SET_F("MQPASS"),fpass); - sappends(dest,'s',SET_F("MQCID"),mqttClientID); - sappends(dest,'s',"MD",mqttDeviceTopic); - sappends(dest,'s',SET_F("MG"),mqttGroupTopic); - sappend(dest,'c',SET_F("BM"),buttonPublishMqtt); - sappend(dest,'c',SET_F("RT"),retainMqttMsg); + printSetValue(dest,PSTR("MQPASS"),fpass); + printSetValue(dest,PSTR("MQCID"),mqttClientID); + printSetValue(dest,PSTR("MD"),mqttDeviceTopic); + printSetValue(dest,PSTR("MG"),mqttGroupTopic); + printSetCheckbox(dest,PSTR("BM"),buttonPublishMqtt); + printSetCheckbox(dest,PSTR("RT"),retainMqttMsg); dest.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else @@ -472,16 +472,16 @@ void getSettingsJS(byte subPage, Print& dest) #endif #ifndef WLED_DISABLE_HUESYNC - sappend(dest,'v',SET_F("H0"),hueIP[0]); - sappend(dest,'v',SET_F("H1"),hueIP[1]); - sappend(dest,'v',SET_F("H2"),hueIP[2]); - sappend(dest,'v',SET_F("H3"),hueIP[3]); - sappend(dest,'v',SET_F("HL"),huePollLightId); - sappend(dest,'v',SET_F("HI"),huePollIntervalMs); - sappend(dest,'c',SET_F("HP"),huePollingEnabled); - sappend(dest,'c',SET_F("HO"),hueApplyOnOff); - sappend(dest,'c',SET_F("HB"),hueApplyBri); - sappend(dest,'c',SET_F("HC"),hueApplyColor); + printSetValue(dest,PSTR("H0"),hueIP[0]); + printSetValue(dest,PSTR("H1"),hueIP[1]); + printSetValue(dest,PSTR("H2"),hueIP[2]); + printSetValue(dest,PSTR("H3"),hueIP[3]); + printSetValue(dest,PSTR("HL"),huePollLightId); + printSetValue(dest,PSTR("HI"),huePollIntervalMs); + printSetCheckbox(dest,PSTR("HP"),huePollingEnabled); + printSetCheckbox(dest,PSTR("HO"),hueApplyOnOff); + printSetCheckbox(dest,PSTR("HB"),hueApplyBri); + printSetCheckbox(dest,PSTR("HC"),hueApplyColor); char hueErrorString[25]; switch (hueError) { @@ -495,11 +495,11 @@ void getSettingsJS(byte subPage, Print& dest) default: sprintf_P(hueErrorString,PSTR("Bridge Error %i"),hueError); } - sappends(dest,'m',SET_F("(\"sip\")[0]"),hueErrorString); + printSetMessage(dest,PSTR("(\"sip\")[0]"),hueErrorString); #else dest.print(F("toggle('Hue');")); // hide Hue Sync settings #endif - sappend(dest,'v',SET_F("BD"),serialBaud); + printSetValue(dest,PSTR("BD"),serialBaud); #ifndef WLED_ENABLE_ADALIGHT dest.print(SET_F("toggle('Serial);")); #endif @@ -507,42 +507,42 @@ void getSettingsJS(byte subPage, Print& dest) if (subPage == SUBPAGE_TIME) { - sappend(dest,'c',SET_F("NT"),ntpEnabled); - sappends(dest,'s',SET_F("NS"),ntpServerName); - sappend(dest,'c',SET_F("CF"),!useAMPM); - sappend(dest,'i',SET_F("TZ"),currentTimezone); - sappend(dest,'v',SET_F("UO"),utcOffsetSecs); + printSetCheckbox(dest,PSTR("NT"),ntpEnabled); + printSetValue(dest,PSTR("NS"),ntpServerName); + printSetCheckbox(dest,PSTR("CF"),!useAMPM); + printSetIndex(dest,PSTR("TZ"),currentTimezone); + printSetValue(dest,PSTR("UO"),utcOffsetSecs); char tm[32]; dtostrf(longitude,4,2,tm); - sappends(dest,'s',SET_F("LN"),tm); + printSetValue(dest,PSTR("LN"),tm); dtostrf(latitude,4,2,tm); - sappends(dest,'s',SET_F("LT"),tm); + printSetValue(dest,PSTR("LT"),tm); getTimeString(tm); - sappends(dest,'m',SET_F("(\"times\")[0]"),tm); + printSetMessage(dest,PSTR("(\"times\")[0]"),tm); if ((int)(longitude*10.0f) || (int)(latitude*10.0f)) { sprintf_P(tm, PSTR("Sunrise: %02d:%02d Sunset: %02d:%02d"), hour(sunrise), minute(sunrise), hour(sunset), minute(sunset)); - sappends(dest,'m',SET_F("(\"times\")[1]"),tm); + printSetMessage(dest,PSTR("(\"times\")[1]"),tm); } - sappend(dest,'c',SET_F("OL"),overlayCurrent); - sappend(dest,'v',SET_F("O1"),overlayMin); - sappend(dest,'v',SET_F("O2"),overlayMax); - sappend(dest,'v',SET_F("OM"),analogClock12pixel); - sappend(dest,'c',SET_F("OS"),analogClockSecondsTrail); - sappend(dest,'c',SET_F("O5"),analogClock5MinuteMarks); - sappend(dest,'c',SET_F("OB"),analogClockSolidBlack); + printSetCheckbox(dest,PSTR("OL"),overlayCurrent); + printSetValue(dest,PSTR("O1"),overlayMin); + printSetValue(dest,PSTR("O2"),overlayMax); + printSetValue(dest,PSTR("OM"),analogClock12pixel); + printSetCheckbox(dest,PSTR("OS"),analogClockSecondsTrail); + printSetCheckbox(dest,PSTR("O5"),analogClock5MinuteMarks); + printSetCheckbox(dest,PSTR("OB"),analogClockSolidBlack); - sappend(dest,'c',SET_F("CE"),countdownMode); - sappend(dest,'v',SET_F("CY"),countdownYear); - sappend(dest,'v',SET_F("CI"),countdownMonth); - sappend(dest,'v',SET_F("CD"),countdownDay); - sappend(dest,'v',SET_F("CH"),countdownHour); - sappend(dest,'v',SET_F("CM"),countdownMin); - sappend(dest,'v',SET_F("CS"),countdownSec); + printSetCheckbox(dest,PSTR("CE"),countdownMode); + printSetValue(dest,PSTR("CY"),countdownYear); + printSetValue(dest,PSTR("CI"),countdownMonth); + printSetValue(dest,PSTR("CD"),countdownDay); + printSetValue(dest,PSTR("CH"),countdownHour); + printSetValue(dest,PSTR("CM"),countdownMin); + printSetValue(dest,PSTR("CS"),countdownSec); - sappend(dest,'v',SET_F("A0"),macroAlexaOn); - sappend(dest,'v',SET_F("A1"),macroAlexaOff); - sappend(dest,'v',SET_F("MC"),macroCountdown); - sappend(dest,'v',SET_F("MN"),macroNl); + printSetValue(dest,PSTR("A0"),macroAlexaOn); + printSetValue(dest,PSTR("A1"),macroAlexaOff); + printSetValue(dest,PSTR("MC"),macroCountdown); + printSetValue(dest,PSTR("MN"),macroNl); for (unsigned i=0; i> 4) & 0x0F); - k[0] = 'P'; sappend(dest,'v',k,timerMonth[i] & 0x0F); - k[0] = 'D'; sappend(dest,'v',k,timerDay[i]); - k[0] = 'E'; sappend(dest,'v',k,timerDayEnd[i]); + k[0] = 'M'; printSetValue(dest,k,(timerMonth[i] >> 4) & 0x0F); + k[0] = 'P'; printSetValue(dest,k,timerMonth[i] & 0x0F); + k[0] = 'D'; printSetValue(dest,k,timerDay[i]); + k[0] = 'E'; printSetValue(dest,k,timerDayEnd[i]); } } } @@ -571,41 +571,41 @@ void getSettingsJS(byte subPage, Print& dest) char fpass[l+1]; //fill PIN field with 0000 fpass[l] = 0; memset(fpass,'0',l); - sappends(dest,'s',SET_F("PIN"),fpass); - sappend(dest,'c',SET_F("NO"),otaLock); - sappend(dest,'c',SET_F("OW"),wifiLock); - sappend(dest,'c',SET_F("AO"),aOtaEnabled); + printSetValue(dest,PSTR("PIN"),fpass); + printSetCheckbox(dest,PSTR("NO"),otaLock); + printSetCheckbox(dest,PSTR("OW"),wifiLock); + printSetCheckbox(dest,PSTR("AO"),aOtaEnabled); char tmp_buf[128]; snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION); - sappends(dest,'m',SET_F("(\"sip\")[0]"),tmp_buf); + printSetMessage(dest,PSTR("(\"sip\")[0]"),tmp_buf); dest.printf_P(PSTR("sd=\"%s\";"), serverDescription); } #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { - sappend(dest,'v',SET_F("PU"),e131ProxyUniverse); + printSetValue(dest,PSTR("PU"),e131ProxyUniverse); - sappend(dest,'v',SET_F("CN"),DMXChannels); - sappend(dest,'v',SET_F("CG"),DMXGap); - sappend(dest,'v',SET_F("CS"),DMXStart); - sappend(dest,'v',SET_F("SL"),DMXStartLED); + printSetValue(dest,PSTR("CN"),DMXChannels); + printSetValue(dest,PSTR("CG"),DMXGap); + printSetValue(dest,PSTR("CS"),DMXStart); + printSetValue(dest,PSTR("SL"),DMXStartLED); - sappend(dest,'i',SET_F("CH1"),DMXFixtureMap[0]); - sappend(dest,'i',SET_F("CH2"),DMXFixtureMap[1]); - sappend(dest,'i',SET_F("CH3"),DMXFixtureMap[2]); - sappend(dest,'i',SET_F("CH4"),DMXFixtureMap[3]); - sappend(dest,'i',SET_F("CH5"),DMXFixtureMap[4]); - sappend(dest,'i',SET_F("CH6"),DMXFixtureMap[5]); - sappend(dest,'i',SET_F("CH7"),DMXFixtureMap[6]); - sappend(dest,'i',SET_F("CH8"),DMXFixtureMap[7]); - sappend(dest,'i',SET_F("CH9"),DMXFixtureMap[8]); - sappend(dest,'i',SET_F("CH10"),DMXFixtureMap[9]); - sappend(dest,'i',SET_F("CH11"),DMXFixtureMap[10]); - sappend(dest,'i',SET_F("CH12"),DMXFixtureMap[11]); - sappend(dest,'i',SET_F("CH13"),DMXFixtureMap[12]); - sappend(dest,'i',SET_F("CH14"),DMXFixtureMap[13]); - sappend(dest,'i',SET_F("CH15"),DMXFixtureMap[14]); + printSetIndex(dest,PSTR("CH1"),DMXFixtureMap[0]); + printSetIndex(dest,PSTR("CH2"),DMXFixtureMap[1]); + printSetIndex(dest,PSTR("CH3"),DMXFixtureMap[2]); + printSetIndex(dest,PSTR("CH4"),DMXFixtureMap[3]); + printSetIndex(dest,PSTR("CH5"),DMXFixtureMap[4]); + printSetIndex(dest,PSTR("CH6"),DMXFixtureMap[5]); + printSetIndex(dest,PSTR("CH7"),DMXFixtureMap[6]); + printSetIndex(dest,PSTR("CH8"),DMXFixtureMap[7]); + printSetIndex(dest,PSTR("CH9"),DMXFixtureMap[8]); + printSetIndex(dest,PSTR("CH10"),DMXFixtureMap[9]); + printSetIndex(dest,PSTR("CH11"),DMXFixtureMap[10]); + printSetIndex(dest,PSTR("CH12"),DMXFixtureMap[11]); + printSetIndex(dest,PSTR("CH13"),DMXFixtureMap[12]); + printSetIndex(dest,PSTR("CH14"),DMXFixtureMap[13]); + printSetIndex(dest,PSTR("CH15"),DMXFixtureMap[14]); } #endif @@ -613,11 +613,11 @@ void getSettingsJS(byte subPage, Print& dest) { appendGPIOinfo(dest); dest.printf_P(PSTR("numM=%d;"), usermods.getModCount()); - sappend(dest,'v',SET_F("SDA"),i2c_sda); - sappend(dest,'v',SET_F("SCL"),i2c_scl); - sappend(dest,'v',SET_F("MOSI"),spi_mosi); - sappend(dest,'v',SET_F("MISO"),spi_miso); - sappend(dest,'v',SET_F("SCLK"),spi_sclk); + printSetValue(dest,PSTR("SDA"),i2c_sda); + printSetValue(dest,PSTR("SCL"),i2c_scl); + printSetValue(dest,PSTR("MOSI"),spi_mosi); + printSetValue(dest,PSTR("MISO"),spi_miso); + printSetValue(dest,PSTR("SCLK"),spi_sclk); dest.printf_P(PSTR("addInfo('SDA','%d');" "addInfo('SCL','%d');" "addInfo('MOSI','%d');" @@ -641,21 +641,21 @@ void getSettingsJS(byte subPage, Print& dest) #endif VERSION); - sappends(dest,'m',SET_F("(\"sip\")[0]"),tmp_buf); + printSetMessage(dest,PSTR("(\"sip\")[0]"),tmp_buf); } if (subPage == SUBPAGE_2D) // 2D matrices { - sappend(dest,'v',SET_F("SOMP"),strip.isMatrix); + printSetValue(dest,PSTR("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D dest.print(F("maxPanels=")); dest.print(WLED_MAX_PANELS); dest.print(F(";")); dest.print(F("resetPanels();")); if (strip.isMatrix) { if(strip.panels>0){ - sappend(dest,'v',SET_F("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience - sappend(dest,'v',SET_F("PH"),strip.panel[0].height); + printSetValue(dest,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience + printSetValue(dest,PSTR("PH"),strip.panel[0].height); } - sappend(dest,'v',SET_F("MPC"),strip.panels); + printSetValue(dest,PSTR("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Thu, 12 Sep 2024 20:39:13 +0200 Subject: [PATCH 482/694] New names --- wled00/fcn_declare.h | 10 +- wled00/util.cpp | 20 +- wled00/xml.cpp | 424 +++++++++++++++++++++---------------------- 3 files changed, 227 insertions(+), 227 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a36f2dc26..29db8ea76 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -372,11 +372,11 @@ void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); bool getBoolVal(JsonVariant elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -size_t printSetCheckbox(Print& dest, const char* key, int val); -size_t printSetValue(Print& dest, const char* key, int val); -size_t printSetValue(Print& dest, const char* key, const char* val); -size_t printSetIndex(Print& dest, const char* key, int index); -size_t printSetMessage(Print& dest, const char* key, const char* val); +size_t printSetFormCheckbox(Print& dest, const char* key, int val); +size_t printSetFormValue(Print& dest, const char* key, int val); +size_t printSetFormValue(Print& dest, const char* key, const char* val); +size_t printSetFormIndex(Print& dest, const char* key, int index); +size_t printSetClassElementHTML(Print& dest, const char* key, const int index, const char* val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 660877d18..07190e37c 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -87,26 +87,26 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv return true; } -static size_t printSetInt(Print& dest, const char* key, const char* selector, int value) { +static size_t printSetFormInput(Print& dest, const char* key, const char* selector, int value) { return dest.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value); } -size_t printSetCheckbox(Print& dest, const char* key, int val) { - return printSetInt(dest, key, PSTR("checked"), val); +size_t printSetFormCheckbox(Print& dest, const char* key, int val) { + return printSetFormInput(dest, key, PSTR("checked"), val); } -size_t printSetValue(Print& dest, const char* key, int val) { - return printSetInt(dest, key, PSTR("value"), val); +size_t printSetFormValue(Print& dest, const char* key, int val) { + return printSetFormInput(dest, key, PSTR("value"), val); } -size_t printSetIndex(Print& dest, const char* key, int index) { - return printSetInt(dest, key, PSTR("selectedIndex"), index); +size_t printSetFormIndex(Print& dest, const char* key, int index) { + return printSetFormInput(dest, key, PSTR("selectedIndex"), index); } -size_t printSetValue(Print& dest, const char* key, const char* val) { +size_t printSetFormValue(Print& dest, const char* key, const char* val) { return dest.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); } -size_t printSetMessage(Print& dest, const char* key, const char* val) { - return dest.printf_P(PSTR("d.getElementsByClassName%s.innerHTML=\"%s\";"), key, val); +size_t printSetClassElementHTML(Print& dest, const char* key, const int index, const char* val) { + return dest.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index e8858066c..8cbd51cde 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -180,41 +180,41 @@ void getSettingsJS(byte subPage, Print& dest) (uint32_t) multiWiFi[n].staticSN); } - printSetValue(dest,PSTR("D0"),dnsAddress[0]); - printSetValue(dest,PSTR("D1"),dnsAddress[1]); - printSetValue(dest,PSTR("D2"),dnsAddress[2]); - printSetValue(dest,PSTR("D3"),dnsAddress[3]); + printSetFormValue(dest,PSTR("D0"),dnsAddress[0]); + printSetFormValue(dest,PSTR("D1"),dnsAddress[1]); + printSetFormValue(dest,PSTR("D2"),dnsAddress[2]); + printSetFormValue(dest,PSTR("D3"),dnsAddress[3]); - printSetValue(dest,PSTR("CM"),cmDNS); - printSetIndex(dest,PSTR("AB"),apBehavior); - printSetValue(dest,PSTR("AS"),apSSID); - printSetCheckbox(dest,PSTR("AH"),apHide); + printSetFormValue(dest,PSTR("CM"),cmDNS); + printSetFormIndex(dest,PSTR("AB"),apBehavior); + printSetFormValue(dest,PSTR("AS"),apSSID); + printSetFormCheckbox(dest,PSTR("AH"),apHide); l = strlen(apPass); char fapass[l+1]; //fill password field with *** fapass[l] = 0; memset(fapass,'*',l); - printSetValue(dest,PSTR("AP"),fapass); + printSetFormValue(dest,PSTR("AP"),fapass); - printSetValue(dest,PSTR("AC"),apChannel); + printSetFormValue(dest,PSTR("AC"),apChannel); #ifdef ARDUINO_ARCH_ESP32 - printSetValue(dest,PSTR("TX"),txPower); + printSetFormValue(dest,PSTR("TX"),txPower); #else dest.print(F("gId('tx').style.display='none';")); #endif - printSetCheckbox(dest,PSTR("FG"),force802_3g); - printSetCheckbox(dest,PSTR("WS"),noWifiSleep); + printSetFormCheckbox(dest,PSTR("FG"),force802_3g); + printSetFormCheckbox(dest,PSTR("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW - printSetCheckbox(dest,PSTR("RE"),enableESPNow); - printSetValue(dest,PSTR("RMAC"),linked_remote); + printSetFormCheckbox(dest,PSTR("RE"),enableESPNow); + printSetFormValue(dest,PSTR("RMAC"),linked_remote); #else //hide remote settings if not compiled dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif #ifdef WLED_USE_ETHERNET - printSetValue(dest,PSTR("ETH"),ethernetType); + printSetFormValue(dest,PSTR("ETH"),ethernetType); #else //hide ethernet setting if not compiled in dest.print(F("gId('ethd').style.display='none';")); @@ -229,10 +229,10 @@ void getSettingsJS(byte subPage, Print& dest) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); #endif - printSetMessage(dest,PSTR("(\"sip\")[0]"),s); + printSetClassElementHTML(dest,PSTR("sip"),0,s); } else { - printSetMessage(dest,PSTR("(\"sip\")[0]"),(char*)F("Not connected")); + printSetClassElementHTML(dest,PSTR("sip"),0,(char*)F("Not connected")); } if (WiFi.softAPIP()[0] != 0) //is active @@ -240,19 +240,19 @@ void getSettingsJS(byte subPage, Print& dest) char s[16]; IPAddress apIP = WiFi.softAPIP(); sprintf(s, "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); - printSetMessage(dest,PSTR("(\"sip\")[1]"),s); + printSetClassElementHTML(dest,PSTR("sip"),1,s); } else { - printSetMessage(dest,PSTR("(\"sip\")[1]"),(char*)F("Not active")); + printSetClassElementHTML(dest,PSTR("sip"),1,(char*)F("Not active")); } #ifndef WLED_DISABLE_ESPNOW if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote - printSetMessage(dest,PSTR("(\"rlid\")[0]"),last_signal_src); + printSetClassElementHTML(dest,PSTR("rlid"),0,last_signal_src); } else if (!enableESPNow) { - printSetMessage(dest,PSTR("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); + printSetClassElementHTML(dest,PSTR("rlid"),0,(char*)F("(Enable ESP-NOW to listen)")); } else { - printSetMessage(dest,PSTR("(\"rlid\")[0]"),(char*)F("None")); + printSetClassElementHTML(dest,PSTR("rlid"),0,(char*)F("None")); } #endif } @@ -277,14 +277,14 @@ void getSettingsJS(byte subPage, Print& dest) WLED_MAX_ANALOG_CHANNELS ); - printSetCheckbox(dest,PSTR("MS"),strip.autoSegments); - printSetCheckbox(dest,PSTR("CCT"),strip.correctWB); - printSetCheckbox(dest,PSTR("IC"),cctICused); - printSetCheckbox(dest,PSTR("CR"),strip.cctFromRgb); - printSetValue(dest,PSTR("CB"),strip.cctBlending); - printSetValue(dest,PSTR("FR"),strip.getTargetFps()); - printSetValue(dest,PSTR("AW"),Bus::getGlobalAWMode()); - printSetCheckbox(dest,PSTR("LD"),useGlobalLedBuffer); + printSetFormCheckbox(dest,PSTR("MS"),strip.autoSegments); + printSetFormCheckbox(dest,PSTR("CCT"),strip.correctWB); + printSetFormCheckbox(dest,PSTR("IC"),cctICused); + printSetFormCheckbox(dest,PSTR("CR"),strip.cctFromRgb); + printSetFormValue(dest,PSTR("CB"),strip.cctBlending); + printSetFormValue(dest,PSTR("FR"),strip.getTargetFps()); + printSetFormValue(dest,PSTR("AW"),Bus::getGlobalAWMode()); + printSetFormCheckbox(dest,PSTR("LD"),useGlobalLedBuffer); unsigned sumMa = 0; for (int s = 0; s < BusManager::getNumBusses(); s++) { @@ -309,17 +309,17 @@ void getSettingsJS(byte subPage, Print& dest) int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) printSetValue(dest,lp,pins[i]); + if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(dest,lp,pins[i]); } - printSetValue(dest,lc,bus->getLength()); - printSetValue(dest,lt,bus->getType()); - printSetValue(dest,co,bus->getColorOrder() & 0x0F); - printSetValue(dest,ls,bus->getStart()); - printSetCheckbox(dest,cv,bus->isReversed()); - printSetValue(dest,sl,bus->skippedLeds()); - printSetCheckbox(dest,rf,bus->isOffRefreshRequired()); - printSetValue(dest,aw,bus->getAutoWhiteMode()); - printSetValue(dest,wo,bus->getColorOrder() >> 4); + printSetFormValue(dest,lc,bus->getLength()); + printSetFormValue(dest,lt,bus->getType()); + printSetFormValue(dest,co,bus->getColorOrder() & 0x0F); + printSetFormValue(dest,ls,bus->getStart()); + printSetFormCheckbox(dest,cv,bus->isReversed()); + printSetFormValue(dest,sl,bus->skippedLeds()); + printSetFormCheckbox(dest,rf,bus->isOffRefreshRequired()); + printSetFormValue(dest,aw,bus->getAutoWhiteMode()); + printSetFormValue(dest,wo,bus->getColorOrder() >> 4); unsigned speed = bus->getFrequency(); if (bus->isPWM()) { switch (speed) { @@ -340,14 +340,14 @@ void getSettingsJS(byte subPage, Print& dest) case 20000 : speed = 4; break; } } - printSetValue(dest,sp,speed); - printSetValue(dest,la,bus->getLEDCurrent()); - printSetValue(dest,ma,bus->getMaxCurrent()); + printSetFormValue(dest,sp,speed); + printSetFormValue(dest,la,bus->getLEDCurrent()); + printSetFormValue(dest,ma,bus->getMaxCurrent()); sumMa += bus->getMaxCurrent(); } - printSetValue(dest,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); - printSetCheckbox(dest,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); - printSetCheckbox(dest,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); + printSetFormValue(dest,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); + printSetFormCheckbox(dest,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); + printSetFormCheckbox(dest,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); dest.printf_P(PSTR("resetCOM(%d);"), WLED_MAX_COLOR_ORDER_MAPPINGS); const ColorOrderMap& com = BusManager::getColorOrderMap(); @@ -357,114 +357,114 @@ void getSettingsJS(byte subPage, Print& dest) dest.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder); } - printSetValue(dest,PSTR("CA"),briS); + printSetFormValue(dest,PSTR("CA"),briS); - printSetCheckbox(dest,PSTR("BO"),turnOnAtBoot); - printSetValue(dest,PSTR("BP"),bootPreset); + printSetFormCheckbox(dest,PSTR("BO"),turnOnAtBoot); + printSetFormValue(dest,PSTR("BP"),bootPreset); - printSetCheckbox(dest,PSTR("GB"),gammaCorrectBri); - printSetCheckbox(dest,PSTR("GC"),gammaCorrectCol); - dtostrf(gammaCorrectVal,3,1,nS); printSetValue(dest,PSTR("GV"),nS); - printSetCheckbox(dest,PSTR("TF"),fadeTransition); - printSetCheckbox(dest,PSTR("EB"),modeBlending); - printSetValue(dest,PSTR("TD"),transitionDelayDefault); - printSetCheckbox(dest,PSTR("PF"),strip.paletteFade); - printSetValue(dest,PSTR("TP"),randomPaletteChangeTime); - printSetCheckbox(dest,PSTR("TH"),useHarmonicRandomPalette); - printSetValue(dest,PSTR("BF"),briMultiplier); - printSetValue(dest,PSTR("TB"),nightlightTargetBri); - printSetValue(dest,PSTR("TL"),nightlightDelayMinsDefault); - printSetValue(dest,PSTR("TW"),nightlightMode); - printSetIndex(dest,PSTR("PB"),strip.paletteBlend); - printSetValue(dest,PSTR("RL"),rlyPin); - printSetCheckbox(dest,PSTR("RM"),rlyMde); - printSetCheckbox(dest,PSTR("RO"),rlyOpenDrain); + printSetFormCheckbox(dest,PSTR("GB"),gammaCorrectBri); + printSetFormCheckbox(dest,PSTR("GC"),gammaCorrectCol); + dtostrf(gammaCorrectVal,3,1,nS); printSetFormValue(dest,PSTR("GV"),nS); + printSetFormCheckbox(dest,PSTR("TF"),fadeTransition); + printSetFormCheckbox(dest,PSTR("EB"),modeBlending); + printSetFormValue(dest,PSTR("TD"),transitionDelayDefault); + printSetFormCheckbox(dest,PSTR("PF"),strip.paletteFade); + printSetFormValue(dest,PSTR("TP"),randomPaletteChangeTime); + printSetFormCheckbox(dest,PSTR("TH"),useHarmonicRandomPalette); + printSetFormValue(dest,PSTR("BF"),briMultiplier); + printSetFormValue(dest,PSTR("TB"),nightlightTargetBri); + printSetFormValue(dest,PSTR("TL"),nightlightDelayMinsDefault); + printSetFormValue(dest,PSTR("TW"),nightlightMode); + printSetFormIndex(dest,PSTR("PB"),strip.paletteBlend); + printSetFormValue(dest,PSTR("RL"),rlyPin); + printSetFormCheckbox(dest,PSTR("RM"),rlyMde); + printSetFormCheckbox(dest,PSTR("RO"),rlyOpenDrain); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { dest.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]); } - printSetCheckbox(dest,PSTR("IP"),disablePullUp); - printSetValue(dest,PSTR("TT"),touchThreshold); + printSetFormCheckbox(dest,PSTR("IP"),disablePullUp); + printSetFormValue(dest,PSTR("TT"),touchThreshold); #ifndef WLED_DISABLE_INFRARED - printSetValue(dest,PSTR("IR"),irPin); - printSetValue(dest,PSTR("IT"),irEnabled); + printSetFormValue(dest,PSTR("IR"),irPin); + printSetFormValue(dest,PSTR("IT"),irEnabled); #endif - printSetCheckbox(dest,PSTR("MSO"),!irApplyToAllSelected); + printSetFormCheckbox(dest,PSTR("MSO"),!irApplyToAllSelected); } if (subPage == SUBPAGE_UI) { - printSetValue(dest,PSTR("DS"),serverDescription); - printSetCheckbox(dest,PSTR("SU"),simplifiedUI); + printSetFormValue(dest,PSTR("DS"),serverDescription); + printSetFormCheckbox(dest,PSTR("SU"),simplifiedUI); } if (subPage == SUBPAGE_SYNC) { [[maybe_unused]] char nS[32]; - printSetValue(dest,PSTR("UP"),udpPort); - printSetValue(dest,PSTR("U2"),udpPort2); + printSetFormValue(dest,PSTR("UP"),udpPort); + printSetFormValue(dest,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW - if (enableESPNow) printSetCheckbox(dest,PSTR("EN"),useESPNowSync); + if (enableESPNow) printSetFormCheckbox(dest,PSTR("EN"),useESPNowSync); else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif - printSetValue(dest,PSTR("GS"),syncGroups); - printSetValue(dest,PSTR("GR"),receiveGroups); + printSetFormValue(dest,PSTR("GS"),syncGroups); + printSetFormValue(dest,PSTR("GR"),receiveGroups); - printSetCheckbox(dest,PSTR("RB"),receiveNotificationBrightness); - printSetCheckbox(dest,PSTR("RC"),receiveNotificationColor); - printSetCheckbox(dest,PSTR("RX"),receiveNotificationEffects); - printSetCheckbox(dest,PSTR("RP"),receiveNotificationPalette); - printSetCheckbox(dest,PSTR("SO"),receiveSegmentOptions); - printSetCheckbox(dest,PSTR("SG"),receiveSegmentBounds); - printSetCheckbox(dest,PSTR("SS"),sendNotifications); - printSetCheckbox(dest,PSTR("SD"),notifyDirect); - printSetCheckbox(dest,PSTR("SB"),notifyButton); - printSetCheckbox(dest,PSTR("SH"),notifyHue); - printSetValue(dest,PSTR("UR"),udpNumRetries); + printSetFormCheckbox(dest,PSTR("RB"),receiveNotificationBrightness); + printSetFormCheckbox(dest,PSTR("RC"),receiveNotificationColor); + printSetFormCheckbox(dest,PSTR("RX"),receiveNotificationEffects); + printSetFormCheckbox(dest,PSTR("RP"),receiveNotificationPalette); + printSetFormCheckbox(dest,PSTR("SO"),receiveSegmentOptions); + printSetFormCheckbox(dest,PSTR("SG"),receiveSegmentBounds); + printSetFormCheckbox(dest,PSTR("SS"),sendNotifications); + printSetFormCheckbox(dest,PSTR("SD"),notifyDirect); + printSetFormCheckbox(dest,PSTR("SB"),notifyButton); + printSetFormCheckbox(dest,PSTR("SH"),notifyHue); + printSetFormValue(dest,PSTR("UR"),udpNumRetries); - printSetCheckbox(dest,PSTR("NL"),nodeListEnabled); - printSetCheckbox(dest,PSTR("NB"),nodeBroadcastEnabled); + printSetFormCheckbox(dest,PSTR("NL"),nodeListEnabled); + printSetFormCheckbox(dest,PSTR("NB"),nodeBroadcastEnabled); - printSetCheckbox(dest,PSTR("RD"),receiveDirect); - printSetCheckbox(dest,PSTR("MO"),useMainSegmentOnly); - printSetCheckbox(dest,PSTR("RLM"),realtimeRespectLedMaps); - printSetValue(dest,PSTR("EP"),e131Port); - printSetCheckbox(dest,PSTR("ES"),e131SkipOutOfSequence); - printSetCheckbox(dest,PSTR("EM"),e131Multicast); - printSetValue(dest,PSTR("EU"),e131Universe); - printSetValue(dest,PSTR("DA"),DMXAddress); - printSetValue(dest,PSTR("XX"),DMXSegmentSpacing); - printSetValue(dest,PSTR("PY"),e131Priority); - printSetValue(dest,PSTR("DM"),DMXMode); - printSetValue(dest,PSTR("ET"),realtimeTimeoutMs); - printSetCheckbox(dest,PSTR("FB"),arlsForceMaxBri); - printSetCheckbox(dest,PSTR("RG"),arlsDisableGammaCorrection); - printSetValue(dest,PSTR("WO"),arlsOffset); + printSetFormCheckbox(dest,PSTR("RD"),receiveDirect); + printSetFormCheckbox(dest,PSTR("MO"),useMainSegmentOnly); + printSetFormCheckbox(dest,PSTR("RLM"),realtimeRespectLedMaps); + printSetFormValue(dest,PSTR("EP"),e131Port); + printSetFormCheckbox(dest,PSTR("ES"),e131SkipOutOfSequence); + printSetFormCheckbox(dest,PSTR("EM"),e131Multicast); + printSetFormValue(dest,PSTR("EU"),e131Universe); + printSetFormValue(dest,PSTR("DA"),DMXAddress); + printSetFormValue(dest,PSTR("XX"),DMXSegmentSpacing); + printSetFormValue(dest,PSTR("PY"),e131Priority); + printSetFormValue(dest,PSTR("DM"),DMXMode); + printSetFormValue(dest,PSTR("ET"),realtimeTimeoutMs); + printSetFormCheckbox(dest,PSTR("FB"),arlsForceMaxBri); + printSetFormCheckbox(dest,PSTR("RG"),arlsDisableGammaCorrection); + printSetFormValue(dest,PSTR("WO"),arlsOffset); #ifndef WLED_DISABLE_ALEXA - printSetCheckbox(dest,PSTR("AL"),alexaEnabled); - printSetValue(dest,PSTR("AI"),alexaInvocationName); - printSetCheckbox(dest,PSTR("SA"),notifyAlexa); - printSetValue(dest,PSTR("AP"),alexaNumPresets); + printSetFormCheckbox(dest,PSTR("AL"),alexaEnabled); + printSetFormValue(dest,PSTR("AI"),alexaInvocationName); + printSetFormCheckbox(dest,PSTR("SA"),notifyAlexa); + printSetFormValue(dest,PSTR("AP"),alexaNumPresets); #else dest.print(F("toggle('Alexa');")); // hide Alexa settings #endif #ifndef WLED_DISABLE_MQTT - printSetCheckbox(dest,PSTR("MQ"),mqttEnabled); - printSetValue(dest,PSTR("MS"),mqttServer); - printSetValue(dest,PSTR("MQPORT"),mqttPort); - printSetValue(dest,PSTR("MQUSER"),mqttUser); + printSetFormCheckbox(dest,PSTR("MQ"),mqttEnabled); + printSetFormValue(dest,PSTR("MS"),mqttServer); + printSetFormValue(dest,PSTR("MQPORT"),mqttPort); + printSetFormValue(dest,PSTR("MQUSER"),mqttUser); byte l = strlen(mqttPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - printSetValue(dest,PSTR("MQPASS"),fpass); - printSetValue(dest,PSTR("MQCID"),mqttClientID); - printSetValue(dest,PSTR("MD"),mqttDeviceTopic); - printSetValue(dest,PSTR("MG"),mqttGroupTopic); - printSetCheckbox(dest,PSTR("BM"),buttonPublishMqtt); - printSetCheckbox(dest,PSTR("RT"),retainMqttMsg); + printSetFormValue(dest,PSTR("MQPASS"),fpass); + printSetFormValue(dest,PSTR("MQCID"),mqttClientID); + printSetFormValue(dest,PSTR("MD"),mqttDeviceTopic); + printSetFormValue(dest,PSTR("MG"),mqttGroupTopic); + printSetFormCheckbox(dest,PSTR("BM"),buttonPublishMqtt); + printSetFormCheckbox(dest,PSTR("RT"),retainMqttMsg); dest.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else @@ -472,16 +472,16 @@ void getSettingsJS(byte subPage, Print& dest) #endif #ifndef WLED_DISABLE_HUESYNC - printSetValue(dest,PSTR("H0"),hueIP[0]); - printSetValue(dest,PSTR("H1"),hueIP[1]); - printSetValue(dest,PSTR("H2"),hueIP[2]); - printSetValue(dest,PSTR("H3"),hueIP[3]); - printSetValue(dest,PSTR("HL"),huePollLightId); - printSetValue(dest,PSTR("HI"),huePollIntervalMs); - printSetCheckbox(dest,PSTR("HP"),huePollingEnabled); - printSetCheckbox(dest,PSTR("HO"),hueApplyOnOff); - printSetCheckbox(dest,PSTR("HB"),hueApplyBri); - printSetCheckbox(dest,PSTR("HC"),hueApplyColor); + printSetFormValue(dest,PSTR("H0"),hueIP[0]); + printSetFormValue(dest,PSTR("H1"),hueIP[1]); + printSetFormValue(dest,PSTR("H2"),hueIP[2]); + printSetFormValue(dest,PSTR("H3"),hueIP[3]); + printSetFormValue(dest,PSTR("HL"),huePollLightId); + printSetFormValue(dest,PSTR("HI"),huePollIntervalMs); + printSetFormCheckbox(dest,PSTR("HP"),huePollingEnabled); + printSetFormCheckbox(dest,PSTR("HO"),hueApplyOnOff); + printSetFormCheckbox(dest,PSTR("HB"),hueApplyBri); + printSetFormCheckbox(dest,PSTR("HC"),hueApplyColor); char hueErrorString[25]; switch (hueError) { @@ -495,11 +495,11 @@ void getSettingsJS(byte subPage, Print& dest) default: sprintf_P(hueErrorString,PSTR("Bridge Error %i"),hueError); } - printSetMessage(dest,PSTR("(\"sip\")[0]"),hueErrorString); + printSetClassElementHTML(dest,PSTR("sip"),0,hueErrorString); #else dest.print(F("toggle('Hue');")); // hide Hue Sync settings #endif - printSetValue(dest,PSTR("BD"),serialBaud); + printSetFormValue(dest,PSTR("BD"),serialBaud); #ifndef WLED_ENABLE_ADALIGHT dest.print(SET_F("toggle('Serial);")); #endif @@ -507,42 +507,42 @@ void getSettingsJS(byte subPage, Print& dest) if (subPage == SUBPAGE_TIME) { - printSetCheckbox(dest,PSTR("NT"),ntpEnabled); - printSetValue(dest,PSTR("NS"),ntpServerName); - printSetCheckbox(dest,PSTR("CF"),!useAMPM); - printSetIndex(dest,PSTR("TZ"),currentTimezone); - printSetValue(dest,PSTR("UO"),utcOffsetSecs); + printSetFormCheckbox(dest,PSTR("NT"),ntpEnabled); + printSetFormValue(dest,PSTR("NS"),ntpServerName); + printSetFormCheckbox(dest,PSTR("CF"),!useAMPM); + printSetFormIndex(dest,PSTR("TZ"),currentTimezone); + printSetFormValue(dest,PSTR("UO"),utcOffsetSecs); char tm[32]; dtostrf(longitude,4,2,tm); - printSetValue(dest,PSTR("LN"),tm); + printSetFormValue(dest,PSTR("LN"),tm); dtostrf(latitude,4,2,tm); - printSetValue(dest,PSTR("LT"),tm); + printSetFormValue(dest,PSTR("LT"),tm); getTimeString(tm); - printSetMessage(dest,PSTR("(\"times\")[0]"),tm); + printSetClassElementHTML(dest,PSTR("times"),0,tm); if ((int)(longitude*10.0f) || (int)(latitude*10.0f)) { sprintf_P(tm, PSTR("Sunrise: %02d:%02d Sunset: %02d:%02d"), hour(sunrise), minute(sunrise), hour(sunset), minute(sunset)); - printSetMessage(dest,PSTR("(\"times\")[1]"),tm); + printSetClassElementHTML(dest,PSTR("times"),1,tm); } - printSetCheckbox(dest,PSTR("OL"),overlayCurrent); - printSetValue(dest,PSTR("O1"),overlayMin); - printSetValue(dest,PSTR("O2"),overlayMax); - printSetValue(dest,PSTR("OM"),analogClock12pixel); - printSetCheckbox(dest,PSTR("OS"),analogClockSecondsTrail); - printSetCheckbox(dest,PSTR("O5"),analogClock5MinuteMarks); - printSetCheckbox(dest,PSTR("OB"),analogClockSolidBlack); + printSetFormCheckbox(dest,PSTR("OL"),overlayCurrent); + printSetFormValue(dest,PSTR("O1"),overlayMin); + printSetFormValue(dest,PSTR("O2"),overlayMax); + printSetFormValue(dest,PSTR("OM"),analogClock12pixel); + printSetFormCheckbox(dest,PSTR("OS"),analogClockSecondsTrail); + printSetFormCheckbox(dest,PSTR("O5"),analogClock5MinuteMarks); + printSetFormCheckbox(dest,PSTR("OB"),analogClockSolidBlack); - printSetCheckbox(dest,PSTR("CE"),countdownMode); - printSetValue(dest,PSTR("CY"),countdownYear); - printSetValue(dest,PSTR("CI"),countdownMonth); - printSetValue(dest,PSTR("CD"),countdownDay); - printSetValue(dest,PSTR("CH"),countdownHour); - printSetValue(dest,PSTR("CM"),countdownMin); - printSetValue(dest,PSTR("CS"),countdownSec); + printSetFormCheckbox(dest,PSTR("CE"),countdownMode); + printSetFormValue(dest,PSTR("CY"),countdownYear); + printSetFormValue(dest,PSTR("CI"),countdownMonth); + printSetFormValue(dest,PSTR("CD"),countdownDay); + printSetFormValue(dest,PSTR("CH"),countdownHour); + printSetFormValue(dest,PSTR("CM"),countdownMin); + printSetFormValue(dest,PSTR("CS"),countdownSec); - printSetValue(dest,PSTR("A0"),macroAlexaOn); - printSetValue(dest,PSTR("A1"),macroAlexaOff); - printSetValue(dest,PSTR("MC"),macroCountdown); - printSetValue(dest,PSTR("MN"),macroNl); + printSetFormValue(dest,PSTR("A0"),macroAlexaOn); + printSetFormValue(dest,PSTR("A1"),macroAlexaOff); + printSetFormValue(dest,PSTR("MC"),macroCountdown); + printSetFormValue(dest,PSTR("MN"),macroNl); for (unsigned i=0; i> 4) & 0x0F); - k[0] = 'P'; printSetValue(dest,k,timerMonth[i] & 0x0F); - k[0] = 'D'; printSetValue(dest,k,timerDay[i]); - k[0] = 'E'; printSetValue(dest,k,timerDayEnd[i]); + k[0] = 'M'; printSetFormValue(dest,k,(timerMonth[i] >> 4) & 0x0F); + k[0] = 'P'; printSetFormValue(dest,k,timerMonth[i] & 0x0F); + k[0] = 'D'; printSetFormValue(dest,k,timerDay[i]); + k[0] = 'E'; printSetFormValue(dest,k,timerDayEnd[i]); } } } @@ -571,41 +571,41 @@ void getSettingsJS(byte subPage, Print& dest) char fpass[l+1]; //fill PIN field with 0000 fpass[l] = 0; memset(fpass,'0',l); - printSetValue(dest,PSTR("PIN"),fpass); - printSetCheckbox(dest,PSTR("NO"),otaLock); - printSetCheckbox(dest,PSTR("OW"),wifiLock); - printSetCheckbox(dest,PSTR("AO"),aOtaEnabled); + printSetFormValue(dest,PSTR("PIN"),fpass); + printSetFormCheckbox(dest,PSTR("NO"),otaLock); + printSetFormCheckbox(dest,PSTR("OW"),wifiLock); + printSetFormCheckbox(dest,PSTR("AO"),aOtaEnabled); char tmp_buf[128]; snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION); - printSetMessage(dest,PSTR("(\"sip\")[0]"),tmp_buf); + printSetClassElementHTML(dest,PSTR("sip"),0,tmp_buf); dest.printf_P(PSTR("sd=\"%s\";"), serverDescription); } #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { - printSetValue(dest,PSTR("PU"),e131ProxyUniverse); + printSetFormValue(dest,PSTR("PU"),e131ProxyUniverse); - printSetValue(dest,PSTR("CN"),DMXChannels); - printSetValue(dest,PSTR("CG"),DMXGap); - printSetValue(dest,PSTR("CS"),DMXStart); - printSetValue(dest,PSTR("SL"),DMXStartLED); + printSetFormValue(dest,PSTR("CN"),DMXChannels); + printSetFormValue(dest,PSTR("CG"),DMXGap); + printSetFormValue(dest,PSTR("CS"),DMXStart); + printSetFormValue(dest,PSTR("SL"),DMXStartLED); - printSetIndex(dest,PSTR("CH1"),DMXFixtureMap[0]); - printSetIndex(dest,PSTR("CH2"),DMXFixtureMap[1]); - printSetIndex(dest,PSTR("CH3"),DMXFixtureMap[2]); - printSetIndex(dest,PSTR("CH4"),DMXFixtureMap[3]); - printSetIndex(dest,PSTR("CH5"),DMXFixtureMap[4]); - printSetIndex(dest,PSTR("CH6"),DMXFixtureMap[5]); - printSetIndex(dest,PSTR("CH7"),DMXFixtureMap[6]); - printSetIndex(dest,PSTR("CH8"),DMXFixtureMap[7]); - printSetIndex(dest,PSTR("CH9"),DMXFixtureMap[8]); - printSetIndex(dest,PSTR("CH10"),DMXFixtureMap[9]); - printSetIndex(dest,PSTR("CH11"),DMXFixtureMap[10]); - printSetIndex(dest,PSTR("CH12"),DMXFixtureMap[11]); - printSetIndex(dest,PSTR("CH13"),DMXFixtureMap[12]); - printSetIndex(dest,PSTR("CH14"),DMXFixtureMap[13]); - printSetIndex(dest,PSTR("CH15"),DMXFixtureMap[14]); + printSetFormIndex(dest,PSTR("CH1"),DMXFixtureMap[0]); + printSetFormIndex(dest,PSTR("CH2"),DMXFixtureMap[1]); + printSetFormIndex(dest,PSTR("CH3"),DMXFixtureMap[2]); + printSetFormIndex(dest,PSTR("CH4"),DMXFixtureMap[3]); + printSetFormIndex(dest,PSTR("CH5"),DMXFixtureMap[4]); + printSetFormIndex(dest,PSTR("CH6"),DMXFixtureMap[5]); + printSetFormIndex(dest,PSTR("CH7"),DMXFixtureMap[6]); + printSetFormIndex(dest,PSTR("CH8"),DMXFixtureMap[7]); + printSetFormIndex(dest,PSTR("CH9"),DMXFixtureMap[8]); + printSetFormIndex(dest,PSTR("CH10"),DMXFixtureMap[9]); + printSetFormIndex(dest,PSTR("CH11"),DMXFixtureMap[10]); + printSetFormIndex(dest,PSTR("CH12"),DMXFixtureMap[11]); + printSetFormIndex(dest,PSTR("CH13"),DMXFixtureMap[12]); + printSetFormIndex(dest,PSTR("CH14"),DMXFixtureMap[13]); + printSetFormIndex(dest,PSTR("CH15"),DMXFixtureMap[14]); } #endif @@ -613,11 +613,11 @@ void getSettingsJS(byte subPage, Print& dest) { appendGPIOinfo(dest); dest.printf_P(PSTR("numM=%d;"), usermods.getModCount()); - printSetValue(dest,PSTR("SDA"),i2c_sda); - printSetValue(dest,PSTR("SCL"),i2c_scl); - printSetValue(dest,PSTR("MOSI"),spi_mosi); - printSetValue(dest,PSTR("MISO"),spi_miso); - printSetValue(dest,PSTR("SCLK"),spi_sclk); + printSetFormValue(dest,PSTR("SDA"),i2c_sda); + printSetFormValue(dest,PSTR("SCL"),i2c_scl); + printSetFormValue(dest,PSTR("MOSI"),spi_mosi); + printSetFormValue(dest,PSTR("MISO"),spi_miso); + printSetFormValue(dest,PSTR("SCLK"),spi_sclk); dest.printf_P(PSTR("addInfo('SDA','%d');" "addInfo('SCL','%d');" "addInfo('MOSI','%d');" @@ -641,21 +641,21 @@ void getSettingsJS(byte subPage, Print& dest) #endif VERSION); - printSetMessage(dest,PSTR("(\"sip\")[0]"),tmp_buf); + printSetClassElementHTML(dest,PSTR("sip"),0,tmp_buf); } if (subPage == SUBPAGE_2D) // 2D matrices { - printSetValue(dest,PSTR("SOMP"),strip.isMatrix); + printSetFormValue(dest,PSTR("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D - dest.print(F("maxPanels=")); dest.print(WLED_MAX_PANELS); dest.print(F(";")); + dest.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); dest.print(F("resetPanels();")); if (strip.isMatrix) { if(strip.panels>0){ - printSetValue(dest,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience - printSetValue(dest,PSTR("PH"),strip.panel[0].height); + printSetFormValue(dest,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience + printSetFormValue(dest,PSTR("PH"),strip.panel[0].height); } - printSetValue(dest,PSTR("MPC"),strip.panels); + printSetFormValue(dest,PSTR("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Wed, 18 Sep 2024 19:19:40 -0400 Subject: [PATCH 483/694] Rename destination for getSettingsJS Use a name that makes it a bit clearer what the output is. The new name is applied consistently through most uses. Usermods are not yet updated. --- wled00/fcn_declare.h | 12 +- wled00/um_manager.cpp | 4 +- wled00/util.cpp | 24 +- wled00/xml.cpp | 556 +++++++++++++++++++++--------------------- 4 files changed, 298 insertions(+), 298 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 29db8ea76..5712c7f8d 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -302,7 +302,7 @@ class Usermod { virtual bool handleButton(uint8_t b) { return false; } // button overrides are possible here virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; // usermod data exchange [see examples for audio effects] virtual void connected() {} // called when WiFi is (re)connected - virtual void appendConfigData(Print&); // helper function called from usermod settings page to add metadata for entry fields + virtual void appendConfigData(Print& settingsScript); // helper function called from usermod settings page to add metadata for entry fields virtual void addToJsonState(JsonObject& obj) {} // add JSON objects for WLED state virtual void addToJsonInfo(JsonObject& obj) {} // add JSON objects for UI Info page virtual void readFromJsonState(JsonObject& obj) {} // process JSON messages received from web server @@ -372,11 +372,11 @@ void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); bool getBoolVal(JsonVariant elem, bool dflt); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -size_t printSetFormCheckbox(Print& dest, const char* key, int val); -size_t printSetFormValue(Print& dest, const char* key, int val); -size_t printSetFormValue(Print& dest, const char* key, const char* val); -size_t printSetFormIndex(Print& dest, const char* key, int index); -size_t printSetClassElementHTML(Print& dest, const char* key, const int index, const char* val); +size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val); +size_t printSetFormValue(Print& settingsScript, const char* key, int val); +size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); +size_t printSetFormIndex(Print& settingsScript, const char* key, int index); +size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 5307d26f6..ff3b62789 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -73,9 +73,9 @@ bool UsermodManager::add(Usermod* um) /* Usermod v2 interface shim for oappend */ Print* Usermod::oappend_shim = nullptr; -void Usermod::appendConfigData(Print& p) { +void Usermod::appendConfigData(Print& settingsScript) { assert(!oappend_shim); - oappend_shim = &p; + oappend_shim = &settingsScript; this->appendConfigData(); oappend_shim = nullptr; } diff --git a/wled00/util.cpp b/wled00/util.cpp index 07190e37c..0b78a4646 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -87,26 +87,26 @@ bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv return true; } -static size_t printSetFormInput(Print& dest, const char* key, const char* selector, int value) { - return dest.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value); +static size_t printSetFormInput(Print& settingsScript, const char* key, const char* selector, int value) { + return settingsScript.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value); } -size_t printSetFormCheckbox(Print& dest, const char* key, int val) { - return printSetFormInput(dest, key, PSTR("checked"), val); +size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val) { + return printSetFormInput(settingsScript, key, PSTR("checked"), val); } -size_t printSetFormValue(Print& dest, const char* key, int val) { - return printSetFormInput(dest, key, PSTR("value"), val); +size_t printSetFormValue(Print& settingsScript, const char* key, int val) { + return printSetFormInput(settingsScript, key, PSTR("value"), val); } -size_t printSetFormIndex(Print& dest, const char* key, int index) { - return printSetFormInput(dest, key, PSTR("selectedIndex"), index); +size_t printSetFormIndex(Print& settingsScript, const char* key, int index) { + return printSetFormInput(settingsScript, key, PSTR("selectedIndex"), index); } -size_t printSetFormValue(Print& dest, const char* key, const char* val) { - return dest.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); +size_t printSetFormValue(Print& settingsScript, const char* key, const char* val) { + return settingsScript.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val); } -size_t printSetClassElementHTML(Print& dest, const char* key, const int index, const char* val) { - return dest.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); +size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val) { + return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 8cbd51cde..5619a0ee3 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -26,19 +26,19 @@ void XML_response(Print& dest) ); } -static void extractPin(Print& dest, JsonObject &obj, const char *key) { +static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) { if (obj[key].is()) { JsonArray pins = obj[key].as(); for (JsonVariant pv : pins) { - if (pv.as() > -1) { dest.print(","); dest.print(pv.as()); } + if (pv.as() > -1) { settingsScript.print(","); settingsScript.print(pv.as()); } } } else { - if (obj[key].as() > -1) { dest.print(","); dest.print(obj[key].as()); } + if (obj[key].as() > -1) { settingsScript.print(","); settingsScript.print(obj[key].as()); } } } -// dest.print used pins by scanning JsonObject (1 level deep) -void fillUMPins(Print& dest, JsonObject &mods) +// print used pins by scanning JsonObject (1 level deep) +static void fillUMPins(Print& settingsScript, JsonObject &mods) { for (JsonPair kv : mods) { // kv.key() is usermod name or subobject key @@ -47,7 +47,7 @@ void fillUMPins(Print& dest, JsonObject &mods) if (!obj.isNull()) { // element is an JsonObject if (!obj["pin"].isNull()) { - extractPin(dest, obj, "pin"); + extractPin(settingsScript, obj, "pin"); } else { // scan keys (just one level deep as is possible with usermods) for (JsonPair so : obj) { @@ -56,7 +56,7 @@ void fillUMPins(Print& dest, JsonObject &mods) // we found a key containing "pin" substring if (strlen(strstr(key, "pin")) == 3) { // and it is at the end, we found another pin - extractPin(dest, obj, key); + extractPin(settingsScript, obj, key); continue; } } @@ -64,7 +64,7 @@ void fillUMPins(Print& dest, JsonObject &mods) JsonObject subObj = obj[so.key()]; if (!subObj["pin"].isNull()) { // get pins from subobject - extractPin(dest, subObj, "pin"); + extractPin(settingsScript, subObj, "pin"); } } } @@ -72,81 +72,81 @@ void fillUMPins(Print& dest, JsonObject &mods) } } -void appendGPIOinfo(Print& dest) { - dest.print(F("d.um_p=[-1")); // has to have 1 element +void appendGPIOinfo(Print& settingsScript) { + settingsScript.print(F("d.um_p=[-1")); // has to have 1 element if (i2c_sda > -1 && i2c_scl > -1) { - dest.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl); + settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl); } if (spi_mosi > -1 && spi_sclk > -1) { - dest.printf_P(PSTR(",%d,%d"), spi_mosi, spi_sclk); + settingsScript.printf_P(PSTR(",%d,%d"), spi_mosi, spi_sclk); } // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins JsonObject mods = pDoc->createNestedObject(F("um")); usermods.addToConfig(mods); - if (!mods.isNull()) fillUMPins(dest, mods); + if (!mods.isNull()) fillUMPins(settingsScript, mods); releaseJSONBufferLock(); } - dest.print(F("];")); + settingsScript.print(F("];")); // add reserved (unusable) pins - dest.print(SET_F("d.rsvd=[")); + settingsScript.print(SET_F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (!pinManager.isPinOk(i, false)) { // include readonly pins - dest.print(i); dest.print(","); + settingsScript.print(i); settingsScript.print(","); } } #ifdef WLED_ENABLE_DMX - dest.print(SET_F("2,")); // DMX hardcoded pin + settingsScript.print(SET_F("2,")); // DMX hardcoded pin #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - dest.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin + settingsScript.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin #endif //Note: Using pin 3 (RX) disables Adalight / Serial JSON #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_power); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { dest.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } + for (unsigned p=0; p=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_power); } + if (ethernetBoards[ethernetType].eth_mdc>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } + if (ethernetBoards[ethernetType].eth_mdio>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: - dest.print(SET_F("0")); + settingsScript.print(SET_F("0")); break; case ETH_CLOCK_GPIO16_OUT: - dest.print(SET_F("16")); + settingsScript.print(SET_F("16")); break; case ETH_CLOCK_GPIO17_OUT: - dest.print(SET_F("17")); + settingsScript.print(SET_F("17")); break; } } #endif - dest.print(SET_F("];")); // rsvd + settingsScript.print(SET_F("];")); // rsvd // add info for read-only GPIO - dest.print(SET_F("d.ro_gpio=[")); + settingsScript.print(SET_F("d.ro_gpio=[")); bool firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (pinManager.isReadOnlyPin(i)) { // No comma before the first pin - if (!firstPin) dest.print(SET_F(",")); - dest.print(i); + if (!firstPin) settingsScript.print(SET_F(",")); + settingsScript.print(i); firstPin = false; } } - dest.print(SET_F("];")); + settingsScript.print(SET_F("];")); // add info about max. # of pins - dest.print(SET_F("d.max_gpio=")); - dest.print(WLED_NUM_PINS); - dest.print(SET_F(";")); + settingsScript.print(SET_F("d.max_gpio=")); + settingsScript.print(WLED_NUM_PINS); + settingsScript.print(SET_F(";")); } //get values for settings form in javascript -void getSettingsJS(byte subPage, Print& dest) +void getSettingsJS(byte subPage, Print& settingsScript) { //0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); @@ -156,23 +156,23 @@ void getSettingsJS(byte subPage, Print& dest) if (subPage == SUBPAGE_MENU) { #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in - dest.print(F("gId('2dbtn').style.display='none';")); + settingsScript.print(F("gId('2dbtn').style.display='none';")); #endif #ifdef WLED_ENABLE_DMX // include only if DMX is enabled - dest.print(F("gId('dmxbtn').style.display='';")); + settingsScript.print(F("gId('dmxbtn').style.display='';")); #endif } if (subPage == SUBPAGE_WIFI) { size_t l; - dest.printf_P(PSTR("resetWiFi(%d);"), WLED_MAX_WIFI_COUNT); + settingsScript.printf_P(PSTR("resetWiFi(%d);"), WLED_MAX_WIFI_COUNT); for (size_t n = 0; n < multiWiFi.size(); n++) { l = strlen(multiWiFi[n].clientPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - dest.printf_P(PSTR("addWiFi(\"%s\",\",%s\",0x%X,0x%X,0x%X);"), + settingsScript.printf_P(PSTR("addWiFi(\"%s\",\",%s\",0x%X,0x%X,0x%X);"), multiWiFi[n].clientSSID, fpass, (uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct @@ -180,44 +180,44 @@ void getSettingsJS(byte subPage, Print& dest) (uint32_t) multiWiFi[n].staticSN); } - printSetFormValue(dest,PSTR("D0"),dnsAddress[0]); - printSetFormValue(dest,PSTR("D1"),dnsAddress[1]); - printSetFormValue(dest,PSTR("D2"),dnsAddress[2]); - printSetFormValue(dest,PSTR("D3"),dnsAddress[3]); + printSetFormValue(settingsScript,PSTR("D0"),dnsAddress[0]); + printSetFormValue(settingsScript,PSTR("D1"),dnsAddress[1]); + printSetFormValue(settingsScript,PSTR("D2"),dnsAddress[2]); + printSetFormValue(settingsScript,PSTR("D3"),dnsAddress[3]); - printSetFormValue(dest,PSTR("CM"),cmDNS); - printSetFormIndex(dest,PSTR("AB"),apBehavior); - printSetFormValue(dest,PSTR("AS"),apSSID); - printSetFormCheckbox(dest,PSTR("AH"),apHide); + printSetFormValue(settingsScript,PSTR("CM"),cmDNS); + printSetFormIndex(settingsScript,PSTR("AB"),apBehavior); + printSetFormValue(settingsScript,PSTR("AS"),apSSID); + printSetFormCheckbox(settingsScript,PSTR("AH"),apHide); l = strlen(apPass); char fapass[l+1]; //fill password field with *** fapass[l] = 0; memset(fapass,'*',l); - printSetFormValue(dest,PSTR("AP"),fapass); + printSetFormValue(settingsScript,PSTR("AP"),fapass); - printSetFormValue(dest,PSTR("AC"),apChannel); + printSetFormValue(settingsScript,PSTR("AC"),apChannel); #ifdef ARDUINO_ARCH_ESP32 - printSetFormValue(dest,PSTR("TX"),txPower); + printSetFormValue(settingsScript,PSTR("TX"),txPower); #else - dest.print(F("gId('tx').style.display='none';")); + settingsScript.print(F("gId('tx').style.display='none';")); #endif - printSetFormCheckbox(dest,PSTR("FG"),force802_3g); - printSetFormCheckbox(dest,PSTR("WS"),noWifiSleep); + printSetFormCheckbox(settingsScript,PSTR("FG"),force802_3g); + printSetFormCheckbox(settingsScript,PSTR("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW - printSetFormCheckbox(dest,PSTR("RE"),enableESPNow); - printSetFormValue(dest,PSTR("RMAC"),linked_remote); + printSetFormCheckbox(settingsScript,PSTR("RE"),enableESPNow); + printSetFormValue(settingsScript,PSTR("RMAC"),linked_remote); #else //hide remote settings if not compiled - dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting + settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif #ifdef WLED_USE_ETHERNET - printSetFormValue(dest,PSTR("ETH"),ethernetType); + printSetFormValue(settingsScript,PSTR("ETH"),ethernetType); #else //hide ethernet setting if not compiled in - dest.print(F("gId('ethd').style.display='none';")); + settingsScript.print(F("gId('ethd').style.display='none';")); #endif if (Network.isConnected()) //is connected @@ -229,10 +229,10 @@ void getSettingsJS(byte subPage, Print& dest) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); #endif - printSetClassElementHTML(dest,PSTR("sip"),0,s); + printSetClassElementHTML(settingsScript,PSTR("sip"),0,s); } else { - printSetClassElementHTML(dest,PSTR("sip"),0,(char*)F("Not connected")); + printSetClassElementHTML(settingsScript,PSTR("sip"),0,(char*)F("Not connected")); } if (WiFi.softAPIP()[0] != 0) //is active @@ -240,19 +240,19 @@ void getSettingsJS(byte subPage, Print& dest) char s[16]; IPAddress apIP = WiFi.softAPIP(); sprintf(s, "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); - printSetClassElementHTML(dest,PSTR("sip"),1,s); + printSetClassElementHTML(settingsScript,PSTR("sip"),1,s); } else { - printSetClassElementHTML(dest,PSTR("sip"),1,(char*)F("Not active")); + printSetClassElementHTML(settingsScript,PSTR("sip"),1,(char*)F("Not active")); } #ifndef WLED_DISABLE_ESPNOW if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote - printSetClassElementHTML(dest,PSTR("rlid"),0,last_signal_src); + printSetClassElementHTML(settingsScript,PSTR("rlid"),0,last_signal_src); } else if (!enableESPNow) { - printSetClassElementHTML(dest,PSTR("rlid"),0,(char*)F("(Enable ESP-NOW to listen)")); + printSetClassElementHTML(settingsScript,PSTR("rlid"),0,(char*)F("(Enable ESP-NOW to listen)")); } else { - printSetClassElementHTML(dest,PSTR("rlid"),0,(char*)F("None")); + printSetClassElementHTML(settingsScript,PSTR("rlid"),0,(char*)F("None")); } #endif } @@ -261,12 +261,12 @@ void getSettingsJS(byte subPage, Print& dest) { char nS[32]; - appendGPIOinfo(dest); + appendGPIOinfo(settingsScript); - dest.print(SET_F("d.ledTypes=")); dest.print(BusManager::getLEDTypesJSONString().c_str()); dest.print(";"); + settingsScript.print(SET_F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); // set limits - dest.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), + settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), WLED_MAX_BUSSES, WLED_MIN_VIRTUAL_BUSSES, MAX_LEDS_PER_BUS, @@ -277,14 +277,14 @@ void getSettingsJS(byte subPage, Print& dest) WLED_MAX_ANALOG_CHANNELS ); - printSetFormCheckbox(dest,PSTR("MS"),strip.autoSegments); - printSetFormCheckbox(dest,PSTR("CCT"),strip.correctWB); - printSetFormCheckbox(dest,PSTR("IC"),cctICused); - printSetFormCheckbox(dest,PSTR("CR"),strip.cctFromRgb); - printSetFormValue(dest,PSTR("CB"),strip.cctBlending); - printSetFormValue(dest,PSTR("FR"),strip.getTargetFps()); - printSetFormValue(dest,PSTR("AW"),Bus::getGlobalAWMode()); - printSetFormCheckbox(dest,PSTR("LD"),useGlobalLedBuffer); + printSetFormCheckbox(settingsScript,PSTR("MS"),strip.autoSegments); + printSetFormCheckbox(settingsScript,PSTR("CCT"),strip.correctWB); + printSetFormCheckbox(settingsScript,PSTR("IC"),cctICused); + printSetFormCheckbox(settingsScript,PSTR("CR"),strip.cctFromRgb); + printSetFormValue(settingsScript,PSTR("CB"),strip.cctBlending); + printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps()); + printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode()); + printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer); unsigned sumMa = 0; for (int s = 0; s < BusManager::getNumBusses(); s++) { @@ -304,22 +304,22 @@ void getSettingsJS(byte subPage, Print& dest) char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current - dest.print(F("addLEDs(1);")); + settingsScript.print(F("addLEDs(1);")); uint8_t pins[5]; int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(dest,lp,pins[i]); + if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]); } - printSetFormValue(dest,lc,bus->getLength()); - printSetFormValue(dest,lt,bus->getType()); - printSetFormValue(dest,co,bus->getColorOrder() & 0x0F); - printSetFormValue(dest,ls,bus->getStart()); - printSetFormCheckbox(dest,cv,bus->isReversed()); - printSetFormValue(dest,sl,bus->skippedLeds()); - printSetFormCheckbox(dest,rf,bus->isOffRefreshRequired()); - printSetFormValue(dest,aw,bus->getAutoWhiteMode()); - printSetFormValue(dest,wo,bus->getColorOrder() >> 4); + printSetFormValue(settingsScript,lc,bus->getLength()); + printSetFormValue(settingsScript,lt,bus->getType()); + printSetFormValue(settingsScript,co,bus->getColorOrder() & 0x0F); + printSetFormValue(settingsScript,ls,bus->getStart()); + printSetFormCheckbox(settingsScript,cv,bus->isReversed()); + printSetFormValue(settingsScript,sl,bus->skippedLeds()); + printSetFormCheckbox(settingsScript,rf,bus->isOffRefreshRequired()); + printSetFormValue(settingsScript,aw,bus->getAutoWhiteMode()); + printSetFormValue(settingsScript,wo,bus->getColorOrder() >> 4); unsigned speed = bus->getFrequency(); if (bus->isPWM()) { switch (speed) { @@ -340,148 +340,148 @@ void getSettingsJS(byte subPage, Print& dest) case 20000 : speed = 4; break; } } - printSetFormValue(dest,sp,speed); - printSetFormValue(dest,la,bus->getLEDCurrent()); - printSetFormValue(dest,ma,bus->getMaxCurrent()); + printSetFormValue(settingsScript,sp,speed); + printSetFormValue(settingsScript,la,bus->getLEDCurrent()); + printSetFormValue(settingsScript,ma,bus->getMaxCurrent()); sumMa += bus->getMaxCurrent(); } - printSetFormValue(dest,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); - printSetFormCheckbox(dest,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); - printSetFormCheckbox(dest,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); + printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); + printSetFormCheckbox(settingsScript,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); + printSetFormCheckbox(settingsScript,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); - dest.printf_P(PSTR("resetCOM(%d);"), WLED_MAX_COLOR_ORDER_MAPPINGS); + settingsScript.printf_P(PSTR("resetCOM(%d);"), WLED_MAX_COLOR_ORDER_MAPPINGS); const ColorOrderMap& com = BusManager::getColorOrderMap(); for (int s = 0; s < com.count(); s++) { const ColorOrderMapEntry* entry = com.get(s); if (entry == nullptr) break; - dest.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder); + settingsScript.printf_P(PSTR("addCOM(%d,%d,%d);"), entry->start, entry->len, entry->colorOrder); } - printSetFormValue(dest,PSTR("CA"),briS); + printSetFormValue(settingsScript,PSTR("CA"),briS); - printSetFormCheckbox(dest,PSTR("BO"),turnOnAtBoot); - printSetFormValue(dest,PSTR("BP"),bootPreset); + printSetFormCheckbox(settingsScript,PSTR("BO"),turnOnAtBoot); + printSetFormValue(settingsScript,PSTR("BP"),bootPreset); - printSetFormCheckbox(dest,PSTR("GB"),gammaCorrectBri); - printSetFormCheckbox(dest,PSTR("GC"),gammaCorrectCol); - dtostrf(gammaCorrectVal,3,1,nS); printSetFormValue(dest,PSTR("GV"),nS); - printSetFormCheckbox(dest,PSTR("TF"),fadeTransition); - printSetFormCheckbox(dest,PSTR("EB"),modeBlending); - printSetFormValue(dest,PSTR("TD"),transitionDelayDefault); - printSetFormCheckbox(dest,PSTR("PF"),strip.paletteFade); - printSetFormValue(dest,PSTR("TP"),randomPaletteChangeTime); - printSetFormCheckbox(dest,PSTR("TH"),useHarmonicRandomPalette); - printSetFormValue(dest,PSTR("BF"),briMultiplier); - printSetFormValue(dest,PSTR("TB"),nightlightTargetBri); - printSetFormValue(dest,PSTR("TL"),nightlightDelayMinsDefault); - printSetFormValue(dest,PSTR("TW"),nightlightMode); - printSetFormIndex(dest,PSTR("PB"),strip.paletteBlend); - printSetFormValue(dest,PSTR("RL"),rlyPin); - printSetFormCheckbox(dest,PSTR("RM"),rlyMde); - printSetFormCheckbox(dest,PSTR("RO"),rlyOpenDrain); + printSetFormCheckbox(settingsScript,PSTR("GB"),gammaCorrectBri); + printSetFormCheckbox(settingsScript,PSTR("GC"),gammaCorrectCol); + dtostrf(gammaCorrectVal,3,1,nS); printSetFormValue(settingsScript,PSTR("GV"),nS); + printSetFormCheckbox(settingsScript,PSTR("TF"),fadeTransition); + printSetFormCheckbox(settingsScript,PSTR("EB"),modeBlending); + printSetFormValue(settingsScript,PSTR("TD"),transitionDelayDefault); + printSetFormCheckbox(settingsScript,PSTR("PF"),strip.paletteFade); + printSetFormValue(settingsScript,PSTR("TP"),randomPaletteChangeTime); + printSetFormCheckbox(settingsScript,PSTR("TH"),useHarmonicRandomPalette); + printSetFormValue(settingsScript,PSTR("BF"),briMultiplier); + printSetFormValue(settingsScript,PSTR("TB"),nightlightTargetBri); + printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault); + printSetFormValue(settingsScript,PSTR("TW"),nightlightMode); + printSetFormIndex(settingsScript,PSTR("PB"),strip.paletteBlend); + printSetFormValue(settingsScript,PSTR("RL"),rlyPin); + printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde); + printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain); for (int i = 0; i < WLED_MAX_BUTTONS; i++) { - dest.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]); + settingsScript.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]); } - printSetFormCheckbox(dest,PSTR("IP"),disablePullUp); - printSetFormValue(dest,PSTR("TT"),touchThreshold); + printSetFormCheckbox(settingsScript,PSTR("IP"),disablePullUp); + printSetFormValue(settingsScript,PSTR("TT"),touchThreshold); #ifndef WLED_DISABLE_INFRARED - printSetFormValue(dest,PSTR("IR"),irPin); - printSetFormValue(dest,PSTR("IT"),irEnabled); + printSetFormValue(settingsScript,PSTR("IR"),irPin); + printSetFormValue(settingsScript,PSTR("IT"),irEnabled); #endif - printSetFormCheckbox(dest,PSTR("MSO"),!irApplyToAllSelected); + printSetFormCheckbox(settingsScript,PSTR("MSO"),!irApplyToAllSelected); } if (subPage == SUBPAGE_UI) { - printSetFormValue(dest,PSTR("DS"),serverDescription); - printSetFormCheckbox(dest,PSTR("SU"),simplifiedUI); + printSetFormValue(settingsScript,PSTR("DS"),serverDescription); + printSetFormCheckbox(settingsScript,PSTR("SU"),simplifiedUI); } if (subPage == SUBPAGE_SYNC) { [[maybe_unused]] char nS[32]; - printSetFormValue(dest,PSTR("UP"),udpPort); - printSetFormValue(dest,PSTR("U2"),udpPort2); + printSetFormValue(settingsScript,PSTR("UP"),udpPort); + printSetFormValue(settingsScript,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW - if (enableESPNow) printSetFormCheckbox(dest,PSTR("EN"),useESPNowSync); - else dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting + if (enableESPNow) printSetFormCheckbox(settingsScript,PSTR("EN"),useESPNowSync); + else settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #else - dest.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting + settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting #endif - printSetFormValue(dest,PSTR("GS"),syncGroups); - printSetFormValue(dest,PSTR("GR"),receiveGroups); + printSetFormValue(settingsScript,PSTR("GS"),syncGroups); + printSetFormValue(settingsScript,PSTR("GR"),receiveGroups); - printSetFormCheckbox(dest,PSTR("RB"),receiveNotificationBrightness); - printSetFormCheckbox(dest,PSTR("RC"),receiveNotificationColor); - printSetFormCheckbox(dest,PSTR("RX"),receiveNotificationEffects); - printSetFormCheckbox(dest,PSTR("RP"),receiveNotificationPalette); - printSetFormCheckbox(dest,PSTR("SO"),receiveSegmentOptions); - printSetFormCheckbox(dest,PSTR("SG"),receiveSegmentBounds); - printSetFormCheckbox(dest,PSTR("SS"),sendNotifications); - printSetFormCheckbox(dest,PSTR("SD"),notifyDirect); - printSetFormCheckbox(dest,PSTR("SB"),notifyButton); - printSetFormCheckbox(dest,PSTR("SH"),notifyHue); - printSetFormValue(dest,PSTR("UR"),udpNumRetries); + printSetFormCheckbox(settingsScript,PSTR("RB"),receiveNotificationBrightness); + printSetFormCheckbox(settingsScript,PSTR("RC"),receiveNotificationColor); + printSetFormCheckbox(settingsScript,PSTR("RX"),receiveNotificationEffects); + printSetFormCheckbox(settingsScript,PSTR("RP"),receiveNotificationPalette); + printSetFormCheckbox(settingsScript,PSTR("SO"),receiveSegmentOptions); + printSetFormCheckbox(settingsScript,PSTR("SG"),receiveSegmentBounds); + printSetFormCheckbox(settingsScript,PSTR("SS"),sendNotifications); + printSetFormCheckbox(settingsScript,PSTR("SD"),notifyDirect); + printSetFormCheckbox(settingsScript,PSTR("SB"),notifyButton); + printSetFormCheckbox(settingsScript,PSTR("SH"),notifyHue); + printSetFormValue(settingsScript,PSTR("UR"),udpNumRetries); - printSetFormCheckbox(dest,PSTR("NL"),nodeListEnabled); - printSetFormCheckbox(dest,PSTR("NB"),nodeBroadcastEnabled); + printSetFormCheckbox(settingsScript,PSTR("NL"),nodeListEnabled); + printSetFormCheckbox(settingsScript,PSTR("NB"),nodeBroadcastEnabled); - printSetFormCheckbox(dest,PSTR("RD"),receiveDirect); - printSetFormCheckbox(dest,PSTR("MO"),useMainSegmentOnly); - printSetFormCheckbox(dest,PSTR("RLM"),realtimeRespectLedMaps); - printSetFormValue(dest,PSTR("EP"),e131Port); - printSetFormCheckbox(dest,PSTR("ES"),e131SkipOutOfSequence); - printSetFormCheckbox(dest,PSTR("EM"),e131Multicast); - printSetFormValue(dest,PSTR("EU"),e131Universe); - printSetFormValue(dest,PSTR("DA"),DMXAddress); - printSetFormValue(dest,PSTR("XX"),DMXSegmentSpacing); - printSetFormValue(dest,PSTR("PY"),e131Priority); - printSetFormValue(dest,PSTR("DM"),DMXMode); - printSetFormValue(dest,PSTR("ET"),realtimeTimeoutMs); - printSetFormCheckbox(dest,PSTR("FB"),arlsForceMaxBri); - printSetFormCheckbox(dest,PSTR("RG"),arlsDisableGammaCorrection); - printSetFormValue(dest,PSTR("WO"),arlsOffset); + printSetFormCheckbox(settingsScript,PSTR("RD"),receiveDirect); + printSetFormCheckbox(settingsScript,PSTR("MO"),useMainSegmentOnly); + printSetFormCheckbox(settingsScript,PSTR("RLM"),realtimeRespectLedMaps); + printSetFormValue(settingsScript,PSTR("EP"),e131Port); + printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence); + printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast); + printSetFormValue(settingsScript,PSTR("EU"),e131Universe); + printSetFormValue(settingsScript,PSTR("DA"),DMXAddress); + printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing); + printSetFormValue(settingsScript,PSTR("PY"),e131Priority); + printSetFormValue(settingsScript,PSTR("DM"),DMXMode); + printSetFormValue(settingsScript,PSTR("ET"),realtimeTimeoutMs); + printSetFormCheckbox(settingsScript,PSTR("FB"),arlsForceMaxBri); + printSetFormCheckbox(settingsScript,PSTR("RG"),arlsDisableGammaCorrection); + printSetFormValue(settingsScript,PSTR("WO"),arlsOffset); #ifndef WLED_DISABLE_ALEXA - printSetFormCheckbox(dest,PSTR("AL"),alexaEnabled); - printSetFormValue(dest,PSTR("AI"),alexaInvocationName); - printSetFormCheckbox(dest,PSTR("SA"),notifyAlexa); - printSetFormValue(dest,PSTR("AP"),alexaNumPresets); + printSetFormCheckbox(settingsScript,PSTR("AL"),alexaEnabled); + printSetFormValue(settingsScript,PSTR("AI"),alexaInvocationName); + printSetFormCheckbox(settingsScript,PSTR("SA"),notifyAlexa); + printSetFormValue(settingsScript,PSTR("AP"),alexaNumPresets); #else - dest.print(F("toggle('Alexa');")); // hide Alexa settings + settingsScript.print(F("toggle('Alexa');")); // hide Alexa settings #endif #ifndef WLED_DISABLE_MQTT - printSetFormCheckbox(dest,PSTR("MQ"),mqttEnabled); - printSetFormValue(dest,PSTR("MS"),mqttServer); - printSetFormValue(dest,PSTR("MQPORT"),mqttPort); - printSetFormValue(dest,PSTR("MQUSER"),mqttUser); + printSetFormCheckbox(settingsScript,PSTR("MQ"),mqttEnabled); + printSetFormValue(settingsScript,PSTR("MS"),mqttServer); + printSetFormValue(settingsScript,PSTR("MQPORT"),mqttPort); + printSetFormValue(settingsScript,PSTR("MQUSER"),mqttUser); byte l = strlen(mqttPass); char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - printSetFormValue(dest,PSTR("MQPASS"),fpass); - printSetFormValue(dest,PSTR("MQCID"),mqttClientID); - printSetFormValue(dest,PSTR("MD"),mqttDeviceTopic); - printSetFormValue(dest,PSTR("MG"),mqttGroupTopic); - printSetFormCheckbox(dest,PSTR("BM"),buttonPublishMqtt); - printSetFormCheckbox(dest,PSTR("RT"),retainMqttMsg); - dest.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + printSetFormValue(settingsScript,PSTR("MQPASS"),fpass); + printSetFormValue(settingsScript,PSTR("MQCID"),mqttClientID); + printSetFormValue(settingsScript,PSTR("MD"),mqttDeviceTopic); + printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); + printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); + printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); + settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else - dest.print(F("toggle('MQTT');")); // hide MQTT settings + settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings #endif #ifndef WLED_DISABLE_HUESYNC - printSetFormValue(dest,PSTR("H0"),hueIP[0]); - printSetFormValue(dest,PSTR("H1"),hueIP[1]); - printSetFormValue(dest,PSTR("H2"),hueIP[2]); - printSetFormValue(dest,PSTR("H3"),hueIP[3]); - printSetFormValue(dest,PSTR("HL"),huePollLightId); - printSetFormValue(dest,PSTR("HI"),huePollIntervalMs); - printSetFormCheckbox(dest,PSTR("HP"),huePollingEnabled); - printSetFormCheckbox(dest,PSTR("HO"),hueApplyOnOff); - printSetFormCheckbox(dest,PSTR("HB"),hueApplyBri); - printSetFormCheckbox(dest,PSTR("HC"),hueApplyColor); + printSetFormValue(settingsScript,PSTR("H0"),hueIP[0]); + printSetFormValue(settingsScript,PSTR("H1"),hueIP[1]); + printSetFormValue(settingsScript,PSTR("H2"),hueIP[2]); + printSetFormValue(settingsScript,PSTR("H3"),hueIP[3]); + printSetFormValue(settingsScript,PSTR("HL"),huePollLightId); + printSetFormValue(settingsScript,PSTR("HI"),huePollIntervalMs); + printSetFormCheckbox(settingsScript,PSTR("HP"),huePollingEnabled); + printSetFormCheckbox(settingsScript,PSTR("HO"),hueApplyOnOff); + printSetFormCheckbox(settingsScript,PSTR("HB"),hueApplyBri); + printSetFormCheckbox(settingsScript,PSTR("HC"),hueApplyColor); char hueErrorString[25]; switch (hueError) { @@ -495,56 +495,56 @@ void getSettingsJS(byte subPage, Print& dest) default: sprintf_P(hueErrorString,PSTR("Bridge Error %i"),hueError); } - printSetClassElementHTML(dest,PSTR("sip"),0,hueErrorString); + printSetClassElementHTML(settingsScript,PSTR("sip"),0,hueErrorString); #else - dest.print(F("toggle('Hue');")); // hide Hue Sync settings + settingsScript.print(F("toggle('Hue');")); // hide Hue Sync settings #endif - printSetFormValue(dest,PSTR("BD"),serialBaud); + printSetFormValue(settingsScript,PSTR("BD"),serialBaud); #ifndef WLED_ENABLE_ADALIGHT - dest.print(SET_F("toggle('Serial);")); + settingsScript.print(SET_F("toggle('Serial);")); #endif } if (subPage == SUBPAGE_TIME) { - printSetFormCheckbox(dest,PSTR("NT"),ntpEnabled); - printSetFormValue(dest,PSTR("NS"),ntpServerName); - printSetFormCheckbox(dest,PSTR("CF"),!useAMPM); - printSetFormIndex(dest,PSTR("TZ"),currentTimezone); - printSetFormValue(dest,PSTR("UO"),utcOffsetSecs); + printSetFormCheckbox(settingsScript,PSTR("NT"),ntpEnabled); + printSetFormValue(settingsScript,PSTR("NS"),ntpServerName); + printSetFormCheckbox(settingsScript,PSTR("CF"),!useAMPM); + printSetFormIndex(settingsScript,PSTR("TZ"),currentTimezone); + printSetFormValue(settingsScript,PSTR("UO"),utcOffsetSecs); char tm[32]; dtostrf(longitude,4,2,tm); - printSetFormValue(dest,PSTR("LN"),tm); + printSetFormValue(settingsScript,PSTR("LN"),tm); dtostrf(latitude,4,2,tm); - printSetFormValue(dest,PSTR("LT"),tm); + printSetFormValue(settingsScript,PSTR("LT"),tm); getTimeString(tm); - printSetClassElementHTML(dest,PSTR("times"),0,tm); + printSetClassElementHTML(settingsScript,PSTR("times"),0,tm); if ((int)(longitude*10.0f) || (int)(latitude*10.0f)) { sprintf_P(tm, PSTR("Sunrise: %02d:%02d Sunset: %02d:%02d"), hour(sunrise), minute(sunrise), hour(sunset), minute(sunset)); - printSetClassElementHTML(dest,PSTR("times"),1,tm); + printSetClassElementHTML(settingsScript,PSTR("times"),1,tm); } - printSetFormCheckbox(dest,PSTR("OL"),overlayCurrent); - printSetFormValue(dest,PSTR("O1"),overlayMin); - printSetFormValue(dest,PSTR("O2"),overlayMax); - printSetFormValue(dest,PSTR("OM"),analogClock12pixel); - printSetFormCheckbox(dest,PSTR("OS"),analogClockSecondsTrail); - printSetFormCheckbox(dest,PSTR("O5"),analogClock5MinuteMarks); - printSetFormCheckbox(dest,PSTR("OB"),analogClockSolidBlack); + printSetFormCheckbox(settingsScript,PSTR("OL"),overlayCurrent); + printSetFormValue(settingsScript,PSTR("O1"),overlayMin); + printSetFormValue(settingsScript,PSTR("O2"),overlayMax); + printSetFormValue(settingsScript,PSTR("OM"),analogClock12pixel); + printSetFormCheckbox(settingsScript,PSTR("OS"),analogClockSecondsTrail); + printSetFormCheckbox(settingsScript,PSTR("O5"),analogClock5MinuteMarks); + printSetFormCheckbox(settingsScript,PSTR("OB"),analogClockSolidBlack); - printSetFormCheckbox(dest,PSTR("CE"),countdownMode); - printSetFormValue(dest,PSTR("CY"),countdownYear); - printSetFormValue(dest,PSTR("CI"),countdownMonth); - printSetFormValue(dest,PSTR("CD"),countdownDay); - printSetFormValue(dest,PSTR("CH"),countdownHour); - printSetFormValue(dest,PSTR("CM"),countdownMin); - printSetFormValue(dest,PSTR("CS"),countdownSec); + printSetFormCheckbox(settingsScript,PSTR("CE"),countdownMode); + printSetFormValue(settingsScript,PSTR("CY"),countdownYear); + printSetFormValue(settingsScript,PSTR("CI"),countdownMonth); + printSetFormValue(settingsScript,PSTR("CD"),countdownDay); + printSetFormValue(settingsScript,PSTR("CH"),countdownHour); + printSetFormValue(settingsScript,PSTR("CM"),countdownMin); + printSetFormValue(settingsScript,PSTR("CS"),countdownSec); - printSetFormValue(dest,PSTR("A0"),macroAlexaOn); - printSetFormValue(dest,PSTR("A1"),macroAlexaOff); - printSetFormValue(dest,PSTR("MC"),macroCountdown); - printSetFormValue(dest,PSTR("MN"),macroNl); + printSetFormValue(settingsScript,PSTR("A0"),macroAlexaOn); + printSetFormValue(settingsScript,PSTR("A1"),macroAlexaOff); + printSetFormValue(settingsScript,PSTR("MC"),macroCountdown); + printSetFormValue(settingsScript,PSTR("MN"),macroNl); for (unsigned i=0; i> 4) & 0x0F); - k[0] = 'P'; printSetFormValue(dest,k,timerMonth[i] & 0x0F); - k[0] = 'D'; printSetFormValue(dest,k,timerDay[i]); - k[0] = 'E'; printSetFormValue(dest,k,timerDayEnd[i]); + k[0] = 'M'; printSetFormValue(settingsScript,k,(timerMonth[i] >> 4) & 0x0F); + k[0] = 'P'; printSetFormValue(settingsScript,k,timerMonth[i] & 0x0F); + k[0] = 'D'; printSetFormValue(settingsScript,k,timerDay[i]); + k[0] = 'E'; printSetFormValue(settingsScript,k,timerDayEnd[i]); } } } @@ -571,61 +571,61 @@ void getSettingsJS(byte subPage, Print& dest) char fpass[l+1]; //fill PIN field with 0000 fpass[l] = 0; memset(fpass,'0',l); - printSetFormValue(dest,PSTR("PIN"),fpass); - printSetFormCheckbox(dest,PSTR("NO"),otaLock); - printSetFormCheckbox(dest,PSTR("OW"),wifiLock); - printSetFormCheckbox(dest,PSTR("AO"),aOtaEnabled); + printSetFormValue(settingsScript,PSTR("PIN"),fpass); + printSetFormCheckbox(settingsScript,PSTR("NO"),otaLock); + printSetFormCheckbox(settingsScript,PSTR("OW"),wifiLock); + printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled); char tmp_buf[128]; snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION); - printSetClassElementHTML(dest,PSTR("sip"),0,tmp_buf); - dest.printf_P(PSTR("sd=\"%s\";"), serverDescription); + printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf); + settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription); } #ifdef WLED_ENABLE_DMX // include only if DMX is enabled if (subPage == SUBPAGE_DMX) { - printSetFormValue(dest,PSTR("PU"),e131ProxyUniverse); + printSetFormValue(settingsScript,PSTR("PU"),e131ProxyUniverse); - printSetFormValue(dest,PSTR("CN"),DMXChannels); - printSetFormValue(dest,PSTR("CG"),DMXGap); - printSetFormValue(dest,PSTR("CS"),DMXStart); - printSetFormValue(dest,PSTR("SL"),DMXStartLED); + printSetFormValue(settingsScript,PSTR("CN"),DMXChannels); + printSetFormValue(settingsScript,PSTR("CG"),DMXGap); + printSetFormValue(settingsScript,PSTR("CS"),DMXStart); + printSetFormValue(settingsScript,PSTR("SL"),DMXStartLED); - printSetFormIndex(dest,PSTR("CH1"),DMXFixtureMap[0]); - printSetFormIndex(dest,PSTR("CH2"),DMXFixtureMap[1]); - printSetFormIndex(dest,PSTR("CH3"),DMXFixtureMap[2]); - printSetFormIndex(dest,PSTR("CH4"),DMXFixtureMap[3]); - printSetFormIndex(dest,PSTR("CH5"),DMXFixtureMap[4]); - printSetFormIndex(dest,PSTR("CH6"),DMXFixtureMap[5]); - printSetFormIndex(dest,PSTR("CH7"),DMXFixtureMap[6]); - printSetFormIndex(dest,PSTR("CH8"),DMXFixtureMap[7]); - printSetFormIndex(dest,PSTR("CH9"),DMXFixtureMap[8]); - printSetFormIndex(dest,PSTR("CH10"),DMXFixtureMap[9]); - printSetFormIndex(dest,PSTR("CH11"),DMXFixtureMap[10]); - printSetFormIndex(dest,PSTR("CH12"),DMXFixtureMap[11]); - printSetFormIndex(dest,PSTR("CH13"),DMXFixtureMap[12]); - printSetFormIndex(dest,PSTR("CH14"),DMXFixtureMap[13]); - printSetFormIndex(dest,PSTR("CH15"),DMXFixtureMap[14]); + printSetFormIndex(settingsScript,PSTR("CH1"),DMXFixtureMap[0]); + printSetFormIndex(settingsScript,PSTR("CH2"),DMXFixtureMap[1]); + printSetFormIndex(settingsScript,PSTR("CH3"),DMXFixtureMap[2]); + printSetFormIndex(settingsScript,PSTR("CH4"),DMXFixtureMap[3]); + printSetFormIndex(settingsScript,PSTR("CH5"),DMXFixtureMap[4]); + printSetFormIndex(settingsScript,PSTR("CH6"),DMXFixtureMap[5]); + printSetFormIndex(settingsScript,PSTR("CH7"),DMXFixtureMap[6]); + printSetFormIndex(settingsScript,PSTR("CH8"),DMXFixtureMap[7]); + printSetFormIndex(settingsScript,PSTR("CH9"),DMXFixtureMap[8]); + printSetFormIndex(settingsScript,PSTR("CH10"),DMXFixtureMap[9]); + printSetFormIndex(settingsScript,PSTR("CH11"),DMXFixtureMap[10]); + printSetFormIndex(settingsScript,PSTR("CH12"),DMXFixtureMap[11]); + printSetFormIndex(settingsScript,PSTR("CH13"),DMXFixtureMap[12]); + printSetFormIndex(settingsScript,PSTR("CH14"),DMXFixtureMap[13]); + printSetFormIndex(settingsScript,PSTR("CH15"),DMXFixtureMap[14]); } #endif if (subPage == SUBPAGE_UM) //usermods { - appendGPIOinfo(dest); - dest.printf_P(PSTR("numM=%d;"), usermods.getModCount()); - printSetFormValue(dest,PSTR("SDA"),i2c_sda); - printSetFormValue(dest,PSTR("SCL"),i2c_scl); - printSetFormValue(dest,PSTR("MOSI"),spi_mosi); - printSetFormValue(dest,PSTR("MISO"),spi_miso); - printSetFormValue(dest,PSTR("SCLK"),spi_sclk); - dest.printf_P(PSTR("addInfo('SDA','%d');" + appendGPIOinfo(settingsScript); + settingsScript.printf_P(PSTR("numM=%d;"), usermods.getModCount()); + printSetFormValue(settingsScript,PSTR("SDA"),i2c_sda); + printSetFormValue(settingsScript,PSTR("SCL"),i2c_scl); + printSetFormValue(settingsScript,PSTR("MOSI"),spi_mosi); + printSetFormValue(settingsScript,PSTR("MISO"),spi_miso); + printSetFormValue(settingsScript,PSTR("SCLK"),spi_sclk); + settingsScript.printf_P(PSTR("addInfo('SDA','%d');" "addInfo('SCL','%d');" "addInfo('MOSI','%d');" "addInfo('MISO','%d');" "addInfo('SCLK','%d');"), HW_PIN_SDA, HW_PIN_SCL, HW_PIN_DATASPI, HW_PIN_MISOSPI, HW_PIN_CLOCKSPI ); - usermods.appendConfigData(dest); + usermods.appendConfigData(settingsScript); } if (subPage == SUBPAGE_UPDATE) // update @@ -641,44 +641,44 @@ void getSettingsJS(byte subPage, Print& dest) #endif VERSION); - printSetClassElementHTML(dest,PSTR("sip"),0,tmp_buf); + printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf); } if (subPage == SUBPAGE_2D) // 2D matrices { - printSetFormValue(dest,PSTR("SOMP"),strip.isMatrix); + printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D - dest.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); - dest.print(F("resetPanels();")); + settingsScript.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); + settingsScript.print(F("resetPanels();")); if (strip.isMatrix) { if(strip.panels>0){ - printSetFormValue(dest,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience - printSetFormValue(dest,PSTR("PH"),strip.panel[0].height); + printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience + printSetFormValue(settingsScript,PSTR("PH"),strip.panel[0].height); } - printSetFormValue(dest,PSTR("MPC"),strip.panels); + printSetFormValue(settingsScript,PSTR("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Thu, 19 Sep 2024 08:49:18 +0200 Subject: [PATCH 484/694] revert removal of adding with saturation, renamed 'fast' to 'saturate' - blurring now uses desaturated adding: it is faster most of the times and blurring adds scaled colors so should rarely (ever?) saturate, I saw no visual difference in tests. - formatting --- wled00/FX.h | 18 +++++++++--------- wled00/FX_2Dfcn.cpp | 2 +- wled00/FX_fcn.cpp | 2 +- wled00/colors.cpp | 31 ++++++++++++++++++++----------- wled00/fcn_declare.h | 4 ++-- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index bea4dbcb8..50bcd6624 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -595,9 +595,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color) { setPixelColor(n, color_add(getPixelColor(n), color)); } - inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } - inline void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColor(int n, uint32_t color, bool saturate = false) { setPixelColor(n, color_add(getPixelColor(n), color, saturate)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(n, RGBW32(r,g,b,w), saturate); } + inline void addPixelColor(int n, CRGB c, bool saturate = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; @@ -632,9 +632,9 @@ typedef struct Segment { // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, saturate)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); @@ -670,9 +670,9 @@ typedef struct Segment { inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } - inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} inline void blur2D(uint8_t blur_amount, bool smear = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 10b85a82e..63595d930 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -188,7 +188,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically - if(yY >= H) break; + if (yY >= H) break; int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally if (xX >= W) continue; // we have reached one dimension's end diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 66aeaab63..7159d21c7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1169,7 +1169,7 @@ uint32_t Segment::color_wheel(uint8_t pos) const { pos = 255 - pos; if (pos < 85) { return RGBW32((255 - pos * 3), 0, (pos * 3), w); - } else if(pos < 170) { + } else if (pos < 170) { pos -= 85; return RGBW32(0, (pos * 3), (255 - pos * 3), w); } else { diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 54469ebe0..13405be6e 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -36,7 +36,7 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule * heavily optimized for speed by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2) +uint32_t color_add(uint32_t c1, uint32_t c2, bool desat) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; @@ -47,18 +47,27 @@ uint32_t color_add(uint32_t c1, uint32_t c2) uint32_t w = wg >> 16; uint32_t g = wg & 0xFFFF; - unsigned max = r; // check for overflow note: not checking and just topping out at 255 (formerly 'fast') is not any faster (but even slower if not overflowing) - max = g > max ? g : max; - max = b > max ? b : max; - max = w > max ? w : max; + if(desat) { // desaturate + unsigned max = r; // check for overflow note + max = g > max ? g : max; + max = b > max ? b : max; + max = w > max ? w : max; - if (max > 255) { - uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead - rb = ((rb * scale) >> 8) & 0x00FF00FF; // - wg = (wg * scale) & 0xFF00FF00; + if (max > 255) { + uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & 0x00FF00FF; // + wg = (wg * scale) & 0xFF00FF00; + } + else wg = wg << 8; //shift white and green back to correct position + return rb | wg; + } + else { + r = r > 255 ? 255 : r; + g = g > 255 ? 255 : g; + b = b > 255 ? 255 : b; + w = w > 255 ? 255 : w; + return RGBW32(r,g,b,w); } - else wg = wg << 8; //shift white and green back to correct position - return rb | wg; } /* diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index be7ed4462..d32356d7b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -79,8 +79,8 @@ class NeoGammaWLEDMethod { }; #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) -[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); -[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t); +[[gnu::hot]] uint32_t color_blend(uint32_t, uint32_t, uint16_t, bool b16=false); +[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool desat = false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); From b50e6e0d90ce47adcc4e7ecb865b671c2c9dec1f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 19 Sep 2024 21:44:11 +0200 Subject: [PATCH 485/694] Static PinManager & UsermodManager - saves a few bytes of flash --- .../Animated_Staircase/Animated_Staircase.h | 10 +- usermods/Animated_Staircase/README.md | 2 +- usermods/Battery/usermod_v2_Battery.h | 4 +- usermods/EXAMPLE_v2/usermod_v2_example.h | 2 +- .../Fix_unreachable_netservices_v2/readme.md | 8 +- .../usermod_LDR_Dusk_Dawn_v2.h | 6 +- usermods/PIR_sensor_switch/readme.md | 2 +- .../usermod_PIR_sensor_switch.h | 4 +- usermods/PWM_fan/usermod_PWM_fan.h | 16 +- usermods/SN_Photoresistor/usermods_list.cpp | 2 +- usermods/ST7789_display/ST7789_display.h | 6 +- usermods/Temperature/usermod_temperature.h | 10 +- usermods/audioreactive/audio_reactive.old.h | 2071 +++++++++++++++++ usermods/audioreactive/audio_source.h | 20 +- usermods/mpu6050_imu/readme.md | 2 +- usermods/mpu6050_imu/usermod_gyro_surge.h | 2 +- usermods/mpu6050_imu/usermod_mpu6050_imu.h | 4 +- usermods/mqtt_switch_v2/README.md | 2 +- usermods/multi_relay/readme.md | 8 +- usermods/multi_relay/usermod_multi_relay.h | 4 +- usermods/pixels_dice_tray/pixels_dice_tray.h | 6 +- usermods/pwm_outputs/usermod_pwm_outputs.h | 10 +- usermods/quinled-an-penta/quinled-an-penta.h | 18 +- .../rgb-rotary-encoder/rgb-rotary-encoder.h | 10 +- usermods/sd_card/usermod_sd_card.h | 10 +- .../usermod_seven_segment_reloaded.h | 2 +- .../usermod_v2_auto_save.h | 2 +- .../usermod_v2_four_line_display_ALT.h | 10 +- .../usermod_v2_rotary_encoder_ui_ALT.h | 14 +- wled00/FX.cpp | 8 +- wled00/FX_fcn.cpp | 8 +- wled00/bus_manager.cpp | 26 +- wled00/bus_manager.h | 2 +- wled00/button.cpp | 2 +- wled00/cfg.cpp | 28 +- wled00/fcn_declare.h | 44 +- wled00/json.cpp | 6 +- wled00/led.cpp | 2 +- wled00/mqtt.cpp | 6 +- wled00/overlay.cpp | 2 +- wled00/pin_manager.cpp | 109 +- wled00/pin_manager.h | 87 +- wled00/set.cpp | 34 +- wled00/udp.cpp | 2 +- wled00/um_manager.cpp | 3 + wled00/usermods_list.cpp | 112 +- wled00/wled.cpp | 22 +- wled00/wled_server.cpp | 4 +- wled00/xml.cpp | 12 +- 49 files changed, 2401 insertions(+), 385 deletions(-) create mode 100644 usermods/audioreactive/audio_reactive.old.h diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 8953756d3..d1ec9bb7f 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -332,7 +332,7 @@ class Animated_Staircase : public Usermod { }; // NOTE: this *WILL* return TRUE if all the pins are set to -1. // this is *BY DESIGN*. - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { topPIRorTriggerPin = -1; topEchoPin = -1; bottomPIRorTriggerPin = -1; @@ -513,10 +513,10 @@ class Animated_Staircase : public Usermod { (oldBottomAPin != bottomPIRorTriggerPin) || (oldBottomBPin != bottomEchoPin)) { changed = true; - pinManager.deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); } if (changed) setup(); } diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 320b744a5..2ad66b5ae 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -18,7 +18,7 @@ Before compiling, you have to make the following modifications: Edit `usermods_list.cpp`: 1. Open `wled00/usermods_list.cpp` 2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file -3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. +3. add `UsermodManager::add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. You can configure usermod using the Usermods settings page. Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo). diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 136d3a71a..e91de850c 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -200,7 +200,7 @@ class UsermodBattery : public Usermod bool success = false; DEBUG_PRINTLN(F("Allocating battery pin...")); if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) - if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { + if (PinManager::allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; } @@ -561,7 +561,7 @@ class UsermodBattery : public Usermod if (newBatteryPin != batteryPin) { // deallocate pin - pinManager.deallocatePin(batteryPin, PinOwner::UM_Battery); + PinManager::deallocatePin(batteryPin, PinOwner::UM_Battery); batteryPin = newBatteryPin; // initialise setup(); diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 32374fde2..3d562b585 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -71,7 +71,7 @@ class MyExampleUsermod : public Usermod { // #endif // in setup() // #ifdef USERMOD_EXAMPLE - // UM = (MyExampleUsermod*) usermods.lookup(USERMOD_ID_EXAMPLE); + // UM = (MyExampleUsermod*) UsermodManager::lookup(USERMOD_ID_EXAMPLE); // #endif // somewhere in loop() or other member method // #ifdef USERMOD_EXAMPLE diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md index 006eaf9f9..07d64bc67 100644 --- a/usermods/Fix_unreachable_netservices_v2/readme.md +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -59,10 +59,10 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - //usermods.add(new UsermodRenameMe()); - usermods.add(new FixUnreachableNetServices()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + //UsermodManager::add(new UsermodRenameMe()); + UsermodManager::add(new FixUnreachableNetServices()); } ``` diff --git a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h index 393fc2232..03f4c078a 100644 --- a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h +++ b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h @@ -30,7 +30,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { void setup() { // register ldrPin if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) { - if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod + if(!PinManager::allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input } else ldrEnabled = false; // invalid pin -> disable usermod initDone = true; @@ -110,7 +110,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { if (initDone && (ldrPin != oldLdrPin)) { // pin changed - un-register previous pin, register new pin - if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); + if (oldLdrPin >= 0) PinManager::deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); setup(); // setup new pin } return configComplete; @@ -139,7 +139,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { //LDR_Off_Count.add(ldrOffCount); //bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)); - //if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; + //if (PinManager::getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; //JsonArray LDR_valid = user.createNestedArray(F("LDR pin")); //LDR_valid.add(ldrPin); //LDR_valid.add(pinValid ? F(" OK"): F(" invalid")); diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md index 4dfdb07bd..fac5419f0 100644 --- a/usermods/PIR_sensor_switch/readme.md +++ b/usermods/PIR_sensor_switch/readme.md @@ -52,7 +52,7 @@ class MyUsermod : public Usermod { void togglePIRSensor() { #ifdef USERMOD_PIR_SENSOR_SWITCH - PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) usermods.lookup(USERMOD_ID_PIRSWITCH); + PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH); if (PIRsensor != nullptr) { PIRsensor->EnablePIRsensor(!PIRsensor->PIRsensorEnabled()); } diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 7a67dd749..29070cf84 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -375,7 +375,7 @@ void PIRsensorSwitch::setup() sensorPinState[i] = LOW; if (PIRsensorPin[i] < 0) continue; // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { + if (PinManager::allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { // PIR Sensor mode INPUT_PULLDOWN #ifdef ESP8266 pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only @@ -564,7 +564,7 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) DEBUG_PRINTLN(F(" config loaded.")); } else { for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) - if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR); + if (oldPin[i] >= 0) PinManager::deallocatePin(oldPin[i], PinOwner::UM_PIR); setup(); DEBUG_PRINTLN(F(" config (re)loaded.")); } diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 1b78cfd4c..c3ef24fe4 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -75,7 +75,7 @@ class PWMFanUsermod : public Usermod { static const char _lock[]; void initTacho(void) { - if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ + if (tachoPin < 0 || !PinManager::allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ tachoPin = -1; return; } @@ -88,7 +88,7 @@ class PWMFanUsermod : public Usermod { void deinitTacho(void) { if (tachoPin < 0) return; detachInterrupt(digitalPinToInterrupt(tachoPin)); - pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(tachoPin, PinOwner::UM_Unspecified); tachoPin = -1; } @@ -111,7 +111,7 @@ class PWMFanUsermod : public Usermod { // https://randomnerdtutorials.com/esp32-pwm-arduino-ide/ void initPWMfan(void) { - if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { + if (pwmPin < 0 || !PinManager::allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { enabled = false; pwmPin = -1; return; @@ -121,7 +121,7 @@ class PWMFanUsermod : public Usermod { analogWriteRange(255); analogWriteFreq(WLED_PWM_FREQ); #else - pwmChannel = pinManager.allocateLedc(1); + pwmChannel = PinManager::allocateLedc(1); if (pwmChannel == 255) { //no more free LEDC channels deinitPWMfan(); return; } @@ -136,9 +136,9 @@ class PWMFanUsermod : public Usermod { void deinitPWMfan(void) { if (pwmPin < 0) return; - pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(pwmPin, PinOwner::UM_Unspecified); #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(pwmChannel, 1); + PinManager::deallocateLedc(pwmChannel, 1); #endif pwmPin = -1; } @@ -191,9 +191,9 @@ class PWMFanUsermod : public Usermod { void setup() override { #ifdef USERMOD_DALLASTEMPERATURE // This Usermod requires Temperature usermod - tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE); + tempUM = (UsermodTemperature*) UsermodManager::lookup(USERMOD_ID_TEMPERATURE); #elif defined(USERMOD_SHT) - tempUM = (ShtUsermod*) usermods.lookup(USERMOD_ID_SHT); + tempUM = (ShtUsermod*) UsermodManager::lookup(USERMOD_ID_SHT); #endif initTacho(); initPWMfan(); diff --git a/usermods/SN_Photoresistor/usermods_list.cpp b/usermods/SN_Photoresistor/usermods_list.cpp index 649e19739..a2c6ca165 100644 --- a/usermods/SN_Photoresistor/usermods_list.cpp +++ b/usermods/SN_Photoresistor/usermods_list.cpp @@ -9,6 +9,6 @@ void registerUsermods() { #ifdef USERMOD_SN_PHOTORESISTOR - usermods.add(new Usermod_SN_Photoresistor()); + UsermodManager::add(new Usermod_SN_Photoresistor()); #endif } \ No newline at end of file diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 59f6d9271..0dbada382 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -138,10 +138,10 @@ class St7789DisplayUsermod : public Usermod { void setup() override { PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + if (!PinManager::allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; return; } diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index d7a9d82a4..ad755eaee 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -73,7 +73,7 @@ class UsermodTemperature : public Usermod { void publishHomeAssistantAutodiscovery(); #endif - static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid usermods.lookup(USERMOD_ID_TEMPERATURE); + static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid UsermodManager::lookup(USERMOD_ID_TEMPERATURE); public: @@ -223,14 +223,14 @@ void UsermodTemperature::setup() { // config says we are enabled DEBUG_PRINTLN(F("Allocating temperature pin...")); // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { + if (temperaturePin >= 0 && PinManager::allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { oneWire = new OneWire(temperaturePin); if (oneWire->reset()) { while (!findSensor() && retries--) { delay(25); // try to find sensor } } - if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { + if (parasite && PinManager::allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { pinMode(parasitePin, OUTPUT); digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) } else { @@ -423,9 +423,9 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { DEBUG_PRINTLN(F("Re-init temperature.")); // deallocate pin and release memory delete oneWire; - pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(temperaturePin, PinOwner::UM_Temperature); temperaturePin = newTemperaturePin; - pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(parasitePin, PinOwner::UM_Temperature); // initialise setup(); } diff --git a/usermods/audioreactive/audio_reactive.old.h b/usermods/audioreactive/audio_reactive.old.h new file mode 100644 index 000000000..4f2e04c08 --- /dev/null +++ b/usermods/audioreactive/audio_reactive.old.h @@ -0,0 +1,2071 @@ +#pragma once + +#include "wled.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include +#include + +#ifdef WLED_ENABLE_DMX + #error This audio reactive usermod is not compatible with DMX Out. +#endif + +#endif + +#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) +#include +#endif + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * This is an audioreactive v2 usermod. + * .... + */ + +#if !defined(FFTTASK_PRIORITY) +#define FFTTASK_PRIORITY 1 // standard: looptask prio +//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp +//#define FFTTASK_PRIORITY 4 // above asyc_tcp +#endif + +// Comment/Uncomment to toggle usb serial debugging +// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) +// #define FFT_SAMPLING_LOG // FFT result debugging +// #define SR_DEBUG // generic SR DEBUG messages + +#ifdef SR_DEBUG + #define DEBUGSR_PRINT(x) DEBUGOUT.print(x) + #define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x) +#else + #define DEBUGSR_PRINT(x) + #define DEBUGSR_PRINTLN(x) + #define DEBUGSR_PRINTF(x...) +#endif + +#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG) + #define PLOT_PRINT(x) DEBUGOUT.print(x) + #define PLOT_PRINTLN(x) DEBUGOUT.println(x) + #define PLOT_PRINTF(x...) DEBUGOUT.printf(x) +#else + #define PLOT_PRINT(x) + #define PLOT_PRINTLN(x) + #define PLOT_PRINTF(x...) +#endif + +#define MAX_PALETTES 3 + +static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. +static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group + +#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! + +// audioreactive variables +#ifdef ARDUINO_ARCH_ESP32 +static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) +static float sampleAgc = 0.0f; // Smoothed AGC sample +static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) +#endif +//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample +static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency +static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after WS2812FX::getMinShowDelay() +static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData +static unsigned long timeOfPeak = 0; // time of last sample peak detection. +static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects + +// TODO: probably best not used by receive nodes +//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 + +// user settable parameters for limitSoundDynamics() +#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF +static bool limiterOn = false; // bool: enable / disable dynamics limiter +#else +static bool limiterOn = true; +#endif +static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec +static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec + +// peak detection +#ifdef ARDUINO_ARCH_ESP32 +static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode +#endif +static void autoResetPeak(void); // peak auto-reset function +static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) + +#ifdef ARDUINO_ARCH_ESP32 + +// use audio source class (ESP32 specific) +#include "audio_source.h" +constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) +constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) + +// globals +static uint8_t inputLevel = 128; // UI slider value +#ifndef SR_SQUELCH + uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) +#else + uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) +#endif +#ifndef SR_GAIN + uint8_t sampleGain = 60; // sample gain (config value) +#else + uint8_t sampleGain = SR_GAIN; // sample gain (config value) +#endif +// user settable options for FFTResult scaling +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root + +// +// AGC presets +// Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" +// +#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy +const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax +const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec +const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs +const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter +const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter +const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +// AGC presets end + +static AudioSource *audioSource = nullptr; +static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. + +//////////////////// +// Begin FFT Code // +//////////////////// + +// some prototypes, to ensure consistent interfaces +static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float +static float fftAddAvg(int from, int to); // average of several FFT result bins +static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels + +static TaskHandle_t FFT_Task = nullptr; + +// Table of multiplication factors so that we can even out the frequency response. +static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; + +// globals and FFT Output variables shared with animations +#if defined(WLED_DEBUG) || defined(SR_DEBUG) +static uint64_t fftTime = 0; +static uint64_t sampleTime = 0; +#endif + +// FFT Task variables (filtering and post-processing) +static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. +static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) +#ifdef SR_DEBUG +static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. +#endif + +// audio source parameters and constant +#ifdef ARDUINO_ARCH_ESP32C3 +constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +#else +constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms +//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms +#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling +//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling +//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling +#endif + +// FFT Constants +constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 +constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. +// the following are observed values, supported by a bit of "educated guessing" +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels +#define LOG_256 5.54517744f // log(256) + +// These are the input and output vectors. Input vectors receive computed results from FFT. +static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins +static float vImag[samplesFFT] = {0.0f}; // imaginary parts + +// Create FFT object +// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 2.0.1 +// these options actually cause slow-downs on all esp32 processors, don't use them. +// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 +// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 +// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() +#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 +#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 + +#include + +/* Create FFT object with weighing factor storage */ +static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); + +// Helper functions + +// float version of map() +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// compute average of several FFT result bins +static float fftAddAvg(int from, int to) { + float result = 0.0f; + for (int i = from; i <= to; i++) { + result += vReal[i]; + } + return result / float(to - from + 1); +} + +// +// FFT main task +// +void FFTcode(void * parameter) +{ + DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; + + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + + // Don't run FFT computing code if we're in Receive mode or in realtime mode + if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + continue; + } + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + uint64_t start = esp_timer_get_time(); + bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid +#endif + + // get a fresh batch of samples from I2S + if (audioSource) audioSource->getSamples(vReal, samplesFFT); + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (start < esp_timer_get_time()) { // filter out overflows + uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding + sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth + } + start = esp_timer_get_time(); // start measuring FFT time +#endif + + xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay + + // band pass filter - can reduce noise floor by a factor of 50 + // downside: frequencies below 100Hz will be ignored + if (useBandPassFilter) runMicFilter(samplesFFT, vReal); + + // find highest sample in the batch + float maxSample = 0.0f; // max sample from FFT batch + for (int i=0; i < samplesFFT; i++) { + // set imaginary parts to 0 + vImag[i] = 0; + // pick our our current mic sample - we take the max value from all samples that go into FFT + if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts + if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); + } + // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function + // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. + micDataReal = maxSample; + +#ifdef SR_DEBUG + if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization +#else + if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. +#endif + + // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) + FFT.dcRemoval(); // remove DC offset + FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy + //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + FFT.compute( FFTDirection::Forward ); // Compute FFT + FFT.complexToMagnitude(); // Compute magnitudes + vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. + + FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + haveDoneFFT = true; +#endif + + } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. + memset(vReal, 0, sizeof(vReal)); + FFT_MajorPeak = 1.0f; + FFT_Magnitude = 0.001f; + } + + for (int i = 0; i < samplesFFT; i++) { + float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way + vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. + } // for() + + // mapping of FFT result bins to frequency channels + if (fabsf(sampleAvg) > 0.5f) { // noise gate open +#if 0 + /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. + * + * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. + * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. + * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins. + * End frequency = Start frequency * multiplier ^ 16 + * Multiplier = (End frequency/ Start frequency) ^ 1/16 + * Multiplier = 1.320367784 + */ // Range + fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 + fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 + fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 + fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 + fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 + fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 + fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 + fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 + fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 + fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 + fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 + fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 + fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 + fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 + fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 + fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate +#else + /* new mapping, optimized for 22050 Hz by softhack007 */ + // bins frequency range + if (useBandPassFilter) { + // skip frequencies below 100hz + fftCalc[ 0] = 0.8f * fftAddAvg(3,4); + fftCalc[ 1] = 0.9f * fftAddAvg(4,5); + fftCalc[ 2] = fftAddAvg(5,6); + fftCalc[ 3] = fftAddAvg(6,7); + // don't use the last bins from 206 to 255. + fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + } else { + fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass + fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass + fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass + fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) + fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + } + fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange + fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange + fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange + fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange + fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange + fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid + fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid + fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid + fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping +#endif + } else { // noise gate closed - just decay old values + for (int i=0; i < NUM_GEQ_CHANNELS; i++) { + fftCalc[i] *= 0.85f; // decay to zero + if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; + } + } + + // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) + postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows + uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding + fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth + } +#endif + // run peak detection + autoResetPeak(); + detectSamplePeak(); + + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC + #endif + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + + } // for(;;)ever +} // FFTcode() task end + + +/////////////////////////// +// Pre / Postprocessing // +/////////////////////////// + +static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass) +{ + // low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency + //constexpr float alpha = 0.04f; // 150Hz + //constexpr float alpha = 0.03f; // 110Hz + constexpr float alpha = 0.0225f; // 80hz + //constexpr float alpha = 0.01693f;// 60hz + // high frequency cutoff parameter + //constexpr float beta1 = 0.75f; // 11Khz + //constexpr float beta1 = 0.82f; // 15Khz + //constexpr float beta1 = 0.8285f; // 18Khz + constexpr float beta1 = 0.85f; // 20Khz + + constexpr float beta2 = (1.0f - beta1) / 2.0f; + static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter + static float lowfilt = 0.0f; // IIR low frequency cutoff filter + + for (int i=0; i < numSamples; i++) { + // FIR lowpass, to remove high frequency noise + float highFilteredSample; + if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes + else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array + last_vals[1] = last_vals[0]; + last_vals[0] = sampleBuffer[i]; + sampleBuffer[i] = highFilteredSample; + // IIR highpass, to remove low frequency noise + lowfilt += alpha * (sampleBuffer[i] - lowfilt); + sampleBuffer[i] = sampleBuffer[i] - lowfilt; + } +} + +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels +{ + for (int i=0; i < numberOfChannels; i++) { + + if (noiseGateOpen) { // noise gate open + // Adjustment for frequency curves. + fftCalc[i] *= fftResultPink[i]; + if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function + // Manual linear adjustment of gain using sampleGain adjustment for different input types. + fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment + if(fftCalc[i] < 0) fftCalc[i] = 0.0f; + } + + // smooth results - rise fast, fall slower + if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + } + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + + float currentResult; + if(limiterOn == true) + currentResult = fftAvg[i]; + else + currentResult = fftCalc[i]; + + switch (FFTScalingMode) { + case 1: + // Logarithmic scaling + currentResult *= 0.42f; // 42 is the answer ;-) + currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] + break; + case 2: + // Linear scaling + currentResult *= 0.30f; // needs a bit more damping, get stay below 255 + currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) + if (currentResult < 1.0f) currentResult = 0.0f; + currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies + break; + case 3: + // square root scaling + currentResult *= 0.38f; + currentResult -= 6.0f; + if (currentResult > 1.0f) currentResult = sqrtf(currentResult); + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined + currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; + + case 0: + default: + // no scaling - leave freq bins as-is + currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) + break; + } + + // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. + if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user + float post_gain = (float)inputLevel/128.0f; + if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; + currentResult *= post_gain; + } + fftResult[i] = constrain((int)currentResult, 0, 255); + } +} +//////////////////// +// Peak detection // +//////////////////// + +// peak detection is called from FFT task when vReal[] contains valid FFT results +static void detectSamplePeak(void) { + bool havePeak = false; + // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. + // Poor man's beat detection by seeing if sample > Average + some value. + // This goes through ALL of the 255 bins - but ignores stupid settings + // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. + if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { + havePeak = true; + } + + if (havePeak) { + samplePeak = true; + timeOfPeak = millis(); + udpSamplePeak = true; + } +} + +#endif + +static void autoResetPeak(void) { + uint16_t MinShowDelay = MAX(50, WS2812FX::getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC + if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + samplePeak = false; + if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData + } +} + + +//////////////////// +// usermod class // +//////////////////// + +//class name. Use something descriptive and leave the ": public Usermod" part :) +class AudioReactive : public Usermod { + + private: +#ifdef ARDUINO_ARCH_ESP32 + + #ifndef AUDIOPIN + int8_t audioPin = -1; + #else + int8_t audioPin = AUDIOPIN; + #endif + #ifndef SR_DMTYPE // I2S mic type + uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S + #define SR_DMTYPE 1 // default type = I2S + #else + uint8_t dmType = SR_DMTYPE; + #endif + #ifndef I2S_SDPIN // aka DOUT + int8_t i2ssdPin = 32; + #else + int8_t i2ssdPin = I2S_SDPIN; + #endif + #ifndef I2S_WSPIN // aka LRCL + int8_t i2swsPin = 15; + #else + int8_t i2swsPin = I2S_WSPIN; + #endif + #ifndef I2S_CKPIN // aka BCLK + int8_t i2sckPin = 14; /*PDM: set to I2S_PIN_NO_CHANGE*/ + #else + int8_t i2sckPin = I2S_CKPIN; + #endif + #ifndef MCLK_PIN + int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ + #else + int8_t mclkPin = MCLK_PIN; + #endif +#endif + + // new "V2" audiosync struct - 44 Bytes + struct __attribute__ ((packed)) audioSyncPacket { // "packed" ensures that there are no additional gaps + char header[6]; // 06 Bytes offset 0 + uint8_t reserved1[2]; // 02 Bytes, offset 6 - gap required by the compiler - not used yet + float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting + float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting + uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude + uint8_t reserved2; // 01 Bytes offset 17 - for future extensions - not used yet + uint8_t fftResult[16]; // 16 Bytes offset 18 + uint16_t reserved3; // 02 Bytes, offset 34 - gap required by the compiler - not used yet + float FFT_Magnitude; // 04 Bytes offset 36 + float FFT_MajorPeak; // 04 Bytes offset 40 + }; + + // old "V1" audiosync struct - 83 Bytes payload, 88 bytes total (with padding added by compiler) - for backwards compatibility + struct audioSyncPacket_v1 { + char header[6]; // 06 Bytes + uint8_t myVals[32]; // 32 Bytes + int sampleAgc; // 04 Bytes + int sampleRaw; // 04 Bytes + float sampleAvg; // 04 Bytes + bool samplePeak; // 01 Bytes + uint8_t fftResult[16]; // 16 Bytes + double FFT_Magnitude; // 08 Bytes + double FFT_MajorPeak; // 08 Bytes + }; + + constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); + + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + #ifdef UM_AUDIOREACTIVE_ENABLE + bool enabled = true; + #else + bool enabled = false; + #endif + + bool initDone = false; + bool addPalettes = false; + int8_t palettes = 0; + + // variables for UDP sound sync + WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) + unsigned long lastTime = 0; // last time of running UDP Microphone Sync + const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED + uint16_t audioSyncPort= 11988;// default port for UDP sound sync + + bool updateIsRunning = false; // true during OTA. + +#ifdef ARDUINO_ARCH_ESP32 + // used for AGC + int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) + float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error + // variables used by getSample() and agcAvg() + int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed + float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. + float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller + float expAdjF = 0.0f; // Used for exponential filter. + float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. + int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) + int16_t rawSampleAgc = 0; // not smoothed AGC sample +#endif + + // variables used in effects + float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample + int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc + float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc + + // used to feed "Info" Page + unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket + int receivedFormat = 0; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) + float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds + unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset + #define CYCLE_SAMPLEMAX 3500 // time window for merasuring + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _config[]; + static const char _dynamics[]; + static const char _frequency[]; + static const char _inputLvl[]; +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + static const char _analogmic[]; +#endif + static const char _digitalmic[]; + static const char _addPalettes[]; + static const char UDP_SYNC_HEADER[]; + static const char UDP_SYNC_HEADER_v1[]; + + // private methods + void removeAudioPalettes(void); + void createAudioPalettes(void); + CRGB getCRGBForBand(int x, int pal); + void fillAudioPalettes(void); + + //////////////////// + // Debug support // + //////////////////// + void logAudio() + { + if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable + #ifdef MIC_LOGGER + // Debugging functions for audio input and sound processing. Comment out the values you want to see + PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t"); + PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t"); + //PLOT_PRINT("volumeRaw:"); PLOT_PRINT(volumeRaw); PLOT_PRINT("\t"); + PLOT_PRINT("DC_Level:"); PLOT_PRINT(micLev); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); + #ifdef ARDUINO_ARCH_ESP32 + //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); + //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); + //PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t"); + //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); + #endif + PLOT_PRINTLN(); + #endif + + #ifdef FFT_SAMPLING_LOG + #if 0 + for(int i=0; i maxVal) maxVal = fftResult[i]; + if(fftResult[i] < minVal) minVal = fftResult[i]; + } + for(int i = 0; i < NUM_GEQ_CHANNELS; i++) { + PLOT_PRINT(i); PLOT_PRINT(":"); + PLOT_PRINTF("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); + } + if(printMaxVal) { + PLOT_PRINTF("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); + } + if(printMinVal) { + PLOT_PRINTF("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter + } + if(mapValuesToPlotterSpace) + PLOT_PRINTF("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis + else { + PLOT_PRINTF("max:%04d ", 256); + } + PLOT_PRINTLN(); + #endif // FFT_SAMPLING_LOG + } // logAudio() + + +#ifdef ARDUINO_ARCH_ESP32 + ////////////////////// + // Audio Processing // + ////////////////////// + + /* + * A "PI controller" multiplier to automatically adjust sound sensitivity. + * + * A few tricks are implemented so that sampleAgc does't only utilize 0% and 100%: + * 0. don't amplify anything below squelch (but keep previous gain) + * 1. gain input = maximum signal observed in the last 5-10 seconds + * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal + * 3. the amplification depends on signal level: + * a) normal zone - very slow adjustment + * b) emergency zone (<10% or >90%) - very fast adjustment + */ + void agcAvg(unsigned long the_time) + { + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + float lastMultAgc = multAgc; // last multiplier used + float multAgcTemp = multAgc; // new multiplier + float tmpAgc = sampleReal * multAgc; // what-if amplified signal + + float control_error; // "control error" input for PI control + + if (last_soundAgc != soundAgc) control_integrated = 0.0f; // new preset - reset integrator + + // For PI controller, we need to have a constant "frequency" + // so let's make sure that the control loop is not running at insane speed + static unsigned long last_time = 0; + unsigned long time_now = millis(); + if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock + + if (time_now - last_time > 2) { + last_time = time_now; + + if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { + // MIC signal is "squelched" - deliver silence + tmpAgc = 0; + // we need to "spin down" the intgrated error buffer + if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; + else control_integrated *= 0.91f; + } else { + // compute new setpoint + if (tmpAgc <= agcTarget0Up[AGC_preset]) + multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint + else + multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint + } + // limit amplification + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + + // compute error terms + control_error = multAgcTemp - lastMultAgc; + + if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping + && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) + control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping + else + control_integrated *= 0.9f; // spin down that beasty integrator + + // apply PI Control + tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone + multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } else { // "normal zone" + multAgcTemp = lastMultAgc + agcFollowSlow[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } + + // limit amplification again - PI controller sometimes "overshoots" + //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + } + + // NOW finally amplify the signal + tmpAgc = sampleReal * multAgcTemp; // apply gain to signal + if (fabsf(sampleReal) < 2.0f) tmpAgc = 0.0f; // apply squelch threshold + //tmpAgc = constrain(tmpAgc, 0, 255); + if (tmpAgc > 255) tmpAgc = 255.0f; // limit to 8bit + if (tmpAgc < 1) tmpAgc = 0.0f; // just to be sure + + // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc + multAgc = multAgcTemp; + rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; + // update smoothed AGC sample + if (fabsf(tmpAgc) < 1.0f) + sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero + else + sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path + + sampleAgc = fabsf(sampleAgc); // // make sure we have a positive value + last_soundAgc = soundAgc; + } // agcAvg() + + // post-processing and filtering of MIC sample (micDataReal) from FFTcode() + void getSample() + { + float sampleAdj; // Gain adjusted sample value + float tmpSample; // An interim sample variable used for calculations. + const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + #ifdef WLED_DISABLE_SOUND + micIn = inoise8(millis(), millis()); // Simulated analog read + micDataReal = micIn; + #else + #ifdef ARDUINO_ARCH_ESP32 + micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; + #else + // this is the minimal code for reading analog mic input on 8266. + // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. + static unsigned long lastAnalogTime = 0; + static float lastAnalogValue = 0.0f; + if (millis() - lastAnalogTime > 20) { + micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. + lastAnalogTime = millis(); + lastAnalogValue = micDataReal; + yield(); + } else micDataReal = lastAnalogValue; + micIn = int(micDataReal); + #endif + #endif + + micLev += (micDataReal-micLev) / 12288.0f; + if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal + + micIn -= micLev; // Let's center it to 0 now + // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. + float micInNoDC = fabsf(micDataReal - micLev); + expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); + expAdjF = fabsf(expAdjF); // Now (!) take the absolute value + + expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" + + tmpSample = expAdjF; + micIn = abs(micIn); // And get the absolute value of each sample + + sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment + sampleReal = tmpSample; + + sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? + sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! + + // keep "peak" sample, but decay value if current sample is below peak + if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { + sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume + if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { + samplePeak = true; + timeOfPeak = millis(); + udpSamplePeak = true; + } + } else { + if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) + sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly + else + sampleMax *= agcSampleDecay[AGC_preset]; // signal to zero --> 5-8sec + } + if (sampleMax < 0.5f) sampleMax = 0.0f; + + sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. + sampleAvg = fabsf(sampleAvg); // make sure we have a positive value + } // getSample() + +#endif + + /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). + * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) + */ + // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) + void limitSampleDynamics(void) { + const float bigChange = 196.0f; // just a representative number - a large, expected sample value + static unsigned long last_time = 0; + static float last_volumeSmth = 0.0f; + + if (limiterOn == false) return; + + long delta_time = millis() - last_time; + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up + float deltaSample = volumeSmth - last_volumeSmth; + + if (attackTime > 0) { // user has defined attack time > 0 + float maxAttack = bigChange * float(delta_time) / float(attackTime); + if (deltaSample > maxAttack) deltaSample = maxAttack; + } + if (decayTime > 0) { // user has defined decay time > 0 + float maxDecay = - bigChange * float(delta_time) / float(decayTime); + if (deltaSample < maxDecay) deltaSample = maxDecay; + } + + volumeSmth = last_volumeSmth + deltaSample; + + last_volumeSmth = volumeSmth; + last_time = millis(); + } + + + ////////////////////// + // UDP Sound Sync // + ////////////////////// + + // try to establish UDP sound sync connection + void connectUDPSoundSync(void) { + // This function tries to establish a UDP sync connection if needed + // necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection + static unsigned long last_connection_attempt = 0; + + if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled + if (udpSyncConnected) return; // already connected + if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable + if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds + if (updateIsRunning) return; + + // if we arrive here, we need a UDP connection but don't have one + last_connection_attempt = millis(); + connected(); // try to start UDP + } + +#ifdef ARDUINO_ARCH_ESP32 + void transmitAudioData() + { + //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); + + audioSyncPacket transmitData; + memset(reinterpret_cast(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized + + strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); + // transmit samples that were not modified by limitSampleDynamics() + transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; + transmitData.samplePeak = udpSamplePeak ? 1:0; + udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it + + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { + transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); + } + + transmitData.FFT_Magnitude = my_magnitude; + transmitData.FFT_MajorPeak = FFT_MajorPeak; + +#ifndef WLED_DISABLE_ESPNOW + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { + EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; + //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); + size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); + memcpy(buffer.data, &transmitData, sizeof(transmitData)); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); + } +#endif + + if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error + fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); + fftUdp.endPacket(); + } + return; + } // transmitAudioData() + +#endif + + static inline bool isValidUdpSyncVersion(const char *header) { + return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; + } + static inline bool isValidUdpSyncVersion_v1(const char *header) { + return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; + } + + void decodeAudioData(int packetSize, uint8_t *fftBuff) { + audioSyncPacket receivedPacket; + memset(&receivedPacket, 0, sizeof(receivedPacket)); // start clean + memcpy(&receivedPacket, fftBuff, min((unsigned)packetSize, (unsigned)sizeof(receivedPacket))); // don't violate alignment - thanks @willmmiles# + + // update samples for effects + volumeSmth = fmaxf(receivedPacket.sampleSmth, 0.0f); + volumeRaw = fmaxf(receivedPacket.sampleRaw, 0.0f); +#ifdef ARDUINO_ARCH_ESP32 + // update internal samples + sampleRaw = volumeRaw; + sampleAvg = volumeSmth; + rawSampleAgc = volumeRaw; + sampleAgc = volumeSmth; + multAgc = 1.0f; +#endif + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + autoResetPeak(); + if (!samplePeak) { + samplePeak = receivedPacket.samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); + } + //These values are only computed by ESP32 + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; + my_magnitude = fmaxf(receivedPacket.FFT_Magnitude, 0.0f); + FFT_Magnitude = my_magnitude; + FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + } + + void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { + audioSyncPacket_v1 *receivedPacket = reinterpret_cast(fftBuff); + // update samples for effects + volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); + volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample +#ifdef ARDUINO_ARCH_ESP32 + // update internal samples + sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); + sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; + sampleAgc = volumeSmth; + rawSampleAgc = volumeRaw; + multAgc = 1.0f; +#endif + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + autoResetPeak(); + if (!samplePeak) { + samplePeak = receivedPacket->samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); + } + //These values are only available on the ESP32 + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); + FFT_Magnitude = my_magnitude; + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + } + + bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. + { + if (!udpSyncConnected) return false; + bool haveFreshData = false; + + size_t packetSize = fftUdp.parsePacket(); +#ifdef ARDUINO_ARCH_ESP32 + if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32 +#endif + if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) { + //DEBUGSR_PRINTLN("Received UDP Sync Packet"); + uint8_t fftBuff[UDPSOUND_MAX_PACKET+1] = { 0 }; // fixed-size buffer for receiving (stack), to avoid heap fragmentation caused by variable sized arrays + fftUdp.read(fftBuff, packetSize); + + // VERIFY THAT THIS IS A COMPATIBLE PACKET + if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { + decodeAudioData(packetSize, fftBuff); + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2"); + haveFreshData = true; + receivedFormat = 2; + } else { + if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { + decodeAudioData_v1(packetSize, fftBuff); + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1"); + haveFreshData = true; + receivedFormat = 1; + } else receivedFormat = 0; // unknown format + } + } + return haveFreshData; + } + + + ////////////////////// + // usermod functions// + ////////////////////// + + public: + //Functions called by WLED or other usermods + + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + * It is called *AFTER* readFromConfig() + */ + void setup() override + { + disableSoundProcessing = true; // just to be sure + if (!initDone) { + // usermod exchangeable data + // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers + um_data = new um_data_t; + um_data->u_size = 8; + um_data->u_type = new um_types_t[um_data->u_size]; + um_data->u_data = new void*[um_data->u_size]; + um_data->u_data[0] = &volumeSmth; //*used (New) + um_data->u_type[0] = UMT_FLOAT; + um_data->u_data[1] = &volumeRaw; // used (New) + um_data->u_type[1] = UMT_UINT16; + um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) + um_data->u_type[2] = UMT_BYTE_ARR; + um_data->u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[3] = UMT_BYTE; + um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) + um_data->u_type[4] = UMT_FLOAT; + um_data->u_data[5] = &my_magnitude; // used (New) + um_data->u_type[5] = UMT_FLOAT; + um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[6] = UMT_BYTE; + um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[7] = UMT_BYTE; + } + + +#ifdef ARDUINO_ARCH_ESP32 + + // Reset I2S peripheral for good measure + i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed + #if !defined(CONFIG_IDF_TARGET_ESP32C3) + delay(100); + periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 + #endif + delay(100); // Give that poor microphone some time to setup. + + useBandPassFilter = false; + + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone + #endif + + switch (dmType) { + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + // stub cases for not-yet-supported I2S modes on other ESP32 chips + case 0: //ADC analog + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: //PDM Microphone + #endif + #endif + case 1: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 2: + DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); + audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + case 3: + DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 4: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: + DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); + useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); + break; + #endif + case 6: + DEBUGSR_PRINTLN(F("AR: ES8388 Source")); + audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + // ADC over I2S is only possible on "classic" ESP32 + case 0: + default: + DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); + audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog + if (audioSource) audioSource->initialize(audioPin); + break; + #endif + } + delay(250); // give microphone enough time to initialise + + if (!audioSource) enabled = false; // audio failed to initialise +#endif + if (enabled) onUpdateBegin(false); // create FFT task, and initialize network + if (enabled) disableSoundProcessing = false; // all good - enable audio processing +#ifdef ARDUINO_ARCH_ESP32 + if (FFT_Task == nullptr) enabled = false; // FFT task creation failed + if ((!audioSource) || (!audioSource->isInitialized())) { + // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync + #ifdef WLED_DEBUG + DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + #else + DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + #endif + disableSoundProcessing = true; + } +#endif + if (enabled) connectUDPSoundSync(); + if (enabled && addPalettes) createAudioPalettes(); + initDone = true; + } + + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() override + { + if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection + udpSyncConnected = false; + fftUdp.stop(); + } + + if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { + #ifdef ARDUINO_ARCH_ESP32 + udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); + #else + udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); + #endif + } + } + + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ + void loop() override + { + static unsigned long lastUMRun = millis(); + + if (!enabled) { + disableSoundProcessing = true; // keep processing suspended (FFT task) + lastUMRun = millis(); // update time keeping + return; + } + // We cannot wait indefinitely before processing audio data + if (WS2812FX::isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice + + // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) + if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed + &&( (realtimeMode == REALTIME_MODE_GENERIC) + ||(realtimeMode == REALTIME_MODE_E131) + ||(realtimeMode == REALTIME_MODE_UDP) + ||(realtimeMode == REALTIME_MODE_ADALIGHT) + ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed + { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" + DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + } + #endif + disableSoundProcessing = true; + } else { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" + DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + } + #endif + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping + disableSoundProcessing = false; + } + + if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode + if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode +#ifdef ARDUINO_ARCH_ESP32 + if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source + + + // Only run the sampling code IF we're not in Receive mode or realtime mode + if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { + if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) + + unsigned long t_now = millis(); // remember current time + int userloopDelay = int(t_now - lastUMRun); + if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. + + #ifdef WLED_DEBUG + // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. + // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS + //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { + // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); + //} + #endif + + // run filters, and repeat in case of loop delays (hick-up compensation) + if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem + if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs + do { + getSample(); // run microphone sampling filters + agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg + userloopDelay -= 2; // advance "simulated time" by 2ms + } while (userloopDelay > 0); + lastUMRun = t_now; // update time keeping + + // update samples for effects (raw, smooth) + volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; + volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + // update FFTMagnitude, taking into account AGC amplification + my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects + if (soundAgc) my_magnitude *= multAgc; + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + + limitSampleDynamics(); + } // if (!disableSoundProcessing) +#endif + + autoResetPeak(); // auto-reset sample peak after strip minShowDelay + if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected + + connectUDPSoundSync(); // ensure we have a connection - if needed + + // UDP Microphone Sync - receive mode + if ((audioSyncEnabled & 0x02) && udpSyncConnected) { + // Only run the audio listener code if we're in Receive mode + static float syncVolumeSmth = 0; + bool have_new_sample = false; + if (millis() - lastTime > delayMs) { + have_new_sample = receiveAudioData(); + if (have_new_sample) last_UDPTime = millis(); +#ifdef ARDUINO_ARCH_ESP32 + else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. +#endif + lastTime = millis(); + } + if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample + else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter + limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups + } + + #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + static unsigned long lastMicLoggerTime = 0; + if (millis()-lastMicLoggerTime > 20) { + lastMicLoggerTime = millis(); + logAudio(); + } + #endif + + // Info Page: keep max sample from last 5 seconds +#ifdef ARDUINO_ARCH_ESP32 + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing + if (sampleAvg < 1) maxSample5sec = 0; // noise gate + } else { + if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume + } +#else // similar functionality for 8266 receive only - use VolumeSmth instead of raw sample data + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15 * maxSample5sec) + 0.85 * volumeSmth; // reset, and start with some smoothing + if (volumeSmth < 1.0f) maxSample5sec = 0; // noise gate + if (maxSample5sec < 0.0f) maxSample5sec = 0; // avoid negative values + } else { + if (volumeSmth >= 1.0f) maxSample5sec = fmaxf(maxSample5sec, volumeRaw); // follow maximum volume + } +#endif + +#ifdef ARDUINO_ARCH_ESP32 + //UDP Microphone Sync - transmit mode + if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { + // Only run the transmit code IF we're in Transmit mode + transmitAudioData(); + lastTime = millis(); + } +#endif + + fillAudioPalettes(); + } + + + bool getUMData(um_data_t **data) override + { + if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit + *data = um_data; + return true; + } + +#ifdef ARDUINO_ARCH_ESP32 + void onUpdateBegin(bool init) override + { +#ifdef WLED_DEBUG + fftTime = sampleTime = 0; +#endif + // gracefully suspend FFT task (if running) + disableSoundProcessing = true; + + // reset sound data + micDataReal = 0.0f; + volumeRaw = 0; volumeSmth = 0.0f; + sampleAgc = 0.0f; sampleAvg = 0.0f; + sampleRaw = 0; rawSampleAgc = 0.0f; + my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; + multAgc = 1.0f; + // reset FFT data + memset(fftCalc, 0, sizeof(fftCalc)); + memset(fftAvg, 0, sizeof(fftAvg)); + memset(fftResult, 0, sizeof(fftResult)); + for(int i=(init?0:1); i don't process audio + updateIsRunning = init; + } +#endif + +#ifdef ARDUINO_ARCH_ESP32 + /** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + */ + bool handleButton(uint8_t b) override { + yield(); + // crude way of determining if audio input is analog + // better would be for AudioSource to implement getType() + if (enabled + && dmType == 0 && audioPin>=0 + && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) + ) { + return true; + } + return false; + } + +#endif + //////////////////////////// + // Settings and Info Page // + //////////////////////////// + + /* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ + void addToJsonInfo(JsonObject& root) override + { +#ifdef ARDUINO_ARCH_ESP32 + char myStringBuffer[16]; // buffer for snprintf() - not used yet on 8266 +#endif + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString = F(""); + infoArr.add(uiDomString); + + if (enabled) { +#ifdef ARDUINO_ARCH_ESP32 + // Input Level Slider + if (disableSoundProcessing == false) { // only show slider when audio processing is running + if (soundAgc > 0) { + infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies + } else { + infoArr = user.createNestedArray(F("Audio Input Level")); + } + uiDomString = F("
"); // + infoArr.add(uiDomString); + } +#endif + // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG + + // current Audio input + infoArr = user.createNestedArray(F("Audio Source")); + if (audioSyncEnabled & 0x02) { + // UDP sound sync - receive mode + infoArr.add(F("UDP sound sync")); + if (udpSyncConnected) { + if (millis() - last_UDPTime < 2500) + infoArr.add(F(" - receiving")); + else + infoArr.add(F(" - idle")); + } else { + infoArr.add(F(" - no connection")); + } +#ifndef ARDUINO_ARCH_ESP32 // substitute for 8266 + } else { + infoArr.add(F("sound sync Off")); + } +#else // ESP32 only + } else { + // Analog or I2S digital input + if (audioSource && (audioSource->isInitialized())) { + // audio source successfully configured + if (audioSource->getType() == AudioSource::Type_I2SAdc) { + infoArr.add(F("ADC analog")); + } else { + infoArr.add(F("I2S digital")); + } + // input level or "silence" + if (maxSample5sec > 1.0f) { + float my_usage = 100.0f * (maxSample5sec / 255.0f); + snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); + infoArr.add(myStringBuffer); + } else { + infoArr.add(F(" - quiet")); + } + } else { + // error during audio source setup + infoArr.add(F("not initialized")); + infoArr.add(F(" - check pin settings")); + } + } + + // Sound processing (FFT and input filters) + infoArr = user.createNestedArray(F("Sound Processing")); + if (audioSource && (disableSoundProcessing == false)) { + infoArr.add(F("running")); + } else { + infoArr.add(F("suspended")); + } + + // AGC or manual Gain + if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("Manual Gain")); + float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets + infoArr.add(roundf(myGain*100.0f) / 100.0f); + infoArr.add("x"); + } + if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("AGC Gain")); + infoArr.add(roundf(multAgc*100.0f) / 100.0f); + infoArr.add("x"); + } +#endif + // UDP Sound Sync status + infoArr = user.createNestedArray(F("UDP Sound Sync")); + if (audioSyncEnabled) { + if (audioSyncEnabled & 0x01) { + infoArr.add(F("send mode")); + if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2")); + } else if (audioSyncEnabled & 0x02) { + infoArr.add(F("receive mode")); + } + } else + infoArr.add("off"); + if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); + if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) { + if (receivedFormat == 1) infoArr.add(F(" v1")); + if (receivedFormat == 2) infoArr.add(F(" v2")); + } + + #if defined(WLED_DEBUG) || defined(SR_DEBUG) + #ifdef ARDUINO_ARCH_ESP32 + infoArr = user.createNestedArray(F("Sampling time")); + infoArr.add(float(sampleTime)/100.0f); + infoArr.add(" ms"); + + infoArr = user.createNestedArray(F("FFT time")); + infoArr.add(float(fftTime)/100.0f); + if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow + infoArr.add("! ms"); + else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability + infoArr.add(" ms!"); + else + infoArr.add(" ms"); + + DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); + DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); + #endif + #endif + } + } + + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject& root) override + { + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (usermod.isNull()) { + usermod = root.createNestedObject(FPSTR(_name)); + } + usermod["on"] = enabled; + } + + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) override + { + if (!initDone) return; // prevent crash on boot applyPreset() + bool prevEnabled = enabled; + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + if (prevEnabled != enabled) onUpdateBegin(!enabled); + if (addPalettes) { + // add/remove custom/audioreactive palettes + if (prevEnabled && !enabled) removeAudioPalettes(); + if (!prevEnabled && enabled) createAudioPalettes(); + } + } +#ifdef ARDUINO_ARCH_ESP32 + if (usermod[FPSTR(_inputLvl)].is()) { + inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); + } +#endif + } + if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { + // handle removal of custom palettes from JSON call so we don't break things + removeAudioPalettes(); + } + } + + void onStateChange(uint8_t callMode) override { + if (initDone && enabled && addPalettes && palettes==0 && WS2812FX::customPalettes.size()<10) { + // if palettes were removed during JSON call re-add them + createAudioPalettes(); + } + } + + /* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will make your settings editable through the Usermod Settings page automatically. + * + * Usermod Settings Overview: + * - Numeric values are treated as floats in the browser. + * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float + * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and + * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. + * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. + * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a + * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. + * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type + * used in the Usermod when reading the value from ArduinoJson. + * - Pin values can be treated differently from an integer value by using the key name "pin" + * - "pin" can contain a single or array of integer values + * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins + * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) + * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used + * + * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings + * + * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. + * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. + * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) override + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_addPalettes)] = addPalettes; + +#ifdef ARDUINO_ARCH_ESP32 + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); + amic["pin"] = audioPin; + #endif + + JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); + dmic["type"] = dmType; + JsonArray pinArray = dmic.createNestedArray("pin"); + pinArray.add(i2ssdPin); + pinArray.add(i2swsPin); + pinArray.add(i2sckPin); + pinArray.add(mclkPin); + + JsonObject cfg = top.createNestedObject(FPSTR(_config)); + cfg[F("squelch")] = soundSquelch; + cfg[F("gain")] = sampleGain; + cfg[F("AGC")] = soundAgc; + + JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); + freqScale[F("scale")] = FFTScalingMode; +#endif + + JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); + dynLim[F("limiter")] = limiterOn; + dynLim[F("rise")] = attackTime; + dynLim[F("fall")] = decayTime; + + JsonObject sync = top.createNestedObject("sync"); + sync["port"] = audioSyncPort; + sync["mode"] = audioSyncEnabled; + } + + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) override + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + bool oldEnabled = enabled; + bool oldAddPalettes = addPalettes; + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); + +#ifdef ARDUINO_ARCH_ESP32 + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); + #else + audioPin = -1; // MCU does not support analog mic + #endif + + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM + #endif + #endif + + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); + + configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch); + configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); + configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); + + configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); + + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); +#endif + configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); + configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); + + if (initDone) { + // add/remove custom/audioreactive palettes + if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes(); + if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes(); + } // else setup() will create palettes + return configComplete; + } + + + void appendConfigData() override + { +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addOption(dd,'Generic Analog',0);")); + #endif + oappend(SET_F("addOption(dd,'Generic I2S',1);")); + oappend(SET_F("addOption(dd,'ES7243',2);")); + oappend(SET_F("addOption(dd,'SPH0654',3);")); + oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); + #endif + oappend(SET_F("addOption(dd,'ES8388',6);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'Normal',1);")); + oappend(SET_F("addOption(dd,'Vivid',2);")); + oappend(SET_F("addOption(dd,'Lazy',3);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'On',1);")); + oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); + + oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); + oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); + oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); +#endif + + oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); + oappend(SET_F("addOption(dd,'Off',0);")); +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("addOption(dd,'Send',1);")); +#endif + oappend(SET_F("addOption(dd,'Receive',2);")); +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); + #else + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); + #endif +#endif + } + + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + //void handleOverlayDraw() override + //{ + //WS2812FX::setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + //} + + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() override + { + return USERMOD_ID_AUDIOREACTIVE; + } +}; + +void AudioReactive::removeAudioPalettes(void) { + DEBUG_PRINTLN(F("Removing audio palettes.")); + while (palettes>0) { + WS2812FX::customPalettes.pop_back(); + DEBUG_PRINTLN(palettes); + palettes--; + } + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); +} + +void AudioReactive::createAudioPalettes(void) { + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); + if (palettes) return; + DEBUG_PRINTLN(F("Adding audio palettes.")); + for (int i=0; i= palettes) lastCustPalette -= palettes; + for (int pal=0; pal= 0) { - irqBound = pinManager.allocatePin(config.interruptPin, false, PinOwner::UM_IMU); + irqBound = PinManager::allocatePin(config.interruptPin, false, PinOwner::UM_IMU); if (!irqBound) { DEBUG_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } pinMode(config.interruptPin, INPUT); }; @@ -408,7 +408,7 @@ class MPU6050Driver : public Usermod { // Previously loaded and config changed if (irqBound && ((old_cfg.interruptPin != config.interruptPin) || !config.enabled)) { detachInterrupt(old_cfg.interruptPin); - pinManager.deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); + PinManager::deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); irqBound = false; } diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index 4cb7ef0e8..382f72d0e 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -19,7 +19,7 @@ Example `usermods_list.cpp`: void registerUsermods() { - usermods.add(new UsermodMqttSwitch()); + UsermodManager::add(new UsermodMqttSwitch()); } ``` diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md index 24dd394b8..eaa069ae7 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -41,7 +41,7 @@ When a relay is switched, a message is published: ## Usermod installation -1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`. +1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `UsermodManager::add(new MultiRelay());` at the bottom of `usermods_list.cpp`. or 2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini @@ -90,9 +90,9 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - usermods.add(new MultiRelay()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + UsermodManager::add(new MultiRelay()); } ``` diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index efb3c8ae1..33a6cf85e 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -516,7 +516,7 @@ void MultiRelay::setup() { if (!_relay[i].external) _relay[i].state = !offMode; state |= (uint8_t)(_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; } else if (_relay[i].pin<100 && _relay[i].pin>=0) { - if (pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { + if (PinManager::allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) _relay[i].state = !offMode; switchRelay(i, _relay[i].state); _relay[i].active = false; @@ -817,7 +817,7 @@ bool MultiRelay::readFromConfig(JsonObject &root) { // deallocate all pins 1st for (int i=0; i=0 && oldPin[i]<100) { - pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); + PinManager::deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); } // allocate new pins setup(); diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index 238af314e..a1e45ba33 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -112,15 +112,15 @@ class PixelsDiceTrayUsermod : public Usermod { SetSPIPinsFromMacros(); PinManagerPinType spiPins[] = { {spi_mosi, true}, {spi_miso, false}, {spi_sclk, true}}; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; } else { PinManagerPinType displayPins[] = { {TFT_CS, true}, {TFT_DC, true}, {TFT_RST, true}, {TFT_BL, true}}; - if (!pinManager.allocateMultiplePins( + if (!PinManager::allocateMultiplePins( displayPins, sizeof(displayPins) / sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; } } diff --git a/usermods/pwm_outputs/usermod_pwm_outputs.h b/usermods/pwm_outputs/usermod_pwm_outputs.h index 1880308c4..09232f043 100644 --- a/usermods/pwm_outputs/usermod_pwm_outputs.h +++ b/usermods/pwm_outputs/usermod_pwm_outputs.h @@ -29,13 +29,13 @@ class PwmOutput { return; DEBUG_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_); - if (!pinManager.allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) + if (!PinManager::allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) return; - channel_ = pinManager.allocateLedc(1); + channel_ = PinManager::allocateLedc(1); if (channel_ == 255) { DEBUG_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_); - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); return; } @@ -49,9 +49,9 @@ class PwmOutput { DEBUG_PRINTF("pwm_output[%d]: close\n", pin_); if (!enabled_) return; - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); if (channel_ != 255) - pinManager.deallocateLedc(channel_, 1); + PinManager::deallocateLedc(channel_, 1); channel_ = 255; duty_ = 0.0f; enabled_ = false; diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index 10b784334..e44672039 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -129,7 +129,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initOledDisplay() { PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } }; - if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name); oledEnabled = oledInitDone = false; return; @@ -164,11 +164,11 @@ class QuinLEDAnPentaUsermod : public Usermod oledDisplay->clear(); } - pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); delete oledDisplay; @@ -184,7 +184,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initSht30TempHumiditySensor() { PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); shtEnabled = shtInitDone = false; return; @@ -212,8 +212,8 @@ class QuinLEDAnPentaUsermod : public Usermod sht30TempHumidSensor->reset(); } - pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); delete sht30TempHumidSensor; diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index e57641bf9..00fc22725 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod void initRotaryEncoder() { PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { eaIo = -1; ebIo = -1; cleanup(); @@ -108,11 +108,11 @@ class RgbRotaryEncoderUsermod : public Usermod { // Only deallocate pins if we allocated them ;) if (eaIo != -1) { - pinManager.deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); eaIo = -1; } if (ebIo != -1) { - pinManager.deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); ebIo = -1; } @@ -303,8 +303,8 @@ class RgbRotaryEncoderUsermod : public Usermod } if (eaIo != oldEaIo || ebIo != oldEbIo || stepsPerClick != oldStepsPerClick || incrementPerClick != oldIncrementPerClick) { - pinManager.deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); - pinManager.deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); delete rotaryEncoder; initRotaryEncoder(); diff --git a/usermods/sd_card/usermod_sd_card.h b/usermods/sd_card/usermod_sd_card.h index 5dac79159..da1999d9b 100644 --- a/usermods/sd_card/usermod_sd_card.h +++ b/usermods/sd_card/usermod_sd_card.h @@ -45,7 +45,7 @@ class UsermodSdCard : public Usermod { { configPinPico, true } }; - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); sdInitDone = false; return; @@ -75,10 +75,10 @@ class UsermodSdCard : public Usermod { SD_ADAPTER.end(); DEBUG_PRINTF("[%s] deallocate pins!\n", _name); - pinManager.deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPoci, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPico, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPoci, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPico, PinOwner::UM_SdCard); sdInitDone = false; } diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 111df2967..1436f8fc4 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -385,7 +385,7 @@ public: _setAllFalse(); #ifdef USERMOD_SN_PHOTORESISTOR - ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR); + ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); #endif DEBUG_PRINTLN(F("Setup done")); } diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 52ff3cc1d..a257413b4 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -103,7 +103,7 @@ class AutoSaveUsermod : public Usermod { #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod has enhanced functionality if // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); #endif initDone = true; if (enabled && applyAutoSaveOnBoot) applyPreset(autoSavePreset); diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 008647fa7..dfab7e6ff 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -543,7 +543,7 @@ void FourLineDisplayUsermod::setup() { type = NONE; } else { PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; - if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } + if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } } } else { if (i2c_scl<0 || i2c_sda<0) { type=NONE; } @@ -569,7 +569,7 @@ void FourLineDisplayUsermod::setup() { if (nullptr == u8x8) { DEBUG_PRINTLN(F("Display init failed.")); if (isSPI) { - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); + PinManager::deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); } type = NONE; return; @@ -1307,7 +1307,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SSD1309_SPI64); bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType == SSD1309_SPI64); if (isSPI) { - if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); + if (pinsChanged || !newSPI) PinManager::deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); if (!newSPI) { // was SPI but is no longer SPI if (i2c_scl<0 || i2c_sda<0) { newType=NONE; } @@ -1315,7 +1315,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { // still SPI but pins changed PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else if (newSPI) { // was I2C but is now SPI @@ -1324,7 +1324,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { } else { PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else { // just I2C type changed diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 5756fbb69..55715b7c7 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -489,7 +489,7 @@ void RotaryEncoderUIUsermod::setup() enabled = false; return; } else { - if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { + if (pinIRQ >= 0 && PinManager::allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { pinMode(pinIRQ, INPUT_PULLUP); attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH DEBUG_PRINTLN(F("Interrupt attached.")); @@ -502,7 +502,7 @@ void RotaryEncoderUIUsermod::setup() } } else { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; - if (pinA<0 || pinB<0 || !pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { + if (pinA<0 || pinB<0 || !PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { pinA = pinB = pinC = -1; enabled = false; return; @@ -525,7 +525,7 @@ void RotaryEncoderUIUsermod::setup() #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod uses FourLineDisplayUsermod for the best experience. // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); if (display != nullptr) { display->setMarkLine(1, 0); } @@ -1138,14 +1138,14 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { if (oldPcf8574) { if (pinIRQ >= 0) { detachInterrupt(pinIRQ); - pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); } pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins } else { - pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old pins.")); } pinA = newDTpin; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0084b09e0..ad843f0f9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -75,7 +75,7 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { static um_data_t* getAudioData() { um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } @@ -6298,7 +6298,7 @@ static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale uint8_t *fftResult = nullptr; float *fftBin = nullptr; um_data_t *um_data; - if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(float*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; @@ -6911,7 +6911,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } float volumeSmth = *(float*) um_data->u_data[0]; @@ -7494,7 +7494,7 @@ uint16_t mode_2DAkemi(void) { const float normalFactor = 0.4f; um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 0c4ec6570..0f197e80d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -183,11 +183,7 @@ void IRAM_ATTR_YN Segment::deallocateData() { if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer free(data); } else { - DEBUG_PRINT(F("---- Released data ")); - DEBUG_PRINTF_P(PSTR("(%p): "), this); - DEBUG_PRINT(F("inconsistent UsedSegmentData ")); - DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); - DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); + DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); } data = nullptr; Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); @@ -1251,7 +1247,7 @@ void WS2812FX::finalizeInit() { // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. // Pin should not be already allocated, read/only or defined for current bus - while (pinManager.isPinAllocated(defPin[j]) || !pinManager.isPinOk(defPin[j],true)) { + while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) { if (validPin) { DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); defPin[j] = 1; // start with GPIO1 and work upwards diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index b20095d4c..3766975f1 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -130,11 +130,11 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) , _colorOrderMap(com) { if (!isDigital(bc.type) || !bc.count) return; - if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _frequencykHz = 0U; _pins[0] = bc.pins[0]; if (is2Pin(bc.type)) { - if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { + if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); return; } @@ -422,8 +422,8 @@ void BusDigital::cleanup() { _valid = false; _busPtr = nullptr; if (_data != nullptr) freeData(); - pinManager.deallocatePin(_pins[1], PinOwner::BusDigital); - pinManager.deallocatePin(_pins[0], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[1], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[0], PinOwner::BusDigital); } @@ -464,16 +464,16 @@ BusPwm::BusPwm(BusConfig &bc) managed_pin_type pins[numPins]; for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; - if (!pinManager.allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; + if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; #ifdef ESP8266 analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer - _ledcStart = pinManager.allocateLedc(numPins); + _ledcStart = PinManager::allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels - pinManager.deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); + PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); return; } // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) @@ -640,8 +640,8 @@ std::vector BusPwm::getLEDTypes() { void BusPwm::deallocatePins() { unsigned numPins = getPins(); for (unsigned i = 0; i < numPins; i++) { - pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); - if (!pinManager.isPinOk(_pins[i])) continue; + PinManager::deallocatePin(_pins[i], PinOwner::BusPwm); + if (!PinManager::isPinOk(_pins[i])) continue; #ifdef ESP8266 digitalWrite(_pins[i], LOW); //turn off PWM interrupt #else @@ -649,7 +649,7 @@ void BusPwm::deallocatePins() { #endif } #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(_ledcStart, numPins); + PinManager::deallocateLedc(_ledcStart, numPins); #endif } @@ -661,7 +661,7 @@ BusOnOff::BusOnOff(BusConfig &bc) if (!Bus::isOnOff(bc.type)) return; uint8_t currentPin = bc.pins[0]; - if (!pinManager.allocatePin(currentPin, true, PinOwner::BusOnOff)) { + if (!PinManager::allocatePin(currentPin, true, PinOwner::BusOnOff)) { return; } _pin = currentPin; //store only after allocatePin() succeeds @@ -904,7 +904,7 @@ void BusManager::esp32RMTInvertIdle() { void BusManager::on() { #ifdef ESP8266 //Fix for turning off onboard LED breaking bus - if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) { uint8_t pins[2] = {255,255}; if (busses[i]->isDigital() && busses[i]->getPins(pins)) { @@ -926,7 +926,7 @@ void BusManager::off() { #ifdef ESP8266 // turn off built-in LED if strip is turned off // this will break digital bus so will need to be re-initialised on On - if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return; pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 24f10f0a7..40fe61f40 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -280,7 +280,7 @@ class BusOnOff : public Bus { uint32_t getPixelColor(uint16_t pix) const override; uint8_t getPins(uint8_t* pinArray) const override; void show() override; - void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } + void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } static std::vector getLEDTypes(); diff --git a/wled00/button.cpp b/wled00/button.cpp index b5a4e9436..f02ed3d6d 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -267,7 +267,7 @@ void handleButton() if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue; #endif - if (usermods.handleButton(b)) continue; // did usermod handle buttons + if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index f99aa8cd5..3f6cfbacb 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -261,12 +261,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { // deallocate existing button pins - for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button + for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) PinManager::deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button unsigned s = 0; for (JsonObject btn : hw_btn_ins) { CJSON(buttonType[s], btn["type"]); int8_t pin = btn["pin"][0] | -1; - if (pin > -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) { + if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) { btnPin[s] = pin; #ifdef ARDUINO_ARCH_ESP32 // ESP32 only: check that analog button pin is a valid ADC gpio @@ -275,7 +275,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // not an ADC analog pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } else { analogReadResolution(12); // see #4040 } @@ -286,7 +286,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // not a touch pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } //if touch pin, enable the touch interrupt on ESP32 S2 & S3 #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so @@ -331,7 +331,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS) { // relies upon only being called once with fromFS == true, which is currently true. for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { - if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !pinManager.allocatePin(btnPin[s], false, PinOwner::Button)) { + if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { btnPin[s] = -1; buttonType[s] = BTN_TYPE_NONE; } @@ -358,8 +358,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 if (hw_ir_pin > -2) { - pinManager.deallocatePin(irPin, PinOwner::IR); - if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) { + PinManager::deallocatePin(irPin, PinOwner::IR); + if (PinManager::allocatePin(hw_ir_pin, false, PinOwner::IR)) { irPin = hw_ir_pin; } else { irPin = -1; @@ -374,8 +374,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain; int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { - pinManager.deallocatePin(rlyPin, PinOwner::Relay); - if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) { + PinManager::deallocatePin(rlyPin, PinOwner::Relay); + if (PinManager::allocatePin(hw_relay_pin,true, PinOwner::Relay)) { rlyPin = hw_relay_pin; pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); } else { @@ -394,7 +394,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(i2c_sda, hw_if_i2c[0]); CJSON(i2c_scl, hw_if_i2c[1]); PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; - if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + if (i2c_scl >= 0 && i2c_sda >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior) else Wire.begin(); @@ -410,7 +410,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(spi_sclk, hw_if_spi[1]); CJSON(spi_miso, hw_if_spi[2]); PinManagerPinType spi[3] = { { spi_mosi, true }, { spi_miso, true }, { spi_sclk, true } }; - if (spi_mosi >= 0 && spi_sclk >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + if (spi_mosi >= 0 && spi_sclk >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { #ifdef ESP32 SPI.begin(spi_sclk, spi_miso, spi_mosi); // SPI global uses VSPI on ESP32 and FSPI on C3, S3 #else @@ -664,7 +664,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { DEBUG_PRINTLN(F("Starting usermod config.")); JsonObject usermods_settings = doc["um"]; if (!usermods_settings.isNull()) { - needsSave = !usermods.readFromConfig(usermods_settings); + needsSave = !UsermodManager::readFromConfig(usermods_settings); } if (fromFS) return needsSave; @@ -700,7 +700,7 @@ void deserializeConfigFromFS() { // save default values to /cfg.json // call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving JsonObject empty = JsonObject(); - usermods.readFromConfig(empty); + UsermodManager::readFromConfig(empty); serializeConfig(); // init Ethernet (in case default type is set at compile time) #ifdef WLED_USE_ETHERNET @@ -1121,7 +1121,7 @@ void serializeConfig() { #endif JsonObject usermods_settings = root.createNestedObject("um"); - usermods.addToConfig(usermods_settings); + UsermodManager::addToConfig(usermods_settings); File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); if (f) serializeJson(root, f); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a95064a2a..8903d1f27 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -318,34 +318,34 @@ class Usermod { class UsermodManager { private: - Usermod* ums[WLED_MAX_USERMODS]; - byte numMods = 0; + static Usermod* ums[WLED_MAX_USERMODS]; + static byte numMods; public: - void loop(); - void handleOverlayDraw(); - bool handleButton(uint8_t b); - bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods - void setup(); - void connected(); - void appendConfigData(); - void addToJsonState(JsonObject& obj); - void addToJsonInfo(JsonObject& obj); - void readFromJsonState(JsonObject& obj); - void addToConfig(JsonObject& obj); - bool readFromConfig(JsonObject& obj); + static void loop(); + static void handleOverlayDraw(); + static bool handleButton(uint8_t b); + static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods + static void setup(); + static void connected(); + static void appendConfigData(); + static void addToJsonState(JsonObject& obj); + static void addToJsonInfo(JsonObject& obj); + static void readFromJsonState(JsonObject& obj); + static void addToConfig(JsonObject& obj); + static bool readFromConfig(JsonObject& obj); #ifndef WLED_DISABLE_MQTT - void onMqttConnect(bool sessionPresent); - bool onMqttMessage(char* topic, char* payload); + static void onMqttConnect(bool sessionPresent); + static bool onMqttMessage(char* topic, char* payload); #endif #ifndef WLED_DISABLE_ESPNOW - bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); + static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif - void onUpdateBegin(bool); - void onStateChange(uint8_t); - bool add(Usermod* um); - Usermod* lookup(uint16_t mod_id); - byte getModCount() {return numMods;}; + static void onUpdateBegin(bool); + static void onStateChange(uint8_t); + static bool add(Usermod* um); + static Usermod* lookup(uint16_t mod_id); + static inline byte getModCount() {return numMods;}; }; //usermods_list.cpp diff --git a/wled00/json.cpp b/wled00/json.cpp index 596bd780e..0df7294c8 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -436,7 +436,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } strip.resume(); - usermods.readFromJsonState(root); + UsermodManager::readFromJsonState(root); loadLedmap = root[F("ledmap")] | loadLedmap; @@ -592,7 +592,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root[F("pl")] = currentPlaylist; root[F("ledmap")] = currentLedmap; - usermods.addToJsonState(root); + UsermodManager::addToJsonState(root); JsonObject nl = root.createNestedObject("nl"); nl["on"] = nightlightActive; @@ -784,7 +784,7 @@ void serializeInfo(JsonObject root) getTimeString(time); root[F("time")] = time; - usermods.addToJsonInfo(root); + UsermodManager::addToJsonInfo(root); uint16_t os = 0; #ifdef WLED_DEBUG diff --git a/wled00/led.cpp b/wled00/led.cpp index ba6ed2550..9de0495b4 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -131,7 +131,7 @@ void stateUpdated(byte callMode) { if (bri == nightlightTargetBri && callMode != CALL_MODE_NO_NOTIFY && nightlightMode != NL_MODE_SUN) nightlightActive = false; // notify usermods of state change - usermods.onStateChange(callMode); + UsermodManager::onStateChange(callMode); if (fadeTransition) { if (strip.getTransition() == 0) { diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 833e6eb7d..6c523c3eb 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -45,7 +45,7 @@ static void onMqttConnect(bool sessionPresent) mqtt->subscribe(subuf, 0); } - usermods.onMqttConnect(sessionPresent); + UsermodManager::onMqttConnect(sessionPresent); DEBUG_PRINTLN(F("MQTT ready")); publishMqtt(); @@ -89,7 +89,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp topic += topicPrefixLen; } else { // Non-Wled Topic used here. Probably a usermod subscribed to this topic. - usermods.onMqttMessage(topic, payloadStr); + UsermodManager::onMqttMessage(topic, payloadStr); delete[] payloadStr; payloadStr = nullptr; return; @@ -115,7 +115,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } } else if (strlen(topic) != 0) { // non standard topic, check with usermods - usermods.onMqttMessage(topic, payloadStr); + UsermodManager::onMqttMessage(topic, payloadStr); } else { // topmost topic (just wled/MAC) parseMQTTBriPayload(payloadStr); diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 239cff528..fcd0a40c2 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -88,7 +88,7 @@ void _overlayAnalogCountdown() } void handleOverlayDraw() { - usermods.handleOverlayDraw(); + UsermodManager::handleOverlayDraw(); if (analogClockSolidBlack) { const Segment* segments = strip.getSegments(); for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index be2a4f977..793b5440c 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -13,34 +13,16 @@ #endif #endif -#ifdef WLED_DEBUG -static void DebugPrintOwnerTag(PinOwner tag) -{ - uint32_t q = static_cast(tag); - if (q) { - DEBUG_PRINTF_P(PSTR("0x%02x (%d)"), q, q); - } else { - DEBUG_PRINT(F("(no owner)")); - } -} -#endif /// Actual allocation/deallocation routines -bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) +bool PinManager::deallocatePin(byte gpio, PinOwner tag) { if (gpio == 0xFF) return true; // explicitly allow clients to free -1 as a no-op if (!isPinOk(gpio, false)) return false; // but return false for any other invalid pin // if a non-zero ownerTag, only allow de-allocation if the owner's tag is provided if ((ownerTag[gpio] != PinOwner::None) && (ownerTag[gpio] != tag)) { - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN DEALLOC: IO ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINT(F(", but attempted de-allocation by ")); - DebugPrintOwnerTag(tag); - #endif + DEBUG_PRINTF_P(PSTR("PIN DEALLOC: FAIL GPIO %d allocated by 0x%02X, but attempted de-allocation by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio]), static_cast(tag)); return false; } @@ -50,7 +32,7 @@ bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) } // support function for deallocating multiple pins -bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag) +bool PinManager::deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag) { bool shouldFail = false; DEBUG_PRINTLN(F("MULTIPIN DEALLOC")); @@ -66,14 +48,7 @@ bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte array // if the current pin is allocated by selected owner it is possible to release it continue; } - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN DEALLOC: IO ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINT(F(", but attempted de-allocation by ")); - DebugPrintOwnerTag(tag); - #endif + DEBUG_PRINTF_P(PSTR("PIN DEALLOC: FAIL GPIO %d allocated by 0x%02X, but attempted de-allocation by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio]), static_cast(tag)); shouldFail = true; } if (shouldFail) { @@ -97,14 +72,14 @@ bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte array return true; } -bool PinManagerClass::deallocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag) +bool PinManager::deallocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag) { uint8_t pins[arrayElementCount]; for (int i=0; i(ownerTag[gpio])); shouldFail = true; } } @@ -158,64 +122,45 @@ bool PinManagerClass::allocateMultiplePins(const managed_pin_type * mptArray, by bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: Pin %d allocated by 0x%02X.\n"), gpio, static_cast(tag)); } + DEBUG_PRINTF_P(PSTR("PIN ALLOC: 0x%014llX.\n"), (unsigned long long)pinAlloc); return true; } -bool PinManagerClass::allocatePin(byte gpio, bool output, PinOwner tag) +bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag) { // HW I2C & SPI pins have to be allocated using allocateMultiplePins variant since there is always SCL/SDA pair if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI) { #ifdef WLED_DEBUG if (gpio < 255) { // 255 (-1) is the "not defined GPIO" if (!isPinOk(gpio, output)) { - DEBUG_PRINT(F("PIN ALLOC: FAIL for owner ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINT(F(": GPIO ")); DEBUG_PRINT(gpio); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL for owner 0x%02X: GPIO %d "), static_cast(tag), gpio); if (output) DEBUG_PRINTLN(F(" cannot be used for i/o on this MCU.")); else DEBUG_PRINTLN(F(" cannot be used as input on this MCU.")); } else { - DEBUG_PRINT(F("PIN ALLOC: FAIL: GPIO ")); DEBUG_PRINT(gpio); - DEBUG_PRINTLN(F(" - HW I2C & SPI pins have to be allocated using allocateMultiplePins()")); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL GPIO %d - HW I2C & SPI pins have to be allocated using allocateMultiplePins.\n"), gpio); } } #endif return false; } if (isPinAllocated(gpio)) { - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" already allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL Pin %d already allocated by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio])); return false; } bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" successfully allocated by ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: Pin %d successfully allocated by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio])); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: 0x%014llX.\n"), (unsigned long long)pinAlloc); return true; } // if tag is set to PinOwner::None, checks for ANY owner of the pin. // if tag is set to any other value, checks if that tag is the current owner of the pin. -bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const +bool PinManager::isPinAllocated(byte gpio, PinOwner tag) { if (!isPinOk(gpio, false)) return true; if ((tag != PinOwner::None) && (ownerTag[gpio] != tag)) return false; @@ -239,7 +184,7 @@ bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const */ // Check if supplied GPIO is ok to use -bool PinManagerClass::isPinOk(byte gpio, bool output) const +bool PinManager::isPinOk(byte gpio, bool output) { if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access #ifdef ARDUINO_ARCH_ESP32 @@ -279,7 +224,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const return false; } -bool PinManagerClass::isReadOnlyPin(byte gpio) +bool PinManager::isReadOnlyPin(byte gpio) { #ifdef ARDUINO_ARCH_ESP32 if (gpio < WLED_NUM_PINS) return (digitalPinIsValid(gpio) && !digitalPinCanOutput(gpio)); @@ -287,14 +232,14 @@ bool PinManagerClass::isReadOnlyPin(byte gpio) return false; } -PinOwner PinManagerClass::getPinOwner(byte gpio) const +PinOwner PinManager::getPinOwner(byte gpio) { if (!isPinOk(gpio, false)) return PinOwner::None; return ownerTag[gpio]; } #ifdef ARDUINO_ARCH_ESP32 -byte PinManagerClass::allocateLedc(byte channels) +byte PinManager::allocateLedc(byte channels) { if (channels > WLED_MAX_ANALOG_CHANNELS || channels == 0) return 255; unsigned ca = 0; @@ -321,7 +266,7 @@ byte PinManagerClass::allocateLedc(byte channels) return 255; //not enough consecutive free LEDC channels } -void PinManagerClass::deallocateLedc(byte pos, byte channels) +void PinManager::deallocateLedc(byte pos, byte channels) { for (unsigned j = pos; j < pos + channels && j < WLED_MAX_ANALOG_CHANNELS; j++) { bitWrite(ledcAlloc, j, false); @@ -329,4 +274,12 @@ void PinManagerClass::deallocateLedc(byte pos, byte channels) } #endif -PinManagerClass pinManager = PinManagerClass(); +#ifdef ESP8266 +uint32_t PinManager::pinAlloc = 0UL; +#else +uint64_t PinManager::pinAlloc = 0ULL; +uint16_t PinManager::ledcAlloc = 0; +#endif +uint8_t PinManager::i2cAllocCount = 0; +uint8_t PinManager::spiAllocCount = 0; +PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None }; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index a64900c89..73a4a3656 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -70,61 +70,54 @@ enum struct PinOwner : uint8_t { }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); -class PinManagerClass { +class PinManager { private: - struct { #ifdef ESP8266 - #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) - uint32_t pinAlloc : 24; // 24bit, 1 bit per pin, we use first 17bits + #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) + static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits #else - #define WLED_NUM_PINS (GPIO_PIN_COUNT) - uint64_t pinAlloc : 56; // 56 bits, 1 bit per pin, we use 50 bits on ESP32-S3 - uint16_t ledcAlloc : 16; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) + #define WLED_NUM_PINS (GPIO_PIN_COUNT) + static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3 + static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) #endif - uint8_t i2cAllocCount : 4; // allow multiple allocation of I2C bus pins but keep track of allocations - uint8_t spiAllocCount : 4; // allow multiple allocation of SPI bus pins but keep track of allocations - } __attribute__ ((packed)); - PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; + static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations + static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations + static PinOwner ownerTag[WLED_NUM_PINS]; public: - PinManagerClass() : pinAlloc(0ULL), i2cAllocCount(0), spiAllocCount(0) { + // De-allocates a single pin + static bool deallocatePin(byte gpio, PinOwner tag); + // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) + static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); + static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); + // Allocates a single pin, with an owner tag. + // De-allocation requires the same owner tag (or override) + static bool allocatePin(byte gpio, bool output, PinOwner tag); + // Allocates all the pins, or allocates none of the pins, with owner tag. + // Provided to simplify error condition handling in clients + // using more than one pin, such as I2C, SPI, rotary encoders, + // ethernet, etc.. + static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); + + [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] + static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } + [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] + static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } + + // will return true for reserved pins + static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + // will return false for reserved pins + static bool isPinOk(byte gpio, bool output = true); + + static bool isReadOnlyPin(byte gpio); + + static PinOwner getPinOwner(byte gpio); + #ifdef ARDUINO_ARCH_ESP32 - ledcAlloc = 0; + static byte allocateLedc(byte channels); + static void deallocateLedc(byte pos, byte channels); #endif - } - // De-allocates a single pin - bool deallocatePin(byte gpio, PinOwner tag); - // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) - bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); - bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); - // Allocates a single pin, with an owner tag. - // De-allocation requires the same owner tag (or override) - bool allocatePin(byte gpio, bool output, PinOwner tag); - // Allocates all the pins, or allocates none of the pins, with owner tag. - // Provided to simplify error condition handling in clients - // using more than one pin, such as I2C, SPI, rotary encoders, - // ethernet, etc.. - bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - - [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } - - // will return true for reserved pins - bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None) const; - // will return false for reserved pins - bool isPinOk(byte gpio, bool output = true) const; - - static bool isReadOnlyPin(byte gpio); - - PinOwner getPinOwner(byte gpio) const; - - #ifdef ARDUINO_ARCH_ESP32 - byte allocateLedc(byte channels); - void deallocateLedc(byte pos, byte channels); - #endif }; -extern PinManagerClass pinManager; +//extern PinManager pinManager; #endif diff --git a/wled00/set.cpp b/wled00/set.cpp index 812bcc52f..96eb3ed13 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -104,18 +104,18 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) { int t = 0; - if (rlyPin>=0 && pinManager.isPinAllocated(rlyPin, PinOwner::Relay)) { - pinManager.deallocatePin(rlyPin, PinOwner::Relay); + if (rlyPin>=0 && PinManager::isPinAllocated(rlyPin, PinOwner::Relay)) { + PinManager::deallocatePin(rlyPin, PinOwner::Relay); } #ifndef WLED_DISABLE_INFRARED - if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) { + if (irPin>=0 && PinManager::isPinAllocated(irPin, PinOwner::IR)) { deInitIR(); - pinManager.deallocatePin(irPin, PinOwner::IR); + PinManager::deallocatePin(irPin, PinOwner::IR); } #endif for (unsigned s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { - pinManager.deallocatePin(btnPin[s], PinOwner::Button); + if (btnPin[s]>=0 && PinManager::isPinAllocated(btnPin[s], PinOwner::Button)) { + PinManager::deallocatePin(btnPin[s], PinOwner::Button); #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing @@ -233,7 +233,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // update other pins #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = request->arg(F("IR")).toInt(); - if (pinManager.allocatePin(hw_ir_pin,false, PinOwner::IR)) { + if (PinManager::allocatePin(hw_ir_pin,false, PinOwner::IR)) { irPin = hw_ir_pin; } else { irPin = -1; @@ -244,7 +244,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) irApplyToAllSelected = !request->hasArg(F("MSO")); int hw_rly_pin = request->arg(F("RL")).toInt(); - if (pinManager.allocatePin(hw_rly_pin,true, PinOwner::Relay)) { + if (PinManager::allocatePin(hw_rly_pin,true, PinOwner::Relay)) { rlyPin = hw_rly_pin; } else { rlyPin = -1; @@ -259,7 +259,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10) char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10) int hw_btn_pin = request->arg(bt).toInt(); - if (hw_btn_pin >= 0 && pinManager.allocatePin(hw_btn_pin,false,PinOwner::Button)) { + if (hw_btn_pin >= 0 && PinManager::allocatePin(hw_btn_pin,false,PinOwner::Button)) { btnPin[i] = hw_btn_pin; buttonType[i] = request->arg(be).toInt(); #ifdef ARDUINO_ARCH_ESP32 @@ -270,7 +270,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // not an ADC analog pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[i], i); btnPin[i] = -1; - pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + PinManager::deallocatePin(hw_btn_pin,PinOwner::Button); } else { analogReadResolution(12); // see #4040 } @@ -282,7 +282,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // not a touch pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i); btnPin[i] = -1; - pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + PinManager::deallocatePin(hw_btn_pin,PinOwner::Button); } #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so else @@ -631,10 +631,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (i2c_sda != hw_sda_pin || i2c_scl != hw_scl_pin) { // only if pins changed uint8_t old_i2c[2] = { static_cast(i2c_scl), static_cast(i2c_sda) }; - pinManager.deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins + PinManager::deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } }; - if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { i2c_sda = hw_sda_pin; i2c_scl = hw_scl_pin; // no bus re-initialisation as usermods do not get any notification @@ -658,9 +658,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (spi_mosi != hw_mosi_pin || spi_miso != hw_miso_pin || spi_sclk != hw_sclk_pin) { // only if pins changed uint8_t old_spi[3] = { static_cast(spi_mosi), static_cast(spi_miso), static_cast(spi_sclk) }; - pinManager.deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins + PinManager::deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } }; - if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { spi_mosi = hw_mosi_pin; spi_miso = hw_miso_pin; spi_sclk = hw_sclk_pin; @@ -750,8 +750,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) DEBUG_PRINTF_P(PSTR(" = %s\n"), value.c_str()); } } - usermods.readFromConfig(um); // force change of usermod parameters - DEBUG_PRINTLN(F("Done re-init usermods.")); + UsermodManager::readFromConfig(um); // force change of usermod parameters + DEBUG_PRINTLN(F("Done re-init UsermodManager::")); releaseJSONBufferLock(); } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 8cf733dff..09e1440ef 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -976,7 +976,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs #ifndef WLED_DISABLE_ESPNOW // usermods hook can override processing - if (usermods.onEspNowMessage(address, data, len)) return; + if (UsermodManager::onEspNowMessage(address, data, len)) return; #endif // handle WiZ Mote data diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 2db29c3cd..d4ed8135f 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -68,3 +68,6 @@ bool UsermodManager::add(Usermod* um) ums[numMods++] = um; return true; } + +Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr}; +byte UsermodManager::numMods = 0; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 25d9ee9ab..36bd122a5 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -249,225 +249,225 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); + //UsermodManager::add(new MyExampleUsermod()); #ifdef USERMOD_BATTERY - usermods.add(new UsermodBattery()); + UsermodManager::add(new UsermodBattery()); #endif #ifdef USERMOD_DALLASTEMPERATURE - usermods.add(new UsermodTemperature()); + UsermodManager::add(new UsermodTemperature()); #endif #ifdef USERMOD_SN_PHOTORESISTOR - usermods.add(new Usermod_SN_Photoresistor()); + UsermodManager::add(new Usermod_SN_Photoresistor()); #endif #ifdef USERMOD_PWM_FAN - usermods.add(new PWMFanUsermod()); + UsermodManager::add(new PWMFanUsermod()); #endif #ifdef USERMOD_BUZZER - usermods.add(new BuzzerUsermod()); + UsermodManager::add(new BuzzerUsermod()); #endif #ifdef USERMOD_BH1750 - usermods.add(new Usermod_BH1750()); + UsermodManager::add(new Usermod_BH1750()); #endif #ifdef USERMOD_BME280 - usermods.add(new UsermodBME280()); + UsermodManager::add(new UsermodBME280()); #endif #ifdef USERMOD_BME68X - usermods.add(new UsermodBME68X()); + UsermodManager::add(new UsermodBME68X()); #endif #ifdef USERMOD_SENSORSTOMQTT - usermods.add(new UserMod_SensorsToMQTT()); + UsermodManager::add(new UserMod_SensorsToMQTT()); #endif #ifdef USERMOD_PIRSWITCH - usermods.add(new PIRsensorSwitch()); + UsermodManager::add(new PIRsensorSwitch()); #endif #ifdef USERMOD_FOUR_LINE_DISPLAY - usermods.add(new FourLineDisplayUsermod()); + UsermodManager::add(new FourLineDisplayUsermod()); #endif #ifdef USERMOD_ROTARY_ENCODER_UI - usermods.add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY + UsermodManager::add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY #endif #ifdef USERMOD_AUTO_SAVE - usermods.add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY + UsermodManager::add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY #endif #ifdef USERMOD_DHT - usermods.add(new UsermodDHT()); + UsermodManager::add(new UsermodDHT()); #endif #ifdef USERMOD_VL53L0X_GESTURES - usermods.add(new UsermodVL53L0XGestures()); + UsermodManager::add(new UsermodVL53L0XGestures()); #endif #ifdef USERMOD_ANIMATED_STAIRCASE - usermods.add(new Animated_Staircase()); + UsermodManager::add(new Animated_Staircase()); #endif #ifdef USERMOD_MULTI_RELAY - usermods.add(new MultiRelay()); + UsermodManager::add(new MultiRelay()); #endif #ifdef USERMOD_RTC - usermods.add(new RTCUsermod()); + UsermodManager::add(new RTCUsermod()); #endif #ifdef USERMOD_ELEKSTUBE_IPS - usermods.add(new ElekstubeIPSUsermod()); + UsermodManager::add(new ElekstubeIPSUsermod()); #endif #ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR - usermods.add(new RotaryEncoderBrightnessColor()); + UsermodManager::add(new RotaryEncoderBrightnessColor()); #endif #ifdef RGB_ROTARY_ENCODER - usermods.add(new RgbRotaryEncoderUsermod()); + UsermodManager::add(new RgbRotaryEncoderUsermod()); #endif #ifdef USERMOD_ST7789_DISPLAY - usermods.add(new St7789DisplayUsermod()); + UsermodManager::add(new St7789DisplayUsermod()); #endif #ifdef USERMOD_PIXELS_DICE_TRAY - usermods.add(new PixelsDiceTrayUsermod()); + UsermodManager::add(new PixelsDiceTrayUsermod()); #endif #ifdef USERMOD_SEVEN_SEGMENT - usermods.add(new SevenSegmentDisplay()); + UsermodManager::add(new SevenSegmentDisplay()); #endif #ifdef USERMOD_SSDR - usermods.add(new UsermodSSDR()); + UsermodManager::add(new UsermodSSDR()); #endif #ifdef USERMOD_CRONIXIE - usermods.add(new UsermodCronixie()); + UsermodManager::add(new UsermodCronixie()); #endif #ifdef QUINLED_AN_PENTA - usermods.add(new QuinLEDAnPentaUsermod()); + UsermodManager::add(new QuinLEDAnPentaUsermod()); #endif #ifdef USERMOD_WIZLIGHTS - usermods.add(new WizLightsUsermod()); + UsermodManager::add(new WizLightsUsermod()); #endif #ifdef USERMOD_WIREGUARD - usermods.add(new WireguardUsermod()); + UsermodManager::add(new WireguardUsermod()); #endif #ifdef USERMOD_WORDCLOCK - usermods.add(new WordClockUsermod()); + UsermodManager::add(new WordClockUsermod()); #endif #ifdef USERMOD_MY9291 - usermods.add(new MY9291Usermod()); + UsermodManager::add(new MY9291Usermod()); #endif #ifdef USERMOD_SI7021_MQTT_HA - usermods.add(new Si7021_MQTT_HA()); + UsermodManager::add(new Si7021_MQTT_HA()); #endif #ifdef USERMOD_SMARTNEST - usermods.add(new Smartnest()); + UsermodManager::add(new Smartnest()); #endif #ifdef USERMOD_AUDIOREACTIVE - usermods.add(new AudioReactive()); + UsermodManager::add(new AudioReactive()); #endif #ifdef USERMOD_ANALOG_CLOCK - usermods.add(new AnalogClockUsermod()); + UsermodManager::add(new AnalogClockUsermod()); #endif #ifdef USERMOD_PING_PONG_CLOCK - usermods.add(new PingPongClockUsermod()); + UsermodManager::add(new PingPongClockUsermod()); #endif #ifdef USERMOD_ADS1115 - usermods.add(new ADS1115Usermod()); + UsermodManager::add(new ADS1115Usermod()); #endif #ifdef USERMOD_KLIPPER_PERCENTAGE - usermods.add(new klipper_percentage()); + UsermodManager::add(new klipper_percentage()); #endif #ifdef USERMOD_BOBLIGHT - usermods.add(new BobLightUsermod()); + UsermodManager::add(new BobLightUsermod()); #endif #ifdef SD_ADAPTER - usermods.add(new UsermodSdCard()); + UsermodManager::add(new UsermodSdCard()); #endif #ifdef USERMOD_PWM_OUTPUTS - usermods.add(new PwmOutputsUsermod()); + UsermodManager::add(new PwmOutputsUsermod()); #endif #ifdef USERMOD_SHT - usermods.add(new ShtUsermod()); + UsermodManager::add(new ShtUsermod()); #endif #ifdef USERMOD_ANIMARTRIX - usermods.add(new AnimartrixUsermod("Animartrix", false)); + UsermodManager::add(new AnimartrixUsermod("Animartrix", false)); #endif #ifdef USERMOD_INTERNAL_TEMPERATURE - usermods.add(new InternalTemperatureUsermod()); + UsermodManager::add(new InternalTemperatureUsermod()); #endif #ifdef USERMOD_HTTP_PULL_LIGHT_CONTROL - usermods.add(new HttpPullLightControl()); + UsermodManager::add(new HttpPullLightControl()); #endif #ifdef USERMOD_MPU6050_IMU - static MPU6050Driver mpu6050; usermods.add(&mpu6050); + static MPU6050Driver mpu6050; UsermodManager::add(&mpu6050); #endif #ifdef USERMOD_GYRO_SURGE - static GyroSurge gyro_surge; usermods.add(&gyro_surge); + static GyroSurge gyro_surge; UsermodManager::add(&gyro_surge); #endif #ifdef USERMOD_LDR_DUSK_DAWN - usermods.add(new LDR_Dusk_Dawn_v2()); + UsermodManager::add(new LDR_Dusk_Dawn_v2()); #endif #ifdef USERMOD_STAIRCASE_WIPE - usermods.add(new StairwayWipeUsermod()); + UsermodManager::add(new StairwayWipeUsermod()); #endif #ifdef USERMOD_MAX17048 - usermods.add(new Usermod_MAX17048()); + UsermodManager::add(new Usermod_MAX17048()); #endif #ifdef USERMOD_TETRISAI - usermods.add(new TetrisAIUsermod()); + UsermodManager::add(new TetrisAIUsermod()); #endif #ifdef USERMOD_AHT10 - usermods.add(new UsermodAHT10()); + UsermodManager::add(new UsermodAHT10()); #endif #ifdef USERMOD_INA226 - usermods.add(new UsermodINA226()); + UsermodManager::add(new UsermodINA226()); #endif #ifdef USERMOD_LD2410 - usermods.add(new LD2410Usermod()); + UsermodManager::add(new LD2410Usermod()); #endif #ifdef USERMOD_POV_DISPLAY - usermods.add(new PovDisplayUsermod()); + UsermodManager::add(new PovDisplayUsermod()); #endif } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index bc1cc7b73..39e0d250b 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -72,7 +72,7 @@ void WLED::loop() unsigned long usermodMillis = millis(); #endif userLoop(); - usermods.loop(); + UsermodManager::loop(); #ifdef WLED_DEBUG usermodMillis = millis() - usermodMillis; avgUsermodMillis += usermodMillis; @@ -410,10 +410,10 @@ void WLED::setup() #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output + PinManager::allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin - pinManager.allocatePin(2, true, PinOwner::DMX); + PinManager::allocatePin(2, true, PinOwner::DMX); #endif DEBUG_PRINTLN(F("Registering usermods ...")); @@ -452,7 +452,7 @@ void WLED::setup() DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #if defined(STATUSLED) && STATUSLED>=0 - if (!pinManager.isPinAllocated(STATUSLED)) { + if (!PinManager::isPinAllocated(STATUSLED)) { // NOTE: Special case: The status LED should *NOT* be allocated. // See comments in handleStatusLed(). pinMode(STATUSLED, OUTPUT); @@ -465,7 +465,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Usermods setup")); userSetup(); - usermods.setup(); + UsermodManager::setup(); DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) @@ -479,8 +479,8 @@ void WLED::setup() findWiFi(true); // start scanning for available WiFi-s // all GPIOs are allocated at this point - serialCanRX = !pinManager.isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) - serialCanTX = !pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) + serialCanRX = !PinManager::isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) + serialCanTX = !PinManager::isPinAllocated(hardwareTX) || PinManager::getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused @@ -685,7 +685,7 @@ bool WLED::initEthernet() return false; } - if (!pinManager.allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) { + if (!PinManager::allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) { DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins")); return false; } @@ -719,7 +719,7 @@ bool WLED::initEthernet() DEBUG_PRINTLN(F("initC: ETH.begin() failed")); // de-allocate the allocated pins for (managed_pin_type mpt : pinsToAllocate) { - pinManager.deallocatePin(mpt.pin, PinOwner::Ethernet); + PinManager::deallocatePin(mpt.pin, PinOwner::Ethernet); } return false; } @@ -1010,7 +1010,7 @@ void WLED::handleConnection() } initInterfaces(); userConnected(); - usermods.connected(); + UsermodManager::connected(); lastMqttReconnectAttempt = 0; // force immediate update // shut down AP @@ -1033,7 +1033,7 @@ void WLED::handleStatusLED() uint32_t c = 0; #if STATUSLED>=0 - if (pinManager.isPinAllocated(STATUSLED)) { + if (PinManager::isPinAllocated(STATUSLED)) { return; //lower priority if something else uses the same pin } #endif diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 9d4e4c85b..7d6fecd8b 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -396,7 +396,7 @@ void initServer() #if WLED_WATCHDOG_TIMEOUT > 0 WLED::instance().disableWatchdog(); #endif - usermods.onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) + UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) lastEditTime = millis(); // make sure PIN does not lock during update strip.suspend(); #ifdef ESP8266 @@ -412,7 +412,7 @@ void initServer() } else { DEBUG_PRINTLN(F("Update Failed")); strip.resume(); - usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init) + UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init) #if WLED_WATCHDOG_TIMEOUT > 0 WLED::instance().enableWatchdog(); #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 71d66d002..a9195a309 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -135,7 +135,7 @@ void appendGPIOinfo() { if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins JsonObject mods = pDoc->createNestedObject(F("um")); - usermods.addToConfig(mods); + UsermodManager::addToConfig(mods); if (!mods.isNull()) fillUMPins(mods); releaseJSONBufferLock(); } @@ -144,7 +144,7 @@ void appendGPIOinfo() { // add reserved (unusable) pins oappend(SET_F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { - if (!pinManager.isPinOk(i, false)) { // include readonly pins + if (!PinManager::isPinOk(i, false)) { // include readonly pins oappendi(i); oappend(","); } } @@ -181,7 +181,7 @@ void appendGPIOinfo() { oappend(SET_F("d.ro_gpio=[")); bool firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { - if (pinManager.isReadOnlyPin(i)) { + if (PinManager::isReadOnlyPin(i)) { // No comma before the first pin if (!firstPin) oappend(SET_F(",")); oappendi(i); @@ -370,7 +370,7 @@ void getSettingsJS(byte subPage, char* dest) int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); + if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); } sappend('v',lc,bus->getLength()); sappend('v',lt,bus->getType()); @@ -694,7 +694,7 @@ void getSettingsJS(byte subPage, char* dest) { appendGPIOinfo(); oappend(SET_F("numM=")); - oappendi(usermods.getModCount()); + oappendi(UsermodManager::getModCount()); oappend(";"); sappend('v',SET_F("SDA"),i2c_sda); sappend('v',SET_F("SCL"),i2c_scl); @@ -706,7 +706,7 @@ void getSettingsJS(byte subPage, char* dest) oappend(SET_F("addInfo('MOSI','")); oappendi(HW_PIN_DATASPI); oappend(SET_F("');")); oappend(SET_F("addInfo('MISO','")); oappendi(HW_PIN_MISOSPI); oappend(SET_F("');")); oappend(SET_F("addInfo('SCLK','")); oappendi(HW_PIN_CLOCKSPI); oappend(SET_F("');")); - usermods.appendConfigData(); + UsermodManager::appendConfigData(); } if (subPage == SUBPAGE_UPDATE) // update From 9cb3531e2d3cd12dbdfc0669dfdbd1f5a58e75b6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 21 Sep 2024 22:24:36 +0200 Subject: [PATCH 486/694] Remove erroneous file Fix constant dependancy --- usermods/audioreactive/audio_reactive.old.h | 2071 ------------------- wled00/FX_fcn.cpp | 3 +- 2 files changed, 1 insertion(+), 2073 deletions(-) delete mode 100644 usermods/audioreactive/audio_reactive.old.h diff --git a/usermods/audioreactive/audio_reactive.old.h b/usermods/audioreactive/audio_reactive.old.h deleted file mode 100644 index 4f2e04c08..000000000 --- a/usermods/audioreactive/audio_reactive.old.h +++ /dev/null @@ -1,2071 +0,0 @@ -#pragma once - -#include "wled.h" - -#ifdef ARDUINO_ARCH_ESP32 - -#include -#include - -#ifdef WLED_ENABLE_DMX - #error This audio reactive usermod is not compatible with DMX Out. -#endif - -#endif - -#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) -#include -#endif - -/* - * Usermods allow you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * - * This is an audioreactive v2 usermod. - * .... - */ - -#if !defined(FFTTASK_PRIORITY) -#define FFTTASK_PRIORITY 1 // standard: looptask prio -//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp -//#define FFTTASK_PRIORITY 4 // above asyc_tcp -#endif - -// Comment/Uncomment to toggle usb serial debugging -// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) -// #define FFT_SAMPLING_LOG // FFT result debugging -// #define SR_DEBUG // generic SR DEBUG messages - -#ifdef SR_DEBUG - #define DEBUGSR_PRINT(x) DEBUGOUT.print(x) - #define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x) - #define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x) -#else - #define DEBUGSR_PRINT(x) - #define DEBUGSR_PRINTLN(x) - #define DEBUGSR_PRINTF(x...) -#endif - -#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG) - #define PLOT_PRINT(x) DEBUGOUT.print(x) - #define PLOT_PRINTLN(x) DEBUGOUT.println(x) - #define PLOT_PRINTF(x...) DEBUGOUT.printf(x) -#else - #define PLOT_PRINT(x) - #define PLOT_PRINTLN(x) - #define PLOT_PRINTF(x...) -#endif - -#define MAX_PALETTES 3 - -static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. -static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) -static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group - -#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! - -// audioreactive variables -#ifdef ARDUINO_ARCH_ESP32 -static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point -static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier -static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) -static float sampleAgc = 0.0f; // Smoothed AGC sample -static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) -#endif -//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample -static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency -static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after WS2812FX::getMinShowDelay() -static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData -static unsigned long timeOfPeak = 0; // time of last sample peak detection. -static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects - -// TODO: probably best not used by receive nodes -//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 - -// user settable parameters for limitSoundDynamics() -#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF -static bool limiterOn = false; // bool: enable / disable dynamics limiter -#else -static bool limiterOn = true; -#endif -static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec -static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec - -// peak detection -#ifdef ARDUINO_ARCH_ESP32 -static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode -#endif -static void autoResetPeak(void); // peak auto-reset function -static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) -static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) - -#ifdef ARDUINO_ARCH_ESP32 - -// use audio source class (ESP32 specific) -#include "audio_source.h" -constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) -constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) - -// globals -static uint8_t inputLevel = 128; // UI slider value -#ifndef SR_SQUELCH - uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) -#else - uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) -#endif -#ifndef SR_GAIN - uint8_t sampleGain = 60; // sample gain (config value) -#else - uint8_t sampleGain = SR_GAIN; // sample gain (config value) -#endif -// user settable options for FFTResult scaling -static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root - -// -// AGC presets -// Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" -// -#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy -const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax -const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone -const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone -const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level -const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% -const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) -const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% -const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec -const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs -const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter -const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter -const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) -// AGC presets end - -static AudioSource *audioSource = nullptr; -static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. - -//////////////////// -// Begin FFT Code // -//////////////////// - -// some prototypes, to ensure consistent interfaces -static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float -static float fftAddAvg(int from, int to); // average of several FFT result bins -static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results -static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels - -static TaskHandle_t FFT_Task = nullptr; - -// Table of multiplication factors so that we can even out the frequency response. -static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; - -// globals and FFT Output variables shared with animations -#if defined(WLED_DEBUG) || defined(SR_DEBUG) -static uint64_t fftTime = 0; -static uint64_t sampleTime = 0; -#endif - -// FFT Task variables (filtering and post-processing) -static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. -static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) -#ifdef SR_DEBUG -static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. -#endif - -// audio source parameters and constant -#ifdef ARDUINO_ARCH_ESP32C3 -constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms -#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -#else -constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms -//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms -//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms -//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms -#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling -//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling -//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling -#endif - -// FFT Constants -constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 -constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. -// the following are observed values, supported by a bit of "educated guessing" -//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels -#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels -#define LOG_256 5.54517744f // log(256) - -// These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins -static float vImag[samplesFFT] = {0.0f}; // imaginary parts - -// Create FFT object -// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 2.0.1 -// these options actually cause slow-downs on all esp32 processors, don't use them. -// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 -// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 -// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 -#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - -#include - -/* Create FFT object with weighing factor storage */ -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); - -// Helper functions - -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - -// compute average of several FFT result bins -static float fftAddAvg(int from, int to) { - float result = 0.0f; - for (int i = from; i <= to; i++) { - result += vReal[i]; - } - return result / float(to - from + 1); -} - -// -// FFT main task -// -void FFTcode(void * parameter) -{ - DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); - - // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; - - TickType_t xLastWakeTime = xTaskGetTickCount(); - for(;;) { - delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. - // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. - - // Don't run FFT computing code if we're in Receive mode or in realtime mode - if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers - continue; - } - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - uint64_t start = esp_timer_get_time(); - bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid -#endif - - // get a fresh batch of samples from I2S - if (audioSource) audioSource->getSamples(vReal, samplesFFT); - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - if (start < esp_timer_get_time()) { // filter out overflows - uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding - sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth - } - start = esp_timer_get_time(); // start measuring FFT time -#endif - - xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay - - // band pass filter - can reduce noise floor by a factor of 50 - // downside: frequencies below 100Hz will be ignored - if (useBandPassFilter) runMicFilter(samplesFFT, vReal); - - // find highest sample in the batch - float maxSample = 0.0f; // max sample from FFT batch - for (int i=0; i < samplesFFT; i++) { - // set imaginary parts to 0 - vImag[i] = 0; - // pick our our current mic sample - we take the max value from all samples that go into FFT - if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts - if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); - } - // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function - // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. - micDataReal = maxSample; - -#ifdef SR_DEBUG - if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization -#else - if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. -#endif - - // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) - FFT.dcRemoval(); // remove DC offset - FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy - //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection - FFT.compute( FFTDirection::Forward ); // Compute FFT - FFT.complexToMagnitude(); // Compute magnitudes - vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. - - FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant - FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - haveDoneFFT = true; -#endif - - } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. - memset(vReal, 0, sizeof(vReal)); - FFT_MajorPeak = 1.0f; - FFT_Magnitude = 0.001f; - } - - for (int i = 0; i < samplesFFT; i++) { - float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way - vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. - } // for() - - // mapping of FFT result bins to frequency channels - if (fabsf(sampleAvg) > 0.5f) { // noise gate open -#if 0 - /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. - * - * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. - * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. - * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins. - * End frequency = Start frequency * multiplier ^ 16 - * Multiplier = (End frequency/ Start frequency) ^ 1/16 - * Multiplier = 1.320367784 - */ // Range - fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 - fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 - fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 - fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 - fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 - fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 - fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 - fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 - fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 - fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 - fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 - fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 - fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 - fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 - fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 - fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate -#else - /* new mapping, optimized for 22050 Hz by softhack007 */ - // bins frequency range - if (useBandPassFilter) { - // skip frequencies below 100hz - fftCalc[ 0] = 0.8f * fftAddAvg(3,4); - fftCalc[ 1] = 0.9f * fftAddAvg(4,5); - fftCalc[ 2] = fftAddAvg(5,6); - fftCalc[ 3] = fftAddAvg(6,7); - // don't use the last bins from 206 to 255. - fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping - } else { - fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass - fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass - fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass - fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange - // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping - } - fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange - fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange - fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange - fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange - fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange - fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid - fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid - fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid - fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping -#endif - } else { // noise gate closed - just decay old values - for (int i=0; i < NUM_GEQ_CHANNELS; i++) { - fftCalc[i] *= 0.85f; // decay to zero - if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; - } - } - - // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) - postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows - uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding - fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth - } -#endif - // run peak detection - autoResetPeak(); - detectSamplePeak(); - - #if !defined(I2S_GRAB_ADC1_COMPLETELY) - if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC - #endif - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers - - } // for(;;)ever -} // FFTcode() task end - - -/////////////////////////// -// Pre / Postprocessing // -/////////////////////////// - -static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass) -{ - // low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency - //constexpr float alpha = 0.04f; // 150Hz - //constexpr float alpha = 0.03f; // 110Hz - constexpr float alpha = 0.0225f; // 80hz - //constexpr float alpha = 0.01693f;// 60hz - // high frequency cutoff parameter - //constexpr float beta1 = 0.75f; // 11Khz - //constexpr float beta1 = 0.82f; // 15Khz - //constexpr float beta1 = 0.8285f; // 18Khz - constexpr float beta1 = 0.85f; // 20Khz - - constexpr float beta2 = (1.0f - beta1) / 2.0f; - static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter - static float lowfilt = 0.0f; // IIR low frequency cutoff filter - - for (int i=0; i < numSamples; i++) { - // FIR lowpass, to remove high frequency noise - float highFilteredSample; - if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes - else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array - last_vals[1] = last_vals[0]; - last_vals[0] = sampleBuffer[i]; - sampleBuffer[i] = highFilteredSample; - // IIR highpass, to remove low frequency noise - lowfilt += alpha * (sampleBuffer[i] - lowfilt); - sampleBuffer[i] = sampleBuffer[i] - lowfilt; - } -} - -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels -{ - for (int i=0; i < numberOfChannels; i++) { - - if (noiseGateOpen) { // noise gate open - // Adjustment for frequency curves. - fftCalc[i] *= fftResultPink[i]; - if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function - // Manual linear adjustment of gain using sampleGain adjustment for different input types. - fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment - if(fftCalc[i] < 0) fftCalc[i] = 0.0f; - } - - // smooth results - rise fast, fall slower - if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] - else { // fall slow - if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero - else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero - else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero - else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero - } - // constrain internal vars - just to be sure - fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); - fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); - - float currentResult; - if(limiterOn == true) - currentResult = fftAvg[i]; - else - currentResult = fftCalc[i]; - - switch (FFTScalingMode) { - case 1: - // Logarithmic scaling - currentResult *= 0.42f; // 42 is the answer ;-) - currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks - if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function - else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined - currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] - break; - case 2: - // Linear scaling - currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) - if (currentResult < 1.0f) currentResult = 0.0f; - currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies - break; - case 3: - // square root scaling - currentResult *= 0.38f; - currentResult -= 6.0f; - if (currentResult > 1.0f) currentResult = sqrtf(currentResult); - else currentResult = 0.0f; // special handling, because sqrt(0) = undefined - currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] - break; - - case 0: - default: - // no scaling - leave freq bins as-is - currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) - break; - } - - // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. - if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user - float post_gain = (float)inputLevel/128.0f; - if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; - currentResult *= post_gain; - } - fftResult[i] = constrain((int)currentResult, 0, 255); - } -} -//////////////////// -// Peak detection // -//////////////////// - -// peak detection is called from FFT task when vReal[] contains valid FFT results -static void detectSamplePeak(void) { - bool havePeak = false; - // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. - // Poor man's beat detection by seeing if sample > Average + some value. - // This goes through ALL of the 255 bins - but ignores stupid settings - // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. - if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { - havePeak = true; - } - - if (havePeak) { - samplePeak = true; - timeOfPeak = millis(); - udpSamplePeak = true; - } -} - -#endif - -static void autoResetPeak(void) { - uint16_t MinShowDelay = MAX(50, WS2812FX::getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC - if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. - samplePeak = false; - if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData - } -} - - -//////////////////// -// usermod class // -//////////////////// - -//class name. Use something descriptive and leave the ": public Usermod" part :) -class AudioReactive : public Usermod { - - private: -#ifdef ARDUINO_ARCH_ESP32 - - #ifndef AUDIOPIN - int8_t audioPin = -1; - #else - int8_t audioPin = AUDIOPIN; - #endif - #ifndef SR_DMTYPE // I2S mic type - uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S - #define SR_DMTYPE 1 // default type = I2S - #else - uint8_t dmType = SR_DMTYPE; - #endif - #ifndef I2S_SDPIN // aka DOUT - int8_t i2ssdPin = 32; - #else - int8_t i2ssdPin = I2S_SDPIN; - #endif - #ifndef I2S_WSPIN // aka LRCL - int8_t i2swsPin = 15; - #else - int8_t i2swsPin = I2S_WSPIN; - #endif - #ifndef I2S_CKPIN // aka BCLK - int8_t i2sckPin = 14; /*PDM: set to I2S_PIN_NO_CHANGE*/ - #else - int8_t i2sckPin = I2S_CKPIN; - #endif - #ifndef MCLK_PIN - int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ - #else - int8_t mclkPin = MCLK_PIN; - #endif -#endif - - // new "V2" audiosync struct - 44 Bytes - struct __attribute__ ((packed)) audioSyncPacket { // "packed" ensures that there are no additional gaps - char header[6]; // 06 Bytes offset 0 - uint8_t reserved1[2]; // 02 Bytes, offset 6 - gap required by the compiler - not used yet - float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting - float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting - uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude - uint8_t reserved2; // 01 Bytes offset 17 - for future extensions - not used yet - uint8_t fftResult[16]; // 16 Bytes offset 18 - uint16_t reserved3; // 02 Bytes, offset 34 - gap required by the compiler - not used yet - float FFT_Magnitude; // 04 Bytes offset 36 - float FFT_MajorPeak; // 04 Bytes offset 40 - }; - - // old "V1" audiosync struct - 83 Bytes payload, 88 bytes total (with padding added by compiler) - for backwards compatibility - struct audioSyncPacket_v1 { - char header[6]; // 06 Bytes - uint8_t myVals[32]; // 32 Bytes - int sampleAgc; // 04 Bytes - int sampleRaw; // 04 Bytes - float sampleAvg; // 04 Bytes - bool samplePeak; // 01 Bytes - uint8_t fftResult[16]; // 16 Bytes - double FFT_Magnitude; // 08 Bytes - double FFT_MajorPeak; // 08 Bytes - }; - - constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); - - // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) - #ifdef UM_AUDIOREACTIVE_ENABLE - bool enabled = true; - #else - bool enabled = false; - #endif - - bool initDone = false; - bool addPalettes = false; - int8_t palettes = 0; - - // variables for UDP sound sync - WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) - unsigned long lastTime = 0; // last time of running UDP Microphone Sync - const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED - uint16_t audioSyncPort= 11988;// default port for UDP sound sync - - bool updateIsRunning = false; // true during OTA. - -#ifdef ARDUINO_ARCH_ESP32 - // used for AGC - int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) - float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error - // variables used by getSample() and agcAvg() - int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. - float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller - float expAdjF = 0.0f; // Used for exponential filter. - float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. - int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) - int16_t rawSampleAgc = 0; // not smoothed AGC sample -#endif - - // variables used in effects - float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample - int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc - float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc - - // used to feed "Info" Page - unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket - int receivedFormat = 0; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) - float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds - unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset - #define CYCLE_SAMPLEMAX 3500 // time window for merasuring - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _config[]; - static const char _dynamics[]; - static const char _frequency[]; - static const char _inputLvl[]; -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - static const char _analogmic[]; -#endif - static const char _digitalmic[]; - static const char _addPalettes[]; - static const char UDP_SYNC_HEADER[]; - static const char UDP_SYNC_HEADER_v1[]; - - // private methods - void removeAudioPalettes(void); - void createAudioPalettes(void); - CRGB getCRGBForBand(int x, int pal); - void fillAudioPalettes(void); - - //////////////////// - // Debug support // - //////////////////// - void logAudio() - { - if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable - #ifdef MIC_LOGGER - // Debugging functions for audio input and sound processing. Comment out the values you want to see - PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t"); - PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t"); - //PLOT_PRINT("volumeRaw:"); PLOT_PRINT(volumeRaw); PLOT_PRINT("\t"); - PLOT_PRINT("DC_Level:"); PLOT_PRINT(micLev); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); - #ifdef ARDUINO_ARCH_ESP32 - //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); - //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); - //PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t"); - //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); - #endif - PLOT_PRINTLN(); - #endif - - #ifdef FFT_SAMPLING_LOG - #if 0 - for(int i=0; i maxVal) maxVal = fftResult[i]; - if(fftResult[i] < minVal) minVal = fftResult[i]; - } - for(int i = 0; i < NUM_GEQ_CHANNELS; i++) { - PLOT_PRINT(i); PLOT_PRINT(":"); - PLOT_PRINTF("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); - } - if(printMaxVal) { - PLOT_PRINTF("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); - } - if(printMinVal) { - PLOT_PRINTF("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter - } - if(mapValuesToPlotterSpace) - PLOT_PRINTF("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis - else { - PLOT_PRINTF("max:%04d ", 256); - } - PLOT_PRINTLN(); - #endif // FFT_SAMPLING_LOG - } // logAudio() - - -#ifdef ARDUINO_ARCH_ESP32 - ////////////////////// - // Audio Processing // - ////////////////////// - - /* - * A "PI controller" multiplier to automatically adjust sound sensitivity. - * - * A few tricks are implemented so that sampleAgc does't only utilize 0% and 100%: - * 0. don't amplify anything below squelch (but keep previous gain) - * 1. gain input = maximum signal observed in the last 5-10 seconds - * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal - * 3. the amplification depends on signal level: - * a) normal zone - very slow adjustment - * b) emergency zone (<10% or >90%) - very fast adjustment - */ - void agcAvg(unsigned long the_time) - { - const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function - - float lastMultAgc = multAgc; // last multiplier used - float multAgcTemp = multAgc; // new multiplier - float tmpAgc = sampleReal * multAgc; // what-if amplified signal - - float control_error; // "control error" input for PI control - - if (last_soundAgc != soundAgc) control_integrated = 0.0f; // new preset - reset integrator - - // For PI controller, we need to have a constant "frequency" - // so let's make sure that the control loop is not running at insane speed - static unsigned long last_time = 0; - unsigned long time_now = millis(); - if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock - - if (time_now - last_time > 2) { - last_time = time_now; - - if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { - // MIC signal is "squelched" - deliver silence - tmpAgc = 0; - // we need to "spin down" the intgrated error buffer - if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; - else control_integrated *= 0.91f; - } else { - // compute new setpoint - if (tmpAgc <= agcTarget0Up[AGC_preset]) - multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint - else - multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint - } - // limit amplification - if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; - if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; - - // compute error terms - control_error = multAgcTemp - lastMultAgc; - - if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping - && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping - else - control_integrated *= 0.9f; // spin down that beasty integrator - - // apply PI Control - tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain - if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone - multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; - multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; - } else { // "normal zone" - multAgcTemp = lastMultAgc + agcFollowSlow[AGC_preset] * agcControlKp[AGC_preset] * control_error; - multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; - } - - // limit amplification again - PI controller sometimes "overshoots" - //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 - if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; - if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; - } - - // NOW finally amplify the signal - tmpAgc = sampleReal * multAgcTemp; // apply gain to signal - if (fabsf(sampleReal) < 2.0f) tmpAgc = 0.0f; // apply squelch threshold - //tmpAgc = constrain(tmpAgc, 0, 255); - if (tmpAgc > 255) tmpAgc = 255.0f; // limit to 8bit - if (tmpAgc < 1) tmpAgc = 0.0f; // just to be sure - - // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc - multAgc = multAgcTemp; - rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; - // update smoothed AGC sample - if (fabsf(tmpAgc) < 1.0f) - sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero - else - sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path - - sampleAgc = fabsf(sampleAgc); // // make sure we have a positive value - last_soundAgc = soundAgc; - } // agcAvg() - - // post-processing and filtering of MIC sample (micDataReal) from FFTcode() - void getSample() - { - float sampleAdj; // Gain adjusted sample value - float tmpSample; // An interim sample variable used for calculations. - const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. - const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function - - #ifdef WLED_DISABLE_SOUND - micIn = inoise8(millis(), millis()); // Simulated analog read - micDataReal = micIn; - #else - #ifdef ARDUINO_ARCH_ESP32 - micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; - #else - // this is the minimal code for reading analog mic input on 8266. - // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. - static unsigned long lastAnalogTime = 0; - static float lastAnalogValue = 0.0f; - if (millis() - lastAnalogTime > 20) { - micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. - lastAnalogTime = millis(); - lastAnalogValue = micDataReal; - yield(); - } else micDataReal = lastAnalogValue; - micIn = int(micDataReal); - #endif - #endif - - micLev += (micDataReal-micLev) / 12288.0f; - if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal - - micIn -= micLev; // Let's center it to 0 now - // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. - float micInNoDC = fabsf(micDataReal - micLev); - expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); - expAdjF = fabsf(expAdjF); // Now (!) take the absolute value - - expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate - if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" - - tmpSample = expAdjF; - micIn = abs(micIn); // And get the absolute value of each sample - - sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment - sampleReal = tmpSample; - - sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? - sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! - - // keep "peak" sample, but decay value if current sample is below peak - if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { - sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering - // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume - if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { - samplePeak = true; - timeOfPeak = millis(); - udpSamplePeak = true; - } - } else { - if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) - sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly - else - sampleMax *= agcSampleDecay[AGC_preset]; // signal to zero --> 5-8sec - } - if (sampleMax < 0.5f) sampleMax = 0.0f; - - sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. - sampleAvg = fabsf(sampleAvg); // make sure we have a positive value - } // getSample() - -#endif - - /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). - * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) - */ - // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) - void limitSampleDynamics(void) { - const float bigChange = 196.0f; // just a representative number - a large, expected sample value - static unsigned long last_time = 0; - static float last_volumeSmth = 0.0f; - - if (limiterOn == false) return; - - long delta_time = millis() - last_time; - delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up - float deltaSample = volumeSmth - last_volumeSmth; - - if (attackTime > 0) { // user has defined attack time > 0 - float maxAttack = bigChange * float(delta_time) / float(attackTime); - if (deltaSample > maxAttack) deltaSample = maxAttack; - } - if (decayTime > 0) { // user has defined decay time > 0 - float maxDecay = - bigChange * float(delta_time) / float(decayTime); - if (deltaSample < maxDecay) deltaSample = maxDecay; - } - - volumeSmth = last_volumeSmth + deltaSample; - - last_volumeSmth = volumeSmth; - last_time = millis(); - } - - - ////////////////////// - // UDP Sound Sync // - ////////////////////// - - // try to establish UDP sound sync connection - void connectUDPSoundSync(void) { - // This function tries to establish a UDP sync connection if needed - // necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection - static unsigned long last_connection_attempt = 0; - - if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled - if (udpSyncConnected) return; // already connected - if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable - if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds - if (updateIsRunning) return; - - // if we arrive here, we need a UDP connection but don't have one - last_connection_attempt = millis(); - connected(); // try to start UDP - } - -#ifdef ARDUINO_ARCH_ESP32 - void transmitAudioData() - { - //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); - - audioSyncPacket transmitData; - memset(reinterpret_cast(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized - - strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - // transmit samples that were not modified by limitSampleDynamics() - transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; - transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; - transmitData.samplePeak = udpSamplePeak ? 1:0; - udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it - - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { - transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); - } - - transmitData.FFT_Magnitude = my_magnitude; - transmitData.FFT_MajorPeak = FFT_MajorPeak; - -#ifndef WLED_DISABLE_ESPNOW - if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { - EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; - //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); - size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); - memcpy(buffer.data, &transmitData, sizeof(transmitData)); - quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); - } -#endif - - if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error - fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); - fftUdp.endPacket(); - } - return; - } // transmitAudioData() - -#endif - - static inline bool isValidUdpSyncVersion(const char *header) { - return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; - } - static inline bool isValidUdpSyncVersion_v1(const char *header) { - return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; - } - - void decodeAudioData(int packetSize, uint8_t *fftBuff) { - audioSyncPacket receivedPacket; - memset(&receivedPacket, 0, sizeof(receivedPacket)); // start clean - memcpy(&receivedPacket, fftBuff, min((unsigned)packetSize, (unsigned)sizeof(receivedPacket))); // don't violate alignment - thanks @willmmiles# - - // update samples for effects - volumeSmth = fmaxf(receivedPacket.sampleSmth, 0.0f); - volumeRaw = fmaxf(receivedPacket.sampleRaw, 0.0f); -#ifdef ARDUINO_ARCH_ESP32 - // update internal samples - sampleRaw = volumeRaw; - sampleAvg = volumeSmth; - rawSampleAgc = volumeRaw; - sampleAgc = volumeSmth; - multAgc = 1.0f; -#endif - // Only change samplePeak IF it's currently false. - // If it's true already, then the animation still needs to respond. - autoResetPeak(); - if (!samplePeak) { - samplePeak = receivedPacket.samplePeak > 0; - if (samplePeak) timeOfPeak = millis(); - } - //These values are only computed by ESP32 - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; - my_magnitude = fmaxf(receivedPacket.FFT_Magnitude, 0.0f); - FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - } - - void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { - audioSyncPacket_v1 *receivedPacket = reinterpret_cast(fftBuff); - // update samples for effects - volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); - volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample -#ifdef ARDUINO_ARCH_ESP32 - // update internal samples - sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); - sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; - sampleAgc = volumeSmth; - rawSampleAgc = volumeRaw; - multAgc = 1.0f; -#endif - // Only change samplePeak IF it's currently false. - // If it's true already, then the animation still needs to respond. - autoResetPeak(); - if (!samplePeak) { - samplePeak = receivedPacket->samplePeak > 0; - if (samplePeak) timeOfPeak = millis(); - } - //These values are only available on the ESP32 - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); - FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - } - - bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. - { - if (!udpSyncConnected) return false; - bool haveFreshData = false; - - size_t packetSize = fftUdp.parsePacket(); -#ifdef ARDUINO_ARCH_ESP32 - if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32 -#endif - if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) { - //DEBUGSR_PRINTLN("Received UDP Sync Packet"); - uint8_t fftBuff[UDPSOUND_MAX_PACKET+1] = { 0 }; // fixed-size buffer for receiving (stack), to avoid heap fragmentation caused by variable sized arrays - fftUdp.read(fftBuff, packetSize); - - // VERIFY THAT THIS IS A COMPATIBLE PACKET - if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { - decodeAudioData(packetSize, fftBuff); - //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2"); - haveFreshData = true; - receivedFormat = 2; - } else { - if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { - decodeAudioData_v1(packetSize, fftBuff); - //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1"); - haveFreshData = true; - receivedFormat = 1; - } else receivedFormat = 0; // unknown format - } - } - return haveFreshData; - } - - - ////////////////////// - // usermod functions// - ////////////////////// - - public: - //Functions called by WLED or other usermods - - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - * It is called *AFTER* readFromConfig() - */ - void setup() override - { - disableSoundProcessing = true; // just to be sure - if (!initDone) { - // usermod exchangeable data - // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers - um_data = new um_data_t; - um_data->u_size = 8; - um_data->u_type = new um_types_t[um_data->u_size]; - um_data->u_data = new void*[um_data->u_size]; - um_data->u_data[0] = &volumeSmth; //*used (New) - um_data->u_type[0] = UMT_FLOAT; - um_data->u_data[1] = &volumeRaw; // used (New) - um_data->u_type[1] = UMT_UINT16; - um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) - um_data->u_type[2] = UMT_BYTE_ARR; - um_data->u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[3] = UMT_BYTE; - um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) - um_data->u_type[4] = UMT_FLOAT; - um_data->u_data[5] = &my_magnitude; // used (New) - um_data->u_type[5] = UMT_FLOAT; - um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[6] = UMT_BYTE; - um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[7] = UMT_BYTE; - } - - -#ifdef ARDUINO_ARCH_ESP32 - - // Reset I2S peripheral for good measure - i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed - #if !defined(CONFIG_IDF_TARGET_ESP32C3) - delay(100); - periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 - #endif - delay(100); // Give that poor microphone some time to setup. - - useBandPassFilter = false; - - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone - #endif - - switch (dmType) { - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) - // stub cases for not-yet-supported I2S modes on other ESP32 chips - case 0: //ADC analog - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) - case 5: //PDM Microphone - #endif - #endif - case 1: - DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); - break; - case 2: - DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); - audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - case 3: - DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); - break; - case 4: - DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - case 5: - DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); - useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); - break; - #endif - case 6: - DEBUGSR_PRINTLN(F("AR: ES8388 Source")); - audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - // ADC over I2S is only possible on "classic" ESP32 - case 0: - default: - DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); - audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog - if (audioSource) audioSource->initialize(audioPin); - break; - #endif - } - delay(250); // give microphone enough time to initialise - - if (!audioSource) enabled = false; // audio failed to initialise -#endif - if (enabled) onUpdateBegin(false); // create FFT task, and initialize network - if (enabled) disableSoundProcessing = false; // all good - enable audio processing -#ifdef ARDUINO_ARCH_ESP32 - if (FFT_Task == nullptr) enabled = false; // FFT task creation failed - if ((!audioSource) || (!audioSource->isInitialized())) { - // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync - #ifdef WLED_DEBUG - DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #else - DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #endif - disableSoundProcessing = true; - } -#endif - if (enabled) connectUDPSoundSync(); - if (enabled && addPalettes) createAudioPalettes(); - initDone = true; - } - - - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - void connected() override - { - if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection - udpSyncConnected = false; - fftUdp.stop(); - } - - if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { - #ifdef ARDUINO_ARCH_ESP32 - udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); - #else - udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); - #endif - } - } - - - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. - */ - void loop() override - { - static unsigned long lastUMRun = millis(); - - if (!enabled) { - disableSoundProcessing = true; // keep processing suspended (FFT task) - lastUMRun = millis(); // update time keeping - return; - } - // We cannot wait indefinitely before processing audio data - if (WS2812FX::isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice - - // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) - if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed - &&( (realtimeMode == REALTIME_MODE_GENERIC) - ||(realtimeMode == REALTIME_MODE_E131) - ||(realtimeMode == REALTIME_MODE_UDP) - ||(realtimeMode == REALTIME_MODE_ADALIGHT) - ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed - { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) - if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); - } - #endif - disableSoundProcessing = true; - } else { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) - if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); - } - #endif - if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping - disableSoundProcessing = false; - } - - if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode - if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode -#ifdef ARDUINO_ARCH_ESP32 - if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source - - - // Only run the sampling code IF we're not in Receive mode or realtime mode - if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { - if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) - - unsigned long t_now = millis(); // remember current time - int userloopDelay = int(t_now - lastUMRun); - if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. - - #ifdef WLED_DEBUG - // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. - // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS - //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); - //} - #endif - - // run filters, and repeat in case of loop delays (hick-up compensation) - if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem - if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs - do { - getSample(); // run microphone sampling filters - agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg - userloopDelay -= 2; // advance "simulated time" by 2ms - } while (userloopDelay > 0); - lastUMRun = t_now; // update time keeping - - // update samples for effects (raw, smooth) - volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; - volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; - // update FFTMagnitude, taking into account AGC amplification - my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects - if (soundAgc) my_magnitude *= multAgc; - if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute - - limitSampleDynamics(); - } // if (!disableSoundProcessing) -#endif - - autoResetPeak(); // auto-reset sample peak after strip minShowDelay - if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected - - connectUDPSoundSync(); // ensure we have a connection - if needed - - // UDP Microphone Sync - receive mode - if ((audioSyncEnabled & 0x02) && udpSyncConnected) { - // Only run the audio listener code if we're in Receive mode - static float syncVolumeSmth = 0; - bool have_new_sample = false; - if (millis() - lastTime > delayMs) { - have_new_sample = receiveAudioData(); - if (have_new_sample) last_UDPTime = millis(); -#ifdef ARDUINO_ARCH_ESP32 - else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. -#endif - lastTime = millis(); - } - if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample - else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter - limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups - } - - #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) - static unsigned long lastMicLoggerTime = 0; - if (millis()-lastMicLoggerTime > 20) { - lastMicLoggerTime = millis(); - logAudio(); - } - #endif - - // Info Page: keep max sample from last 5 seconds -#ifdef ARDUINO_ARCH_ESP32 - if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { - sampleMaxTimer = millis(); - maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing - if (sampleAvg < 1) maxSample5sec = 0; // noise gate - } else { - if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume - } -#else // similar functionality for 8266 receive only - use VolumeSmth instead of raw sample data - if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { - sampleMaxTimer = millis(); - maxSample5sec = (0.15 * maxSample5sec) + 0.85 * volumeSmth; // reset, and start with some smoothing - if (volumeSmth < 1.0f) maxSample5sec = 0; // noise gate - if (maxSample5sec < 0.0f) maxSample5sec = 0; // avoid negative values - } else { - if (volumeSmth >= 1.0f) maxSample5sec = fmaxf(maxSample5sec, volumeRaw); // follow maximum volume - } -#endif - -#ifdef ARDUINO_ARCH_ESP32 - //UDP Microphone Sync - transmit mode - if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { - // Only run the transmit code IF we're in Transmit mode - transmitAudioData(); - lastTime = millis(); - } -#endif - - fillAudioPalettes(); - } - - - bool getUMData(um_data_t **data) override - { - if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit - *data = um_data; - return true; - } - -#ifdef ARDUINO_ARCH_ESP32 - void onUpdateBegin(bool init) override - { -#ifdef WLED_DEBUG - fftTime = sampleTime = 0; -#endif - // gracefully suspend FFT task (if running) - disableSoundProcessing = true; - - // reset sound data - micDataReal = 0.0f; - volumeRaw = 0; volumeSmth = 0.0f; - sampleAgc = 0.0f; sampleAvg = 0.0f; - sampleRaw = 0; rawSampleAgc = 0.0f; - my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; - multAgc = 1.0f; - // reset FFT data - memset(fftCalc, 0, sizeof(fftCalc)); - memset(fftAvg, 0, sizeof(fftAvg)); - memset(fftResult, 0, sizeof(fftResult)); - for(int i=(init?0:1); i don't process audio - updateIsRunning = init; - } -#endif - -#ifdef ARDUINO_ARCH_ESP32 - /** - * handleButton() can be used to override default button behaviour. Returning true - * will prevent button working in a default way. - */ - bool handleButton(uint8_t b) override { - yield(); - // crude way of determining if audio input is analog - // better would be for AudioSource to implement getType() - if (enabled - && dmType == 0 && audioPin>=0 - && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) - ) { - return true; - } - return false; - } - -#endif - //////////////////////////// - // Settings and Info Page // - //////////////////////////// - - /* - * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. - * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. - * Below it is shown how this could be used for e.g. a light sensor - */ - void addToJsonInfo(JsonObject& root) override - { -#ifdef ARDUINO_ARCH_ESP32 - char myStringBuffer[16]; // buffer for snprintf() - not used yet on 8266 -#endif - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - - String uiDomString = F(""); - infoArr.add(uiDomString); - - if (enabled) { -#ifdef ARDUINO_ARCH_ESP32 - // Input Level Slider - if (disableSoundProcessing == false) { // only show slider when audio processing is running - if (soundAgc > 0) { - infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies - } else { - infoArr = user.createNestedArray(F("Audio Input Level")); - } - uiDomString = F("
"); // - infoArr.add(uiDomString); - } -#endif - // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG - - // current Audio input - infoArr = user.createNestedArray(F("Audio Source")); - if (audioSyncEnabled & 0x02) { - // UDP sound sync - receive mode - infoArr.add(F("UDP sound sync")); - if (udpSyncConnected) { - if (millis() - last_UDPTime < 2500) - infoArr.add(F(" - receiving")); - else - infoArr.add(F(" - idle")); - } else { - infoArr.add(F(" - no connection")); - } -#ifndef ARDUINO_ARCH_ESP32 // substitute for 8266 - } else { - infoArr.add(F("sound sync Off")); - } -#else // ESP32 only - } else { - // Analog or I2S digital input - if (audioSource && (audioSource->isInitialized())) { - // audio source successfully configured - if (audioSource->getType() == AudioSource::Type_I2SAdc) { - infoArr.add(F("ADC analog")); - } else { - infoArr.add(F("I2S digital")); - } - // input level or "silence" - if (maxSample5sec > 1.0f) { - float my_usage = 100.0f * (maxSample5sec / 255.0f); - snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); - infoArr.add(myStringBuffer); - } else { - infoArr.add(F(" - quiet")); - } - } else { - // error during audio source setup - infoArr.add(F("not initialized")); - infoArr.add(F(" - check pin settings")); - } - } - - // Sound processing (FFT and input filters) - infoArr = user.createNestedArray(F("Sound Processing")); - if (audioSource && (disableSoundProcessing == false)) { - infoArr.add(F("running")); - } else { - infoArr.add(F("suspended")); - } - - // AGC or manual Gain - if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { - infoArr = user.createNestedArray(F("Manual Gain")); - float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets - infoArr.add(roundf(myGain*100.0f) / 100.0f); - infoArr.add("x"); - } - if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { - infoArr = user.createNestedArray(F("AGC Gain")); - infoArr.add(roundf(multAgc*100.0f) / 100.0f); - infoArr.add("x"); - } -#endif - // UDP Sound Sync status - infoArr = user.createNestedArray(F("UDP Sound Sync")); - if (audioSyncEnabled) { - if (audioSyncEnabled & 0x01) { - infoArr.add(F("send mode")); - if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2")); - } else if (audioSyncEnabled & 0x02) { - infoArr.add(F("receive mode")); - } - } else - infoArr.add("off"); - if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); - if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) { - if (receivedFormat == 1) infoArr.add(F(" v1")); - if (receivedFormat == 2) infoArr.add(F(" v2")); - } - - #if defined(WLED_DEBUG) || defined(SR_DEBUG) - #ifdef ARDUINO_ARCH_ESP32 - infoArr = user.createNestedArray(F("Sampling time")); - infoArr.add(float(sampleTime)/100.0f); - infoArr.add(" ms"); - - infoArr = user.createNestedArray(F("FFT time")); - infoArr.add(float(fftTime)/100.0f); - if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow - infoArr.add("! ms"); - else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability - infoArr.add(" ms!"); - else - infoArr.add(" ms"); - - DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); - DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); - #endif - #endif - } - } - - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject& root) override - { - if (!initDone) return; // prevent crash on boot applyPreset() - JsonObject usermod = root[FPSTR(_name)]; - if (usermod.isNull()) { - usermod = root.createNestedObject(FPSTR(_name)); - } - usermod["on"] = enabled; - } - - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject& root) override - { - if (!initDone) return; // prevent crash on boot applyPreset() - bool prevEnabled = enabled; - JsonObject usermod = root[FPSTR(_name)]; - if (!usermod.isNull()) { - if (usermod[FPSTR(_enabled)].is()) { - enabled = usermod[FPSTR(_enabled)].as(); - if (prevEnabled != enabled) onUpdateBegin(!enabled); - if (addPalettes) { - // add/remove custom/audioreactive palettes - if (prevEnabled && !enabled) removeAudioPalettes(); - if (!prevEnabled && enabled) createAudioPalettes(); - } - } -#ifdef ARDUINO_ARCH_ESP32 - if (usermod[FPSTR(_inputLvl)].is()) { - inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); - } -#endif - } - if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { - // handle removal of custom palettes from JSON call so we don't break things - removeAudioPalettes(); - } - } - - void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && WS2812FX::customPalettes.size()<10) { - // if palettes were removed during JSON call re-add them - createAudioPalettes(); - } - } - - /* - * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will make your settings editable through the Usermod Settings page automatically. - * - * Usermod Settings Overview: - * - Numeric values are treated as floats in the browser. - * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float - * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and - * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. - * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. - * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a - * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. - * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type - * used in the Usermod when reading the value from ArduinoJson. - * - Pin values can be treated differently from an integer value by using the key name "pin" - * - "pin" can contain a single or array of integer values - * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins - * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) - * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used - * - * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings - * - * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. - * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. - * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) override - { - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_addPalettes)] = addPalettes; - -#ifdef ARDUINO_ARCH_ESP32 - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); - amic["pin"] = audioPin; - #endif - - JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); - dmic["type"] = dmType; - JsonArray pinArray = dmic.createNestedArray("pin"); - pinArray.add(i2ssdPin); - pinArray.add(i2swsPin); - pinArray.add(i2sckPin); - pinArray.add(mclkPin); - - JsonObject cfg = top.createNestedObject(FPSTR(_config)); - cfg[F("squelch")] = soundSquelch; - cfg[F("gain")] = sampleGain; - cfg[F("AGC")] = soundAgc; - - JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); - freqScale[F("scale")] = FFTScalingMode; -#endif - - JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); - dynLim[F("limiter")] = limiterOn; - dynLim[F("rise")] = attackTime; - dynLim[F("fall")] = decayTime; - - JsonObject sync = top.createNestedObject("sync"); - sync["port"] = audioSyncPort; - sync["mode"] = audioSyncEnabled; - } - - - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - * - * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) - * - * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present - * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them - * - * This function is guaranteed to be called on boot, but could also be called every time settings are updated - */ - bool readFromConfig(JsonObject& root) override - { - JsonObject top = root[FPSTR(_name)]; - bool configComplete = !top.isNull(); - bool oldEnabled = enabled; - bool oldAddPalettes = addPalettes; - - configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); - configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); - -#ifdef ARDUINO_ARCH_ESP32 - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); - #else - audioPin = -1; // MCU does not support analog mic - #endif - - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) - if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) - if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM - #endif - #endif - - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); - - configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch); - configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); - configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); - - configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); - - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); -#endif - configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); - configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); - - if (initDone) { - // add/remove custom/audioreactive palettes - if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes(); - if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes(); - } // else setup() will create palettes - return configComplete; - } - - - void appendConfigData() override - { -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addOption(dd,'Generic Analog',0);")); - #endif - oappend(SET_F("addOption(dd,'Generic I2S',1);")); - oappend(SET_F("addOption(dd,'ES7243',2);")); - oappend(SET_F("addOption(dd,'SPH0654',3);")); - oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); - #endif - oappend(SET_F("addOption(dd,'ES8388',6);")); - - oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'Normal',1);")); - oappend(SET_F("addOption(dd,'Vivid',2);")); - oappend(SET_F("addOption(dd,'Lazy',3);")); - - oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); - oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); - - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); - oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); - oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); -#endif - - oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); - oappend(SET_F("addOption(dd,'Off',0);")); -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addOption(dd,'Send',1);")); -#endif - oappend(SET_F("addOption(dd,'Receive',2);")); -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); - #else - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); - #endif -#endif - } - - - /* - * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. - * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. - * Commonly used for custom clocks (Cronixie, 7 segment) - */ - //void handleOverlayDraw() override - //{ - //WS2812FX::setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black - //} - - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() override - { - return USERMOD_ID_AUDIOREACTIVE; - } -}; - -void AudioReactive::removeAudioPalettes(void) { - DEBUG_PRINTLN(F("Removing audio palettes.")); - while (palettes>0) { - WS2812FX::customPalettes.pop_back(); - DEBUG_PRINTLN(palettes); - palettes--; - } - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); -} - -void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); - if (palettes) return; - DEBUG_PRINTLN(F("Adding audio palettes.")); - for (int i=0; i= palettes) lastCustPalette -= palettes; - for (int pal=0; palgetStart() >= segStopIdx) continue; if (bus->getStart() + bus->getLength() <= segStartIdx) continue; - //uint8_t type = bus->getType(); if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) @@ -1563,7 +1562,7 @@ uint16_t WS2812FX::getLengthPhysical() const { unsigned len = 0; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + if (bus->isVirtual()) continue; //exclude non-physical network busses len += bus->getLength(); } return len; From 17d59d333710264a6e0772b3e9d29c8a59c1e189 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 09:02:42 +0200 Subject: [PATCH 487/694] adding initialization to vStrip, added comment on padding bytes --- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index 50bcd6624..49277ba11 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -368,6 +368,7 @@ typedef struct Segment { }; uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + //note: here are 3 free bytes of padding char *name; // runtime data diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7159d21c7..e78608a14 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -712,7 +712,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D - int vStrip; + int vStrip = 0; #endif if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call { From 0a5400263be34b6a9b45e7d11acefce584f17614 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 13:52:56 +0200 Subject: [PATCH 488/694] removed IRAM_ATTR from inlined function when the function is inlined into a IRAM_ATTR function, it will also reside in IRAM. Forced inlining is recommended by Espressif if I understand this correctly: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/hardware-abstraction.html --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e78608a14..9fd78e314 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1884,7 +1884,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { return (customMappingSize > 0); } -__attribute__ ((always_inline)) inline uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const { +__attribute__ ((always_inline)) inline uint16_t WS2812FX::getMappedPixelIndex(uint16_t index) const { // convert logical address to physical if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; From bd7cd32f911d85963ec5aba22bdf3420a8770866 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 22 Sep 2024 13:56:14 +0200 Subject: [PATCH 489/694] Add mandatory refresh capability to remove type dependency. --- wled00/bus_manager.cpp | 2 +- wled00/bus_manager.h | 2 ++ wled00/data/settings_leds.htm | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 3766975f1..5b948b9c4 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -830,7 +830,7 @@ static String LEDTypesToJson(const std::vector& types) { String json; for (const auto &type : types) { // capabilities follows similar pattern as JSON API - int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4; + int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4 | Bus::mustRefresh(type.id)<<5; char str[256]; sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\"},"), type.id, capabilities, type.type, type.name); json += str; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 40fe61f40..e96b9de71 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -104,6 +104,7 @@ class Bus { inline bool isPWM() const { return isPWM(_type); } inline bool isVirtual() const { return isVirtual(_type); } inline bool is16bit() const { return is16bit(_type); } + inline bool mustRefresh() const { return mustRefresh(_type); } inline void setReversed(bool reversed) { _reversed = reversed; } inline void setStart(uint16_t start) { _start = start; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } @@ -142,6 +143,7 @@ class Bus { static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } + static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; } static constexpr int numPWMPins(uint8_t type) { return (type - 40); } static inline int16_t getCCT() { return _cct; } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 54ba9d8ba..dd0e8ee8b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -22,6 +22,7 @@ function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type + function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins function S() { getLoc(); @@ -255,7 +256,7 @@ d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1; d.Sf["MA"+n].min = (isVir(t) || isAna(t)) ? 0 : 250; } - gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 + gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory gRGBW |= hasW(t); // RGBW checkbox gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown @@ -457,9 +458,9 @@ mA/LED:

From 37f32ab197dac8399e2f2b302992344071094e7c Mon Sep 17 00:00:00 2001 From: Luis <84397555+LuisFadini@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:56:40 -0300 Subject: [PATCH 534/694] Added BRT timezone --- wled00/data/settings_time.htm | 1 + wled00/ntp.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index 52f79eb7d..df054f417 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -156,6 +156,7 @@ +
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 7b7dac96e..8d44e634e 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -36,8 +36,9 @@ Timezone* tz; #define TZ_ANCHORAGE 20 #define TZ_MX_CENTRAL 21 #define TZ_PAKISTAN 22 +#define TZ_BRASILIA 23 -#define TZ_COUNT 23 +#define TZ_COUNT 24 #define TZ_INIT 255 byte tzCurrent = TZ_INIT; //uninitialized @@ -135,6 +136,10 @@ static const std::pair TZ_TABLE[] PROGMEM = { /* TZ_PAKISTAN */ { {Last, Sun, Mar, 1, 300}, //Pakistan Standard Time = UTC + 5 hours {Last, Sun, Mar, 1, 300} + }, + /* TZ_BRASILIA */ { + {Last, Sun, Mar, 1, -180}, //Brasília Standard Time = UTC - 3 hours + {Last, Sun, Mar, 1, -180} } }; From 49f044ecde8119d03d78fe167f375e71eabf6150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 13 Oct 2024 10:43:56 +0200 Subject: [PATCH 535/694] Better fix for #4154 --- wled00/json.cpp | 19 ++++++++++++------- wled00/presets.cpp | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index c877d1f3b..06eb3015e 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -454,20 +454,25 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) handleSet(nullptr, apireq, false); // may set stateChanged } - // applying preset (2 cases: a) API call includes all preset values ("pd"), b) API only specifies preset ID ("ps")) + // Applying preset from JSON API has 2 cases: a) "pd" AKA "preset direct" and b) "ps" AKA "preset select" + // a) "preset direct" can only be an integer value representing preset ID. "preset direct" assumes JSON API contains the rest of preset content (i.e. from UI call) + // "preset direct" JSON can contain "ps" API (i.e. call from UI to cycle presets) in such case stateChanged has to be false (i.e. no "win" or "seg" API) + // b) "preset select" can be cycling ("1~5~""), random ("r" or "1~5r"), ID, etc. value allowed from JSON API. This type of call assumes no state changing content in API call byte presetToRestore = 0; - // a) already applied preset content (requires "seg" or "win" but will ignore the rest) if (!root[F("pd")].isNull() && stateChanged) { + // a) already applied preset content (requires "seg" or "win" but will ignore the rest) currentPreset = root[F("pd")] | currentPreset; - if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise it was set in handleSet() [set.cpp] + if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise presetCycCurr was set in handleSet() [set.cpp] presetToRestore = currentPreset; // stateUpdated() will clear the preset, so we need to restore it after + DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset); } else if (!root["ps"].isNull()) { - ps = presetCycCurr; - if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) { + // we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call) + if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 0, 0) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) { + DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr); // b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal()) - applyPreset(ps, callMode); // async load from file system (only preset ID was specified) + applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified) return stateResponse; - } + } else presetCycCurr = currentPreset; // restore presetCycCurr } JsonObject playlist = root[F("playlist")]; diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 2749d4677..20edfd91e 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -118,7 +118,7 @@ void initPresetsFile() bool applyPresetFromPlaylist(byte index) { DEBUG_PRINTF_P(PSTR("Request to apply preset: %d\n"), index); - presetToApply = presetCycCurr = index; + presetToApply = index; callModeToApply = CALL_MODE_DIRECT_CHANGE; return true; } @@ -127,7 +127,7 @@ bool applyPreset(byte index, byte callMode) { unloadPlaylist(); // applying a preset unloads the playlist (#3827) DEBUG_PRINTF_P(PSTR("Request to apply preset: %u\n"), index); - presetToApply = presetCycCurr = index; + presetToApply = index; callModeToApply = callMode; return true; } From 01e07ca0bc0157bcb279f7715c33916e39d7e0be Mon Sep 17 00:00:00 2001 From: AlDIY <87589371+dosipod@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:34:18 +0300 Subject: [PATCH 536/694] Update xml.cpp --- wled00/xml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 424842a1d..1ac22c9ce 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -172,7 +172,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) char fpass[l+1]; //fill password field with *** fpass[l] = 0; memset(fpass,'*',l); - settingsScript.printf_P(PSTR("addWiFi(\"%s\",\",%s\",0x%X,0x%X,0x%X);"), + settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",0x%X,0x%X,0x%X);"), multiWiFi[n].clientSSID, fpass, (uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct From a0e81da8c5327e2083d616c5b7d45031b09a27d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 14 Oct 2024 20:13:59 +0200 Subject: [PATCH 537/694] WLED 0.15.0-b6 release (#4180) * modified Improv chip & version handling * Update build and changelog --- CHANGELOG.md | 17 +++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- wled00/improv.cpp | 30 ++++++++++++++---------------- wled00/wled.h | 4 ++-- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e377418e3..4dad83d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,26 @@ ## WLED changelog +#### Build 2410140 +- WLED 0.15.0-b6 release +- Added BRT timezone (#4188 by @LuisFadini) +- Fixed the positioning of the "Download the latest binary" button (#4184 by @maxi4329) +- Add WLED_AUTOSEGMENTS compile flag (#4183 by @PaoloTK) +- New 512kB FS parition map for 4MB devices +- Internal API change: Static PinManager & UsermodManager +- Change in Improv chip ID and version generation +- Various optimisations, bugfixes and enhancements (#4005, #4174 & #4175 by @Xevel, #4180, #4168, #4154, #4189 by @dosipod) + +#### Build 2409170 +- UI: Introduce common.js in settings pages (size optimisation) +- Add the ability to toggle the reception of palette synchronizations (#4137 by @felddy) +- Usermod/FX: Temperature usermod added Temperature effect (example usermod effect by @blazoncek) +- Fix AsyncWebServer version pin + #### Build 2409140 - Configure different kinds of busses at compile (#4107 by @PaoloTK) - BREAKING: removes LEDPIN and DEFAULT_LED_TYPE compile overrides - Fetch LED types from Bus classes (dynamic UI) (#4129 by @netmindz, @blazoncek, @dedehai) +- Temperature usermod: update OneWire to 2.3.8 (#4131 by @iammattcoleman) #### Build 2409100 - WLED 0.15.0-b5 release diff --git a/package-lock.json b/package-lock.json index 415d88151..85ee1df0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b5", + "version": "0.15.0-b6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b5", + "version": "0.15.0-b6", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index 721455bff..d76d87687 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b5", + "version": "0.15.0-b6", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 31547f86c..197148b2b 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -194,24 +194,22 @@ void sendImprovIPRPCResult(ImprovRPCType type) { } void sendImprovInfoResponse() { - const char* bString = - #ifdef ESP8266 - "esp8266" - #elif CONFIG_IDF_TARGET_ESP32C3 - "esp32-c3" - #elif CONFIG_IDF_TARGET_ESP32S2 - "esp32-s2" - #elif CONFIG_IDF_TARGET_ESP32S3 - "esp32-s3"; - #else // ESP32 - "esp32"; - #endif - ; - + char bString[32]; + #ifdef ESP8266 + strcpy(bString, "esp8266"); + #else // ESP32 + strncpy(bString, ESP.getChipModel(), 31); + #if CONFIG_IDF_TARGET_ESP32 + bString[5] = '\0'; // disregard chip revision for classic ESP32 + #else + bString[31] = '\0'; // just in case + #endif + strlwr(bString); + #endif //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); - char vString[20]; - sprintf_P(vString, PSTR("0.15.0-b5/%i"), VERSION); + char vString[32]; + sprintf_P(vString, PSTR("%s/%i"), versionString, VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index 173bd6554..bc525cd6f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-b5 + @version 0.15.0-b6 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2409170 +#define VERSION 2410140 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 44e28f96e0af0c78cb1b902a45b6332dcacd10e0 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 15 Oct 2024 13:16:18 +0200 Subject: [PATCH 538/694] Fix for Octopus on ESP32 C3 Apparently the C3 can not convert negative floats to uint8_t directly, casting it into an int first fixes it. --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e7429d19f..c6c8222be 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7749,7 +7749,7 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2f((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI + rMap[XY(x, y)].angle = int(40.7436f * atan2f((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu } } From 5e29f2c1b7844492c5ccaf194f524fe810947c3f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 15 Oct 2024 20:11:33 +0200 Subject: [PATCH 539/694] fixed atan2_t approximation was incorrect, now doing it right. also removed hypotf() from octopus, saving a little flash. --- wled00/FX.cpp | 6 ++++-- wled00/wled_math.cpp | 39 +++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 338dd2df9..5b4513dc7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7749,8 +7749,10 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2_t((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI - rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu + rMap[XY(x, y)].angle = int(40.7436f * atan2_t((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI + int dx = (x - C_X); + int dy = (y - C_Y); + rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu } } } diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 080cc9425..1825ab198 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -88,8 +88,8 @@ uint8_t cos8_t(uint8_t theta) { float sin_approx(float theta) { theta = modd(theta, TWO_PI); // modulo: bring to -2pi to 2pi range - if(theta < 0) theta += M_TWOPI; // 0-2pi range - uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / M_TWOPI)); + if(theta < 0) theta += TWO_PI; // 0-2pi range + uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / TWO_PI)); int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; @@ -110,28 +110,23 @@ float tan_approx(float x) { #define ATAN2_CONST_A 0.1963f #define ATAN2_CONST_B 0.9817f -// fast atan2() approximation source: public domain +// atan2_t approximation, with the idea from https://gist.github.com/volkansalma/2972237?permalink_comment_id=3872525#gistcomment-3872525 float atan2_t(float y, float x) { - if (x == 0.0f) return (y > 0.0f) ? M_PI_2 : (y < 0.0f) ? -M_PI_2 : 0.0f; - - float abs_y = (y < 0.0f) ? -y : y + 1e-10f; // make sure y is not zero to prevent division by 0 - float z = abs_y / x; - float atan_approx; - - if (z < 1.0f) { - atan_approx = z / (1.0f + ATAN2_CONST_A * z * z); - if (x < 0.0f) { - return (y >= 0.0f) ? atan_approx + PI : atan_approx - PI; - } + float abs_y = fabs(y); + float abs_x = fabs(x); + float r = (abs_x - abs_y) / (abs_y + abs_x + 1e-10f); // avoid division by zero by adding a small nubmer + float angle; + if(x < 0) { + r = -r; + angle = M_PI/2.0f + M_PI/4.f; } - else { - z = x / abs_y; - atan_approx = M_PI_2 - z / (1.0f + ATAN2_CONST_A * z * z); - if (y < 0.0f) { - return -atan_approx; - } - } - return atan_approx; + else + angle = M_PI/2.0f - M_PI/4.f; + + float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r; + angle += add; + angle = y < 0 ? -angle : angle; + return angle; } //https://stackoverflow.com/questions/3380628 From e9d2182390d43d7dd25492f6555d082280e79b3b Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Wed, 16 Oct 2024 00:07:19 +0200 Subject: [PATCH 540/694] Re-license the WLED project from MIT to EUPL (#4194) --- LICENSE | 307 +++++++++++++++++++++++++++++++++-- readme.md | 4 +- wled00/FX.cpp | 20 +-- wled00/FX.h | 20 +-- wled00/FX_2Dfcn.cpp | 19 +-- wled00/FX_fcn.cpp | 20 +-- wled00/data/settings_sec.htm | 2 +- 7 files changed, 304 insertions(+), 88 deletions(-) diff --git a/LICENSE b/LICENSE index 69325d21c..cca21c008 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,294 @@ -MIT License +Copyright (c) 2016-present Christian Schwinne and individual WLED contributors +Licensed under the EUPL v. 1.2 or later -Copyright (c) 2016 Christian Schwinne + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This European Union Public Licence (the ‘EUPL’) applies to the Work (as +defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This + Licence does not define the extent of modification or dependence on the + Original Work required in order to classify a work as a Derivative Work; + this extent is determined by copyright law applicable in the country + mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which + is meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under + the Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright +vested in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case + may be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make +effective the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights +to any patents held by the Licensor, to the extent necessary to make use of +the rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, +in a notice following the copyright notice attached to the Work, a repository +where the Source Code is easily and freely accessible for as long as the +Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits +from any exception or limitation to the exclusive rights of the rights owners +in the Work, of the exhaustion of those rights or of other applicable +limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and +a copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of +the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions +on the Work or Derivative Work that alter or restrict the terms of the +Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed +under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, +‘Compatible Licence’ refers to the licences listed in the appendix attached to +this Licence. Should the Licensee's obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade +names, trademarks, service marks, or names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she +brings to the Work are owned by him/her or licensed to him/her and that he/she +has the power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ +basis and without warranties of any kind concerning the Work, including +without limitation merchantability, fitness for a particular purpose, absence +of defects or errors, accuracy, non-infringement of intellectual property +rights other than copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a +condition for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the +use of the Work, including without limitation, damages for loss of goodwill, +work stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional +agreement, defining obligations or services consistent with this Licence. +However, if accepting obligations, You may act only on your own behalf and on +your sole responsibility, not on behalf of the original Licensor or any other +Contributor, and only if You agree to indemnify, defend, and hold each +Contributor harmless for any liability incurred by, or claims asserted against +such Contributor by the fact You have accepted any warranty or additional +liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I +agree’ placed under the bottom of a window displaying the text of this Licence +or by affirming consent in any other similar way, in accordance with the rules +of applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this +Licence, such as the use of the Work, the creation by You of a Derivative Work +or the Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions +of this Licence or updated versions of the Appendix, so far this is required +and reasonable, without reducing the scope of the rights granted by the +Licence. New versions of the Licence will be published with a unique version +number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty + on the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive + jurisdiction of the competent court where the Licensor resides or conducts + its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the +above licences without producing a new version of the EUPL, as long as they +provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a +new EUPL version. \ No newline at end of file diff --git a/readme.md b/readme.md index 11c1733f8..80256560a 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,7 @@ See [here](https://kno.wled.ge/basics/compatible-hardware)! ## ✌️ Other -Licensed under the MIT license +Licensed under the EUPL v1.2 license Credits [here](https://kno.wled.ge/about/contributors/)! Join the Discord server to discuss everything about WLED! @@ -80,5 +80,5 @@ If WLED really brightens up your day, you can [![](https://img.shields.io/badge/ If you are prone to photosensitive epilepsy, we recommended you do **not** use this software. If you still want to try, don't use strobe, lighting or noise modes or high effect speed settings. -As per the MIT license, I assume no liability for any damage to you or any other person or equipment. +As per the EUPL license, I assume no liability for any damage to you or any other person or equipment. diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c6c8222be..d4b83de6c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2,24 +2,10 @@ WS2812FX.cpp contains all effect methods Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified heavily for WLED */ diff --git a/wled00/FX.h b/wled00/FX.h index f16f07924..385c52476 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -2,24 +2,10 @@ WS2812FX.h - Library for WS2812 LED effects. Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified for WLED */ diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 0f6690549..e38602ebc 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -1,24 +1,9 @@ /* FX_2Dfcn.cpp contains all 2D utility functions - LICENSE - The MIT License (MIT) Copyright (c) 2022 Blaz Kristan (https://blaz.at/home) - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Parts of the code adapted from WLED Sound Reactive */ diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1bbfa365b..79189ef57 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -2,24 +2,10 @@ WS2812FX_fcn.cpp contains all utility functions Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified heavily for WLED */ diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index ce9bd8aa3..fa75882c0 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -72,7 +72,7 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the MIT license

+ Licensed under the EUPL v1.2 license

Server message: Response error!
From 98a6907976bfa3142545467359e876bd6d0ddc22 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 16 Oct 2024 19:53:58 +0200 Subject: [PATCH 541/694] cleanup and improvement to sin_appros() -replaced all PI references with M_PI version -there is no need to do the angle-modulo in float, casting it to an integer does the same BUT it has to be cast to an `int` first, see comment. --- wled00/wled_math.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 1825ab198..a191968e1 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -15,17 +15,17 @@ // Note: cos_t, sin_t and tan_t are very accurate but may be slow // the math.h functions use several kB of flash and are to be avoided if possible // sin16_t / cos16_t are faster and much more accurate than the fastled variants -// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy of +/-0.0015 compared to sinf() +// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf() // sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate float cos_t(float phi) { - float x = modd(phi, TWO_PI); + float x = modd(phi, M_TWOPI); if (x < 0) x = -1 * x; int8_t sign = 1; - if (x > PI) + if (x > M_PI) { - x -= PI; + x -= M_PI; sign = -1; } float xx = x * x; @@ -38,7 +38,7 @@ float cos_t(float phi) } float sin_t(float phi) { - float res = cos_t(HALF_PI - phi); + float res = cos_t(M_PI_2 - phi); #ifdef WLED_DEBUG_MATH Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x)); #endif @@ -87,9 +87,7 @@ uint8_t cos8_t(uint8_t theta) { float sin_approx(float theta) { - theta = modd(theta, TWO_PI); // modulo: bring to -2pi to 2pi range - if(theta < 0) theta += TWO_PI; // 0-2pi range - uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / TWO_PI)); + uint16_t scaled_theta = (int)(theta * (0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; @@ -118,10 +116,10 @@ float atan2_t(float y, float x) { float angle; if(x < 0) { r = -r; - angle = M_PI/2.0f + M_PI/4.f; + angle = M_PI_2 + M_PI_4; } else - angle = M_PI/2.0f - M_PI/4.f; + angle = M_PI_2 - M_PI_4; float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r; angle += add; @@ -140,10 +138,10 @@ float acos_t(float x) { ret = ret * xabs; ret = ret - 0.2121144f; ret = ret * xabs; - ret = ret + HALF_PI; + ret = ret + M_PI_2; ret = ret * sqrt(1.0f-xabs); ret = ret - 2 * negate * ret; - float res = negate * PI + ret; + float res = negate * M_PI + ret; #ifdef WLED_DEBUG_MATH Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x)); #endif @@ -151,7 +149,7 @@ float acos_t(float x) { } float asin_t(float x) { - float res = HALF_PI - acos_t(x); + float res = M_PI_2 - acos_t(x); #ifdef WLED_DEBUG_MATH Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x)); #endif @@ -167,7 +165,7 @@ float atan_t(float x) { //For A/B/C, see https://stackoverflow.com/a/42542593 static const double A { 0.0776509570923569 }; static const double B { -0.287434475393028 }; - static const double C { ((HALF_PI/2) - A - B) }; + static const double C { ((M_PI_4) - A - B) }; // polynominal factors for approximation between 1 and 5 static const float C0 { 0.089494f }; static const float C1 { 0.974207f }; @@ -182,7 +180,7 @@ float atan_t(float x) { x = std::abs(x); float res; if (x > 5.0f) { // atan(x) converges to pi/2 - (1/x) for large values - res = HALF_PI - (1.0f/x); + res = M_PI_2 - (1.0f/x); } else if (x > 1.0f) { //1 < x < 5 float xx = x * x; res = (C4*xx*xx)+(C3*xx*x)+(C2*xx)+(C1*x)+C0; From bd68b977d512e28b072cfeb1d3c146e4af221e31 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Thu, 17 Oct 2024 18:07:01 +0200 Subject: [PATCH 542/694] minor webui enhancements --- wled00/data/index.css | 20 +++++++++++++++++--- wled00/data/index.htm | 2 +- wled00/data/settings_2D.htm | 12 ++++++------ wled00/data/settings_leds.htm | 8 ++++---- wled00/data/settings_pin.htm | 11 ++++++++++- wled00/data/settings_sec.htm | 4 ++-- wled00/data/settings_sync.htm | 10 +++++----- wled00/data/settings_time.htm | 6 +++--- wled00/data/settings_um.htm | 10 +++++----- wled00/data/settings_wifi.htm | 8 ++++---- wled00/data/update.htm | 2 +- 11 files changed, 58 insertions(+), 35 deletions(-) diff --git a/wled00/data/index.css b/wled00/data/index.css index 6f465e407..2ae17ffe5 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -97,6 +97,7 @@ button { .labels { margin: 0; padding: 8px 0 2px 0; + font-size: 19px; } #namelabel { @@ -890,12 +891,12 @@ a.btn { line-height: 28px; } -/* Quick color select Black button (has white border) */ -.qcsb { +/* Quick color select Black and White button (has white/black border, depending on the theme) */ +.qcsb, .qcsw { width: 26px; height: 26px; line-height: 26px; - border: 1px solid #fff; + border: 1px solid var(--c-f); } /* Hex color input wrapper div */ @@ -1299,6 +1300,14 @@ TD .checkmark, TD .radiomark { width: 100%; } +#segutil { + margin-bottom: 12px; +} + +#segcont > div:first-child, #fxFind { + margin-top: 4px; +} + /* Simplify segments */ .simplified #segcont .lstI { margin-top: 4px; @@ -1433,6 +1442,11 @@ dialog { position: relative; } +.presin { + width: 100%; + box-sizing: border-box; +} + .btn-s, .btn-n { border: 1px solid var(--c-2); diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e74ea0076..8adec791f 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -106,7 +106,7 @@
-
+

diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index a70ca76be..2aec03682 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -54,8 +54,8 @@ Orientation:
Serpentine:
Dimensions (WxH): x
-Offset X: -Y:
(offset from top-left corner in # LEDs) +Offset X: +Y:
(offset from top-left corner in # LEDs)
`; p.insertAdjacentHTML("beforeend", b); } @@ -246,7 +246,7 @@ Y:

2D setup

- Strip or panel: + Strip or panel:  Panel dimensions (WxH): x
Horizontal panels: Vertical panels:
- 1st panel:
- Orientation:
@@ -286,7 +286,7 @@ Y:
Gap file:
- Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
+ Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..0a28fda3f 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -747,7 +747,7 @@ Swap:

- LED memory usage: 0 / ? B
+ LED memory usage: 0 / ? B

Advanced

- Palette wrapping: + Palette wrapping:  +
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..6cc03e9eb 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -72,8 +72,8 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the EUPL v1.2 license

- Server message: Response error!
+ Licensed under the EUPL v1.2 license

+ Server message:  Response error!
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 34b9fc6cd..754605bf0 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -118,7 +118,7 @@ Receive UDP realtime:
Use main segment only:
Respect LED Maps:

Network DMX input
-Type: +Type: 
Multicast:
Start universe:
-Reboot required. Check out LedFx!
+Reboot required. Check out LedFx!
Skip out-of-sequence packets:
DMX start address:
DMX segment spacing:
E1.31 port priority:
-DMX mode: +DMX mode: 
Group Topic:
Publish on button press:
Retain brightness & color messages:
-Reboot required to apply changes. MQTT info +Reboot required to apply changes. MQTT info

Philips Hue

@@ -209,7 +209,7 @@ Hue status: Disabled in this build This firmware build does not support Serial interface.
-Baud rate: +Baud rate: 

Use 24h format:
- Time zone: + Time zone: 
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
- Latitude:
- Longitude:
+ Latitude: 
+ Longitude: 
(opens new tab, only works in browser)
diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index c2f0ffbf2..9c53d560d 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -283,14 +283,14 @@

Usermod Setup

Global I2C GPIOs (HW)
(change requires reboot!)
- SDA: - SCL: + SDA:  + SCL: 
Global SPI GPIOs (HW)
(only changable on ESP32, change requires reboot!)
- MOSI: - MISO: - SCLK: + MOSI:  + MISO:  + SCLK: 
Reboot after save?
Loading settings...
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 30b6600ae..3b840f509 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -166,7 +166,7 @@ Static subnet mask:
Hide AP name:
AP password (leave empty for open):

Access Point WiFi channel:
- AP opens: + AP opens: 
- AP IP: Not active
+ AP IP:  Not active

Experimental

Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
Can help with connectivity issues and Audioreactive sync.
Disabling WiFi sleep increases power consumption.

-
TX power: @@ -205,7 +205,7 @@ Static subnet mask:
Listen for events over ESP-NOW
Keep disabled if not using a remote or wireless sync, increases power consumption.
Paired Remote MAC:
- Last device seen: None
+ Last device seen: None
diff --git a/wled00/data/update.htm b/wled00/data/update.htm index b68645a52..791a76f6b 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -16,7 +16,7 @@

WLED Software Update

- Installed version: ##VERSION##
+ Installed version: ##VERSION##
Download the latest binary: 
From caa997fff1750bd08e7d815674c3ff7728a14029 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Fri, 18 Oct 2024 18:43:41 +0200 Subject: [PATCH 543/694] removed onkeydown tried to find a replacement for the nbsp --- package-lock.json | 2 +- wled00/data/settings_2D.htm | 8 ++++---- wled00/data/settings_leds.htm | 8 ++++---- wled00/data/settings_pin.htm | 11 +---------- wled00/data/settings_sec.htm | 4 ++-- wled00/data/settings_sync.htm | 10 +++++----- wled00/data/settings_time.htm | 6 +++--- wled00/data/settings_um.htm | 10 +++++----- wled00/data/settings_wifi.htm | 8 ++++---- wled00/data/style.css | 3 +++ wled00/data/update.htm | 2 +- 11 files changed, 33 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85ee1df0f..e85857017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2" + "nodemon": "^3.1.7" } }, "node_modules/@jridgewell/gen-mapping": { diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index 2aec03682..e372b3e21 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -246,7 +246,7 @@ Y: Back

2D setup

- Strip or panel:  + Strip or panel: x
Horizontal panels: Vertical panels:
- 1st panel: 
- Orientation: 
@@ -286,7 +286,7 @@ Y:
Gap file:
- Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
+ Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 0a28fda3f..6be5becd1 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -747,7 +747,7 @@ Swap:

- LED memory usage: 0 / ? B
+ LED memory usage: 0 / ? B

Advanced

- Palette wrapping:  + Palette wrapping: +
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 6cc03e9eb..fa75882c0 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -72,8 +72,8 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the EUPL v1.2 license

- Server message:  Response error!
+ Licensed under the EUPL v1.2 license

+ Server message: Response error!
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 754605bf0..34b9fc6cd 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -118,7 +118,7 @@ Receive UDP realtime:
Use main segment only:
Respect LED Maps:

Network DMX input
-Type:  +Type:
Multicast:
Start universe:
-Reboot required. Check out LedFx!
+Reboot required. Check out LedFx!
Skip out-of-sequence packets:
DMX start address:
DMX segment spacing:
E1.31 port priority:
-DMX mode:  +DMX mode:
Group Topic:
Publish on button press:
Retain brightness & color messages:
-Reboot required to apply changes. MQTT info +Reboot required to apply changes. MQTT info

Philips Hue

@@ -209,7 +209,7 @@ Hue status: Disabled in this build This firmware build does not support Serial interface.
-Baud rate:  +Baud rate:

Use 24h format:
- Time zone:  + Time zone:
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
- Latitude: 
- Longitude: 
+ Latitude:
+ Longitude:
(opens new tab, only works in browser)
diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index 9c53d560d..c2f0ffbf2 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -283,14 +283,14 @@

Usermod Setup

Global I2C GPIOs (HW)
(change requires reboot!)
- SDA:  - SCL:  + SDA: + SCL:
Global SPI GPIOs (HW)
(only changable on ESP32, change requires reboot!)
- MOSI:  - MISO:  - SCLK:  + MOSI: + MISO: + SCLK:
Reboot after save?
Loading settings...
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 3b840f509..30b6600ae 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -166,7 +166,7 @@ Static subnet mask:
Hide AP name:
AP password (leave empty for open):

Access Point WiFi channel:
- AP opens:  + AP opens:
- AP IP:  Not active
+ AP IP: Not active

Experimental

Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
Can help with connectivity issues and Audioreactive sync.
Disabling WiFi sleep increases power consumption.

-
TX power:  @@ -205,7 +205,7 @@ Static subnet mask:
Listen for events over ESP-NOW
Keep disabled if not using a remote or wireless sync, increases power consumption.
Paired Remote MAC:
- Last device seen: None
+ Last device seen: None
diff --git a/wled00/data/style.css b/wled00/data/style.css index b6cb0f9e6..42e49d304 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -44,6 +44,9 @@ button.sml { min-width: 40px; margin: 0 0 0 10px; } +span:before, b:before, b:after, i:after{ + content: "\00A0"; +} #scan { margin-top: -10px; } diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 791a76f6b..b68645a52 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -16,7 +16,7 @@

WLED Software Update

- Installed version: ##VERSION##
+ Installed version: ##VERSION##
Download the latest binary: 
From 4fa8a3898a8771c6680bf84eaa5ab5101dd82b5b Mon Sep 17 00:00:00 2001 From: 1Prototype1 Date: Sat, 19 Oct 2024 00:30:24 +0530 Subject: [PATCH 544/694] Added Distribute for cpal Added a button to distribute the color markers in palette equally --- wled00/data/cpal/cpal.htm | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b58c0987a..8fa715bc8 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -167,9 +167,10 @@
-
- Currently in use custom palettes -
+
+
+ Currently in use custom palettes +
@@ -187,7 +188,7 @@ Available static palettes
- + @@ -204,6 +205,13 @@ var paletteName = []; // Holds the names of the palettes after load. var svgSave = '' var svgEdit = '' + var svgDist = '' + var svgTrash = '' + + const distDiv = gId("distDiv"); + distDiv.addEventListener('click', distribute); + distDiv.setAttribute('title', 'Distribute colors equally'); + distDiv.innerHTML = svgDist; function recOf() { rect = gradientBox.getBoundingClientRect(); @@ -433,7 +441,7 @@ renderY = e.srcElement.getBoundingClientRect().y + 13; trash.id = "trash"; - trash.innerHTML = ''; + trash.innerHTML = svgTrash; trash.style.position = "absolute"; trash.style.left = (renderX) + "px"; trash.style.top = (renderY) + "px"; @@ -712,9 +720,27 @@ } } + function distribute() { + let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')]; + colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos')); + colorMarkers = colorMarkers.slice(1, -1); + const spacing = Math.round(256 / (colorMarkers.length + 1)); + + colorMarkers.forEach((e, i) => { + const markerId = e.id.match(/\d+/)[0]; + const trueCol = e.getAttribute("data-truecol"); + gradientBox.removeChild(e); + gradientBox.removeChild(gId(`colorPicker${markerId}`)); + gradientBox.removeChild(gId(`colorPickerMarker${markerId}`)); + gradientBox.removeChild(gId(`deleteMarker${markerId}`)); + addC(spacing * (i + 1), trueCol); + }); + } + function rgbToHex(r, g, b) { const hex = ((r << 16) | (g << 8) | b).toString(16); return "#" + "0".repeat(6 - hex.length) + hex; } + From 0a97e28aab766ada3a7cf0fc2c9eb63a5f6afdf3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 19 Oct 2024 18:43:10 +0200 Subject: [PATCH 545/694] bugfix: prevent preset loading from resetting other errors without this fix, any not-yet reported error - like filesystem problems at startup, or out-of-memory - was rest by successfully loading a preset. --- wled00/presets.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 20edfd91e..04474113d 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -143,6 +143,7 @@ void applyPresetWithFallback(uint8_t index, uint8_t callMode, uint8_t effectID, void handlePresets() { + byte presetErrFlag = ERR_NONE; if (presetToSave) { strip.suspend(); doSaveState(); @@ -166,14 +167,16 @@ void handlePresets() #ifdef ARDUINO_ARCH_ESP32 if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { deserializeJson(*pDoc,tmpRAMbuffer); - errorFlag = ERR_NONE; } else #endif { - errorFlag = readObjectFromFileUsingId(getPresetsFileName(tmpPreset < 255), tmpPreset, pDoc) ? ERR_NONE : ERR_FS_PLOAD; + presetErrFlag = readObjectFromFileUsingId(getPresetsFileName(tmpPreset < 255), tmpPreset, pDoc) ? ERR_NONE : ERR_FS_PLOAD; } fdo = pDoc->as(); + // only reset errorflag if previous error was preset-related + if ((errorFlag == ERR_NONE) || (errorFlag == ERR_FS_PLOAD)) errorFlag = presetErrFlag; + //HTTP API commands const char* httpwin = fdo["win"]; if (httpwin) { From 95b4bde918c00c2d3191a0f71a391965d69b3d34 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:42:02 -0400 Subject: [PATCH 546/694] UsermodManager: Make into namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/fcn_declare.h | 47 ++++++++++++++++++++----------------------- wled00/um_manager.cpp | 5 +++-- wled00/wled.h | 3 --- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 71b00599c..78655b271 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -326,36 +326,33 @@ class Usermod { template static inline void oappend(const T& t) { oappend_shim->print(t); }; }; -class UsermodManager { - private: - static Usermod* ums[WLED_MAX_USERMODS]; - static byte numMods; +namespace UsermodManager { + extern byte numMods; - public: - static void loop(); - static void handleOverlayDraw(); - static bool handleButton(uint8_t b); - static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods - static void setup(); - static void connected(); - static void appendConfigData(Print&); - static void addToJsonState(JsonObject& obj); - static void addToJsonInfo(JsonObject& obj); - static void readFromJsonState(JsonObject& obj); - static void addToConfig(JsonObject& obj); - static bool readFromConfig(JsonObject& obj); + void loop(); + void handleOverlayDraw(); + bool handleButton(uint8_t b); + bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods + void setup(); + void connected(); + void appendConfigData(Print&); + void addToJsonState(JsonObject& obj); + void addToJsonInfo(JsonObject& obj); + void readFromJsonState(JsonObject& obj); + void addToConfig(JsonObject& obj); + bool readFromConfig(JsonObject& obj); #ifndef WLED_DISABLE_MQTT - static void onMqttConnect(bool sessionPresent); - static bool onMqttMessage(char* topic, char* payload); + void onMqttConnect(bool sessionPresent); + bool onMqttMessage(char* topic, char* payload); #endif #ifndef WLED_DISABLE_ESPNOW - static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); + bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif - static void onUpdateBegin(bool); - static void onStateChange(uint8_t); - static bool add(Usermod* um); - static Usermod* lookup(uint16_t mod_id); - static inline byte getModCount() {return numMods;}; + void onUpdateBegin(bool); + void onStateChange(uint8_t); + bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); + inline byte getModCount() {return numMods;}; }; //usermods_list.cpp diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 46fdf5b3b..1fdb6d688 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -3,6 +3,9 @@ * Registration and management utility for v2 usermods */ +static Usermod* ums[WLED_MAX_USERMODS] = {nullptr}; +byte UsermodManager::numMods = 0; + //Usermod Manager internals void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); } void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } @@ -69,8 +72,6 @@ bool UsermodManager::add(Usermod* um) return true; } -Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr}; -byte UsermodManager::numMods = 0; /* Usermod v2 interface shim for oappend */ Print* Usermod::oappend_shim = nullptr; diff --git a/wled00/wled.h b/wled00/wled.h index bc525cd6f..5f1952bec 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -896,9 +896,6 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps #endif -// Usermod manager -WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); - // global I2C SDA pin (used for usermods) #ifndef I2CSDAPIN WLED_GLOBAL int8_t i2c_sda _INIT(-1); From 32eee3365ac06858b5d6821931c39c020af233bc Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:48:31 -0400 Subject: [PATCH 547/694] PinManager: Make in to namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/pin_manager.cpp | 20 +++++------ wled00/pin_manager.h | 78 +++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 793b5440c..14209977a 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -13,6 +13,16 @@ #endif #endif +// Pin management state variables +#ifdef ESP8266 +static uint32_t pinAlloc = 0UL; // 1 bit per pin, we use first 17bits +#else +static uint64_t pinAlloc = 0ULL; // 1 bit per pin, we use 50 bits on ESP32-S3 +static uint16_t ledcAlloc = 0; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) +#endif +static uint8_t i2cAllocCount = 0; // allow multiple allocation of I2C bus pins but keep track of allocations +static uint8_t spiAllocCount = 0; // allow multiple allocation of SPI bus pins but keep track of allocations +static PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; /// Actual allocation/deallocation routines bool PinManager::deallocatePin(byte gpio, PinOwner tag) @@ -273,13 +283,3 @@ void PinManager::deallocateLedc(byte pos, byte channels) } } #endif - -#ifdef ESP8266 -uint32_t PinManager::pinAlloc = 0UL; -#else -uint64_t PinManager::pinAlloc = 0ULL; -uint16_t PinManager::ledcAlloc = 0; -#endif -uint8_t PinManager::i2cAllocCount = 0; -uint8_t PinManager::spiAllocCount = 0; -PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None }; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 73a4a3656..c8fb165ce 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -9,6 +9,12 @@ #endif #include "const.h" // for USERMOD_* values +#ifdef ESP8266 +#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) +#else +#define WLED_NUM_PINS (GPIO_PIN_COUNT) +#endif + typedef struct PinManagerPinType { int8_t pin; bool isOutput; @@ -70,53 +76,39 @@ enum struct PinOwner : uint8_t { }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); -class PinManager { - private: - #ifdef ESP8266 - #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) - static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits - #else - #define WLED_NUM_PINS (GPIO_PIN_COUNT) - static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3 - static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) - #endif - static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations - static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations - static PinOwner ownerTag[WLED_NUM_PINS]; +namespace PinManager { + // De-allocates a single pin + bool deallocatePin(byte gpio, PinOwner tag); + // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) + bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); + bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); + // Allocates a single pin, with an owner tag. + // De-allocation requires the same owner tag (or override) + bool allocatePin(byte gpio, bool output, PinOwner tag); + // Allocates all the pins, or allocates none of the pins, with owner tag. + // Provided to simplify error condition handling in clients + // using more than one pin, such as I2C, SPI, rotary encoders, + // ethernet, etc.. + bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - public: - // De-allocates a single pin - static bool deallocatePin(byte gpio, PinOwner tag); - // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) - static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); - static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); - // Allocates a single pin, with an owner tag. - // De-allocation requires the same owner tag (or override) - static bool allocatePin(byte gpio, bool output, PinOwner tag); - // Allocates all the pins, or allocates none of the pins, with owner tag. - // Provided to simplify error condition handling in clients - // using more than one pin, such as I2C, SPI, rotary encoders, - // ethernet, etc.. - static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); + [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] + inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } + [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] + inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } - [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } + // will return true for reserved pins + bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + // will return false for reserved pins + bool isPinOk(byte gpio, bool output = true); + + bool isReadOnlyPin(byte gpio); - // will return true for reserved pins - static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); - // will return false for reserved pins - static bool isPinOk(byte gpio, bool output = true); - - static bool isReadOnlyPin(byte gpio); + PinOwner getPinOwner(byte gpio); - static PinOwner getPinOwner(byte gpio); - - #ifdef ARDUINO_ARCH_ESP32 - static byte allocateLedc(byte channels); - static void deallocateLedc(byte pos, byte channels); - #endif + #ifdef ARDUINO_ARCH_ESP32 + byte allocateLedc(byte channels); + void deallocateLedc(byte pos, byte channels); + #endif }; //extern PinManager pinManager; From 7db198909398feeab431031c0ae3f21e47266bd3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:42:48 +0200 Subject: [PATCH 548/694] fix major performance regression in ArduinoFFT since v2.0.0, we cannot override the internal sqrt function by #define --> moved to build_flags. Average FFT time on esp32 : 4.5ms --> 1.8ms --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 3005ba220..b130b687c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -259,7 +259,8 @@ lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive -AR_build_flags = -D USERMOD_AUDIOREACTIVE +AR_build_flags = -D USERMOD_AUDIOREACTIVE + -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) AR_lib_deps = kosme/arduinoFFT @ 2.0.1 [esp32_idf_V4] From 01d43c69fb5d9fa616bc276039fc00c33742bad0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:45:32 +0200 Subject: [PATCH 549/694] AR memory optimization - part 1 allocating FFT buffers late makes up to 16Kb heap available when audioreactive is not enabled. Already tested in MM fork. --- usermods/audioreactive/audio_reactive.h | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index fde7afded..1a91c333f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -191,8 +191,8 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul #define LOG_256 5.54517744f // log(256) // These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins -static float vImag[samplesFFT] = {0.0f}; // imaginary parts +static float* vReal = nullptr; // FFT sample inputs / freq output - these are our raw result bins +static float* vImag = nullptr; // imaginary parts // Create FFT object // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 @@ -200,14 +200,9 @@ static float vImag[samplesFFT] = {0.0f}; // imaginary parts // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 // Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 -#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - -#include - -/* Create FFT object with weighing factor storage */ -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); +// #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - since v2.0.0 this must be done in build_flags +#include // FFT object is created in FFTcode // Helper functions // compute average of several FFT result bins @@ -226,6 +221,18 @@ void FFTcode(void * parameter) { DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + // allocate FFT buffers on first call + if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT); + if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT); + if ((vReal == nullptr) || (vImag == nullptr)) { + // something went wrong + if (vReal) free(vReal); vReal = nullptr; + if (vImag) free(vImag); vImag = nullptr; + return; + } + // Create FFT object with weighing factor storage + ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); + // see https://www.freertos.org/vtaskdelayuntil.html const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; @@ -247,6 +254,7 @@ void FFTcode(void * parameter) // get a fresh batch of samples from I2S if (audioSource) audioSource->getSamples(vReal, samplesFFT); + memset(vImag, 0, samplesFFT * sizeof(float)); // set imaginary parts to 0 #if defined(WLED_DEBUG) || defined(SR_DEBUG) if (start < esp_timer_get_time()) { // filter out overflows @@ -265,8 +273,6 @@ void FFTcode(void * parameter) // find highest sample in the batch float maxSample = 0.0f; // max sample from FFT batch for (int i=0; i < samplesFFT; i++) { - // set imaginary parts to 0 - vImag[i] = 0; // pick our our current mic sample - we take the max value from all samples that go into FFT if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); @@ -297,7 +303,7 @@ void FFTcode(void * parameter) #endif } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. - memset(vReal, 0, sizeof(vReal)); + memset(vReal, 0, samplesFFT * sizeof(float)); FFT_MajorPeak = 1; FFT_Magnitude = 0.001; } From 26a47537f98f06dc9645e7eaf16016d349c2b297 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:15:14 +0200 Subject: [PATCH 550/694] AR memory optimization - part 2 shorten strings in UI script - saves a few hundred bytes on RAM --- usermods/audioreactive/audio_reactive.h | 74 +++++++++++++------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 1a91c333f..ad449fc83 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1885,57 +1885,59 @@ class AudioReactive : public Usermod { } - void appendConfigData() override + void appendConfigData(Print& uiScript) override { -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + uiScript.print(F("ux='AudioReactive';")); // ux = shortcut for Audioreactive - fingers crossed that "ux" isn't already used as JS var, html post parameter or css style +#ifdef ARDUINO_ARCH_ESP32 + uiScript.print(F("uxp=ux+':digitalmic:pin[]';")); // uxp = shortcut for AudioReactive:digitalmic:pin[] + uiScript.print(F("dd=addDropdown(ux,'digitalmic:type');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addOption(dd,'Generic Analog',0);")); + uiScript.print(F("addOption(dd,'Generic Analog',0);")); #endif - oappend(SET_F("addOption(dd,'Generic I2S',1);")); - oappend(SET_F("addOption(dd,'ES7243',2);")); - oappend(SET_F("addOption(dd,'SPH0654',3);")); - oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); + uiScript.print(F("addOption(dd,'Generic I2S',1);")); + uiScript.print(F("addOption(dd,'ES7243',2);")); + uiScript.print(F("addOption(dd,'SPH0654',3);")); + uiScript.print(F("addOption(dd,'Generic I2S with Mclk',4);")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); + uiScript.print(F("addOption(dd,'Generic I2S PDM',5);")); #endif - oappend(SET_F("addOption(dd,'ES8388',6);")); + uiScript.print(F("addOption(dd,'ES8388',6);")); - oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'Normal',1);")); - oappend(SET_F("addOption(dd,'Vivid',2);")); - oappend(SET_F("addOption(dd,'Lazy',3);")); + uiScript.print(F("dd=addDropdown(ux,'config:AGC');")); + uiScript.print(F("addOption(dd,'Off',0);")); + uiScript.print(F("addOption(dd,'Normal',1);")); + uiScript.print(F("addOption(dd,'Vivid',2);")); + uiScript.print(F("addOption(dd,'Lazy',3);")); - oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); - oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); + uiScript.print(F("dd=addDropdown(ux,'dynamics:limiter');")); + uiScript.print(F("addOption(dd,'Off',0);")); + uiScript.print(F("addOption(dd,'On',1);")); + uiScript.print(F("addInfo(ux+':dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field + uiScript.print(F("addInfo(ux+':dynamics:rise',1,'ms (♪ effects only)');")); + uiScript.print(F("addInfo(ux+':dynamics:fall',1,'ms (♪ effects only)');")); - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); - oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); - oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); + uiScript.print(F("dd=addDropdown(ux,'frequency:scale');")); + uiScript.print(F("addOption(dd,'None',0);")); + uiScript.print(F("addOption(dd,'Linear (Amplitude)',2);")); + uiScript.print(F("addOption(dd,'Square Root (Energy)',3);")); + uiScript.print(F("addOption(dd,'Logarithmic (Loudness)',1);")); #endif - oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); - oappend(SET_F("addOption(dd,'Off',0);")); + uiScript.print(F("dd=addDropdown(ux,'sync:mode');")); + uiScript.print(F("addOption(dd,'Off',0);")); #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addOption(dd,'Send',1);")); + uiScript.print(F("addOption(dd,'Send',1);")); #endif - oappend(SET_F("addOption(dd,'Receive',2);")); + uiScript.print(F("addOption(dd,'Receive',2);")); #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); + uiScript.print(F("addInfo(ux+':digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + uiScript.print(F("addInfo(uxp,0,'sd/data/dout','I2S SD');")); + uiScript.print(F("addInfo(uxp,1,'ws/clk/lrck','I2S WS');")); + uiScript.print(F("addInfo(uxp,2,'sck/bclk','I2S SCK');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); + uiScript.print(F("addInfo(uxp,3,'only use -1, 0, 1 or 3','I2S MCLK');")); #else - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); + uiScript.print(F("addInfo(uxp,3,'master clock','I2S MCLK');")); #endif #endif } From 6d1126b8aa123ebf351bb73b2618ccfcb9199682 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:19:46 +0200 Subject: [PATCH 551/694] Update audioreactive readme.md added `-D sqrt_internal=sqrtf` -> needed for good performance --- usermods/audioreactive/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index 4668ca881..aad269c67 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -30,7 +30,7 @@ There are however plans to create a lightweight audioreactive for the 8266, with ### using latest _arduinoFFT_ library version 2.x The latest arduinoFFT release version should be used for audioreactive. -* `build_flags` = `-D USERMOD_AUDIOREACTIVE` +* `build_flags` = `-D USERMOD_AUDIOREACTIVE -D sqrt_internal=sqrtf` * `lib_deps`= `kosme/arduinoFFT @ 2.0.1` ## Configuration From 2a094883ad7b8d59630d88733c0df1ee43969811 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Fri, 27 Sep 2024 22:33:20 -0400 Subject: [PATCH 552/694] Better oappend shim on ESP8266 Detect IRAM pointers if we can't be sure. --- wled00/fcn_declare.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 71b00599c..d44ed43a0 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -324,6 +324,10 @@ class Usermod { protected: // Shim for oappend(), which used to exist in utils.cpp template static inline void oappend(const T& t) { oappend_shim->print(t); }; +#ifdef ESP8266 + // Handle print(PSTR()) without crashing by detecting PROGMEM strings + static void oappend(const char* c) { if ((intptr_t) c >= 0x40000000) oappend_shim->print(FPSTR(c)); else oappend_shim->print(c); }; +#endif }; class UsermodManager { From 2bb2caf2d2ae2d06d7dd45be6e898e3b731fbcf7 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 23 Oct 2024 19:47:44 -0400 Subject: [PATCH 553/694] Enable NON32XFER_HANDLER on ESP8266 This is a platform feature that asks forgiveness for PROGMEM misuse: it adds a handler such that incorrectly used PROGMEM will work without crashing, just really, *really* inefficiently. Given that most of our real-world use cases for PROGMEM strings are relatively infrequent text calls, we can err on the side of developer convenience and address performance problems if and when they arise. --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index b130b687c..9628722aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -197,6 +197,7 @@ build_flags = ; decrease code cache size and increase IRAM to fit all pixel functions -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown + -D NON32XFER_HANDLER ;; ask forgiveness for PROGMEM misuse lib_deps = #https://github.com/lorol/LITTLEFS.git From b3b326738c14808ed2cac069a70bf9ea18bccd20 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 23 Oct 2024 19:58:52 -0400 Subject: [PATCH 554/694] Fix incorrect SET_F calls Replace with F() or PSTR() as appropriate. --- wled00/wled_server.cpp | 8 ++++---- wled00/xml.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 958b51330..e8cbb41ae 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -193,12 +193,12 @@ void createEditHandler(bool enable) { editHandler = &server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); #endif #else - editHandler = &server.on(SET_F("/edit"), HTTP_GET, [](AsyncWebServerRequest *request){ + editHandler = &server.on(F("/edit"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 501, FPSTR(s_notimplemented), F("The FS editor is disabled in this build."), 254); }); #endif } else { - editHandler = &server.on(SET_F("/edit"), HTTP_ANY, [](AsyncWebServerRequest *request){ + editHandler = &server.on(F("/edit"), HTTP_ANY, [](AsyncWebServerRequest *request){ serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254); }); } @@ -427,11 +427,11 @@ void initServer() #ifdef WLED_ENABLE_DMX - server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor); }); #else - server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 501, FPSTR(s_notimplemented), F("DMX support is not enabled in this build."), 254); }); #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 1ac22c9ce..6d1ff2f86 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -227,7 +227,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) - if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); + if (Network.isEthernet()) strcat_P(s ,PSTR(" (Ethernet)")); #endif printSetClassElementHTML(settingsScript,PSTR("sip"),0,s); } else From 7d067d8c305c3c6f397b6863f6849659a0aadd24 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 23 Oct 2024 20:00:22 -0400 Subject: [PATCH 555/694] Replace SET_F with F in usermods Since oappend() is now strongly typed, pass the correct type. This is a step towards removing the extra shim logic on ESP8266. --- .../Animated_Staircase/Animated_Staircase.h | 8 ++--- usermods/BME68X_v2/usermod_bme68x.h | 20 +++++------ usermods/Battery/usermod_v2_Battery.h | 34 +++++++++---------- usermods/EXAMPLE_v2/usermod_v2_example.h | 10 +++--- .../usermod_internal_temperature.h | 6 ++-- .../usermod_PIR_sensor_switch.h | 4 +-- usermods/ST7789_display/ST7789_display.h | 8 ++--- usermods/Temperature/usermod_temperature.h | 8 ++--- usermods/boblight/boblight.h | 16 ++++----- usermods/multi_relay/usermod_multi_relay.h | 10 +++--- usermods/pixels_dice_tray/pixels_dice_tray.h | 22 ++++++------ usermods/sht/usermod_sht.h | 24 ++++++------- .../usermod_v2_four_line_display_ALT.h | 30 ++++++++-------- .../usermod_v2_rotary_encoder_ui_ALT.h | 4 +-- .../usermod_v2_word_clock.h | 4 +-- usermods/wireguard/wireguard.h | 14 ++++---- 16 files changed, 111 insertions(+), 111 deletions(-) diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index d1ec9bb7f..54a9b3331 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -425,10 +425,10 @@ class Animated_Staircase : public Usermod { } void appendConfigData() { - //oappend(SET_F("dd=addDropdown('staircase','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - //oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field + //oappend(F("dd=addDropdown('staircase','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + //oappend(F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field } diff --git a/usermods/BME68X_v2/usermod_bme68x.h b/usermods/BME68X_v2/usermod_bme68x.h index 8e360515a..aca24d0a2 100644 --- a/usermods/BME68X_v2/usermod_bme68x.h +++ b/usermods/BME68X_v2/usermod_bme68x.h @@ -767,22 +767,22 @@ void UsermodBME68X::appendConfigData() { // snprintf_P(charbuffer, 127, PSTR("addInfo('%s:%s',1,'*) Set to minus to deactivate (all sensors)');"), UMOD_NAME, _nameTemp); oappend(charbuffer); /* Dropdown for Celsius/Fahrenheit*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameTempScale); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); /* i²C Address*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameI2CAdr); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'0x76',0x76);")); - oappend(SET_F("addOption(dd,'0x77',0x77);")); + oappend(F("');")); + oappend(F("addOption(dd,'0x76',0x76);")); + oappend(F("addOption(dd,'0x77',0x77);")); } /** diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index e91de850c..b36c5f4d6 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -478,29 +478,29 @@ class UsermodBattery : public Usermod void appendConfigData() { // Total: 462 Bytes - oappend(SET_F("td=addDropdown('Battery','type');")); // 34 Bytes - oappend(SET_F("addOption(td,'Unkown','0');")); // 28 Bytes - oappend(SET_F("addOption(td,'LiPo','1');")); // 26 Bytes - oappend(SET_F("addOption(td,'LiOn','2');")); // 26 Bytes - oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes - oappend(SET_F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes - oappend(SET_F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes - oappend(SET_F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes - oappend(SET_F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes + oappend(F("td=addDropdown('Battery','type');")); // 34 Bytes + oappend(F("addOption(td,'Unkown','0');")); // 28 Bytes + oappend(F("addOption(td,'LiPo','1');")); // 26 Bytes + oappend(F("addOption(td,'LiOn','2');")); // 26 Bytes + oappend(F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes + oappend(F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes + oappend(F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes + oappend(F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes + oappend(F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes + oappend(F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes // this option list would exeed the oappend() buffer // a list of all presets to select one from - // oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); - // the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);")); + // oappend(F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); + // the loop generates: oappend(F("addOption(bd, 'preset name', preset id);")); // for(int8_t i=1; i < 42; i++) { - // oappend(SET_F("addOption(bd, 'Preset#")); + // oappend(F("addOption(bd, 'Preset#")); // oappendi(i); - // oappend(SET_F("',")); + // oappend(F("',")); // oappendi(i); - // oappend(SET_F(");")); + // oappend(F(");")); // } } diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 3d562b585..df05f3e3d 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -287,11 +287,11 @@ class MyExampleUsermod : public Usermod { */ void appendConfigData() override { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'(this is a great config value)');")); - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');")); - oappend(SET_F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F("','testInt');")); - oappend(SET_F("addOption(dd,'Nothing',0);")); - oappend(SET_F("addOption(dd,'Everything',42);")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":great")); oappend(F("',1,'(this is a great config value)');")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":testString")); oappend(F("',1,'enter any string you want');")); + oappend(F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(F("','testInt');")); + oappend(F("addOption(dd,'Nothing',0);")); + oappend(F("addOption(dd,'Everything',42);")); } diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index 2236bfeab..c24b4c628 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -149,11 +149,11 @@ public: void appendConfigData() { // Display 'ms' next to the 'Loop Interval' setting - oappend(SET_F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); + oappend(F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); // Display '°C' next to the 'Activation Threshold' setting - oappend(SET_F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); + oappend(F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); // Display '0 = Disabled' next to the 'Preset To Activate' setting - oappend(SET_F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); + oappend(F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); } bool readFromConfig(JsonObject &root) diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 29070cf84..0deda181c 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -511,8 +511,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) void PIRsensorSwitch::appendConfigData() { - oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { char str[128]; sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 0dbada382..65f4cae5d 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -377,10 +377,10 @@ class St7789DisplayUsermod : public Usermod { void appendConfigData() override { - oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('ST7789:pin[]',3,'','SPI BL');")); + oappend(F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('ST7789:pin[]',3,'','SPI BL');")); } /* diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index ad755eaee..178bc05a0 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -435,10 +435,10 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { } void UsermodTemperature::appendConfigData() { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); - oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); - oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field } float UsermodTemperature::getTemperature() { diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index 916f7da98..b04b78fac 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -305,14 +305,14 @@ class BobLightUsermod : public Usermod { } void appendConfigData() override { - //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + //oappend(F("dd=addDropdown('usermod','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + oappend(F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) override { diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 33a6cf85e..c4446c7a2 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -264,7 +264,7 @@ void MultiRelay::handleOffTimer() { void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { + server.on(F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { DEBUG_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; @@ -765,10 +765,10 @@ void MultiRelay::addToConfig(JsonObject &root) { } void MultiRelay::appendConfigData() { - oappend(SET_F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); - //oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); - oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); + oappend(F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); + //oappend(F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); + oappend(F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index a1e45ba33..61348ebb8 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -387,23 +387,23 @@ class PixelsDiceTrayUsermod : public Usermod { // To work around this, add info text to the end of the preceding item. // // See addInfo in wled00/data/settings_um.htm for details on what this function does. - oappend(SET_F( + oappend(F( "addInfo('DiceTray:ble_scan_duration',1,'

Set to \"*\" to " "connect to any die.
Leave Blank to disable.

Saving will replace \"*\" with die names.','');")); #if USING_TFT_DISPLAY - oappend(SET_F("ddr=addDropdown('DiceTray','rotation');")); - oappend(SET_F("addOption(ddr,'0 deg',0);")); - oappend(SET_F("addOption(ddr,'90 deg',1);")); - oappend(SET_F("addOption(ddr,'180 deg',2);")); - oappend(SET_F("addOption(ddr,'270 deg',3);")); - oappend(SET_F( + oappend(F("ddr=addDropdown('DiceTray','rotation');")); + oappend(F("addOption(ddr,'0 deg',0);")); + oappend(F("addOption(ddr,'90 deg',1);")); + oappend(F("addOption(ddr,'180 deg',2);")); + oappend(F("addOption(ddr,'270 deg',3);")); + oappend(F( "addInfo('DiceTray:rotation',1,'
DO NOT CHANGE " "SPI PINS.
CHANGES ARE IGNORED.','');")); - oappend(SET_F("addInfo('TFT:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('TFT:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('TFT:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('TFT:pin[]',3,'','SPI BL');")); + oappend(F("addInfo('TFT:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('TFT:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('TFT:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('TFT:pin[]',3,'','SPI BL');")); #endif } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index c6e17221b..f10c78a25 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -310,22 +310,22 @@ void ShtUsermod::onMqttConnect(bool sessionPresent) { * @return void */ void ShtUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_shtType); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'SHT30',0);")); - oappend(SET_F("addOption(dd,'SHT31',1);")); - oappend(SET_F("addOption(dd,'SHT35',2);")); - oappend(SET_F("addOption(dd,'SHT85',3);")); - oappend(SET_F("dd=addDropdown('")); + oappend(F("');")); + oappend(F("addOption(dd,'SHT30',0);")); + oappend(F("addOption(dd,'SHT31',1);")); + oappend(F("addOption(dd,'SHT35',2);")); + oappend(F("addOption(dd,'SHT85',3);")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_unitOfTemp); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); } /** diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index dfab7e6ff..684dd86e4 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -1202,21 +1202,21 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { //} void FourLineDisplayUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'SSD1306',1);")); - oappend(SET_F("addOption(dd,'SH1106',2);")); - oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); - oappend(SET_F("addOption(dd,'SSD1305',4);")); - oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); - oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); + oappend(F("dd=addDropdown('4LineDisplay','type');")); + oappend(F("addOption(dd,'None',0);")); + oappend(F("addOption(dd,'SSD1306',1);")); + oappend(F("addOption(dd,'SH1106',2);")); + oappend(F("addOption(dd,'SSD1306 128x64',3);")); + oappend(F("addOption(dd,'SSD1305',4);")); + oappend(F("addOption(dd,'SSD1305 128x64',5);")); + oappend(F("addOption(dd,'SSD1309 128x64',9);")); + oappend(F("addOption(dd,'SSD1306 SPI',6);")); + oappend(F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(F("addOption(dd,'SSD1309 SPI 128x64',8);")); + oappend(F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); } /* diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 55715b7c7..383c1193e 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -1090,8 +1090,8 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { } void RotaryEncoderUIUsermod::appendConfigData() { - oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); + oappend(F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index b66be290a..7ecec08e5 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -433,8 +433,8 @@ class WordClockUsermod : public Usermod void appendConfigData() { - oappend(SET_F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); - oappend(SET_F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); + oappend(F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); + oappend(F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); } /* diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h index 8c88d0001..8656a704a 100644 --- a/usermods/wireguard/wireguard.h +++ b/usermods/wireguard/wireguard.h @@ -54,13 +54,13 @@ class WireguardUsermod : public Usermod { } void appendConfigData() { - oappend(SET_F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) { From 2e01fe0b5bf853f068fc255896994b324a56c2d8 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 23 Oct 2024 21:34:35 -0400 Subject: [PATCH 556/694] Revert "Replace SET_F with F in usermods" This reverts commit 7d067d8c305c3c6f397b6863f6849659a0aadd24. --- .../Animated_Staircase/Animated_Staircase.h | 8 ++--- usermods/BME68X_v2/usermod_bme68x.h | 20 +++++------ usermods/Battery/usermod_v2_Battery.h | 34 +++++++++---------- usermods/EXAMPLE_v2/usermod_v2_example.h | 10 +++--- .../usermod_internal_temperature.h | 6 ++-- .../usermod_PIR_sensor_switch.h | 4 +-- usermods/ST7789_display/ST7789_display.h | 8 ++--- usermods/Temperature/usermod_temperature.h | 8 ++--- usermods/boblight/boblight.h | 16 ++++----- usermods/multi_relay/usermod_multi_relay.h | 10 +++--- usermods/pixels_dice_tray/pixels_dice_tray.h | 22 ++++++------ usermods/sht/usermod_sht.h | 24 ++++++------- .../usermod_v2_four_line_display_ALT.h | 30 ++++++++-------- .../usermod_v2_rotary_encoder_ui_ALT.h | 4 +-- .../usermod_v2_word_clock.h | 4 +-- usermods/wireguard/wireguard.h | 14 ++++---- 16 files changed, 111 insertions(+), 111 deletions(-) diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 54a9b3331..d1ec9bb7f 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -425,10 +425,10 @@ class Animated_Staircase : public Usermod { } void appendConfigData() { - //oappend(F("dd=addDropdown('staircase','selectfield');")); - //oappend(F("addOption(dd,'1st value',0);")); - //oappend(F("addOption(dd,'2nd value',1);")); - //oappend(F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field + //oappend(SET_F("dd=addDropdown('staircase','selectfield');")); + //oappend(SET_F("addOption(dd,'1st value',0);")); + //oappend(SET_F("addOption(dd,'2nd value',1);")); + //oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field } diff --git a/usermods/BME68X_v2/usermod_bme68x.h b/usermods/BME68X_v2/usermod_bme68x.h index aca24d0a2..8e360515a 100644 --- a/usermods/BME68X_v2/usermod_bme68x.h +++ b/usermods/BME68X_v2/usermod_bme68x.h @@ -767,22 +767,22 @@ void UsermodBME68X::appendConfigData() { // snprintf_P(charbuffer, 127, PSTR("addInfo('%s:%s',1,'*) Set to minus to deactivate (all sensors)');"), UMOD_NAME, _nameTemp); oappend(charbuffer); /* Dropdown for Celsius/Fahrenheit*/ - oappend(F("dd=addDropdown('")); + oappend(SET_F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(F("','")); + oappend(SET_F("','")); oappend(_nameTempScale); - oappend(F("');")); - oappend(F("addOption(dd,'Celsius',0);")); - oappend(F("addOption(dd,'Fahrenheit',1);")); + oappend(SET_F("');")); + oappend(SET_F("addOption(dd,'Celsius',0);")); + oappend(SET_F("addOption(dd,'Fahrenheit',1);")); /* i²C Address*/ - oappend(F("dd=addDropdown('")); + oappend(SET_F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(F("','")); + oappend(SET_F("','")); oappend(_nameI2CAdr); - oappend(F("');")); - oappend(F("addOption(dd,'0x76',0x76);")); - oappend(F("addOption(dd,'0x77',0x77);")); + oappend(SET_F("');")); + oappend(SET_F("addOption(dd,'0x76',0x76);")); + oappend(SET_F("addOption(dd,'0x77',0x77);")); } /** diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index b36c5f4d6..e91de850c 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -478,29 +478,29 @@ class UsermodBattery : public Usermod void appendConfigData() { // Total: 462 Bytes - oappend(F("td=addDropdown('Battery','type');")); // 34 Bytes - oappend(F("addOption(td,'Unkown','0');")); // 28 Bytes - oappend(F("addOption(td,'LiPo','1');")); // 26 Bytes - oappend(F("addOption(td,'LiOn','2');")); // 26 Bytes - oappend(F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes - oappend(F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes - oappend(F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes - oappend(F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes - oappend(F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes - oappend(F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes - oappend(F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes - oappend(F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes + oappend(SET_F("td=addDropdown('Battery','type');")); // 34 Bytes + oappend(SET_F("addOption(td,'Unkown','0');")); // 28 Bytes + oappend(SET_F("addOption(td,'LiPo','1');")); // 26 Bytes + oappend(SET_F("addOption(td,'LiOn','2');")); // 26 Bytes + oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes + oappend(SET_F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes + oappend(SET_F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes + oappend(SET_F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes + oappend(SET_F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes + oappend(SET_F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes + oappend(SET_F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes + oappend(SET_F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes // this option list would exeed the oappend() buffer // a list of all presets to select one from - // oappend(F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); - // the loop generates: oappend(F("addOption(bd, 'preset name', preset id);")); + // oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); + // the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);")); // for(int8_t i=1; i < 42; i++) { - // oappend(F("addOption(bd, 'Preset#")); + // oappend(SET_F("addOption(bd, 'Preset#")); // oappendi(i); - // oappend(F("',")); + // oappend(SET_F("',")); // oappendi(i); - // oappend(F(");")); + // oappend(SET_F(");")); // } } diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index df05f3e3d..3d562b585 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -287,11 +287,11 @@ class MyExampleUsermod : public Usermod { */ void appendConfigData() override { - oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":great")); oappend(F("',1,'(this is a great config value)');")); - oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":testString")); oappend(F("',1,'enter any string you want');")); - oappend(F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(F("','testInt');")); - oappend(F("addOption(dd,'Nothing',0);")); - oappend(F("addOption(dd,'Everything',42);")); + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'(this is a great config value)');")); + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');")); + oappend(SET_F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F("','testInt');")); + oappend(SET_F("addOption(dd,'Nothing',0);")); + oappend(SET_F("addOption(dd,'Everything',42);")); } diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index c24b4c628..2236bfeab 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -149,11 +149,11 @@ public: void appendConfigData() { // Display 'ms' next to the 'Loop Interval' setting - oappend(F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); + oappend(SET_F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); // Display '°C' next to the 'Activation Threshold' setting - oappend(F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); + oappend(SET_F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); // Display '0 = Disabled' next to the 'Preset To Activate' setting - oappend(F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); + oappend(SET_F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); } bool readFromConfig(JsonObject &root) diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 0deda181c..29070cf84 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -511,8 +511,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) void PIRsensorSwitch::appendConfigData() { - oappend(F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { char str[128]; sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 65f4cae5d..0dbada382 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -377,10 +377,10 @@ class St7789DisplayUsermod : public Usermod { void appendConfigData() override { - oappend(F("addInfo('ST7789:pin[]',0,'','SPI CS');")); - oappend(F("addInfo('ST7789:pin[]',1,'','SPI DC');")); - oappend(F("addInfo('ST7789:pin[]',2,'','SPI RST');")); - oappend(F("addInfo('ST7789:pin[]',3,'','SPI BL');")); + oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(SET_F("addInfo('ST7789:pin[]',3,'','SPI BL');")); } /* diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 178bc05a0..ad755eaee 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -435,10 +435,10 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { } void UsermodTemperature::appendConfigData() { - oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasite)).c_str()); - oappend(F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); - oappend(F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field } float UsermodTemperature::getTemperature() { diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index b04b78fac..916f7da98 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -305,14 +305,14 @@ class BobLightUsermod : public Usermod { } void appendConfigData() override { - //oappend(F("dd=addDropdown('usermod','selectfield');")); - //oappend(F("addOption(dd,'1st value',0);")); - //oappend(F("addOption(dd,'2nd value',1);")); - oappend(F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); + //oappend(SET_F("addOption(dd,'1st value',0);")); + //oappend(SET_F("addOption(dd,'2nd value',1);")); + oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) override { diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index c4446c7a2..33a6cf85e 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -264,7 +264,7 @@ void MultiRelay::handleOffTimer() { void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - server.on(F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { + server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { DEBUG_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; @@ -765,10 +765,10 @@ void MultiRelay::addToConfig(JsonObject &root) { } void MultiRelay::appendConfigData() { - oappend(F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); - oappend(F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); - //oappend(F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); - oappend(F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(SET_F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); + oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); + //oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); + oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index 61348ebb8..a1e45ba33 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -387,23 +387,23 @@ class PixelsDiceTrayUsermod : public Usermod { // To work around this, add info text to the end of the preceding item. // // See addInfo in wled00/data/settings_um.htm for details on what this function does. - oappend(F( + oappend(SET_F( "addInfo('DiceTray:ble_scan_duration',1,'

Set to \"*\" to " "connect to any die.
Leave Blank to disable.

Saving will replace \"*\" with die names.','');")); #if USING_TFT_DISPLAY - oappend(F("ddr=addDropdown('DiceTray','rotation');")); - oappend(F("addOption(ddr,'0 deg',0);")); - oappend(F("addOption(ddr,'90 deg',1);")); - oappend(F("addOption(ddr,'180 deg',2);")); - oappend(F("addOption(ddr,'270 deg',3);")); - oappend(F( + oappend(SET_F("ddr=addDropdown('DiceTray','rotation');")); + oappend(SET_F("addOption(ddr,'0 deg',0);")); + oappend(SET_F("addOption(ddr,'90 deg',1);")); + oappend(SET_F("addOption(ddr,'180 deg',2);")); + oappend(SET_F("addOption(ddr,'270 deg',3);")); + oappend(SET_F( "addInfo('DiceTray:rotation',1,'
DO NOT CHANGE " "SPI PINS.
CHANGES ARE IGNORED.','');")); - oappend(F("addInfo('TFT:pin[]',0,'','SPI CS');")); - oappend(F("addInfo('TFT:pin[]',1,'','SPI DC');")); - oappend(F("addInfo('TFT:pin[]',2,'','SPI RST');")); - oappend(F("addInfo('TFT:pin[]',3,'','SPI BL');")); + oappend(SET_F("addInfo('TFT:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('TFT:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('TFT:pin[]',2,'','SPI RST');")); + oappend(SET_F("addInfo('TFT:pin[]',3,'','SPI BL');")); #endif } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index f10c78a25..c6e17221b 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -310,22 +310,22 @@ void ShtUsermod::onMqttConnect(bool sessionPresent) { * @return void */ void ShtUsermod::appendConfigData() { - oappend(F("dd=addDropdown('")); + oappend(SET_F("dd=addDropdown('")); oappend(_name); - oappend(F("','")); + oappend(SET_F("','")); oappend(_shtType); - oappend(F("');")); - oappend(F("addOption(dd,'SHT30',0);")); - oappend(F("addOption(dd,'SHT31',1);")); - oappend(F("addOption(dd,'SHT35',2);")); - oappend(F("addOption(dd,'SHT85',3);")); - oappend(F("dd=addDropdown('")); + oappend(SET_F("');")); + oappend(SET_F("addOption(dd,'SHT30',0);")); + oappend(SET_F("addOption(dd,'SHT31',1);")); + oappend(SET_F("addOption(dd,'SHT35',2);")); + oappend(SET_F("addOption(dd,'SHT85',3);")); + oappend(SET_F("dd=addDropdown('")); oappend(_name); - oappend(F("','")); + oappend(SET_F("','")); oappend(_unitOfTemp); - oappend(F("');")); - oappend(F("addOption(dd,'Celsius',0);")); - oappend(F("addOption(dd,'Fahrenheit',1);")); + oappend(SET_F("');")); + oappend(SET_F("addOption(dd,'Celsius',0);")); + oappend(SET_F("addOption(dd,'Fahrenheit',1);")); } /** diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 684dd86e4..dfab7e6ff 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -1202,21 +1202,21 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { //} void FourLineDisplayUsermod::appendConfigData() { - oappend(F("dd=addDropdown('4LineDisplay','type');")); - oappend(F("addOption(dd,'None',0);")); - oappend(F("addOption(dd,'SSD1306',1);")); - oappend(F("addOption(dd,'SH1106',2);")); - oappend(F("addOption(dd,'SSD1306 128x64',3);")); - oappend(F("addOption(dd,'SSD1305',4);")); - oappend(F("addOption(dd,'SSD1305 128x64',5);")); - oappend(F("addOption(dd,'SSD1309 128x64',9);")); - oappend(F("addOption(dd,'SSD1306 SPI',6);")); - oappend(F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(F("addOption(dd,'SSD1309 SPI 128x64',8);")); - oappend(F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); - oappend(F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); - oappend(F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); - oappend(F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); + oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'SSD1306',1);")); + oappend(SET_F("addOption(dd,'SH1106',2);")); + oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); + oappend(SET_F("addOption(dd,'SSD1305',4);")); + oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); + oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); + oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); } /* diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 383c1193e..55715b7c7 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -1090,8 +1090,8 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { } void RotaryEncoderUIUsermod::appendConfigData() { - oappend(F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); - oappend(F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); + oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index 7ecec08e5..b66be290a 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -433,8 +433,8 @@ class WordClockUsermod : public Usermod void appendConfigData() { - oappend(F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); - oappend(F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); + oappend(SET_F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); + oappend(SET_F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); } /* diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h index 8656a704a..8c88d0001 100644 --- a/usermods/wireguard/wireguard.h +++ b/usermods/wireguard/wireguard.h @@ -54,13 +54,13 @@ class WireguardUsermod : public Usermod { } void appendConfigData() { - oappend(F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field - oappend(F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) { From 4f48ddfaec8a4d3d5dfe548204e3fde90baf8e9f Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 23 Oct 2024 20:00:22 -0400 Subject: [PATCH 557/694] Replace SET_F with F in usermods Since oappend() is now strongly typed, pass the correct type. This is a step towards removing the extra shim logic on ESP8266. --- .../Animated_Staircase/Animated_Staircase.h | 8 ++--- usermods/BME68X_v2/usermod_bme68x.h | 20 +++++------ usermods/Battery/usermod_v2_Battery.h | 34 +++++++++---------- usermods/EXAMPLE_v2/usermod_v2_example.h | 10 +++--- .../usermod_internal_temperature.h | 6 ++-- .../usermod_PIR_sensor_switch.h | 4 +-- usermods/ST7789_display/ST7789_display.h | 8 ++--- usermods/Temperature/usermod_temperature.h | 8 ++--- usermods/boblight/boblight.h | 16 ++++----- usermods/multi_relay/usermod_multi_relay.h | 10 +++--- usermods/pixels_dice_tray/pixels_dice_tray.h | 22 ++++++------ usermods/sht/usermod_sht.h | 24 ++++++------- .../usermod_v2_four_line_display_ALT.h | 30 ++++++++-------- .../usermod_v2_rotary_encoder_ui_ALT.h | 4 +-- .../usermod_v2_word_clock.h | 4 +-- usermods/wireguard/wireguard.h | 14 ++++---- 16 files changed, 111 insertions(+), 111 deletions(-) diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index d1ec9bb7f..54a9b3331 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -425,10 +425,10 @@ class Animated_Staircase : public Usermod { } void appendConfigData() { - //oappend(SET_F("dd=addDropdown('staircase','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - //oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field + //oappend(F("dd=addDropdown('staircase','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + //oappend(F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field } diff --git a/usermods/BME68X_v2/usermod_bme68x.h b/usermods/BME68X_v2/usermod_bme68x.h index 8e360515a..aca24d0a2 100644 --- a/usermods/BME68X_v2/usermod_bme68x.h +++ b/usermods/BME68X_v2/usermod_bme68x.h @@ -767,22 +767,22 @@ void UsermodBME68X::appendConfigData() { // snprintf_P(charbuffer, 127, PSTR("addInfo('%s:%s',1,'*) Set to minus to deactivate (all sensors)');"), UMOD_NAME, _nameTemp); oappend(charbuffer); /* Dropdown for Celsius/Fahrenheit*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameTempScale); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); /* i²C Address*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameI2CAdr); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'0x76',0x76);")); - oappend(SET_F("addOption(dd,'0x77',0x77);")); + oappend(F("');")); + oappend(F("addOption(dd,'0x76',0x76);")); + oappend(F("addOption(dd,'0x77',0x77);")); } /** diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index e91de850c..b36c5f4d6 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -478,29 +478,29 @@ class UsermodBattery : public Usermod void appendConfigData() { // Total: 462 Bytes - oappend(SET_F("td=addDropdown('Battery','type');")); // 34 Bytes - oappend(SET_F("addOption(td,'Unkown','0');")); // 28 Bytes - oappend(SET_F("addOption(td,'LiPo','1');")); // 26 Bytes - oappend(SET_F("addOption(td,'LiOn','2');")); // 26 Bytes - oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes - oappend(SET_F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes - oappend(SET_F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes - oappend(SET_F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes - oappend(SET_F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes + oappend(F("td=addDropdown('Battery','type');")); // 34 Bytes + oappend(F("addOption(td,'Unkown','0');")); // 28 Bytes + oappend(F("addOption(td,'LiPo','1');")); // 26 Bytes + oappend(F("addOption(td,'LiOn','2');")); // 26 Bytes + oappend(F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes + oappend(F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes + oappend(F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes + oappend(F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes + oappend(F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes + oappend(F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes // this option list would exeed the oappend() buffer // a list of all presets to select one from - // oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); - // the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);")); + // oappend(F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); + // the loop generates: oappend(F("addOption(bd, 'preset name', preset id);")); // for(int8_t i=1; i < 42; i++) { - // oappend(SET_F("addOption(bd, 'Preset#")); + // oappend(F("addOption(bd, 'Preset#")); // oappendi(i); - // oappend(SET_F("',")); + // oappend(F("',")); // oappendi(i); - // oappend(SET_F(");")); + // oappend(F(");")); // } } diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 3d562b585..df05f3e3d 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -287,11 +287,11 @@ class MyExampleUsermod : public Usermod { */ void appendConfigData() override { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'(this is a great config value)');")); - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');")); - oappend(SET_F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F("','testInt');")); - oappend(SET_F("addOption(dd,'Nothing',0);")); - oappend(SET_F("addOption(dd,'Everything',42);")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":great")); oappend(F("',1,'(this is a great config value)');")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":testString")); oappend(F("',1,'enter any string you want');")); + oappend(F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(F("','testInt');")); + oappend(F("addOption(dd,'Nothing',0);")); + oappend(F("addOption(dd,'Everything',42);")); } diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index 2236bfeab..c24b4c628 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -149,11 +149,11 @@ public: void appendConfigData() { // Display 'ms' next to the 'Loop Interval' setting - oappend(SET_F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); + oappend(F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); // Display '°C' next to the 'Activation Threshold' setting - oappend(SET_F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); + oappend(F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); // Display '0 = Disabled' next to the 'Preset To Activate' setting - oappend(SET_F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); + oappend(F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); } bool readFromConfig(JsonObject &root) diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 29070cf84..0deda181c 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -511,8 +511,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) void PIRsensorSwitch::appendConfigData() { - oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { char str[128]; sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 0dbada382..65f4cae5d 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -377,10 +377,10 @@ class St7789DisplayUsermod : public Usermod { void appendConfigData() override { - oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('ST7789:pin[]',3,'','SPI BL');")); + oappend(F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('ST7789:pin[]',3,'','SPI BL');")); } /* diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index ad755eaee..178bc05a0 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -435,10 +435,10 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { } void UsermodTemperature::appendConfigData() { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); - oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); - oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field } float UsermodTemperature::getTemperature() { diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index 916f7da98..b04b78fac 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -305,14 +305,14 @@ class BobLightUsermod : public Usermod { } void appendConfigData() override { - //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + //oappend(F("dd=addDropdown('usermod','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + oappend(F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) override { diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 33a6cf85e..c4446c7a2 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -264,7 +264,7 @@ void MultiRelay::handleOffTimer() { void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { + server.on(F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { DEBUG_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; @@ -765,10 +765,10 @@ void MultiRelay::addToConfig(JsonObject &root) { } void MultiRelay::appendConfigData() { - oappend(SET_F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); - //oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); - oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); + oappend(F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); + //oappend(F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); + oappend(F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index a1e45ba33..61348ebb8 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -387,23 +387,23 @@ class PixelsDiceTrayUsermod : public Usermod { // To work around this, add info text to the end of the preceding item. // // See addInfo in wled00/data/settings_um.htm for details on what this function does. - oappend(SET_F( + oappend(F( "addInfo('DiceTray:ble_scan_duration',1,'

Set to \"*\" to " "connect to any die.
Leave Blank to disable.

Saving will replace \"*\" with die names.','');")); #if USING_TFT_DISPLAY - oappend(SET_F("ddr=addDropdown('DiceTray','rotation');")); - oappend(SET_F("addOption(ddr,'0 deg',0);")); - oappend(SET_F("addOption(ddr,'90 deg',1);")); - oappend(SET_F("addOption(ddr,'180 deg',2);")); - oappend(SET_F("addOption(ddr,'270 deg',3);")); - oappend(SET_F( + oappend(F("ddr=addDropdown('DiceTray','rotation');")); + oappend(F("addOption(ddr,'0 deg',0);")); + oappend(F("addOption(ddr,'90 deg',1);")); + oappend(F("addOption(ddr,'180 deg',2);")); + oappend(F("addOption(ddr,'270 deg',3);")); + oappend(F( "addInfo('DiceTray:rotation',1,'
DO NOT CHANGE " "SPI PINS.
CHANGES ARE IGNORED.','');")); - oappend(SET_F("addInfo('TFT:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('TFT:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('TFT:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('TFT:pin[]',3,'','SPI BL');")); + oappend(F("addInfo('TFT:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('TFT:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('TFT:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('TFT:pin[]',3,'','SPI BL');")); #endif } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index c6e17221b..f10c78a25 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -310,22 +310,22 @@ void ShtUsermod::onMqttConnect(bool sessionPresent) { * @return void */ void ShtUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_shtType); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'SHT30',0);")); - oappend(SET_F("addOption(dd,'SHT31',1);")); - oappend(SET_F("addOption(dd,'SHT35',2);")); - oappend(SET_F("addOption(dd,'SHT85',3);")); - oappend(SET_F("dd=addDropdown('")); + oappend(F("');")); + oappend(F("addOption(dd,'SHT30',0);")); + oappend(F("addOption(dd,'SHT31',1);")); + oappend(F("addOption(dd,'SHT35',2);")); + oappend(F("addOption(dd,'SHT85',3);")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_unitOfTemp); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); } /** diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index dfab7e6ff..684dd86e4 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -1202,21 +1202,21 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { //} void FourLineDisplayUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'SSD1306',1);")); - oappend(SET_F("addOption(dd,'SH1106',2);")); - oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); - oappend(SET_F("addOption(dd,'SSD1305',4);")); - oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); - oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); + oappend(F("dd=addDropdown('4LineDisplay','type');")); + oappend(F("addOption(dd,'None',0);")); + oappend(F("addOption(dd,'SSD1306',1);")); + oappend(F("addOption(dd,'SH1106',2);")); + oappend(F("addOption(dd,'SSD1306 128x64',3);")); + oappend(F("addOption(dd,'SSD1305',4);")); + oappend(F("addOption(dd,'SSD1305 128x64',5);")); + oappend(F("addOption(dd,'SSD1309 128x64',9);")); + oappend(F("addOption(dd,'SSD1306 SPI',6);")); + oappend(F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(F("addOption(dd,'SSD1309 SPI 128x64',8);")); + oappend(F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); } /* diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 55715b7c7..383c1193e 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -1090,8 +1090,8 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { } void RotaryEncoderUIUsermod::appendConfigData() { - oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); + oappend(F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index b66be290a..7ecec08e5 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -433,8 +433,8 @@ class WordClockUsermod : public Usermod void appendConfigData() { - oappend(SET_F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); - oappend(SET_F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); + oappend(F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); + oappend(F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); } /* diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h index 8c88d0001..8656a704a 100644 --- a/usermods/wireguard/wireguard.h +++ b/usermods/wireguard/wireguard.h @@ -54,13 +54,13 @@ class WireguardUsermod : public Usermod { } void appendConfigData() { - oappend(SET_F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) { From 832599b8c51ba7df3a565901ddf338b18d82f8a4 Mon Sep 17 00:00:00 2001 From: Svennte <105973347+Svennte@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:33:05 +0200 Subject: [PATCH 558/694] Fix alexa devices invisible/uncontrollable (#4214) Fix for LED and Scenes uncontrollable using Alexa. Weird behavior regarding to the device names and shared scenes fixed with this. Seen in issue Aircoookie/Espalexa#228 and fixed from @ams-hh Tested by myself and works just fine. Created second pull request here because the library seems to be a bit different from the official Espalexa repo. --------- Co-authored-by: Frank <91616163+softhack007@users.noreply.github.com> Co-authored-by: Blaz Kristan --- wled00/src/dependencies/espalexa/Espalexa.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wled00/src/dependencies/espalexa/Espalexa.h b/wled00/src/dependencies/espalexa/Espalexa.h index 5c780e248..ae761e9fa 100644 --- a/wled00/src/dependencies/espalexa/Espalexa.h +++ b/wled00/src/dependencies/espalexa/Espalexa.h @@ -120,10 +120,8 @@ private: void encodeLightId(uint8_t idx, char* out) { - uint8_t mac[6]; - WiFi.macAddress(mac); - - sprintf_P(out, PSTR("%02X:%02X:%02X:%02X:%02X:%02X:00:11-%02X"), mac[0],mac[1],mac[2],mac[3],mac[4],mac[5], idx); + String mymac = WiFi.macAddress(); + sprintf_P(out, PSTR("%02X:%s:AB-%02X"), idx, mymac.c_str(), idx); } // construct 'globally unique' Json dict key fitting into signed int From dcfdca63515f2d3f80674d05c6ee7186f9458354 Mon Sep 17 00:00:00 2001 From: ingDIY <10012263+ingDIY@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:14:37 +0200 Subject: [PATCH 559/694] cleaned up old references to USE_ALT_DISPlAY that aren't used anymore in the code/source files; fixed readme.md documents and updated instructions; removed referencies to old usermods; fixed invalid filenames; removed referencies to old usermods; checked cycle options; splitted and fixed platformio_override.sample.ini; --- platformio_override.sample.ini | 2 -- .../platformio_override.sample.ini} | 11 +++---- .../readme.md | 30 +++++-------------- .../platformio_override.sample.ini | 14 +++++++++ .../readme.md | 22 ++++---------- 5 files changed, 32 insertions(+), 47 deletions(-) rename usermods/{usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini => usermod_v2_four_line_display_ALT/platformio_override.sample.ini} (54%) create mode 100644 usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf003..c445cd32e 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -111,7 +111,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Use 4 Line Display usermod with SPI display ; -D USERMOD_FOUR_LINE_DISPLAY -; -D USE_ALT_DISPlAY # mandatory ; -DFLD_SPI_DEFAULT ; -D FLD_TYPE=SSD1306_SPI64 ; -D FLD_PIN_CLOCKSPI=14 @@ -377,7 +376,6 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D USERMOD_DALLASTEMPERATURE -D USERMOD_FOUR_LINE_DISPLAY -D TEMPERATURE_PIN=23 - -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI -D USERMOD_AUDIOREACTIVE lib_deps = ${esp32.lib_deps} OneWire@~2.3.5 diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini similarity index 54% rename from usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini rename to usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini index 6b32c71fb..9010648b3 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini +++ b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini @@ -7,11 +7,12 @@ platform = ${esp32.platform} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} - -D USERMOD_FOUR_LINE_DISPLAY -D USE_ALT_DISPlAY - -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 -upload_speed = 460800 + -D USERMOD_FOUR_LINE_DISPLAY + -D FLD_TYPE=SH1106 + -D I2CSCLPIN=27 + -D I2CSDAPIN=26 + lib_deps = ${esp32.lib_deps} - U8g2@~2.34.4 + U8g2 Wire - diff --git a/usermods/usermod_v2_four_line_display_ALT/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md index a8f386dac..39bb5d28e 100644 --- a/usermods/usermod_v2_four_line_display_ALT/readme.md +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -1,16 +1,8 @@ # I2C/SPI 4 Line Display Usermod ALT -Thank you to the authors of the original version of these usermods. It would not have been possible without them! -"usermod_v2_four_line_display" -"usermod_v2_rotary_encoder_ui" +This usermod could be used in compination with `usermod_v2_rotary_encoder_ui_ALT`. -The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. -The display usermod UI has been completely changed. - - -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display, it functions identical to the original. -The original "usermod_v2_auto_save" will not work with the display just yet. +## Functionalities Press the encoder to cycle through the options: * Brightness @@ -18,26 +10,18 @@ Press the encoder to cycle through the options: * Intensity * Palette * Effect -* Main Color (only if display is used) -* Saturation (only if display is used) +* Main Color +* Saturation -Press and hold the encoder to display Network Info. If AP is active, it will display AP, SSID and password +Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password -Also shows if the timer is enabled +Also shows if the timer is enabled. [See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI) ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions. - -Copy the example `platformio_override.sample.ini` from the usermod_v2_rotary_encoder_ui_ALT folder to the root directory of your particular build and rename it to `platformio_override.ini`. - -This file should be placed in the same directory as `platformio.ini`. - -Then, to activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, - or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file - +Copy the example `platformio_override.sample.ini` to the root directory of your particular build. ## Configuration diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini new file mode 100644 index 000000000..8a88fd6b5 --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini @@ -0,0 +1,14 @@ +[platformio] +default_envs = esp32dev + +[env:esp32dev] +board = esp32dev +platform = ${esp32.platform} +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp32} + -D USERMOD_ROTARY_ENCODER_UI + -D USERMOD_ROTARY_ENCODER_GPIO=INPUT + -D ENCODER_DT_PIN=21 + -D ENCODER_CLK_PIN=23 + -D ENCODER_SW_PIN=0 diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md index 10db879fb..c46e87663 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md @@ -1,16 +1,8 @@ # Rotary Encoder UI Usermod ALT -Thank you to the authors of the original version of these usermods. It would not have been possible without them! -"usermod_v2_four_line_display" -"usermod_v2_rotary_encoder_ui" +This usermod supports the UI of the `usermod_v2_rotary_encoder_ui_ALT`. -The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. -The display usermod UI has been completely changed. - - -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display, it functions identical to the original. -The original "usermod_v2_auto_save" will not work with the display just yet. +## Functionalities Press the encoder to cycle through the options: * Brightness @@ -21,8 +13,7 @@ Press the encoder to cycle through the options: * Main Color (only if display is used) * Saturation (only if display is used) -Press and hold the encoder to display Network Info - if AP is active, it will display the AP, SSID and Password +Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password Also shows if the timer is enabled. @@ -30,9 +21,7 @@ Also shows if the timer is enabled. ## Installation -Copy the example `platformio_override.sample.ini` to the root directory of your particular build and rename it to `platformio_override.ini`. - -To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to your `platformio_override.ini` file +Copy the example `platformio_override.sample.ini` to the root directory of your particular build. ### Define Your Options @@ -40,7 +29,6 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE * `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) -* `USE_ALT_DISPlAY` - Mandatory to use Four Line Display * `ENCODER_DT_PIN` - defaults to 18 * `ENCODER_CLK_PIN` - defaults to 5 * `ENCODER_SW_PIN` - defaults to 19 @@ -50,7 +38,7 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE ### PlatformIO requirements -Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. +No special requirements. ## Change Log From 4cc2cc4ad40b4611124587f7496f1554abd9ef07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 26 Oct 2024 15:16:11 +0200 Subject: [PATCH 560/694] Multiple fixes - increase WLED_MAX_BUSSES for C3 (fixes #4215) - fix for #4228 - fix for very long running effect (strip.now, strip.timebase) - C++ API change to allow `seg.setColor().setOpacity()` --- .../stairway-wipe-usermod-v2.h | 2 +- .../stairway_wipe_basic/wled06_usermod.ino | 2 +- wled00/FX.h | 35 ++++++---- wled00/FX_2Dfcn.cpp | 4 +- wled00/FX_fcn.cpp | 70 ++++++++----------- wled00/button.cpp | 1 + wled00/const.h | 2 +- wled00/fcn_declare.h | 1 - wled00/json.cpp | 4 +- wled00/led.cpp | 9 +-- wled00/wled.h | 2 +- 11 files changed, 62 insertions(+), 70 deletions(-) diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index f712316b8..707479df1 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ void setup() { jsonTransitionOnce = true; strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino index c1264ebfb..dc2159ee9 100644 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ b/usermods/stairway_wipe_basic/wled06_usermod.ino @@ -86,7 +86,7 @@ void startWipe() bri = briLast; //turn on transitionDelayTemp = 0; //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/wled00/FX.h b/wled00/FX.h index 385c52476..ad39a7c06 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -525,12 +525,12 @@ typedef struct Segment { inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); - bool setColor(uint8_t slot, uint32_t c); //returns true if changed - void setCCT(uint16_t k); - void setOpacity(uint8_t o); - void setOption(uint8_t n, bool val); - void setMode(uint8_t fx, bool loadDefaults = false); - void setPalette(uint8_t pal); + Segment &setColor(uint8_t slot, uint32_t c); + Segment &setCCT(uint16_t k); + Segment &setOpacity(uint8_t o); + Segment &setOption(uint8_t n, bool val); + Segment &setMode(uint8_t fx, bool loadDefaults = false); + Segment &setPalette(uint8_t pal); uint8_t differs(Segment& b) const; void refreshLightCapabilities(); @@ -545,7 +545,7 @@ typedef struct Segment { * Call resetIfRequired before calling the next effect function. * Safe to call from interrupts and network requests. */ - inline void markForReset() { reset = true; } // setOption(SEG_OPTION_RESET, true) + inline Segment &markForReset() { reset = true; return *this; } // setOption(SEG_OPTION_RESET, true) // transition functions void startTransition(uint16_t dur); // transition has to start before actual segment values change @@ -599,9 +599,15 @@ typedef struct Segment { } // 2D matrix - [[gnu::hot]] uint16_t virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) - [[gnu::hot]] uint16_t virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) - uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + [[gnu::hot]] unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) + [[gnu::hot]] unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) + inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + #ifndef WLED_DISABLE_2D + return (is2D() && map1D2D == M12_pBar) ? virtualWidth() : 1; + #else + return 1; + #endif + } #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color @@ -778,7 +784,8 @@ class WS2812FX { // 96 bytes setTargetFps(uint8_t fps), setupEffectData(); // add default effects to the list; defined in FX.cpp - inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } + inline void resetTimebase() { timebase = 0UL - millis(); } + inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } @@ -834,10 +841,8 @@ class WS2812FX { // 96 bytes inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) - uint32_t - now, - timebase, - getPixelColor(uint16_t) const; + unsigned long now, timebase; + uint32_t getPixelColor(unsigned) const; inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index e38602ebc..7c1ae366b 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -156,7 +156,7 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { @@ -251,7 +251,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 79189ef57..949b6a932 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -509,46 +509,53 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t } -bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed - if (slot >= NUM_COLORS || c == colors[slot]) return false; +Segment &Segment::setColor(uint8_t slot, uint32_t c) { + if (slot >= NUM_COLORS || c == colors[slot]) return *this; if (!_isRGB && !_hasW) { - if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black - if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black + if (slot == 0 && c == BLACK) return *this; // on/off segment cannot have primary color black + if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black } if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast - return true; + return *this; } -void Segment::setCCT(uint16_t k) { +Segment &Segment::setCCT(uint16_t k) { if (k > 255) { //kelvin value, convert to 0-255 if (k < 1900) k = 1900; if (k > 10091) k = 10091; k = (k - 1900) >> 5; } - if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - cct = k; - stateChanged = true; // send UDP/WS broadcast + if (cct != k) { + //DEBUGFX_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); + startTransition(strip.getTransition()); // start transition prior to change + cct = k; + stateChanged = true; // send UDP/WS broadcast + } + return *this; } -void Segment::setOpacity(uint8_t o) { - if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - opacity = o; - stateChanged = true; // send UDP/WS broadcast +Segment &Segment::setOpacity(uint8_t o) { + if (opacity != o) { + //DEBUGFX_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); + startTransition(strip.getTransition()); // start transition prior to change + opacity = o; + stateChanged = true; // send UDP/WS broadcast + } + return *this; } -void Segment::setOption(uint8_t n, bool val) { +Segment &Segment::setOption(uint8_t n, bool val) { bool prevOn = on; if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast + return *this; } -void Segment::setMode(uint8_t fx, bool loadDefaults) { +Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { // skip reserved while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++; if (fx >= strip.getModeCount()) fx = 0; // set solid mode @@ -580,9 +587,10 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { markForReset(); stateChanged = true; // send UDP/WS broadcast } + return *this; } -void Segment::setPalette(uint8_t pal) { +Segment &Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { @@ -590,37 +598,24 @@ void Segment::setPalette(uint8_t pal) { palette = pal; stateChanged = true; // send UDP/WS broadcast } + return *this; } // 2D matrix -uint16_t IRAM_ATTR Segment::virtualWidth() const { +unsigned IRAM_ATTR Segment::virtualWidth() const { unsigned groupLen = groupLength(); unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } -uint16_t IRAM_ATTR Segment::virtualHeight() const { +unsigned IRAM_ATTR Segment::virtualHeight() const { unsigned groupLen = groupLength(); unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } -uint16_t IRAM_ATTR_YN Segment::nrOfVStrips() const { - unsigned vLen = 1; -#ifndef WLED_DISABLE_2D - if (is2D()) { - switch (map1D2D) { - case M12_pBar: - vLen = virtualWidth(); - break; - } - } -#endif - return vLen; -} - // Constants for mapping mode "Pinwheel" #ifndef WLED_DISABLE_2D constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 @@ -1187,10 +1182,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ //do not call this method from system context (network callback) void WS2812FX::finalizeInit() { //reset segment runtimes - for (segment &seg : _segments) { - seg.markForReset(); - seg.resetIfRequired(); - } + restartRuntime(); // for the lack of better place enumerate ledmaps here // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs @@ -1402,7 +1394,7 @@ void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { BusManager::setPixelColor(i, col); } -uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const { +uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const { i = getMappedPixelIndex(i); if (i >= _length) return 0; return BusManager::getPixelColor(i); diff --git a/wled00/button.cpp b/wled00/button.cpp index f02ed3d6d..4d6f954f6 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -215,6 +215,7 @@ void handleAnalog(uint8_t b) briLast = bri; bri = 0; } else { + if (bri == 0) strip.restartRuntime(); bri = aRead; } } else if (macroDoublePress[b] == 249) { diff --git a/wled00/const.h b/wled00/const.h index 14ec23b58..07873deca 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -53,7 +53,7 @@ #else #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB + #define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white #define WLED_MAX_DIGITAL_CHANNELS 2 //#define WLED_MAX_ANALOG_CHANNELS 6 #define WLED_MIN_VIRTUAL_BUSSES 3 diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d44ed43a0..1855a8b63 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -169,7 +169,6 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); void setValuesFromSegment(uint8_t s); void setValuesFromMainSeg(); void setValuesFromFirstSelectedSeg(); -void resetTimebase(); void toggleOnOff(); void applyBri(); void applyFinalBri(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 06eb3015e..288059653 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -346,7 +346,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } - int tr = -1; + long tr = -1; if (!presetId || currentPlaylist < 0) { //do not apply transition time from preset if playlist active, as it would override playlist transition times tr = root[F("transition")] | -1; if (tr >= 0) { @@ -363,7 +363,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } tr = root[F("tb")] | -1; - if (tr >= 0) strip.timebase = ((uint32_t)tr) - millis(); + if (tr >= 0) strip.timebase = (unsigned long)tr - millis(); JsonObject nl = root["nl"]; nightlightActive = getBoolVal(nl["on"], nightlightActive); diff --git a/wled00/led.cpp b/wled00/led.cpp index 9de0495b4..9b97091e6 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -47,17 +47,12 @@ void applyValuesToSelectedSegs() } -void resetTimebase() -{ - strip.timebase = 0 - millis(); -} - - void toggleOnOff() { if (bri == 0) { bri = briLast; + strip.restartRuntime(); } else { briLast = bri; @@ -122,7 +117,7 @@ void stateUpdated(byte callMode) { nightlightStartTime = millis(); } if (briT == 0) { - if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning + if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning } if (bri > 0) briLast = bri; diff --git a/wled00/wled.h b/wled00/wled.h index bc525cd6f..912e0b3c0 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2410140 +#define VERSION 2410260 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 798c398f23f9ea2c6e826acafa5a4a81ce35945a Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 20:33:38 +0200 Subject: [PATCH 561/694] specified required nodejs ver --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index d76d87687..0c016329b 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,8 @@ "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", "nodemon": "^3.1.7" + }, + "engines": { + "node": ">=20.0.0" } } From 7c6bc5c4211c5c45342b2b833bf3d04991ee76ea Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 21:07:17 +0200 Subject: [PATCH 562/694] indened formating --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c016329b..35fda6002 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=21.0.0" } } From d05c358fd23d86697ec343b9c46c231d30346fcb Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sun, 27 Oct 2024 11:13:56 +0100 Subject: [PATCH 563/694] version changed to 20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35fda6002..0c016329b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=21.0.0" + "node": ">=20.0.0" } } From 2703c9899afd6a9c6f3f41eccce03b6b11208a0a Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 27 Oct 2024 15:08:25 +0100 Subject: [PATCH 564/694] Bugfix in FX `ripple_base()` --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d4b83de6c..2f24f745a 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2506,9 +2506,9 @@ static uint16_t ripple_base() { #endif { int left = rippleorigin - propI -1; - int right = rippleorigin + propI +3; + int right = rippleorigin + propI +2; for (int v = 0; v < 4; v++) { - unsigned mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); + unsigned mag = scale8(cubicwave8((propF>>2) + v * 64), amp); SEGMENT.setPixelColor(left + v, color_blend(SEGMENT.getPixelColor(left + v), col, mag)); // TODO SEGMENT.setPixelColor(right - v, color_blend(SEGMENT.getPixelColor(right - v), col, mag)); // TODO } From 6e89346f00e8b240915313f9d99a9121cbc60190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 27 Oct 2024 18:47:10 +0100 Subject: [PATCH 565/694] WLED 0.15.0-b7 release - fix for #4172 - fix for #4230 - /json/live enabled when WS disabled --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- wled00/data/index.css | 7 ++++++- wled00/wled.h | 7 ++++--- wled00/xml.cpp | 2 +- 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dad83d9b..452e02b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## WLED changelog +#### Build 2410270 +- WLED 0.15.0-b7 release +- Add visual expand button on hover (#4172) +- `/json/live` (JSON live data/peek) only enabled when WebSockets are disabled +- Bugfixes: #4179, #4215, #4219, #4224, #4228, #4230 + #### Build 2410140 - WLED 0.15.0-b6 release - Added BRT timezone (#4188 by @LuisFadini) diff --git a/package-lock.json b/package-lock.json index 85ee1df0f..0f73bff0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b6", + "version": "0.15.0-b7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b6", + "version": "0.15.0-b7", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index d76d87687..9d095c82c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b6", + "version": "0.15.0-b7", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/data/index.css b/wled00/data/index.css index 6f465e407..0e36ff08c 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -1040,7 +1040,7 @@ textarea { .segname .flr, .pname .flr { transform: rotate(0deg); - right: -6px; + /*right: -6px;*/ } /* segment power wrapper */ @@ -1335,6 +1335,11 @@ TD .checkmark, TD .radiomark { box-shadow: 0 0 10px 4px var(--c-1); } +.lstI .flr:hover { + background: var(--c-6); + border-radius: 100%; +} + #pcont .selected:not([class*="expanded"]) { bottom: 52px; top: 42px; diff --git a/wled00/wled.h b/wled00/wled.h index 912e0b3c0..2b3a77d24 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-b6 + @version 0.15.0-b7 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2410260 +#define VERSION 2410270 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -36,12 +36,13 @@ #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif //#define WLED_ENABLE_DMX // uses 3.5kb -//#define WLED_ENABLE_JSONLIVE // peek LED output via /json/live (WS binary peek is always enabled) #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb #endif #ifndef WLED_DISABLE_WEBSOCKETS #define WLED_ENABLE_WEBSOCKETS +#else + #define WLED_ENABLE_JSONLIVE // peek LED output via /json/live (WS binary peek is always enabled) #endif //#define WLED_DISABLE_ESPNOW // Removes dependence on esp now diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 6d1ff2f86..dc2673271 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -501,7 +501,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #endif printSetFormValue(settingsScript,PSTR("BD"),serialBaud); #ifndef WLED_ENABLE_ADALIGHT - settingsScript.print(F("toggle('Serial);")); + settingsScript.print(F("toggle('Serial');")); #endif } From 4588219e3134481a9f3dc66077811bf5d16f7f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 28 Oct 2024 12:42:53 +0100 Subject: [PATCH 566/694] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 452e02b25..c570ac1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ #### Build 2410270 - WLED 0.15.0-b7 release +- Re-license the WLED project from MIT to EUPL (#4194 by @Aircoookie) +- Fix alexa devices invisible/uncontrollable (#4214 by @Svennte) - Add visual expand button on hover (#4172) +- Usermod: Audioreactive tuning and performance enhancements (by @softhack007) - `/json/live` (JSON live data/peek) only enabled when WebSockets are disabled -- Bugfixes: #4179, #4215, #4219, #4224, #4228, #4230 +- Various bugfixes and optimisations: #4179, #4215, #4219, #4222, #4223, #4224, #4228, #4230 #### Build 2410140 - WLED 0.15.0-b6 release From fa053b7e60effbbb76dc49822003ea5acb4b4dcf Mon Sep 17 00:00:00 2001 From: ingDIY <10012263+ingDIY@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:14:02 +0100 Subject: [PATCH 567/694] Update platformio_override.sample.ini fixed back U8g2 version --- .../platformio_override.sample.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini index 9010648b3..e59637453 100644 --- a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini +++ b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini @@ -14,5 +14,5 @@ build_flags = lib_deps = ${esp32.lib_deps} - U8g2 + U8g2@~2.34.4 Wire From 1898be2fe1c9c3e9c05b5b41d2a2d9b597930156 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 21:13:43 +0100 Subject: [PATCH 568/694] S3 WROOM-2 buildenv this chip has 16MB or 32MB flash, and requires .memory_type = opi_opi --- platformio.ini | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/platformio.ini b/platformio.ini index 9628722aa..48731f39b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -556,6 +556,32 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32S3_wroom2] +;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_build.arduino.memory_type = opi_opi +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D LEDPIN=38 ;; buildin LED + -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + ${esp32.AR_build_flags} + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +monitor_filters = esp32_exception_decoder + [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM From 749d34cd30a76e7c17be04f71016960d2331a4de Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 21:31:57 +0100 Subject: [PATCH 569/694] pinmanager support for S3 WROOM-2 (pin 33-37 reserved for flash) --- wled00/pin_manager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 793b5440c..0d4c2ad5c 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -201,7 +201,12 @@ bool PinManager::isPinOk(byte gpio, bool output) if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses. #endif if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH - if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + #if CONFIG_ESPTOOLPY_FLASHMODE_OPI // 33-37: never available if using _octal_ Flash (opi_opi) + if (gpio > 32 && gpio < 38) return false; + #endif + #if CONFIG_SPIRAM_MODE_OCT // 33-37: not available if using _octal_ PSRAM (qio_opi), but free to use on _quad_ PSRAM (qio_qspi) + if (gpio > 32 && gpio < 38) return !psramFound(); + #endif // 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // strapping pins: 0, 45 & 46 From 3c2c5bedc50845c168f7e0881e3a1d6aa973e7d5 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 21:42:54 +0100 Subject: [PATCH 570/694] LEDPIN --> DATA_PINS --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 48731f39b..6d4aa1dc1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -570,8 +570,9 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM - -D LEDPIN=38 ;; buildin LED + -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D WLED_DEBUG ${esp32.AR_build_flags} -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic lib_deps = ${esp32s3.lib_deps} From d98ca9a202795f9851bbeca7e895cb790abd2727 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 21:51:46 +0100 Subject: [PATCH 571/694] show correct flash mode in WLED_DEBUG --- wled00/wled.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 13d43218a..d6a39a399 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -380,6 +380,12 @@ void WLED::setup() case FM_QOUT: DEBUG_PRINT(F("(QOUT)"));break; case FM_DIO: DEBUG_PRINT(F("(DIO)")); break; case FM_DOUT: DEBUG_PRINT(F("(DOUT)"));break; + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_ESPTOOLPY_FLASHMODE_OPI + case FM_FAST_READ: DEBUG_PRINT(F("(OPI)")); break; + #else + case FM_FAST_READ: DEBUG_PRINT(F("(fast_read)")); break; + #endif + case FM_SLOW_READ: DEBUG_PRINT(F("(slow_read)")); break; default: break; } #endif From 451cd4c74aff2faff9ab11f746f9e454ab10bd2e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:19:38 +0100 Subject: [PATCH 572/694] Improved framerate control in strip.show(), strip.service() * separate fps calculation (strip.show) from framerate control (strio.service) * improved condition for early exit in strip.show * make MIN_SHOW_DELAY depend on target fps * strip.show consideres complete time for effect calculation + show; old code wrongly used the time between completion of last show and start of next effect drawing, causing unexpected slowdown * add "unlimited FPS mode" for testing * increase warning limits for "slow strip" and "slow effects" --- wled00/FX.h | 22 +++++++++++++++++----- wled00/FX_fcn.cpp | 37 +++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index ad39a7c06..acdb62c81 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -44,8 +44,20 @@ /* Not used in all effects yet */ #define WLED_FPS 42 -#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) + #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100 + #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% + // used to initialize for strip attributes: + #define WLED_FPS_SLOW 42 + #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms +#else + #define FRAMETIME_FIXED (1000/WLED_FPS) + #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates + #define WLED_FPS_SLOW WLED_FPS + #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED +#endif +#define FPS_UNLIMITED 119 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -68,8 +80,6 @@ assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) -#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) - #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()] @@ -727,8 +737,8 @@ class WS2812FX { // 96 bytes _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), _transitionDur(750), - _targetFps(WLED_FPS), - _frametime(FRAMETIME_FIXED), + _targetFps(WLED_FPS_SLOW), + _frametime(FRAMETIME_FIXED_SLOW), _cumulativeFps(2), _isServicing(false), _isOffRefreshRequired(false), @@ -739,6 +749,7 @@ class WS2812FX { // 96 bytes customMappingTable(nullptr), customMappingSize(0), _lastShow(0), + _lastServiceShow(0), _segment_index(0), _mainSegment(0) { @@ -949,6 +960,7 @@ class WS2812FX { // 96 bytes uint16_t customMappingSize; unsigned long _lastShow; + unsigned long _lastServiceShow; uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 949b6a932..5856ad786 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1309,11 +1309,23 @@ void WS2812FX::finalizeInit() { void WS2812FX::service() { unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; - if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; + if (_suspend) return; + unsigned long elapsed = nowUp - _lastServiceShow; + + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if (elapsed < 2) return; // keep wifi alive + if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) { + if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service + } + #else // legacy + if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + #endif + bool doShow = false; _isServicing = true; _segment_index = 0; + unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1326,10 +1338,10 @@ void WS2812FX::service() { if (!seg.isActive()) continue; // last condition ensures all solid segments are updated at the same time - if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) + if (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned delay = FRAMETIME; + unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) @@ -1349,7 +1361,8 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode + frameDelay = (*_mode[seg.mode])(); // run new/current mode + if (frameDelay < speedLimit) frameDelay = FRAMETIME; // limit effects that want to go faster than target FPS #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1358,16 +1371,16 @@ void WS2812FX::service() { _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay + frameDelay = min(frameDelay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } #endif seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } @@ -1376,15 +1389,16 @@ void WS2812FX::service() { _triggered = false; #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif if (doShow) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); + _lastServiceShow = nowUp; // correct timestamp, for better FPS control } #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif } @@ -1404,18 +1418,19 @@ void WS2812FX::show() { // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); + unsigned long showNow = millis(); // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods BusManager::show(); - unsigned long showNow = millis(); size_t diff = showNow - _lastShow; size_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) _lastShow = showNow; + _lastServiceShow = showNow; } /** @@ -1438,6 +1453,8 @@ uint16_t WS2812FX::getFps() const { void WS2812FX::setTargetFps(uint8_t fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; + if (_frametime < 1) _frametime = 1; // better safe than sorry + if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { From 70323b947745e81644b955d3d16cc2bd24059636 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:50:30 +0100 Subject: [PATCH 573/694] rename delay -> frameDelay Avoiding name collisions with the 'delay' function. --- wled00/FX_fcn.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 949b6a932..f45256f0f 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1329,7 +1329,7 @@ void WS2812FX::service() { if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned delay = FRAMETIME; + unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) @@ -1349,7 +1349,7 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode + frameDelay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1358,16 +1358,16 @@ void WS2812FX::service() { _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay + frameDelay = min(frameDelay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } #endif seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } From 50934e6840995154109ab7d532dbb61af0377f90 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 2 Nov 2024 18:16:51 +0100 Subject: [PATCH 574/694] adressing some review comments * keep FRAMETIME_FIXED as a fixed value * remove WLED_FPS_SLOW and FRAMETIME_FIXED_SLOW * explicit test "(_targetFps != FPS_UNLIMITED)" for debug messages * don't modify _lastServiceShow in show() * test for "fps == FPS_UNLIMITED" explicitly, so we could pick a different magic number later --- wled00/FX.h | 13 ++++--------- wled00/FX_fcn.cpp | 12 +++++------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index acdb62c81..1f0df9da3 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -44,20 +44,15 @@ /* Not used in all effects yet */ #define WLED_FPS 42 +#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100 #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% // used to initialize for strip attributes: - #define WLED_FPS_SLOW 42 - #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms #else - #define FRAMETIME_FIXED (1000/WLED_FPS) #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates - #define WLED_FPS_SLOW WLED_FPS - #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED #endif -#define FPS_UNLIMITED 119 +#define FPS_UNLIMITED 120 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -737,8 +732,8 @@ class WS2812FX { // 96 bytes _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), _transitionDur(750), - _targetFps(WLED_FPS_SLOW), - _frametime(FRAMETIME_FIXED_SLOW), + _targetFps(WLED_FPS), + _frametime(FRAMETIME_FIXED), _cumulativeFps(2), _isServicing(false), _isOffRefreshRequired(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 5856ad786..3972dad2c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1314,7 +1314,7 @@ void WS2812FX::service() { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) { + if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) { if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service } #else // legacy @@ -1325,7 +1325,7 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; - unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime + unsigned speedLimit = (_targetFps != FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1389,7 +1389,7 @@ void WS2812FX::service() { _triggered = false; #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif if (doShow) { yield(); @@ -1398,7 +1398,7 @@ void WS2812FX::service() { _lastServiceShow = nowUp; // correct timestamp, for better FPS control } #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif } @@ -1430,7 +1430,6 @@ void WS2812FX::show() { if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) _lastShow = showNow; - _lastServiceShow = showNow; } /** @@ -1453,8 +1452,7 @@ uint16_t WS2812FX::getFps() const { void WS2812FX::setTargetFps(uint8_t fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; - if (_frametime < 1) _frametime = 1; // better safe than sorry - if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode + if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { From bf37ac53a3dd5c666027932027502c72e69b6996 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 08:10:05 +0100 Subject: [PATCH 575/694] improved FPS calc resolution, added averaging & multiplier compileflags Fixed point calculation for improved accuracy, dithering in debug builds only. Averaging and optional multiplier can be set as compile flags, example for speed testing with long averaging and a 10x multiplier: -D FPS_CALC_AVG=200 -D FPS_MULTIPLIER=10 The calculation resolution is limited (9.7bit fixed point) so values larger than 200 can hit resolution limit and get stuck before reaching the final value. If WLED_DEBUG is defined, dithering is added to the returned value so sub-frame accuracy is possible in post-processingwithout enabling the multiplier. --- wled00/FX.h | 10 +++++++++- wled00/FX_fcn.cpp | 16 +++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index ad39a7c06..1579a5bcb 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -47,6 +47,14 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() +// FPS calculation (can be defined as compile flag for debugging) +#ifndef FPS_CALC_AVG +#define FPS_CALC_AVG 7 // average FPS calculation over this many frames (moving average) +#endif +#ifndef FPS_MULTIPLIER +#define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats +#endif + /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 @@ -729,7 +737,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(2), + _cumulativeFps(50<<6), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f45256f0f..395451466 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1412,10 +1412,12 @@ void WS2812FX::show() { unsigned long showNow = millis(); size_t diff = showNow - _lastShow; - size_t fpsCurr = 200; - if (diff > 0) fpsCurr = 1000 / diff; - _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - _lastShow = showNow; + + if (diff > 0) { // skip calculation if no time has passed + size_t fpsCurr = (1000<<7) / diff; // fixed point 9.7 bit + _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding + _lastShow = showNow; + } } /** @@ -1432,7 +1434,11 @@ bool WS2812FX::isUpdating() const { */ uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; - return _cumulativeFps +1; + #ifdef WLED_DEBUG + return (FPS_MULTIPLIER * (_cumulativeFps + (random16() & 63))) >> 7; // + random("0.5") for dithering + #else + return (FPS_MULTIPLIER * _cumulativeFps) >> 7; // _cumulativeFps is stored in fixed point 9.7 bit + #endif } void WS2812FX::setTargetFps(uint8_t fps) { From 3733715184df2a684820a54f0d7caf61ab1dc0ac Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 17:38:45 +0100 Subject: [PATCH 576/694] bugfix bitshift was still set from testing, forgot to update --- wled00/FX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1579a5bcb..e5b7a0e95 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -737,7 +737,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50<<6), + _cumulativeFps(50<<7), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), From 4634ace74e9e7295ff1518b449112146475df349 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 19:33:42 +0100 Subject: [PATCH 577/694] Added define for bitshift, removed dithering dithering is not really needed, the FPS_MULTIPLIER is a much better option. --- wled00/FX.h | 3 ++- wled00/FX_fcn.cpp | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index e5b7a0e95..545161546 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -54,6 +54,7 @@ #ifndef FPS_MULTIPLIER #define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats #endif +#define FPS_CALC_SHIFT 7 // bit shift for fixed point math /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -737,7 +738,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50<<7), + _cumulativeFps(50 << FPS_CALC_SHIFT), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 395451466..e706f2b43 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1414,7 +1414,7 @@ void WS2812FX::show() { size_t diff = showNow - _lastShow; if (diff > 0) { // skip calculation if no time has passed - size_t fpsCurr = (1000<<7) / diff; // fixed point 9.7 bit + size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding _lastShow = showNow; } @@ -1434,11 +1434,7 @@ bool WS2812FX::isUpdating() const { */ uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; - #ifdef WLED_DEBUG - return (FPS_MULTIPLIER * (_cumulativeFps + (random16() & 63))) >> 7; // + random("0.5") for dithering - #else - return (FPS_MULTIPLIER * _cumulativeFps) >> 7; // _cumulativeFps is stored in fixed point 9.7 bit - #endif + return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point } void WS2812FX::setTargetFps(uint8_t fps) { From cf1630a94a3de7b59e2dd21562a607627c9fa2d2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:49:43 +0100 Subject: [PATCH 578/694] 0 FPS = unlimited --- wled00/FX.h | 2 +- wled00/FX_fcn.cpp | 8 ++++---- wled00/data/settings_leds.htm | 8 +++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1f0df9da3..5c80686d6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -52,7 +52,7 @@ #else #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates #endif -#define FPS_UNLIMITED 120 +#define FPS_UNLIMITED 0 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3972dad2c..30882c4e7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1314,7 +1314,7 @@ void WS2812FX::service() { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) { + if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service } #else // legacy @@ -1450,9 +1450,9 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; - _frametime = 1000 / _targetFps; - if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode + if (fps <= 120) _targetFps = fps; + if (_targetFps > 0) _frametime = 1000 / _targetFps; + else _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..35bbbd312 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -380,6 +380,10 @@ gId('psu').innerHTML = s; gId('psu2').innerHTML = s2; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; + + // show/hide unlimited FPS message + gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none'; + gId('fpsHelp').style.display = (d.Sf.FR.value == 0)? 'none':'block'; } function lastEnd(i) { if (i-- < 1) return 0; @@ -870,7 +874,9 @@ Swap:
- Target refresh rate: FPS + Target refresh rate: FPS +
use 0 for unlimited
+
Config template:

From 029293a08621df71ed0546ed5bf3c0e4cc9d49c7 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:11:10 +0100 Subject: [PATCH 579/694] simplify sheduler logic * _frametime ensures that effects are not serviced too often * MIN_SHOW_DELAY is the minimum allowed FRAMETIME that can be requested by effects --- wled00/FX.h | 3 +-- wled00/FX_fcn.cpp | 13 ++++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 5c80686d6..d749d7796 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -47,8 +47,7 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% - // used to initialize for strip attributes: + #define MIN_SHOW_DELAY 3 // supports higher framerates and better animation control #else #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates #endif diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 30882c4e7..6bc61d10d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1312,20 +1312,15 @@ void WS2812FX::service() { if (_suspend) return; unsigned long elapsed = nowUp - _lastServiceShow; - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { - if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service + if (elapsed < 2) return; // keep wifi alive - no matter if triggered or unlimited + if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime + if (elapsed < _frametime) return; // too early for service } - #else // legacy - if (nowUp - _lastShow < MIN_SHOW_DELAY) return; - #endif bool doShow = false; _isServicing = true; _segment_index = 0; - unsigned speedLimit = (_targetFps != FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1362,7 +1357,6 @@ void WS2812FX::service() { // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition frameDelay = (*_mode[seg.mode])(); // run new/current mode - if (frameDelay < speedLimit) frameDelay = FRAMETIME; // limit effects that want to go faster than target FPS #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1375,6 +1369,7 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } #endif + frameDelay = max(frameDelay, unsigned(MIN_SHOW_DELAY)); // limit effects that want to go faster than target FPS seg.call++; if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments From ab7b2d729e68099b140473079dc22ea9b186d7b5 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:24:20 +0100 Subject: [PATCH 580/694] use class="warn" for unlimited mode message --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 35bbbd312..caeaacea9 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -876,7 +876,7 @@ Swap:
Target refresh rate: FPS
use 0 for unlimited
- +
Config template:

From 1e761c31bdd53850aca805c9400ef24b10f3afda Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 6 Nov 2024 22:09:33 +0100 Subject: [PATCH 581/694] simpler hight FPS warning * removed "use 0 for unlimited" * added "high FPS mode is experimental" warning * added "backup first!" warning * added anchor #backup to sec page --- wled00/data/settings_leds.htm | 10 ++++++---- wled00/data/settings_sec.htm | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index caeaacea9..55f8122e8 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -381,9 +381,10 @@ gId('psu2').innerHTML = s2; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; - // show/hide unlimited FPS message + // show/hide FPS warning messages gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none'; - gId('fpsHelp').style.display = (d.Sf.FR.value == 0)? 'none':'block'; + gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none'; + gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none'; } function lastEnd(i) { if (i-- < 1) return 0; @@ -875,8 +876,9 @@ Swap:
Target refresh rate: FPS -
use 0 for unlimited
- + + +
Config template:

diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..5181858d5 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -57,7 +57,7 @@

Software Update


Enable ArduinoOTA: -
+

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
From 6ff5c88ebf0525f8101c49f19702c0ee0cec0e65 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 7 Nov 2024 08:17:08 +0000 Subject: [PATCH 582/694] List ESP32 first --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 11c1733f8..595b9ee0b 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ # Welcome to my project WLED! ✨ -A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! +A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! ## ⚙️ Features - WS2812FX library with more than 100 special effects From 0404ec988137acca997e620384cf446b19dce350 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:15:39 +0100 Subject: [PATCH 583/694] changes in response to feedback from @willmmiles * MIN_SHOW_DELAY -> MIN_FRAME_DELAY * allow up to 250 for target FPS * minor cleanup * added specific MIN_FRAME_DELAY for -S2 --- wled00/FX.h | 10 ++++++---- wled00/FX_fcn.cpp | 11 +++++------ wled00/data/settings_leds.htm | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index d749d7796..56a0c9bd0 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -46,10 +46,12 @@ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define MIN_SHOW_DELAY 3 // supports higher framerates and better animation control +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) + #define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + #define MIN_FRAME_DELAY 4 // S2 is slower than normal esp32, and only has one core #else - #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates + #define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY #endif #define FPS_UNLIMITED 0 @@ -842,7 +844,7 @@ class WS2812FX { // 96 bytes getMappedPixelIndex(uint16_t index) const; inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) - inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) + inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6bc61d10d..f4b46dade 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1312,7 +1312,7 @@ void WS2812FX::service() { if (_suspend) return; unsigned long elapsed = nowUp - _lastServiceShow; - if (elapsed < 2) return; // keep wifi alive - no matter if triggered or unlimited + if (elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime if (elapsed < _frametime) return; // too early for service } @@ -1369,7 +1369,6 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } #endif - frameDelay = max(frameDelay, unsigned(MIN_SHOW_DELAY)); // limit effects that want to go faster than target FPS seg.call++; if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments @@ -1390,7 +1389,7 @@ void WS2812FX::service() { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); - _lastServiceShow = nowUp; // correct timestamp, for better FPS control + _lastServiceShow = nowUp; // update timestamp, for precise FPS control } #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); @@ -1445,9 +1444,9 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps <= 120) _targetFps = fps; + if (fps <= 250) _targetFps = fps; if (_targetFps > 0) _frametime = 1000 / _targetFps; - else _frametime = 3; // unlimited mode + else _frametime = MIN_FRAME_DELAY; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { @@ -1495,7 +1494,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { BusManager::setBrightness(b); if (!direct) { unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon } } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 55f8122e8..b467d5b9b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -875,7 +875,7 @@ Swap:
- Target refresh rate: FPS + Target refresh rate: FPS From 001e2ad2875742b51861ba826baa9a9727698ea0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 8 Nov 2024 19:35:42 +0100 Subject: [PATCH 584/694] adjust audioreactive for the new FRAME_DELAY logic minor --- usermods/audioreactive/audio_reactive.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ad449fc83..9c463e0a1 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -75,7 +75,7 @@ static uint8_t soundAgc = 0; // Automagic gain control: 0 - n //static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getFrameTime() static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData static unsigned long timeOfPeak = 0; // time of last sample peak detection. static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects @@ -536,8 +536,8 @@ static void detectSamplePeak(void) { #endif static void autoResetPeak(void) { - uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC - if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + uint16_t peakDelay = max(uint16_t(50), strip.getFrameTime()); + if (millis() - timeOfPeak > peakDelay) { // Auto-reset of samplePeak after at least one complete frame has passed. samplePeak = false; if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData } From ef1e24cec26cdb05b7a342e7a432f03692ca521e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 9 Nov 2024 10:42:49 +0100 Subject: [PATCH 585/694] Bugfix & code reduction - correctly clear segment spacing change - renamed Segment::setUp() to Segment::setGeometry() - removed WS2812FX::setSegment() - removed obsolete/unfunctional word clock usermod .cpp file --- .../usermod_word_clock_matrix.h | 128 ++++---- .../word-clock-matrix/word-clock-matrix.cpp | 305 ------------------ wled00/FX.h | 6 +- wled00/FX_fcn.cpp | 34 +- wled00/json.cpp | 16 +- wled00/set.cpp | 4 +- wled00/udp.cpp | 15 +- 7 files changed, 99 insertions(+), 409 deletions(-) delete mode 100644 usermods/word-clock-matrix/word-clock-matrix.cpp diff --git a/usermods/word-clock-matrix/usermod_word_clock_matrix.h b/usermods/word-clock-matrix/usermod_word_clock_matrix.h index 506c1275e..82499c0ce 100644 --- a/usermods/word-clock-matrix/usermod_word_clock_matrix.h +++ b/usermods/word-clock-matrix/usermod_word_clock_matrix.h @@ -31,14 +31,14 @@ public: //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //select first two segments (background color + FX settable) - WS2812FX::Segment &seg = strip.getSegment(0); + Segment &seg = strip.getSegment(0); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(2, false); //other segments are text for (int i = 1; i < 10; i++) { - WS2812FX::Segment &seg = strip.getSegment(i); + Segment &seg = strip.getSegment(i); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); strip.getSegment(i).setOption(0, true); strip.setBrightness(64); @@ -80,61 +80,61 @@ public: void displayTime(byte hour, byte minute) { bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is + strip.getSegment(0).setGeometry(0, 64); // background + strip.getSegment(1).setGeometry(0, 2); //It is - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock + strip.getSegment(2).setGeometry(0, 0); + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //past + strip.getSegment(6).setGeometry(0, 0); //to + strip.getSegment(8).setGeometry(0, 0); //disable o'clock if (hour < 24) //valid time, display { if (minute == 30) { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(2).setGeometry(3, 6); //half + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 15 || minute == 45) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 10) { - //strip.setSegment(5, 6, 8); //ten + //strip.getSegment(5).setGeometry(6, 8); //ten } else if (minute == 5) { - //strip.setSegment(5, 16, 18); //five + //strip.getSegment(5).setGeometry(16, 18); //five } else if (minute == 0) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes //hourChime(); } else { - strip.setSegment(3, 18, 22); //minutes + strip.getSegment(3).setGeometry(18, 22); //minutes } //past or to? if (minute == 0) { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //disable past + strip.getSegment(6).setGeometry(0, 0); //disable to + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute > 34) { - //strip.setSegment(6, 22, 24); //to + //strip.getSegment(6).setGeometry(22, 24); //to //minute = 60 - minute; isToHour = true; } else { - //strip.setSegment(4, 24, 27); //past + //strip.getSegment(4).setGeometry(24, 27); //past //isToHour = false; } } @@ -143,68 +143,68 @@ public: if (minute <= 4) { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //nothing + strip.getSegment(5).setGeometry(0, 0); //nothing + strip.getSegment(6).setGeometry(0, 0); //nothing + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute <= 9) { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(16, 18); // five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 14) { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(6, 8); // ten past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 19) { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(8, 12); // quarter past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 24) { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 16); // twenty past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 29) { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 18); // twenty-five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 34) { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(3, 6); // half past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 39) { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 18); // twenty-five to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 44) { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 16); // twenty to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 49) { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(8, 12); // quarter to + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 54) { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(6, 8); // ten to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 59) { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(16, 18); // five to + strip.getSegment(6).setGeometry(22, 24); //to } //hours @@ -220,45 +220,45 @@ public: switch (hour) { case 1: - strip.setSegment(7, 27, 29); + strip.getSegment(7).setGeometry(27, 29); break; //one case 2: - strip.setSegment(7, 35, 37); + strip.getSegment(7).setGeometry(35, 37); break; //two case 3: - strip.setSegment(7, 29, 32); + strip.getSegment(7).setGeometry(29, 32); break; //three case 4: - strip.setSegment(7, 32, 35); + strip.getSegment(7).setGeometry(32, 35); break; //four case 5: - strip.setSegment(7, 37, 40); + strip.getSegment(7).setGeometry(37, 40); break; //five case 6: - strip.setSegment(7, 43, 45); + strip.getSegment(7).setGeometry(43, 45); break; //six case 7: - strip.setSegment(7, 40, 43); + strip.getSegment(7).setGeometry(40, 43); break; //seven case 8: - strip.setSegment(7, 45, 48); + strip.getSegment(7).setGeometry(45, 48); break; //eight case 9: - strip.setSegment(7, 48, 50); + strip.getSegment(7).setGeometry(48, 50); break; //nine case 10: - strip.setSegment(7, 54, 56); + strip.getSegment(7).setGeometry(54, 56); break; //ten case 11: - strip.setSegment(7, 50, 54); + strip.getSegment(7).setGeometry(50, 54); break; //eleven case 12: - strip.setSegment(7, 56, 60); + strip.getSegment(7).setGeometry(56, 60); break; //twelve } selectWordSegments(true); - applyMacro(1); + applyPreset(1); } void timeOfDay() diff --git a/usermods/word-clock-matrix/word-clock-matrix.cpp b/usermods/word-clock-matrix/word-clock-matrix.cpp deleted file mode 100644 index 67c5b1e47..000000000 --- a/usermods/word-clock-matrix/word-clock-matrix.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "wled.h" -/* - * This v1 usermod file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h) - * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) - * - * Consider the v2 usermod API if you need a more advanced feature set! - */ - - -uint8_t minuteLast = 99; -int dayBrightness = 128; -int nightBrightness = 16; - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ -saveMacro(14, "A=128", false); -saveMacro(15, "A=64", false); -saveMacro(16, "A=16", false); - -saveMacro(1, "&FX=0&R=255&G=255&B=255", false); - -//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - - //select first two segments (background color + FX settable) - Segment &seg = strip.getSegment(0); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); - strip.getSegment(0).setOption(0, false); - strip.getSegment(0).setOption(2, false); - //other segments are text - for (int i = 1; i < 10; i++) - { - Segment &seg = strip.getSegment(i); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); - strip.getSegment(i).setOption(0, true); - strip.setBrightness(128); - } -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ -} - -void selectWordSegments(bool state) -{ - for (int i = 1; i < 10; i++) - { - //Segment &seg = strip.getSegment(i); - strip.getSegment(i).setOption(0, state); - // strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - //seg.mode = 12; - //seg.palette = 1; - //strip.setBrightness(255); - } - strip.getSegment(0).setOption(0, !state); -} - -void hourChime() -{ - //strip.resetSegments(); - selectWordSegments(true); - colorUpdated(CALL_MODE_FX_CHANGED); - //savePreset(255); - selectWordSegments(false); - //strip.getSegment(0).setOption(0, true); - strip.getSegment(0).setOption(2, true); - applyPreset(12); - colorUpdated(CALL_MODE_FX_CHANGED); -} - -void displayTime(byte hour, byte minute) -{ - bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is - - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock - - if (hour < 24) //valid time, display - { - if (minute == 30) - { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 15 || minute == 45) - { - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 10) - { - //strip.setSegment(5, 6, 8); //ten - } - else if (minute == 5) - { - //strip.setSegment(5, 16, 18); //five - } - else if (minute == 0) - { - strip.setSegment(3, 0, 0); //minutes - //hourChime(); - } - else - { - strip.setSegment(3, 18, 22); //minutes - } - - //past or to? - if (minute == 0) - { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute > 34) - { - //strip.setSegment(6, 22, 24); //to - //minute = 60 - minute; - isToHour = true; - } - else - { - //strip.setSegment(4, 24, 27); //past - //isToHour = false; - } - } - else - { //temperature display - } - - //byte minuteRem = minute %10; - - if (minute <= 4) - { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute <= 9) - { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 14) - { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 19) - { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 24) - { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 29) - { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 34) - { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 39) - { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 44) - { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 49) - { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 54) - { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 59) - { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to - } - - //hours - if (hour > 23) - return; - if (isToHour) - hour++; - if (hour > 12) - hour -= 12; - if (hour == 0) - hour = 12; - - switch (hour) - { - case 1: - strip.setSegment(7, 27, 29); - break; //one - case 2: - strip.setSegment(7, 35, 37); - break; //two - case 3: - strip.setSegment(7, 29, 32); - break; //three - case 4: - strip.setSegment(7, 32, 35); - break; //four - case 5: - strip.setSegment(7, 37, 40); - break; //five - case 6: - strip.setSegment(7, 43, 45); - break; //six - case 7: - strip.setSegment(7, 40, 43); - break; //seven - case 8: - strip.setSegment(7, 45, 48); - break; //eight - case 9: - strip.setSegment(7, 48, 50); - break; //nine - case 10: - strip.setSegment(7, 54, 56); - break; //ten - case 11: - strip.setSegment(7, 50, 54); - break; //eleven - case 12: - strip.setSegment(7, 56, 60); - break; //twelve - } - -selectWordSegments(true); -applyMacro(1); -} - -void timeOfDay() { -// NOT USED: use timed macros instead - //Used to set brightness dependant of time of day - lights dimmed at night - - //monday to thursday and sunday - - if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) { - if (hour(localTime) > 0 | hour(localTime) < 8) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } - else { - if (hour(localTime) < 6 | hour(localTime) >= 22) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - if (minute(localTime) != minuteLast) - { - updateLocalTime(); - //timeOfDay(); - minuteLast = minute(localTime); - displayTime(hour(localTime), minute(localTime)); - if (minute(localTime) == 0){ - hourChime(); - } - if (minute(localTime) == 1){ - //turn off background segment; - strip.getSegment(0).setOption(2, false); - //applyPreset(255); - } - } -} diff --git a/wled00/FX.h b/wled00/FX.h index c06332c76..d56c0fa99 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -539,6 +539,7 @@ typedef struct Segment { inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t groupLength() const { return grouping + spacing; } inline uint8_t getLightCapabilities() const { return _capabilities; } + inline void deactivate() { setGeometry(0,0); } inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } @@ -554,14 +555,14 @@ typedef struct Segment { static void handleRandomPalette(); void beginDraw(); // set up parameters for current effect - void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); + void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12=0); bool setColor(uint8_t slot, uint32_t c); //returns true if changed void setCCT(uint16_t k); void setOpacity(uint8_t o); void setOption(uint8_t n, bool val); void setMode(uint8_t fx, bool loadDefaults = false); void setPalette(uint8_t pal); - uint8_t differs(Segment& b) const; + uint8_t differs(const Segment& b) const; void refreshLightCapabilities(); // runtime data functions @@ -783,7 +784,6 @@ class WS2812FX { // 96 bytes setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) - setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(unsigned n = 0), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 613b41fad..69b0c028a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -489,8 +489,10 @@ void Segment::handleRandomPalette() { nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } -// segId is given when called from network callback, changes are queued if that segment is currently in its effect function -void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { +// sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping) +// strip must be suspended (strip.suspend()) before calling this function +// this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw()) +void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) { // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D @@ -498,11 +500,19 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t #endif if (boundsUnchanged && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset)) return; + && (ofs == UINT16_MAX || ofs == offset) + && (m12 == map1D2D) + ) return; stateChanged = true; // send UDP/WS broadcast - if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) + if (stop || spc != spacing || m12 != map1D2D) { + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + _segBri = currentBri(); + fill(BLACK); // turn old segment range off or clears pixels if changing spacing (requires _vWidth/_vHeight/_vLength/_segBri) + } if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -511,6 +521,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t spacing = 0; } if (ofs < UINT16_MAX) offset = ofs; + map1D2D = constrain(m12, 0, 7); DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); DEBUG_PRINT(','); DEBUG_PRINT(i2); @@ -993,7 +1004,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return strip.getPixelColor(i); } -uint8_t Segment::differs(Segment& b) const { +uint8_t Segment::differs(const Segment& b) const { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; @@ -1595,19 +1606,6 @@ Segment& WS2812FX::getSegment(unsigned id) { return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors } -// sets new segment bounds, queues if that segment is currently running -void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { - if (segId >= getSegmentsNum()) { - if (i2 <= i1) return; // do not append empty/inactive segments - appendSegment(Segment(0, strip.getLengthTotal())); - segId = getSegmentsNum()-1; // segments are added at the end of list - } - suspend(); - _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); - resume(); - if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector -} - void WS2812FX::resetSegments() { _segments.clear(); // destructs all Segment as part of clearing #ifndef WLED_DISABLE_2D diff --git a/wled00/json.cpp b/wled00/json.cpp index 0df7294c8..64b9ddd8d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -34,7 +34,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) //DEBUG_PRINTLN(F("-- JSON deserialize segment.")); Segment& seg = strip.getSegment(id); //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); - Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) + const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) //DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); int start = elem["start"] | seg.start; @@ -96,17 +96,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) uint16_t of = seg.offset; uint8_t soundSim = elem["si"] | seg.soundSim; uint8_t map1D2D = elem["m12"] | seg.map1D2D; - - if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps - - seg.map1D2D = constrain(map1D2D, 0, 7); + uint8_t set = elem[F("set")] | seg.set; + seg.set = constrain(set, 0, 3); seg.soundSim = constrain(soundSim, 0, 3); - uint8_t set = elem[F("set")] | seg.set; - seg.set = constrain(set, 0, 3); - - int len = 1; - if (stop > start) len = stop - start; + int len = (stop > start) ? stop - start : 1; int offset = elem[F("of")] | INT32_MAX; if (offset != INT32_MAX) { int offsetAbs = abs(offset); @@ -117,7 +111,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (stop > start && of > len -1) of = len -1; // update segment (delete if necessary) - seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues + seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues if (newSeg) seg.refreshLightCapabilities(); // fix for #3403 diff --git a/wled00/set.cpp b/wled00/set.cpp index cf3a07dd0..15981d30d 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -874,7 +874,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (pos > 0) { spcI = std::max(0,getNumVal(&req, pos)); } - strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY); + strip.suspend(); // must suspend strip operations before changing geometry + selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D); + strip.resume(); pos = req.indexOf(F("RV=")); //Segment reverse if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0'; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index a6a0f6aa2..47398bc8a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -260,11 +260,12 @@ void parseNotifyPacket(uint8_t *udpIn) { // are we syncing bounds and slave has more active segments than master? if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) { DEBUG_PRINTLN(F("Removing excessive segments.")); - for (size_t i=strip.getSegmentsNum(); i>numSrcSegs; i--) { - if (strip.getSegment(i).isActive()) { - strip.setSegment(i-1,0,0); // delete segment - } + strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" + for (size_t i=strip.getSegmentsNum(); i>numSrcSegs && i>0; i--) { + Segment &seg = strip.getSegment(i-1); + if (seg.isActive()) seg.deactivate(); // delete segment } + strip.resume(); } size_t inactiveSegs = 0; for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { @@ -300,7 +301,7 @@ void parseNotifyPacket(uint8_t *udpIn) { if (!receiveSegmentOptions) { DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY); + selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY, selseg.map1D2D); strip.resume(); continue; // we do receive bounds, but not options } @@ -342,12 +343,12 @@ void parseNotifyPacket(uint8_t *udpIn) { if (receiveSegmentBounds) { DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY); + selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY, selseg.map1D2D); strip.resume(); } else { DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY); + selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY, selseg.map1D2D); strip.resume(); } } From 536444f9d1fae4c092a6cfd2f3d944e31cf30055 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 9 Nov 2024 16:38:18 +0100 Subject: [PATCH 586/694] fixed palette FX to more closely match original 1D version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rotation scale is now exactly 180° (divide slider input by 255 instead of 256) - removed shift offset: offset is now zero at slider 0, to hit 128 on touch input devices is really hard - added a 90° shift to input rotation, enabling to rotate from 0 to 180° instead of +90 to -90 (which is not useful in 1D) - changed default settings values to more closely match the old 1D effect --- wled00/FX.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f24f745a..47200655d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1940,7 +1940,7 @@ uint16_t mode_palette() { using angleType = unsigned; constexpr mathType sInt16Scale = 0x7FFF; constexpr mathType maxAngle = 0x8000; - constexpr mathType staticRotationScale = 256; + constexpr mathType staticRotationScale = 255; constexpr mathType animatedRotationScale = 1; constexpr int16_t (*sinFunction)(uint16_t) = &sin16; constexpr int16_t (*cosFunction)(uint16_t) = &cos16; @@ -1949,7 +1949,7 @@ uint16_t mode_palette() { using wideMathType = float; using angleType = float; constexpr mathType sInt16Scale = 1.0f; - constexpr mathType maxAngle = M_PI / 256.0; + constexpr mathType maxAngle = M_PI / 255.0; constexpr mathType staticRotationScale = 1.0f; constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF); constexpr float (*sinFunction)(float) = &sin_t; @@ -1961,7 +1961,7 @@ uint16_t mode_palette() { const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; - const int inputRotation = SEGMENT.custom1; + const int inputRotation = SEGMENT.custom1 + 128; const bool inputAnimateShift = SEGMENT.check1; const bool inputAnimateRotation = SEGMENT.check2; const bool inputAssumeSquare = SEGMENT.check3; @@ -1985,7 +1985,7 @@ uint16_t mode_palette() { // So the rectangle needs to have exactly the right size. That size depends on the rotation. // This scale computation here only considers one dimension. You can think of it like the rectangle is always scaled so that // the left and right most points always match the left and right side of the display. - const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut); + const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut); // 2D simulation: // If we are dealing with a 1D setup, we assume that each segment represents one line on a 2-dimensional display. // The function is called once per segments, so we need to handle one line at a time. @@ -2016,7 +2016,7 @@ uint16_t mode_palette() { colorIndex = ((inputSize - 112) * colorIndex) / 16; } // Finally, shift the palette a bit. - const int paletteOffset = (!inputAnimateShift) ? (inputShift-128) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); + const int paletteOffset = (!inputAnimateShift) ? (inputShift) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); colorIndex += paletteOffset; const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex); if (isMatrix) { @@ -2028,7 +2028,7 @@ uint16_t mode_palette() { } return FRAMETIME; } -static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0"; +static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;ix=112,c1=0,o1=1,o2=0,o3=1"; // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active From 5de86d3d9166e1d629d5fdcd128008360378dccd Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 9 Nov 2024 23:55:09 +0100 Subject: [PATCH 587/694] fix problem with spaces --- tools/cdata.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cdata.js b/tools/cdata.js index d65573a8e..c5d3c6aa5 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -101,6 +101,7 @@ function adoptVersionAndRepo(html) { async function minify(str, type = "plain") { const options = { collapseWhitespace: true, + conservativeCollapse: true, // preserve spaces in text collapseBooleanAttributes: true, collapseInlineTagWhitespace: true, minifyCSS: true, From 9a564ee20457664e09f2d12aeceac874d1edef53 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:45:55 +0100 Subject: [PATCH 588/694] readme.md - link to multi-strip KB page --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 595b9ee0b..0a02826af 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to cont - Segments to set different effects and colors to user defined parts of the LED string - Settings page - configuration via the network - Access Point and station mode - automatic failsafe AP -- Up to 10 LED outputs per instance +- [Up to 10 LED outputs](https://kno.wled.ge/features/multi-strip/#esp32) per instance - Support for RGBW strips - Up to 250 user presets to save and load colors/effects easily, supports cycling through them. - Presets can be used to automatically execute API calls From d437027f2630c88ea660fa4e4ad199f537b1abea Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 10 Nov 2024 22:39:52 +0100 Subject: [PATCH 589/694] Replaced single palette cases with an array to consolidate code - all palettes are defined in palettes.h - access to fastled palettes as an array to remove the switch cases - palette createn in json.cpp in a loop instead of repeaded calls to save flash --- wled00/FX_fcn.cpp | 16 ++------------- wled00/json.cpp | 52 ++++++++--------------------------------------- wled00/palettes.h | 11 ++++++++++ 3 files changed, 21 insertions(+), 58 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e706f2b43..d666513ae 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -236,23 +236,11 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); } break;} - case 6: //Party colors - targetPalette = PartyColors_p; break; - case 7: //Cloud colors - targetPalette = CloudColors_p; break; - case 8: //Lava colors - targetPalette = LavaColors_p; break; - case 9: //Ocean colors - targetPalette = OceanColors_p; break; - case 10: //Forest colors - targetPalette = ForestColors_p; break; - case 11: //Rainbow colors - targetPalette = RainbowColors_p; break; - case 12: //Rainbow stripe colors - targetPalette = RainbowStripeColors_p; break; default: //progmem palettes if (pal>245) { targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + } else if (pal < 13) { // palette 6 - 12, fastled palettes + targetPalette = *fastledPalettes[pal-6]; } else { byte tcp[72]; memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); diff --git a/wled00/json.cpp b/wled00/json.cpp index 288059653..c74bf6a8d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -902,10 +902,7 @@ void serializePalettes(JsonObject root, int page) setPaletteColors(curPalette, PartyColors_p); break; case 1: //random - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); + for (int j = 0; j < 4; j++) curPalette.add("r"); break; case 2: //primary color only curPalette.add("c1"); @@ -922,53 +919,20 @@ void serializePalettes(JsonObject root, int page) curPalette.add("c1"); break; case 5: //primary + secondary (+tertiary if not off), more distinct + for (int j = 0; j < 5; j++) curPalette.add("c1"); + for (int j = 0; j < 5; j++) curPalette.add("c2"); + for (int j = 0; j < 5; j++) curPalette.add("c3"); curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("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>=palettesCount) { + if (i >= palettesCount) setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]); - } else { + else if (i < 13) // palette 6 - 12, fastled palettes + setPaletteColors(curPalette, *fastledPalettes[i-6]); + else { memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72); setPaletteColors(curPalette, tcp); } - } break; } } diff --git a/wled00/palettes.h b/wled00/palettes.h index 41dfbbc16..1ead342bb 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -844,6 +844,17 @@ const byte candy2_gp[] PROGMEM = { 211, 39, 33, 34, 255, 1, 1, 1}; +// array of fastled palettes (palette 6 - 12) +const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = { + &PartyColors_p, //06-00 Party + &CloudColors_p, //07-01 Cloud + &LavaColors_p, //08-02 Lava + &OceanColors_p, //09-03 Ocean + &ForestColors_p, //10-04 Forest + &RainbowColors_p, //11-05 Rainbow + &RainbowStripeColors_p //12-06 Rainbow Bands +}; + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly From 223b97b8848d8f44c891ea511dadc20e40270cf7 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 10 Nov 2024 18:04:07 -0500 Subject: [PATCH 590/694] handleSet: Fix incorrect response generation Don't generate a response if there's no HTTP request. Fixes #4269 --- wled00/set.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index c446a2eff..712e5f254 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1191,7 +1191,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) // internal call, does not send XML response pos = req.indexOf(F("IN")); - if (pos < 1) { + if ((request != nullptr) && (pos < 1)) { auto response = request->beginResponseStream("text/xml"); XML_response(*response); request->send(response); From 8c5e0cd4e94ebc4a2d59c24fce620f6e3540c903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 15 Nov 2024 16:01:58 +0100 Subject: [PATCH 591/694] Keep selected FX while searching --- wled00/data/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index d9c64bdfb..cbc389391 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2850,7 +2850,7 @@ function search(field, listId = null) { if (listId!=='pcont' && i===0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); - listItem.style.display = (searchIndex < 0) ? 'none' : ''; + listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; listItem.dataset.searchIndex = searchIndex; }); @@ -2924,7 +2924,7 @@ function filterFx() { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); - listItem.style.display = hide ? 'none' : ''; + listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : ''; }); } From 86be5df475468824cee3654efc6175170d3dbf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 15 Nov 2024 19:03:46 +0100 Subject: [PATCH 592/694] xml comma bugfix + few optimisations --- wled00/xml.cpp | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..a4a72abea 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -83,7 +83,7 @@ void appendGPIOinfo(Print& settingsScript) { // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins - JsonObject mods = pDoc->createNestedObject(F("um")); + JsonObject mods = pDoc->createNestedObject("um"); UsermodManager::addToConfig(mods); if (!mods.isNull()) fillUMPins(settingsScript, mods); releaseJSONBufferLock(); @@ -91,35 +91,42 @@ void appendGPIOinfo(Print& settingsScript) { settingsScript.print(F("];")); // add reserved (unusable) pins + bool firstPin = true; settingsScript.print(F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (!PinManager::isPinOk(i, false)) { // include readonly pins - settingsScript.print(i); settingsScript.print(","); + if (!firstPin) settingsScript.print(','); + settingsScript.print(i); + firstPin = false; } } #ifdef WLED_ENABLE_DMX - settingsScript.print(F("2,")); // DMX hardcoded pin + if (!firstPin) settingsScript.print(','); + settingsScript.print(2); // DMX hardcoded pin + firstPin = false; #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - settingsScript.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin + if (!firstPin) settingsScript.print(','); + settingsScript.print(hardwareTX); // debug output (TX) pin + firstPin = false; #endif - //Note: Using pin 3 (RX) disables Adalight / Serial JSON #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_power); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } - switch (ethernetBoards[ethernetType].eth_clk_mode) { + if (!firstPin) settingsScript.print(','); + for (unsigned p=0; p= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_power); } + if (ethernetBoards[ethernetType].eth_mdc >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdc); } + if (ethernetBoards[ethernetType].eth_mdio >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdio); } + switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: - settingsScript.print(F("0")); + settingsScript.print(0); break; case ETH_CLOCK_GPIO16_OUT: - settingsScript.print(F("16")); + settingsScript.print(16); break; case ETH_CLOCK_GPIO17_OUT: - settingsScript.print(F("17")); + settingsScript.print(17); break; } } @@ -128,11 +135,11 @@ void appendGPIOinfo(Print& settingsScript) { // add info for read-only GPIO settingsScript.print(F("d.ro_gpio=[")); - bool firstPin = true; + firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (PinManager::isReadOnlyPin(i)) { // No comma before the first pin - if (!firstPin) settingsScript.print(F(",")); + if (!firstPin) settingsScript.print(','); settingsScript.print(i); firstPin = false; } @@ -142,7 +149,7 @@ void appendGPIOinfo(Print& settingsScript) { // add info about max. # of pins settingsScript.print(F("d.max_gpio=")); settingsScript.print(WLED_NUM_PINS); - settingsScript.print(F(";")); + settingsScript.print(';'); } //get values for settings form in javascript @@ -152,6 +159,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); if (subPage <0 || subPage >10) return; + char nS[32]; if (subPage == SUBPAGE_MENU) { @@ -259,8 +267,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_LEDS) { - char nS[32]; - appendGPIOinfo(settingsScript); settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); @@ -399,7 +405,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_SYNC) { - [[maybe_unused]] char nS[32]; printSetFormValue(settingsScript,PSTR("UP"),udpPort); printSetFormValue(settingsScript,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW @@ -637,7 +642,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #if defined(ARDUINO_ARCH_ESP32) ESP.getChipModel(), #else - F("esp8266"), + "esp8266", #endif VERSION); From 7f69a0bc5ec42c507f3923b456257c517b65e1ef Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 16 Nov 2024 12:37:24 +0100 Subject: [PATCH 593/694] removed obsolete code as of #4267 --- wled00/data/style.css | 3 --- wled00/data/update.htm | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/wled00/data/style.css b/wled00/data/style.css index 42e49d304..b6cb0f9e6 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -44,9 +44,6 @@ button.sml { min-width: 40px; margin: 0 0 0 10px; } -span:before, b:before, b:after, i:after{ - content: "\00A0"; -} #scan { margin-top: -10px; } diff --git a/wled00/data/update.htm b/wled00/data/update.htm index b68645a52..23a6a866e 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -17,7 +17,7 @@

WLED Software Update

Installed version: ##VERSION##
- Download the latest binary: 

From 6fe2024542b12fc17af98fa6402ca5f557a6d958 Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:53:10 +0100 Subject: [PATCH 594/694] move selected to the bottom in search result --- wled00/data/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index cbc389391..f93511aaf 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2827,7 +2827,7 @@ function search(field, listId = null) { // restore default preset sorting if no search term is entered if (!search) { - if (listId === 'pcont') { populatePresets(); return; } + if (listId === 'pcont') { populatePresets(); return; } if (listId === 'pallist') { let id = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); // preserve selected palette populatePalettes(); @@ -2846,12 +2846,17 @@ function search(field, listId = null) { // filter list items but leave (Default & Solid) always visible const listItems = gId(listId).querySelectorAll('.lstI'); - listItems.forEach((listItem,i)=>{ - if (listId!=='pcont' && i===0) return; + listItems.forEach((listItem, i) => { const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); - listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; - listItem.dataset.searchIndex = searchIndex; + if (searchIndex < 0) { + listItem.dataset.searchIndex = Number.MAX_SAFE_INTEGER; + } else { + listItem.dataset.searchIndex = searchIndex; + } + + if ((listId !== 'pcont' && i === 0) || listItem.classList.contains("selected")) return; + listItem.style.display = (searchIndex < 0) ? 'none' : ''; }); // sort list items by search index and name @@ -2920,10 +2925,10 @@ function filterFx() { inputField.value = ''; inputField.focus(); clean(inputField.nextElementSibling); - gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => { + gId("fxlist").querySelectorAll('.lstI').forEach((listItem, i) => { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; - gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); + gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i > 0 /*true*/; }); listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : ''; }); } From 4d3df5d98fb283e936db9eb7f19406a3a5a38b24 Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:12:29 +0100 Subject: [PATCH 595/694] Fix default/solid not being first --- wled00/data/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index f93511aaf..1482c27b5 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2847,6 +2847,7 @@ function search(field, listId = null) { // filter list items but leave (Default & Solid) always visible const listItems = gId(listId).querySelectorAll('.lstI'); listItems.forEach((listItem, i) => { + if (listId !== 'pcont' && i === 0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); if (searchIndex < 0) { @@ -2854,9 +2855,7 @@ function search(field, listId = null) { } else { listItem.dataset.searchIndex = searchIndex; } - - if ((listId !== 'pcont' && i === 0) || listItem.classList.contains("selected")) return; - listItem.style.display = (searchIndex < 0) ? 'none' : ''; + listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; }); // sort list items by search index and name From a765903a41d3272c166ff5351da5c0338947c183 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Nov 2024 17:10:08 -0500 Subject: [PATCH 596/694] Fix array overflow in exploding_fireworks Attempt to allocate enough room for the "minimum" sparks; and ensure that we never overrun the allocated array size. Fixes #4120 --- wled00/FX.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f24f745a..947788fee 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3547,7 +3547,7 @@ uint16_t mode_exploding_fireworks(void) if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - unsigned numSparks = min(2 + ((rows*cols) >> 1), maxSparks); + unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3602,7 +3602,8 @@ uint16_t mode_exploding_fireworks(void) * Size is proportional to the height. */ unsigned nSparks = flare->pos + random8(4); - nSparks = constrain(nSparks, 4, numSparks); + nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 + nSparks = std::min(nSparks, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { From 84dd26c1b7d90b2f4466253bc9e6b9faa545451a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 17 Nov 2024 10:47:09 +0100 Subject: [PATCH 597/694] Some more optimisations. --- wled00/xml.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index a4a72abea..df52b6248 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -147,9 +147,7 @@ void appendGPIOinfo(Print& settingsScript) { settingsScript.print(F("];")); // add info about max. # of pins - settingsScript.print(F("d.max_gpio=")); - settingsScript.print(WLED_NUM_PINS); - settingsScript.print(';'); + settingsScript.printf_P(PSTR("d.max_gpio=%d;"),WLED_NUM_PINS); } //get values for settings form in javascript @@ -269,7 +267,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) { appendGPIOinfo(settingsScript); - settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); + settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str()); // set limits settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), @@ -653,8 +651,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) { printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D - settingsScript.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); - settingsScript.print(F("resetPanels();")); + settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS); if (strip.isMatrix) { if(strip.panels>0){ printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience @@ -664,11 +661,9 @@ void getSettingsJS(byte subPage, Print& settingsScript) // panels for (unsigned i=0; i Date: Sun, 17 Nov 2024 14:31:23 +0100 Subject: [PATCH 598/694] Bugfix --- wled00/xml.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index df52b6248..597db73f2 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -660,8 +660,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Wed, 20 Nov 2024 12:39:39 +0100 Subject: [PATCH 599/694] Use MQTT_MAX_TOPIC_LEN in places where it was not used before to avoid buffer overflows when value is increased --- wled00/button.cpp | 8 ++++---- wled00/mqtt.cpp | 22 +++++++++++----------- wled00/wled.h | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 4d6f954f6..6f9c84560 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -29,7 +29,7 @@ void shortPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "short"); } @@ -62,7 +62,7 @@ void longPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "long"); } @@ -83,7 +83,7 @@ void doublePressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "double"); } @@ -151,7 +151,7 @@ void handleSwitch(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on"); diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index a476db87a..d909494ee 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -23,24 +23,24 @@ static void parseMQTTBriPayload(char* payload) static void onMqttConnect(bool sessionPresent) { //(re)subscribe to required topics - char subuf[38]; + char subuf[MQTT_MAX_TOPIC_LEN + 6]; if (mqttDeviceTopic[0] != 0) { - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } if (mqttGroupTopic[0] != 0) { - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } @@ -158,19 +158,19 @@ void publishMqtt() #ifndef USERMOD_SMARTNEST char s[10]; - char subuf[48]; + char subuf[MQTT_MAX_TOPIC_LEN + 16]; sprintf_P(s, PSTR("%u"), bri); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/g")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/c")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/status")); mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT @@ -178,7 +178,7 @@ void publishMqtt() DynamicBuffer buf(1024); bufferPrint pbuf(buf.data(), buf.size()); XML_response(pbuf); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/v")); mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263) #endif @@ -211,7 +211,7 @@ bool initMqtt() if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); #ifndef USERMOD_SMARTNEST - strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); + strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(mqttStatusTopic, PSTR("/status")); mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message #endif diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24..29b43753a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,10 +483,10 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers -WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN+1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) -WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN+1] _INIT("wled/all"); // second MQTT topic (for example to group devices) -WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN+1] _INIT(""); // both domains and IPs should work (no SSL) +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) +WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) +WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID From cec89788865bf29aae0799f03ba0c8c43638d297 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 20 Nov 2024 12:45:39 +0100 Subject: [PATCH 600/694] Fix comment alignment --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 29b43753a..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,7 +483,7 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) From 0db47a8586526541b0cdd810b466923c64b80cd6 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 09:51:13 +0100 Subject: [PATCH 601/694] Add comment warning about modification of MQTT_MAX_TOPIC_LEN --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 3630170f9..5dbc013d9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 + #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 From 49fb16e2c6528ddc98cccc352cc8f0d4a5f62a13 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 10:52:22 +0100 Subject: [PATCH 602/694] Introduce printSetInputMaxlength to properly set an inputs maxlength variable --- wled00/fcn_declare.h | 1 + wled00/util.cpp | 4 ++++ wled00/xml.cpp | 5 +++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1855a8b63..91176160f 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -380,6 +380,7 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); +size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 0b78a4646..1c1282eda 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -109,6 +109,10 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } +size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val) { + return settingsScript.printf_P(PSTR("d.Sf.%s.setAttribute(\"maxlength\", %d);"),key,val); +} + void prepareHostname(char* hostname) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..6f837a9b6 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,8 +465,9 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), - MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); + printSetInputMaxLength(settingsScript, PSTR("MD"), MQTT_MAX_TOPIC_LEN); + printSetInputMaxLength(settingsScript, PSTR("MG"), MQTT_MAX_TOPIC_LEN); + printSetInputMaxLength(settingsScript, PSTR("MS"), MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings #endif From 8f8afd98a5dc5982271babbcce84ffff28c32fab Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 11:20:42 +0100 Subject: [PATCH 603/694] Replace comment with compile-time error and warning --- wled00/mqtt.cpp | 4 ++++ wled00/wled.h | 2 +- wled00/wled_eeprom.cpp | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) mode change 100755 => 100644 wled00/wled_eeprom.cpp diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index d909494ee..38afeffe3 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -7,6 +7,10 @@ #ifndef WLED_DISABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds +#if MQTT_MAX_TOPIC_LEN > 32 +#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!" +#endif + static void parseMQTTBriPayload(char* payload) { if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} diff --git a/wled00/wled.h b/wled00/wled.h index 5dbc013d9..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. + #define MQTT_MAX_TOPIC_LEN 32 #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp old mode 100755 new mode 100644 index 4f2c14d47..8582b49df --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -2,6 +2,10 @@ #include #include "wled.h" +#if defined(WLED_ENABLE_MQTT) && MQTT_MAX_TOPIC_LEN < 32 +#error "MQTT topics length < 32 is not supported by the EEPROM module!" +#endif + /* * DEPRECATED, do not use for new settings * Only used to restore config from pre-0.11 installations using the deEEP() methods From 5ac8ba9bae80688ab9c9151e8775447d8e86cbf1 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 12:02:55 +0100 Subject: [PATCH 604/694] Revert "Introduce printSetInputMaxlength to properly set an inputs maxlength variable" This reverts commit 49fb16e2c6528ddc98cccc352cc8f0d4a5f62a13. --- wled00/fcn_declare.h | 1 - wled00/util.cpp | 4 ---- wled00/xml.cpp | 5 ++--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 91176160f..1855a8b63 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -380,7 +380,6 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); -size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 1c1282eda..0b78a4646 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -109,10 +109,6 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } -size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val) { - return settingsScript.printf_P(PSTR("d.Sf.%s.setAttribute(\"maxlength\", %d);"),key,val); -} - void prepareHostname(char* hostname) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 6f837a9b6..dc2673271 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,9 +465,8 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - printSetInputMaxLength(settingsScript, PSTR("MD"), MQTT_MAX_TOPIC_LEN); - printSetInputMaxLength(settingsScript, PSTR("MG"), MQTT_MAX_TOPIC_LEN); - printSetInputMaxLength(settingsScript, PSTR("MS"), MQTT_MAX_SERVER_LEN); + settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings #endif From 5c8b2ebf7a015983890daecabe0db1cfcc7628d0 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 12:04:01 +0100 Subject: [PATCH 605/694] maxlength -> maxLength to fix this attribute not being modified correctly --- wled00/xml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..0893e5d25 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,7 +465,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + settingsScript.printf_P(PSTR("d.Sf.MD.maxLength=%d;d.Sf.MG.maxLength=%d;d.Sf.MS.maxLength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings From 6790f8af084a4f4fe04672d82c6f55744d6d095d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:16:03 +0100 Subject: [PATCH 606/694] Same MIN_FRAME_DELAY=3 for -C3 and -S2 --- wled00/FX.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 56a0c9bd0..c15500aa3 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -48,8 +48,8 @@ #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) #define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive -#elif defined(CONFIG_IDF_TARGET_ESP32S2) - #define MIN_FRAME_DELAY 4 // S2 is slower than normal esp32, and only has one core +#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + #define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core #else #define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY #endif From 548736f432d5c1484b85045816ce1cfbafaa4e46 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:50:55 +0100 Subject: [PATCH 607/694] SparkFunDMX fix for possible array bounds violation in DMX.write Align with code in espdmx.cpp --- wled00/src/dependencies/dmx/SparkFunDMX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.cpp b/wled00/src/dependencies/dmx/SparkFunDMX.cpp index dbc9b1590..064b9ff62 100644 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ b/wled00/src/dependencies/dmx/SparkFunDMX.cpp @@ -34,8 +34,8 @@ static const int enablePin = -1; // disable the enable pin because it is not ne static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable" static const int txPin = 2; // transmit DMX data over this pin (default is pin 2) -//DMX value array and size. Entry 0 will hold startbyte -static uint8_t dmxData[dmxMaxChannel] = { 0 }; +//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements +static uint8_t dmxData[dmxMaxChannel+1] = { 0 }; static int chanSize = 0; #if !defined(DMX_SEND_ONLY) static int currentChannel = 0; From 32dc54ce7239c9947260db272750fb5c5cfacb19 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 22 Nov 2024 08:43:45 +0100 Subject: [PATCH 608/694] reverted rotation scale, offset only on static rotation, inverted shift direction - inverting the shift direction in signed int is computationally safe as it is cast into an uint8_t and it matches the original FX in 1D --- wled00/FX.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 47200655d..1463121eb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1940,7 +1940,7 @@ uint16_t mode_palette() { using angleType = unsigned; constexpr mathType sInt16Scale = 0x7FFF; constexpr mathType maxAngle = 0x8000; - constexpr mathType staticRotationScale = 255; + constexpr mathType staticRotationScale = 256; constexpr mathType animatedRotationScale = 1; constexpr int16_t (*sinFunction)(uint16_t) = &sin16; constexpr int16_t (*cosFunction)(uint16_t) = &cos16; @@ -1949,7 +1949,7 @@ uint16_t mode_palette() { using wideMathType = float; using angleType = float; constexpr mathType sInt16Scale = 1.0f; - constexpr mathType maxAngle = M_PI / 255.0; + constexpr mathType maxAngle = M_PI / 256.0; constexpr mathType staticRotationScale = 1.0f; constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF); constexpr float (*sinFunction)(float) = &sin_t; @@ -1961,12 +1961,12 @@ uint16_t mode_palette() { const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; - const int inputRotation = SEGMENT.custom1 + 128; + const int inputRotation = SEGMENT.custom1; const bool inputAnimateShift = SEGMENT.check1; const bool inputAnimateRotation = SEGMENT.check2; const bool inputAssumeSquare = SEGMENT.check3; - const angleType theta = (!inputAnimateRotation) ? (inputRotation * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale); + const angleType theta = (!inputAnimateRotation) ? ((inputRotation + 128) * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale); const mathType sinTheta = sinFunction(theta); const mathType cosTheta = cosFunction(theta); @@ -2017,7 +2017,7 @@ uint16_t mode_palette() { } // Finally, shift the palette a bit. const int paletteOffset = (!inputAnimateShift) ? (inputShift) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); - colorIndex += paletteOffset; + colorIndex -= paletteOffset; const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex); if (isMatrix) { SEGMENT.setPixelColorXY(x, y, color); From 6aef0e145c234cabb1129feed4068830de45a409 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:31:07 +0000 Subject: [PATCH 609/694] Remove TOSTRING for releaseString and add quotes to WLED_RELEASE_NAME --- platformio.ini | 42 +++++++++++++++++++++--------------------- wled00/wled.h | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6d4aa1dc1..fe02213ff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -345,7 +345,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder @@ -354,13 +354,13 @@ extends = env:nodemcuv2 ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D -D USERMOD_AUDIOREACTIVE [env:esp8266_2m] @@ -369,7 +369,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_compat] @@ -377,12 +377,12 @@ extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" -D USERMOD_AUDIOREACTIVE [env:esp01_1m_full] @@ -391,7 +391,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} @@ -400,12 +400,12 @@ extends = env:esp01_1m_full ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM @@ -414,7 +414,7 @@ board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} @@ -426,7 +426,7 @@ board = esp32dev platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -442,7 +442,7 @@ board = esp32dev platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_16M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -458,7 +458,7 @@ board_build.flash_mode = dio ;platform = ${esp32.platform} ;platform_packages = ${esp32.platform_packages} ;build_unflags = ${common.build_unflags} -;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET ; ${esp32.AR_build_flags} ;lib_deps = ${esp32.lib_deps} ; ${esp32.AR_lib_deps} @@ -473,7 +473,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} @@ -489,7 +489,7 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D DATA_PINS=25 ${esp32.AR_build_flags} @@ -503,7 +503,7 @@ platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\" -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -520,7 +520,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -543,7 +543,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -565,7 +565,7 @@ board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -590,7 +590,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -611,7 +611,7 @@ board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 +build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24..163ce6b04 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] +WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // somehow this will not work if using "const char releaseString[] #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) From 0be1df7ee89d01a53b0043dd4c4b66c0e741ccd4 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:46:41 +0000 Subject: [PATCH 610/694] Stip \" from WLED_RELEASE_NAME --- pio-scripts/output_bins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 633654008..bdd6fc941 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,7 +19,7 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME").replace("\\\"", "") if release_name: version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") From d53d7aa2e2d22085979e0134fb7ce1c5dd6684df Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 23 Nov 2024 12:29:46 -0500 Subject: [PATCH 611/694] Defer calling begin() on digital buses NeoPixelBus requires that all parallel I2S bus members be constructed before any of them call Begin(). Implement this by deferring the call to the end of bus construction. Fixes #4301. --- wled00/FX_fcn.cpp | 11 +++-------- wled00/bus_manager.cpp | 4 ++-- wled00/bus_manager.h | 3 ++- wled00/bus_wrapper.h | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e706f2b43..7177ca89e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1286,14 +1286,9 @@ void WS2812FX::finalizeInit() { _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; - #ifdef ESP8266 - // why do we need to reinitialise GPIO3??? - //if (!bus->isDigital() || bus->is2Pin()) continue; - //uint8_t pins[5]; - //if (!bus->getPins(pins)) continue; - //BusDigital* bd = static_cast(bus); - //if (pins[0] == 3) bd->reinit(); - #endif + + // This must be done after all buses have been created, as some kinds (parallel I2S) interact + bus->begin(); } Segment::maxWidth = _length; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b948b9c4..8281211c1 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -410,7 +410,7 @@ std::vector BusDigital::getLEDTypes() { }; } -void BusDigital::reinit() { +void BusDigital::begin() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); } @@ -910,7 +910,7 @@ void BusManager::on() { if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital *bus = static_cast(busses[i]); - bus->reinit(); + bus->begin(); break; } } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e96b9de71..ecebc120e 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -79,6 +79,7 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus + virtual void begin() {}; virtual void show() = 0; virtual bool canShow() const { return true; } virtual void setStatusPixel(uint32_t c) {} @@ -213,7 +214,7 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } - void reinit(); + void begin() override; void cleanup(); static std::vector getLEDTypes(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 84c32f46b..7e6e74d85 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -597,7 +597,7 @@ class PolyBus { case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; } - begin(busPtr, busType, pins, clock_kHz); + return busPtr; } From 0c77dbb7ea63c0b228d5d0f576d6eb0dcb7daeb1 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 24 Nov 2024 13:55:23 +0000 Subject: [PATCH 612/694] cleanup WLED_RELEASE_NAME only if present --- pio-scripts/output_bins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index bdd6fc941..4d1594d84 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,8 +19,9 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME").replace("\\\"", "") - if release_name: + release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + if release_name_def: + release_name = release_name_def.replace("\\\"", "") version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") release_gz_file = release_file + ".gz" From 20f8d3c8a9cc0812261089714baaf08538aabb19 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 24 Nov 2024 15:26:30 +0000 Subject: [PATCH 613/694] Builds without WLED_RELEASE_NAME should be called Custom --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 163ce6b04..bb647340b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -264,7 +264,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #define WLED_VERSION dev #endif #ifndef WLED_RELEASE_NAME - #define WLED_RELEASE_NAME dev_release + #define WLED_RELEASE_NAME "Custom" #endif // Global Variable definitions From 5b989adebc666f5d6314e0ece81c1141a0ae454f Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:27:53 +0100 Subject: [PATCH 614/694] Allow TV Simulator on single LED segments I've checked the code - nothing preventing the effect to run with SEGLEN=1 --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 947788fee..80be13c62 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4631,7 +4631,7 @@ uint16_t mode_tv_simulator(void) { return FRAMETIME; } -static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;"; +static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01"; /* From dd533a9ab4f37c8bc271f4a7cdbd7c0fe51456a3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 25 Nov 2024 23:00:31 +0000 Subject: [PATCH 615/694] Update sample WLED_RELEASE_NAME --- platformio_override.sample.ini | 2 +- wled00/wled.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf003..a4dea6677 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -37,7 +37,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. ; ; Set a release name that may be used to distinguish required binary for flashing -; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS +; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" ; ; disable specific features ; -D WLED_DISABLE_OTA diff --git a/wled00/wled.h b/wled00/wled.h index bb647340b..fcbc11978 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // somehow this will not work if using "const char releaseString[] +WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) From d87c5035dd0ac1c908c6a4680640999ab52bbddd Mon Sep 17 00:00:00 2001 From: AlDIY <87589371+dosipod@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:33:22 +0300 Subject: [PATCH 616/694] Update settings_sec.htm --- wled00/data/settings_sec.htm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..7fe0fe62e 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -60,8 +60,8 @@

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
- Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
- For security reasons, passwords are not backed up. + Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
+ For security reasons, passwords are not backed up. Backup presets
Restore presets


Backup configuration
@@ -78,4 +78,4 @@ - \ No newline at end of file + From cd1c13b4b1e242e736cad086abcb7a9dcc727b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 26 Nov 2024 19:35:15 +0100 Subject: [PATCH 617/694] Fix for #4300 --- wled00/set.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/set.cpp b/wled00/set.cpp index 712e5f254..2a6fdd3fb 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -215,6 +215,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed // we will not bother with pre-allocating ColorOrderMappings vector + BusManager::getColorOrderMap().reset(); for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { int offset = s < 10 ? 48 : 55; char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED From 1a8aaa3b26feba106259b6842ad9f8998fb3233d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 26 Nov 2024 20:32:39 +0100 Subject: [PATCH 618/694] Speed improvements, commented legacy _t trig functions - speed improvement: by default M_TWOPI is treated as a double float - directly calling sin16_t in cos_approx() saves a lot of overhead --- wled00/wled_math.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index a191968e1..583381d66 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -10,14 +10,17 @@ //#define WLED_DEBUG_MATH -#define modd(x, y) ((x) - (int)((x) / (y)) * (y)) - -// Note: cos_t, sin_t and tan_t are very accurate but may be slow +// Note: cos_t, sin_t and tan_t are very accurate but slow // the math.h functions use several kB of flash and are to be avoided if possible // sin16_t / cos16_t are faster and much more accurate than the fastled variants // sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf() // sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate + +// Taylor series approximations, replaced with Bhaskara I's approximation +/* +#define modd(x, y) ((x) - (int)((x) / (y)) * (y)) + float cos_t(float phi) { float x = modd(phi, M_TWOPI); @@ -54,6 +57,7 @@ float tan_t(float x) { #endif return res; } +*/ // 16-bit, integer based Bhaskara I's sine approximation: 16*x*(pi - x) / (5*pi^2 - 4*x*(pi - x)) // input is 16bit unsigned (0-65535), output is 16bit signed (-32767 to +32767) @@ -85,17 +89,18 @@ uint8_t cos8_t(uint8_t theta) { return sin8_t(theta + 64); //cos(x) = sin(x+pi/2) } -float sin_approx(float theta) -{ - uint16_t scaled_theta = (int)(theta * (0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) +float sin_approx(float theta) { + uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; } -float cos_approx(float theta) -{ - return sin_approx(theta + M_PI_2); +float cos_approx(float theta) { + uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) + int32_t result = sin16_t(scaled_theta + 16384); + float cos = float(result) / 0x7FFF; + return cos; } float tan_approx(float x) { From 0a05611e1d5a8dcccf515ba616042692197258f6 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 26 Nov 2024 20:59:36 +0100 Subject: [PATCH 619/694] more improvements to setPixelColor - code is a bit cleaner and faster as well - chaning array access to pointer access in bus_manager makes it a few instructions faster - changed getNumberOfPins and getNumberOfChannels to return 32bit values, saving the unnecessary 8bit conversion --- wled00/FX_2Dfcn.cpp | 41 +++++++++++++++++++++-------------------- wled00/bus_manager.cpp | 26 ++++++++++++++++---------- wled00/bus_manager.h | 6 +++--- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index adb9f8bca..ba0c69322 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -156,21 +156,26 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) // raw setColor function without checks (checks are done in setPixelColorXY()) void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) { + const int baseX = start + x; + const int baseY = startY + y; #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel - if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + x, startY + y), col, 0xFFFFU - progress(), true); + if (_modeBlend) col = color_blend(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress(), true); #endif - strip.setPixelColorXY(start + x, startY + y, col); - if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + x, startY + height() - y - 1, col); - else strip.setPixelColorXY(start + width() - x - 1, startY + y, col); - } - if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - x - 1, startY + y, col); - else strip.setPixelColorXY(start + x, startY + height() - y - 1, col); - } - if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(start + width() - x - 1, startY + height() - y - 1, col); + strip.setPixelColorXY(baseX, baseY, col); + + // Apply mirroring + if (mirror || mirror_y) { + auto setMirroredPixel = [&](int mx, int my) { + strip.setPixelColorXY(mx, my, col); + }; + + const int mirrorX = start + width() - x - 1; + const int mirrorY = startY + height() - y - 1; + + if (mirror) setMirroredPixel(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY); + if (mirror_y) setMirroredPixel(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY); + if (mirror && mirror_y) setMirroredPixel(mirrorX, mirrorY); } } @@ -196,16 +201,12 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int H = height(); x *= groupLen; // expand to physical pixels y *= groupLen; // expand to physical pixels - int yY = y; - for (int j = 0; j < grouping; j++) { // groupping vertically - if (yY >= H) break; - int xX = x; - for (int g = 0; g < grouping; g++) { // groupping horizontally - if (xX >= W) break; // we have reached X dimension's end + const int maxY = std::min(y + grouping, H); + const int maxX = std::min(x + grouping, W); + for (int yY = y; yY < maxY; yY++) { + for (int xX = x; xX < maxX; xX++) { _setPixelColorXY_raw(xX, yY, col); - xX++; } - yY++; } } else { _setPixelColorXY_raw(x, y, col); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 404c33449..941135497 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -308,20 +308,20 @@ void BusDigital::setStatusPixel(uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - uint8_t cctWW = 0, cctCW = 0; if (hasWhite()) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (_data) { size_t offset = pix * getNumberOfChannels(); + uint8_t* dataptr = _data + offset; if (hasRGB()) { - _data[offset++] = R(c); - _data[offset++] = G(c); - _data[offset++] = B(c); + *dataptr++ = R(c); + *dataptr++ = G(c); + *dataptr++ = B(c); } - if (hasWhite()) _data[offset++] = W(c); + if (hasWhite()) *dataptr++ = W(c); // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) - if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it + if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it } else { if (_reversed) pix = _len - pix -1; pix += _skip; @@ -336,8 +336,14 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); + uint16_t wwcw = 0; + if (hasCCT()) { + uint8_t cctWW = 0, cctCW = 0; + Bus::calculateCCT(c, cctWW, cctCW); + wwcw = (cctCW<<8) | cctWW; + } + + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } } @@ -345,7 +351,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; if (_data) { - size_t offset = pix * getNumberOfChannels(); + const size_t offset = pix * getNumberOfChannels(); uint32_t c; if (!hasRGB()) { c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); @@ -356,7 +362,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { } else { if (_reversed) pix = _len - pix -1; pix += _skip; - unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs unsigned r = R(c); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e25a06849..49077f27c 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -110,7 +110,7 @@ class Bus { inline void setStart(uint16_t start) { _start = start; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } - inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline uint32_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } inline uint16_t getStart() const { return _start; } inline uint8_t getType() const { return _type; } inline bool isOk() const { return _valid; } @@ -119,8 +119,8 @@ class Bus { inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes - static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK - static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static constexpr uint32_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr uint32_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr bool hasRGB(uint8_t type) { return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); } From ca176c7549f92ae6ba611b9b6f9e9edbf13bad15 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:31:41 +0000 Subject: [PATCH 620/694] rename workflow ready for workflow refactor --- .github/workflows/{wled-ci.yml => build.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{wled-ci.yml => build.yml} (100%) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/build.yml similarity index 100% rename from .github/workflows/wled-ci.yml rename to .github/workflows/build.yml From feab27295db32f9ad367ec3a68d3d7ad9b3779eb Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:33:44 +0000 Subject: [PATCH 621/694] workflow refactor to used shared build file --- .github/workflows/build.yml | 8 +++++--- .github/workflows/wled-ci.yml | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/wled-ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dcab26ab..80fce38c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,9 @@ -name: WLED CI - -on: [push, pull_request] +name: WLED Build +# Only included into other workflows +on: + workflow_call: + jobs: get_default_envs: diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml new file mode 100644 index 000000000..3c862c185 --- /dev/null +++ b/.github/workflows/wled-ci.yml @@ -0,0 +1,11 @@ +name: WLED CI + +on: + push: + branches: + - '*' + pull_request: + +jobs: + wled_build: + uses: ./.github/workflows/build.yml From 95718ab6ecb830b57f74d7cbed0b142d70b33c63 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:35:04 +0000 Subject: [PATCH 622/694] Dedicated release workflow --- .github/workflows/build.yml | 16 ---------------- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80fce38c3..e5fdfc5a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,22 +64,6 @@ jobs: path: | build_output/release/*.bin build_output/release/*_ESP02*.bin.gz - release: - name: Create Release - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - - name: Create draft release - uses: softprops/action-gh-release@v1 - with: - draft: True - files: | - *.bin - *.bin.gz testCdata: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..27beec99c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: WLED Release CI + +on: + push: + tags: + - '*' + +jobs: + + wled_build: + uses: ./.github/workflows/build.yml + + release: + name: Create Release + runs-on: ubuntu-latest + needs: wled_build + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + - name: Create draft release + uses: softprops/action-gh-release@v1 + with: + draft: True + files: | + *.bin + *.bin.gz + From cc55f6015dfff436cb4ff3f595ae53695393f8b3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:56:53 +0000 Subject: [PATCH 623/694] Cache always misses for tags, so save space and do not save back --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5fdfc5a3..ac1db6445 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,7 @@ jobs: - run: npm ci - name: Cache PlatformIO uses: actions/cache@v4 + if: !startsWith(github.ref, 'refs/tags/') with: path: | ~/.platformio/.cache From 6cbdd825eb6cea9c2cc5c6219f4fd7db567cd8ff Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:59:23 +0000 Subject: [PATCH 624/694] Revert "Cache always misses for tags, so save space and do not save back" This reverts commit cc55f6015dfff436cb4ff3f595ae53695393f8b3. --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac1db6445..e5fdfc5a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,6 @@ jobs: - run: npm ci - name: Cache PlatformIO uses: actions/cache@v4 - if: !startsWith(github.ref, 'refs/tags/') with: path: | ~/.platformio/.cache From acc8b9cdbc22086762d5c05e6d2ff419c96fc074 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:00:25 -0500 Subject: [PATCH 625/694] BusDigital::begin: Pass clock rate argument Fixes bug introduced by #4312. --- wled00/bus_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 8281211c1..715470800 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -412,7 +412,7 @@ std::vector BusDigital::getLEDTypes() { void BusDigital::begin() { if (!_valid) return; - PolyBus::begin(_busPtr, _iType, _pins); + PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); } void BusDigital::cleanup() { From dcba1aad109c29b9594973d99270f62006dd0289 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:05:10 -0500 Subject: [PATCH 626/694] PolyBus: Clarify use of clock_kHz While not used by most bus types, it's not an optional parameter. --- wled00/bus_manager.cpp | 2 +- wled00/bus_wrapper.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 715470800..5b031bebb 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -150,7 +150,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) //_buffering = bc.doubleBuffer; uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); _valid = (_busPtr != nullptr); DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 7e6e74d85..d2a18c9d8 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -336,7 +336,7 @@ class PolyBus { // initialize SPI bus speed for DotStar methods template - static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { + static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) { T dotStar_strip = static_cast(busPtr); #ifdef ESP8266 dotStar_strip->Begin(); @@ -363,7 +363,7 @@ class PolyBus { tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly } - static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) { switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -480,7 +480,7 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation From b1dd27b516fa93919edd967c85bd8b287b9b914d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Wed, 27 Nov 2024 16:08:10 +0100 Subject: [PATCH 627/694] Fix for #4321 --- wled00/data/settings_leds.htm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..baf80a5d7 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -42,15 +42,14 @@ if (loc) d.Sf.action = getURL('/settings/leds'); } function bLimits(b,v,p,m,l,o=5,d=2,a=6) { - // maxB - max buses (can be changed if using ESP32 parallel I2S) - // maxD - max digital channels (can be changed if using ESP32 parallel I2S) - // maxA - max analog channels - // maxV - min virtual buses - // maxPB - max LEDs per bus - // maxM - max LED memory - // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) - // maxCO - max Color Order mappings - oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o; + oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S) + maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S) + maxA = a; // maxA - max analog channels + maxV = v; // maxV - min virtual buses + maxPB = p; // maxPB - max LEDs per bus + maxM = m; // maxM - max LED memory + maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) + maxCO = o; // maxCO - max Color Order mappings } function pinsOK() { var ok = true; From 4cd0563a9398812f65f8dbbe22d0ab48799f4de8 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 27 Nov 2024 21:53:32 +0100 Subject: [PATCH 628/694] =?UTF-8?q?changed=2090=C2=B0=20offset=20to=20hex,?= =?UTF-8?q?=20fixed=20potential=20bug=20in=20FX=20using=20sin/cos=20with?= =?UTF-8?q?=20incrementing=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sin/cos calls with incrementing numbers can lead to bad outcomes, the functions (_approx or original sinf/cosf) return bad values for very large float inputs --- wled00/FX.cpp | 5 +++-- wled00/wled_math.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5b4513dc7..da2967a3c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5314,8 +5314,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)strip.now/305.f)/20.f; - imAg += sin_t((float)strip.now/405.f)/20.f; + reAl += (float)sin16_t(strip.now * 34) / 655340.f; + imAg += (float)sin16_t(strip.now * 26) / 655340.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -6279,6 +6279,7 @@ uint16_t mode_2Dplasmarotozoom() { } } *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed + if(*a < -6283.18530718f) *a += 6283.18530718f; // 1000*2*PI, protect sin/cos from very large input float values (will give wrong results) return FRAMETIME; } diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 583381d66..a8ec55400 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -76,7 +76,7 @@ int16_t sin16_t(uint16_t theta) { } int16_t cos16_t(uint16_t theta) { - return sin16_t(theta + 16384); //cos(x) = sin(x+pi/2) + return sin16_t(theta + 0x4000); //cos(x) = sin(x+pi/2) } uint8_t sin8_t(uint8_t theta) { @@ -98,7 +98,7 @@ float sin_approx(float theta) { float cos_approx(float theta) { uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) - int32_t result = sin16_t(scaled_theta + 16384); + int32_t result = sin16_t(scaled_theta + 0x4000); float cos = float(result) / 0x7FFF; return cos; } From fa4c23b76ec7843aff6a8f3e5b9bc7a46a41858e Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 27 Nov 2024 22:23:48 +0100 Subject: [PATCH 629/694] minor improvement --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a2ceb591a..a0d080818 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7737,9 +7737,9 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = int(40.7436f * atan2_t((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI int dx = (x - C_X); int dy = (y - C_Y); + rMap[XY(x, y)].angle = int(40.7436f * atan2_t(dy, dx)); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu } } From 8b1d712e1e04a696086b7e5b9df06a4dff97b289 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 1 Dec 2024 13:00:37 -0500 Subject: [PATCH 630/694] settings_leds: Initialize current limiter field When adding a new bus, the numeric current limit field was not being initialized; this was causing it to save 0 when saved instead of the default 55mA value. --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index baf80a5d7..ff5087aed 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -422,7 +422,7 @@ mA/LED:
- +
PSU: mA
Color Order: From ae8c3b02d04064cb809af2f3f10413563fae2dd7 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:35:48 +0100 Subject: [PATCH 631/694] blends FX - hotfix for black pixels fixing an off-by-one error to solve #4335 --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a0d080818..61d7a66a8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4497,7 +4497,7 @@ uint16_t mode_blends(void) { unsigned offset = 0; for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, pixels[offset++]); - if (offset > pixelLen) offset = 0; + if (offset >= pixelLen) offset = 0; } return FRAMETIME; From 8db8ecfef3cbbf8cd70862d856742874e2a9306f Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 2 Dec 2024 21:50:29 +0000 Subject: [PATCH 632/694] settings_leds: Fix quotes on LA value --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index ff5087aed..ce9c8ceed 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -422,7 +422,7 @@ mA/LED:
- +
PSU: mA
Color Order: From d620930f10f06bce563d95c94e6513739959a012 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 2 Dec 2024 21:52:41 +0000 Subject: [PATCH 633/694] settings_leds: Remove unused variables Remove a couple of leftover variables from previous revisions. --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index ce9c8ceed..71fec2798 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -6,7 +6,7 @@ LED Settings