Merge branch 'dev' into feature/live-preview-websockets
This commit is contained in:
		
							
								
								
									
										31
									
								
								.github/workflows/wled-ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/wled-ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| name: PlatformIO CI | ||||
|  | ||||
| on: [push] | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|  | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: Cache pip | ||||
|       uses: actions/cache@v2 | ||||
|       with: | ||||
|         path: ~/.cache/pip | ||||
|         key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} | ||||
|         restore-keys: | | ||||
|           ${{ runner.os }}-pip- | ||||
|     - name: Cache PlatformIO | ||||
|       uses: actions/cache@v2 | ||||
|       with: | ||||
|         path: ~/.platformio | ||||
|         key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} | ||||
|     - name: Set up Python | ||||
|       uses: actions/setup-python@v2 | ||||
|     - name: Install PlatformIO | ||||
|       run: | | ||||
|         python -m pip install --upgrade pip | ||||
|         pip install --upgrade platformio | ||||
|     - name: Run PlatformIO | ||||
|       run: pio run | ||||
							
								
								
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,65 @@ | ||||
| ## WLED changelog | ||||
|  | ||||
| ### WLED version 0.11.0 | ||||
| ### Development versions after 0.11.1 release | ||||
|  | ||||
| #### Build 2012210 | ||||
|  | ||||
| -  Split index.htm in separate CSS + JS files (PR #1542) | ||||
| -  Minify UI HTML, saving >1.5kB flash | ||||
| -  Fixed JShint warnings | ||||
|  | ||||
| #### Build 2012180 | ||||
|  | ||||
| -  Boot brightness 0 will now use the brightness from preset | ||||
| -  Add iOS scrolling momentum (from PR #1528) | ||||
|  | ||||
| ### WLED release 0.11.1 | ||||
|  | ||||
| #### Build 2012180 | ||||
|  | ||||
| -   Release of WLED 0.11.1 "Mirai" | ||||
| -   Fixed AP hide not saving (fixes #1520) | ||||
| -   Fixed MQTT password re-transmitted to HTML | ||||
| -   Hide Update buttons while uploading, accept .bin | ||||
| -   Make sure AP password is at least 8 characters long | ||||
|  | ||||
| ### Development versions after 0.11.0 release | ||||
|  | ||||
| #### Build 2012160 | ||||
|  | ||||
| -   Bump Espalexa to 2.5.0, fixing discovery (PR Espalexa/#152, originally PR #1497) | ||||
|  | ||||
| #### Build 2012150 | ||||
|  | ||||
| -   Added Blends FX (PR #1491) | ||||
| -   Fixed an issue that made it impossible to deactivate timed presets | ||||
|  | ||||
| #### Build 2012140 | ||||
|  | ||||
| -   Added Preset ID quick display option (PR #1462) | ||||
| -   Fixed LEDs not turning on when using gamma correct brightness and LEDPIN 2 (default) | ||||
| -   Fixed notifier applying main segment to selected segments on notification with FX/Col disabled  | ||||
|  | ||||
| #### Build 2012130 | ||||
|  | ||||
| -   Fixed RGBW mode not saved between reboots (fixes #1457) | ||||
| -   Added brightness scaling in palette function for default (PR #1484) | ||||
|  | ||||
| #### Build 2012101 | ||||
|  | ||||
| -   Fixed preset cycle default duration rounded down to nearest 10sec interval (#1458) | ||||
| -   Enabled E1.31/DDP/Art-Net in AP mode | ||||
|  | ||||
| #### Build 2012100 | ||||
|  | ||||
| -   Fixed multi-segment preset cycle | ||||
| -   Fixed EEPROM (pre-0.11 settings) not cleared on factory reset | ||||
| -   Fixed an issue with intermittent crashes on FX change (PR #1465) | ||||
| -   Added function to know if strip is updating (PR #1466) | ||||
| -   Fixed using colorwheel sliding the UI (PR #1459) | ||||
| -   Fixed analog clock settings not saving (PR #1448) | ||||
| -   Added Temperature palette (PR #1430) | ||||
| -   Added Candy cane FX (PR #1445) | ||||
|  | ||||
| #### Build 2012020 | ||||
|  | ||||
| @@ -11,6 +70,8 @@ | ||||
|  | ||||
| -   Fixed compilation for analog (PWM) LEDs | ||||
|  | ||||
| ### WLED version 0.11.0 | ||||
|  | ||||
| #### Build 2011290 | ||||
|  | ||||
| -   Release of WLED 0.11.0 "Mirai" | ||||
|   | ||||
							
								
								
									
										5
									
								
								images/Readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								images/Readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| ### Additional Logos | ||||
|  | ||||
| Additional awesome logos for WLED can be found here [Aircoookie/Akemi](https://github.com/Aircoookie/Akemi). | ||||
|  | ||||
| <img src="https://github.com/Aircoookie/Akemi/blob/master/akemi/001_cheerful.png"> | ||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "wled", | ||||
|   "version": "0.10.2", | ||||
|   "version": "0.11.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -958,9 +958,9 @@ | ||||
|       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" | ||||
|     }, | ||||
|     "ini": { | ||||
|       "version": "1.3.5", | ||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", | ||||
|       "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" | ||||
|       "version": "1.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", | ||||
|       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" | ||||
|     }, | ||||
|     "inliner": { | ||||
|       "version": "1.13.1", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "wled", | ||||
|   "version": "0.11.0", | ||||
|   "version": "0.11.1", | ||||
|   "description": "Tools for WLED project", | ||||
|   "main": "tools/cdata.js", | ||||
|   "directories": { | ||||
|   | ||||
| @@ -179,7 +179,10 @@ extra_scripts             = pio/name-firmware.py | ||||
| framework = arduino | ||||
| board_build.flash_mode = dout | ||||
| monitor_speed = 115200 | ||||
| # slow upload speed (comment this out with a ';' when building for development use) | ||||
| upload_speed = 115200 | ||||
| # fast upload speed (remove ';' when building for development use) | ||||
| ; upload_speed = 921600 | ||||
|  | ||||
| # ------------------------------------------------------------------------------ | ||||
| # LIBRARIES: required dependencies | ||||
| @@ -361,6 +364,14 @@ board_build.ldscript = ${common.ldscript_4m1m} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags_esp8266} | ||||
|  | ||||
| [env:anavi_miracle_controller] | ||||
| board = d1_mini | ||||
| 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 | ||||
|  | ||||
| # ------------------------------------------------------------------------------ | ||||
| # custom board configurations | ||||
| # ------------------------------------------------------------------------------ | ||||
|   | ||||
| @@ -69,6 +69,8 @@ function writeHtmlGzipped(sourceFile, resultFile) { | ||||
|   console.info("Reading " + sourceFile); | ||||
|   new inliner(sourceFile, function (error, html) { | ||||
|     console.info("Inlined " + html.length + " characters"); | ||||
|     html = filter(html, "html-minify-ui"); | ||||
|     console.info("Minified to " + html.length + " characters"); | ||||
|  | ||||
|     if (error) { | ||||
|       console.warn(error); | ||||
| @@ -123,6 +125,16 @@ function filter(str, type) { | ||||
|       continueOnParseError: false, | ||||
|       removeComments: true, | ||||
|     }); | ||||
|   } else if (type == "html-minify-ui") { | ||||
|     return MinifyHTML(str, { | ||||
|       collapseWhitespace: true, | ||||
|       conservativeCollapse: true, | ||||
|       maxLineLength: 80, | ||||
|       minifyCSS: true, | ||||
|       minifyJS: true,  | ||||
|       continueOnParseError: false, | ||||
|       removeComments: true, | ||||
|     }); | ||||
|   } else { | ||||
|     console.warn("Unknown filter: " + type); | ||||
|     return str; | ||||
| @@ -132,7 +144,7 @@ function filter(str, type) { | ||||
| function specToChunk(srcDir, s) { | ||||
|   if (s.method == "plaintext") { | ||||
|     const buf = fs.readFileSync(srcDir + "/" + s.file); | ||||
|     const str = buf.toString("ascii"); | ||||
|     const str = buf.toString("utf-8"); | ||||
|     const chunk = ` | ||||
| // Autogenerated from ${srcDir}/${s.file}, do not edit!! | ||||
| const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${ | ||||
|   | ||||
							
								
								
									
										40
									
								
								usermods/BME280_v2/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								usermods/BME280_v2/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.  | ||||
|  | ||||
| - Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`). | ||||
| - Data is published over MQTT so make sure you've enabled the MQTT sync interface. | ||||
| - This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages! | ||||
|  | ||||
| To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`) | ||||
| ```ini | ||||
| build_flags = | ||||
|   ${common.build_flags_esp8266} | ||||
|   -D USERMOD_BME280 | ||||
| ``` | ||||
| or define `USERMOD_BME280` in `my_config.h` | ||||
| ```c++ | ||||
| #define USERMOD_BME280 | ||||
| ``` | ||||
|  | ||||
| Changes include: | ||||
| - Adjustable measure intervals | ||||
|   - Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude | ||||
| - Adjustment of number of decimal places in published sensor values | ||||
|   - Separate adjustment for temperature, humidity and pressure values | ||||
|   - Values are rounded to the specified number of decimal places | ||||
| - Pressure measured in units of hPa instead of Pa | ||||
| - Calculation of heat index (apparent temperature) and dew point | ||||
|   - These, along with humidity measurements, are disabled if the sensor is a BMP280 | ||||
| - 16x oversampling of sensor during measurement | ||||
| - Values are only published if they are different from the previous value | ||||
| - Values are published on startup (continually until the MQTT broker acknowledges a successful publication) | ||||
|  | ||||
| Adjustments are made through preprocessor definitions at the start of the class definition. | ||||
|  | ||||
| MQTT topics are as follows: | ||||
| Measurement type | MQTT topic | ||||
| --- | --- | ||||
| Temperature | `<deviceTopic>/temperature` | ||||
| Humidity | `<deviceTopic>/humidity` | ||||
| Pressure | `<deviceTopic>/pressure` | ||||
| Heat index | `<deviceTopic>/heat_index` | ||||
| Dew point | `<deviceTopic>/dew_point` | ||||
							
								
								
									
										212
									
								
								usermods/BME280_v2/usermod_bme280.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								usermods/BME280_v2/usermod_bme280.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "wled.h" | ||||
| #include <Arduino.h> | ||||
| #include <Wire.h> | ||||
| #include <BME280I2C.h>               // BME280 sensor | ||||
| #include <EnvironmentCalculations.h> // BME280 extended measurements | ||||
|  | ||||
| class UsermodBME280 : public Usermod | ||||
| { | ||||
| private: | ||||
| // User-defined configuration | ||||
| #define Celsius               // Show temperature mesaurement in Celcius. Comment out for Fahrenheit | ||||
| #define TemperatureDecimals 1 // Number of decimal places in published temperaure values | ||||
| #define HumidityDecimals 0    // Number of decimal places in published humidity values | ||||
| #define PressureDecimals 2    // Number of decimal places in published pressure values | ||||
| #define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds | ||||
| #define PressureInterval 300  // Interval to measure pressure in seconds | ||||
|  | ||||
| // Sanity checks | ||||
| #if !defined(TemperatureDecimals) || TemperatureDecimals < 0 | ||||
|   #define TemperatureDecimals 0 | ||||
| #endif | ||||
| #if !defined(HumidityDecimals) || HumidityDecimals < 0 | ||||
|   #define HumidityDecimals 0 | ||||
| #endif | ||||
| #if !defined(PressureDecimals) || PressureDecimals < 0 | ||||
|   #define PressureDecimals 0 | ||||
| #endif | ||||
| #if !defined(TemperatureInterval) || TemperatureInterval < 0 | ||||
|   #define TemperatureInterval 1 | ||||
| #endif | ||||
| #if !defined(PressureInterval) || PressureInterval < 0 | ||||
|   #define PressureInterval TemperatureInterval | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 // ESP32 boards | ||||
|   uint8_t SCL_PIN = 22; | ||||
|   uint8_t SDA_PIN = 21; | ||||
| #else // ESP8266 boards | ||||
|   uint8_t SCL_PIN = 5; | ||||
|   uint8_t SDA_PIN = 4; | ||||
|   //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 | ||||
| #endif | ||||
|  | ||||
|   // BME280 sensor settings | ||||
|   BME280I2C::Settings settings{ | ||||
|       BME280::OSR_X16, // Temperature oversampling x16 | ||||
|       BME280::OSR_X16, // Humidity oversampling x16 | ||||
|       BME280::OSR_X16, // Pressure oversampling x16 | ||||
|       // Defaults | ||||
|       BME280::Mode_Forced, | ||||
|       BME280::StandbyTime_1000ms, | ||||
|       BME280::Filter_Off, | ||||
|       BME280::SpiEnable_False, | ||||
|       BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76 | ||||
|   }; | ||||
|  | ||||
|   BME280I2C bme{settings}; | ||||
|  | ||||
|   uint8_t SensorType; | ||||
|  | ||||
|   // Measurement timers | ||||
|   long timer; | ||||
|   long lastTemperatureMeasure = 0; | ||||
|   long lastPressureMeasure = 0; | ||||
|  | ||||
|   // Current sensor values | ||||
|   float SensorTemperature; | ||||
|   float SensorHumidity; | ||||
|   float SensorHeatIndex; | ||||
|   float SensorDewPoint; | ||||
|   float SensorPressure; | ||||
|   // Track previous sensor values | ||||
|   float lastTemperature; | ||||
|   float lastHumidity; | ||||
|   float lastHeatIndex; | ||||
|   float lastDewPoint; | ||||
|   float lastPressure; | ||||
|  | ||||
|   // Store packet IDs of MQTT publications | ||||
|   uint16_t mqttTemperaturePub = 0; | ||||
|   uint16_t mqttPressurePub = 0; | ||||
|  | ||||
|   void UpdateBME280Data(int SensorType) | ||||
|   { | ||||
|     float _temperature, _humidity, _pressure; | ||||
|     #ifdef Celsius | ||||
|       BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); | ||||
|       EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius); | ||||
|     #else | ||||
|       BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit); | ||||
|       EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit); | ||||
|     #endif | ||||
|     BME280::PresUnit presUnit(BME280::PresUnit_hPa); | ||||
|  | ||||
|     bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); | ||||
|  | ||||
|     SensorTemperature = _temperature; | ||||
|     SensorHumidity = _humidity; | ||||
|     SensorPressure = _pressure; | ||||
|     if (SensorType == 1) | ||||
|     { | ||||
|       SensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit); | ||||
|       SensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| public: | ||||
|   void setup() | ||||
|   { | ||||
|     Wire.begin(SDA_PIN, SCL_PIN); | ||||
|  | ||||
|     if (!bme.begin()) | ||||
|     { | ||||
|       SensorType = 0; | ||||
|       Serial.println("Could not find BME280I2C sensor!"); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       switch (bme.chipModel()) | ||||
|       { | ||||
|       case BME280::ChipModel_BME280: | ||||
|         SensorType = 1; | ||||
|         Serial.println("Found BME280 sensor! Success."); | ||||
|         break; | ||||
|       case BME280::ChipModel_BMP280: | ||||
|         SensorType = 2; | ||||
|         Serial.println("Found BMP280 sensor! No Humidity available."); | ||||
|         break; | ||||
|       default: | ||||
|         SensorType = 0; | ||||
|         Serial.println("Found UNKNOWN sensor! Error!"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void loop() | ||||
|   { | ||||
|     // BME280 sensor MQTT publishing | ||||
|     // Check if sensor present and MQTT Connected, otherwise it will crash the MCU | ||||
|     if (SensorType != 0 && mqtt != nullptr) | ||||
|     { | ||||
|       // Timer to fetch new temperature, humidity and pressure data at intervals | ||||
|       timer = millis(); | ||||
|  | ||||
|       if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000 || mqttTemperaturePub == 0) | ||||
|       { | ||||
|         lastTemperatureMeasure = timer; | ||||
|  | ||||
|         UpdateBME280Data(SensorType); | ||||
|  | ||||
|         float Temperature = roundf(SensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); | ||||
|         float Humidity, HeatIndex, DewPoint; | ||||
|  | ||||
|         // If temperature has changed since last measure, create string populated with device topic | ||||
|         // from the UI and values read from sensor, then publish to broker | ||||
|         if (Temperature != lastTemperature) | ||||
|         { | ||||
|           String topic = String(mqttDeviceTopic) + "/temperature"; | ||||
|           mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(Temperature, TemperatureDecimals).c_str()); | ||||
|         } | ||||
|  | ||||
|         lastTemperature = Temperature; // Update last sensor temperature for next loop | ||||
|  | ||||
|         if (SensorType == 1) // Only if sensor is a BME280 | ||||
|         { | ||||
|           Humidity = roundf(SensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals); | ||||
|           HeatIndex = roundf(SensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); | ||||
|           DewPoint = roundf(SensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); | ||||
|  | ||||
|           if (Humidity != lastHumidity) | ||||
|           { | ||||
|             String topic = String(mqttDeviceTopic) + "/humidity"; | ||||
|             mqtt->publish(topic.c_str(), 0, false, String(Humidity, HumidityDecimals).c_str()); | ||||
|           } | ||||
|  | ||||
|           if (HeatIndex != lastHeatIndex) | ||||
|           { | ||||
|             String topic = String(mqttDeviceTopic) + "/heat_index"; | ||||
|             mqtt->publish(topic.c_str(), 0, false, String(HeatIndex, TemperatureDecimals).c_str()); | ||||
|           } | ||||
|  | ||||
|           if (DewPoint != lastDewPoint) | ||||
|           { | ||||
|             String topic = String(mqttDeviceTopic) + "/dew_point"; | ||||
|             mqtt->publish(topic.c_str(), 0, false, String(DewPoint, TemperatureDecimals).c_str()); | ||||
|           } | ||||
|  | ||||
|           lastHumidity = Humidity; | ||||
|           lastHeatIndex = HeatIndex; | ||||
|           lastDewPoint = DewPoint; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (timer - lastPressureMeasure >= PressureInterval * 1000 || mqttPressurePub == 0) | ||||
|       { | ||||
|         lastPressureMeasure = timer; | ||||
|  | ||||
|         float Pressure = roundf(SensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals); | ||||
|  | ||||
|         if (Pressure != lastPressure) | ||||
|         { | ||||
|           String topic = String(mqttDeviceTopic) + "/pressure"; | ||||
|           mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(Pressure, PressureDecimals).c_str()); | ||||
|         } | ||||
|  | ||||
|         lastPressure = Pressure; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| @@ -1,17 +1,32 @@ | ||||
| # Fix unreachable net services V2 | ||||
|  | ||||
| **Attention: This usermod compiles only for ESP8266** | ||||
|  | ||||
| This usermod-v2 modification performs a ping request to the local IP address every 60 seconds. By this procedure the net services of WLED remains accessible in some problematic WLAN environments. | ||||
|  | ||||
| The modification works with static or DHCP IP address configuration. | ||||
|  | ||||
| **Webinterface**: The number of pings and reconnects is displayed on the info page in the web interface. | ||||
|  | ||||
| _Story:_ | ||||
|  | ||||
| Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible.  Now I found out that the connection is at least reestablished when a ping request is executed by the device. | ||||
|  | ||||
| With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request. | ||||
|  | ||||
| ## Webinterface | ||||
|  | ||||
| The number of pings and reconnects is displayed on the info page in the web interface. | ||||
| The ping delay can be changed. Changes persist after a reboot. | ||||
|  | ||||
| ## JSON API | ||||
|  | ||||
| The usermod supports the following state changes: | ||||
|  | ||||
| | JSON key    | Value range      | Description                     | | ||||
| |-------------|------------------|---------------------------------| | ||||
| | PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor | | ||||
|  | ||||
|  Changes also persist after a reboot. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| 1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory. | ||||
|   | ||||
| @@ -1,6 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "wled.h" | ||||
| #if defined(ESP32) | ||||
| #warning "Usermod FixUnreachableNetServices works only with ESP8266 builds" | ||||
| class FixUnreachableNetServices : public Usermod | ||||
| { | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #if defined(ESP8266) | ||||
| #include <ping.h> | ||||
|  | ||||
| /* | ||||
| @@ -23,34 +31,37 @@ | ||||
|  * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp | ||||
|  */ | ||||
|  | ||||
| class FixUnreachableNetServices : public Usermod { | ||||
| class FixUnreachableNetServices : public Usermod | ||||
| { | ||||
| private: | ||||
|   //Private class members. You can declare variables and functions only accessible to your usermod here | ||||
|   unsigned long m_lastTime = 0; | ||||
|  | ||||
|     // desclare required variables | ||||
|     const unsigned int PingDelayMs = 60000; | ||||
|   // declare required variables | ||||
|   unsigned long m_pingDelayMs = 60000; | ||||
|   unsigned long m_connectedWiFi = 0; | ||||
|   ping_option m_pingOpt; | ||||
|   unsigned int m_pingCount = 0; | ||||
|   bool m_updateConfig = false; | ||||
|  | ||||
| public: | ||||
|   //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. | ||||
|    */ | ||||
|     void setup() { | ||||
|   void setup() | ||||
|   { | ||||
|     //Serial.println("Hello from my usermod!"); | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * connected() is called every time the WiFi is (re)connected | ||||
|    * Use it to initialize network interfaces | ||||
|    */ | ||||
|     void connected() { | ||||
|   void connected() | ||||
|   { | ||||
|     //Serial.println("Connected to WiFi!"); | ||||
|  | ||||
|     ++m_connectedWiFi; | ||||
| @@ -59,31 +70,27 @@ class FixUnreachableNetServices : public Usermod { | ||||
|     memset(&m_pingOpt, 0, sizeof(struct ping_option)); | ||||
|     m_pingOpt.count = 1; | ||||
|     m_pingOpt.ip = WiFi.localIP(); | ||||
|  | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * 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. | ||||
|   /** | ||||
|    * loop | ||||
|    */ | ||||
|     void loop() { | ||||
|       if (m_connectedWiFi > 0 && millis()-m_lastTime > PingDelayMs) | ||||
|   void loop() | ||||
|   { | ||||
|     if (m_connectedWiFi > 0 && millis() - m_lastTime > m_pingDelayMs) | ||||
|     { | ||||
|       ping_start(&m_pingOpt); | ||||
|       m_lastTime = millis(); | ||||
|       ++m_pingCount; | ||||
|     } | ||||
|     if (m_updateConfig) | ||||
|     { | ||||
|       serializeConfig(); | ||||
|       m_updateConfig = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * 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 | ||||
| @@ -92,9 +99,15 @@ class FixUnreachableNetServices : public Usermod { | ||||
|   { | ||||
|     //this code adds "u":{"⚡ Ping fix pings": m_pingCount} to the info object | ||||
|     JsonObject user = root["u"]; | ||||
|       if (user.isNull()) user = root.createNestedObject("u"); | ||||
|     if (user.isNull()) | ||||
|       user = root.createNestedObject("u"); | ||||
|  | ||||
|       JsonArray infoArr = user.createNestedArray("⚡ Ping fix pings"); //name | ||||
|     String uiDomString = "⚡ Ping fix pings<span style=\"display:block;padding-left:25px;\">\ | ||||
| Delay <input type=\"number\" min=\"5\" max=\"300\" value=\""; | ||||
|     uiDomString += (unsigned long)(m_pingDelayMs / 1000); | ||||
|     uiDomString += "\" onchange=\"requestJson({PingDelay:parseInt(this.value)});\">sec</span>"; | ||||
|  | ||||
|     JsonArray infoArr = user.createNestedArray(uiDomString); //name | ||||
|     infoArr.add(m_pingCount);                                              //value | ||||
|  | ||||
|     //this code adds "u":{"⚡ Reconnects": m_connectedWiFi - 1} to the info object | ||||
| @@ -102,29 +115,48 @@ class FixUnreachableNetServices : public Usermod { | ||||
|     infoArr.add(m_connectedWiFi - 1);                        //value | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * 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) | ||||
|   { | ||||
|       //root["user0"] = userVar0; | ||||
|     root["PingDelay"] = (m_pingDelayMs/1000); | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * 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) | ||||
|   { | ||||
|       //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value | ||||
|       //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); | ||||
|     if (root["PingDelay"] != nullptr) | ||||
|     { | ||||
|       m_pingDelayMs = (1000 * max(1UL, min(300UL, root["PingDelay"].as<unsigned long>()))); | ||||
|       m_updateConfig = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * provide the changeable values | ||||
|    */ | ||||
|   void addToConfig(JsonObject &root) | ||||
|   { | ||||
|     JsonObject top = root.createNestedObject("FixUnreachableNetServices"); | ||||
|     top["PingDelayMs"] = m_pingDelayMs; | ||||
|   } | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * restore the changeable values | ||||
|    */ | ||||
|   void readFromConfig(JsonObject &root) | ||||
|   { | ||||
|     JsonObject top = root["FixUnreachableNetServices"]; | ||||
|     m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs; | ||||
|     m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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. | ||||
|    */ | ||||
| @@ -132,7 +164,5 @@ class FixUnreachableNetServices : public Usermod { | ||||
|   { | ||||
|     return USERMOD_ID_FIXNETSERVICES; | ||||
|   } | ||||
|  | ||||
|    //More methods can be added in the future, this example will then be extended. | ||||
|    //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! | ||||
| }; | ||||
| #endif | ||||
|   | ||||
| @@ -1,17 +0,0 @@ | ||||
| # Fix unreachable Webserver | ||||
|  | ||||
| This modification performs a ping request to the local IP address every 60 seconds. By this procedure the web server remains accessible in some problematic WLAN environments. | ||||
|  | ||||
| The modification works with static or DHCP IP address configuration  | ||||
|  | ||||
| _Story:_ | ||||
|  | ||||
| Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible.  Now I found out that the connection is at least reestablished when a ping request is executed by the device. | ||||
|  | ||||
| With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request. | ||||
|  | ||||
| ## Installation  | ||||
|  | ||||
| Copy and replace the file `usermod.cpp` in wled00 directory. | ||||
|  | ||||
|  | ||||
| @@ -1,43 +0,0 @@ | ||||
| #include "wled.h" | ||||
| /* | ||||
|  * This 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) | ||||
|  * bytes 2400+ are currently ununsed, but might be used for future wled features | ||||
|  */ | ||||
|  | ||||
| #include <ping.h> | ||||
|  | ||||
| const int PingDelayMs = 60000; | ||||
| long lastCheckTime = 0; | ||||
| bool connectedWiFi = false; | ||||
| ping_option pingOpt; | ||||
|  | ||||
| //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() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| //gets called every time WiFi is (re-)connected. Initialize own network interfaces here | ||||
| void userConnected() | ||||
| { | ||||
|   connectedWiFi = true; | ||||
|   // initialize ping_options structure | ||||
|   memset(&pingOpt, 0, sizeof(struct ping_option)); | ||||
|   pingOpt.count = 1; | ||||
|   pingOpt.ip = WiFi.localIP(); | ||||
| } | ||||
|  | ||||
| //loop. You can use "if (WLED_CONNECTED)" to check for successful connection | ||||
| void userLoop() | ||||
| { | ||||
|   if (connectedWiFi && millis()-lastCheckTime > PingDelayMs) | ||||
|   { | ||||
|     ping_start(&pingOpt); | ||||
|     lastCheckTime = millis(); | ||||
|   } | ||||
| } | ||||
| @@ -11,8 +11,8 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik | ||||
|  | ||||
| The info page in the web interface shows the items below | ||||
|  | ||||
| - the state of the sensor. By clicking on the state the sensor can be deactivated/activated.  | ||||
| **I recommend to deactivate the sensor before installing an OTA update**. | ||||
| - the state of the sensor. By clicking on the state the sensor can be deactivated/activated. Changes persist after a reboot. | ||||
| **I recommend to deactivate the sensor before an OTA update and activate it again afterwards**. | ||||
| - the remaining time of the off timer.  | ||||
|  | ||||
| ## JSON API | ||||
| @@ -24,6 +24,8 @@ The usermod supports  the following state changes: | ||||
| | PIRenabled | bool        | Deactivdate/activate the sensor | | ||||
| | PIRoffSec  | 60 to 43200 | Off timer seconds               | | ||||
|  | ||||
|  Changes also persist after a reboot. | ||||
|  | ||||
| ## Sensor connection | ||||
|  | ||||
| My setup uses an HC-SR501 sensor, a HC-SR505 should also work. | ||||
| @@ -72,26 +74,36 @@ void registerUsermods() | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Usermod installation (advanced mode) | ||||
| ## API to enable/disable the PIR sensor from outside. For example from another usermod. | ||||
|  | ||||
| In this mode IR sensor will disable PIR when light ON by remote controller and enable PIR when light OFF. | ||||
| The class provides the static method `PIRsensorSwitch* PIRsensorSwitch::GetInstance()` to get a pointer to the usermod object. | ||||
|  | ||||
| 1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory. | ||||
| 2. Register the usermod by adding `#include "usermod_PIR_sensor_switch.h"` in the top and `registerUsermod(new PIRsensorSwitch());` in the bottom of `usermods_list.cpp`. | ||||
| 3. Add to the line 237, on `wled.h` in the `wled00` directory: | ||||
| To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.  | ||||
|  | ||||
| 	`WLED_GLOBAL bool m_PIRenabled _INIT(true);                        // enable PIR sensor` | ||||
| ### There are two options to get access to the usermod instance: | ||||
|  | ||||
| 4. On `ir.cpp` in the `wled00` directory, add to the IR controller's mapping (beyond line 200): | ||||
| 1. Include `usermod_PIR_sensor_switch.h` **before** you include the other usermod in `usermods_list.cpp' | ||||
|  | ||||
| - To the off button: | ||||
| 		`m_PIRenabled = true;` | ||||
| or | ||||
|  | ||||
| - To the on button: | ||||
| 		`m_PIRenabled = false;` | ||||
| 2. Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it. | ||||
|  | ||||
| 5. Edit line 40, on `usermod_PIR_sensor_switch.h` in the `wled00` directory: | ||||
| **Example usermod.h :** | ||||
| ```cpp | ||||
| #include "wled.h" | ||||
|  | ||||
|     `\\bool m_PIRenabled = true;` | ||||
| #include "usermod_PIR_sensor_switch.h" | ||||
|  | ||||
| class MyUsermod : public Usermod { | ||||
|   //... | ||||
|  | ||||
|   void togglePIRSensor() { | ||||
|     if (PIRsensorSwitch::GetInstance() != nullptr) { | ||||
|       PIRsensorSwitch::GetInstance()->EnablePIRsensor(!PIRsensorSwitch::GetInstance()->PIRsensorEnabled()); | ||||
|     } | ||||
|   } | ||||
|   //... | ||||
| }; | ||||
| ``` | ||||
|  | ||||
| Have fun - @gegu | ||||
|   | ||||
| @@ -24,7 +24,40 @@ | ||||
|  * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp | ||||
|  */ | ||||
|  | ||||
| class PIRsensorSwitch : public Usermod { | ||||
| class PIRsensorSwitch : public Usermod | ||||
| { | ||||
| public: | ||||
|   /** | ||||
|    * constructor | ||||
|    */ | ||||
|   PIRsensorSwitch() | ||||
|   { | ||||
|     // set static instance pointer | ||||
|     PIRsensorSwitchInstance(this); | ||||
|   } | ||||
|   /** | ||||
|    * desctructor | ||||
|    */ | ||||
|   ~PIRsensorSwitch() | ||||
|   { | ||||
|     PIRsensorSwitchInstance(nullptr, true); | ||||
|     ; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * return the instance pointer of the class | ||||
|    */ | ||||
|   static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); } | ||||
|  | ||||
|   /** | ||||
|    * Enable/Disable the PIR sensor | ||||
|    */ | ||||
|   void EnablePIRsensor(bool enable) { m_PIRenabled = enable; } | ||||
|   /** | ||||
|    * Get PIR sensor enabled/disabled state | ||||
|    */ | ||||
|   bool PIRsensorEnabled() { return m_PIRenabled; } | ||||
|  | ||||
| private: | ||||
|   // PIR sensor pin | ||||
|   const uint8_t PIRsensorPin = 13; // D7 on D1 mini | ||||
| @@ -38,53 +71,59 @@ class PIRsensorSwitch : public Usermod { | ||||
|   byte m_PIRsensorPinState = LOW; | ||||
|   // PIR sensor enabled - ISR attached | ||||
|   bool m_PIRenabled = true; | ||||
|   // state if serializeConfig() should be called | ||||
|   bool m_updateConfig = false; | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * return or change if new PIR sensor state is available | ||||
|    */ | ||||
|     static volatile bool newPIRsensorState(bool changeState = false, bool newState = false) { | ||||
|       static volatile bool s_PIRsensorState = false; | ||||
|       if (changeState) { | ||||
|         s_PIRsensorState = newState; | ||||
|       } | ||||
|       return s_PIRsensorState; | ||||
|     } | ||||
|   static volatile bool newPIRsensorState(bool changeState = false, bool newState = false); | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * PIR sensor state has changed | ||||
|    */ | ||||
|     static void IRAM_ATTR ISR_PIRstateChange() { | ||||
|       newPIRsensorState(true, true); | ||||
|     } | ||||
|   static void IRAM_ATTR ISR_PIRstateChange(); | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * Set/get instance pointer | ||||
|    */ | ||||
|   static PIRsensorSwitch *PIRsensorSwitchInstance(PIRsensorSwitch *pInstance = nullptr, bool bRemoveInstance = false); | ||||
|  | ||||
|   /** | ||||
|    * switch strip on/off | ||||
|    */ | ||||
|     void switchStrip(bool switchOn) { | ||||
|       if (switchOn && bri == 0) { | ||||
|   void switchStrip(bool switchOn) | ||||
|   { | ||||
|     if (switchOn && bri == 0) | ||||
|     { | ||||
|       bri = briLast; | ||||
|       colorUpdated(NotifyUpdateMode); | ||||
|     } | ||||
|       else if (!switchOn && bri != 0) { | ||||
|     else if (!switchOn && bri != 0) | ||||
|     { | ||||
|       briLast = bri; | ||||
|       bri = 0; | ||||
|       colorUpdated(NotifyUpdateMode); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * Read and update PIR sensor state. | ||||
|    * Initilize/reset switch off timer | ||||
|    */ | ||||
|     bool updatePIRsensorState() { | ||||
|       if (newPIRsensorState()) { | ||||
|   bool updatePIRsensorState() | ||||
|   { | ||||
|     if (newPIRsensorState()) | ||||
|     { | ||||
|       m_PIRsensorPinState = digitalRead(PIRsensorPin); | ||||
|  | ||||
|         if (m_PIRsensorPinState == HIGH) { | ||||
|       if (m_PIRsensorPinState == HIGH) | ||||
|       { | ||||
|         m_offTimerStart = 0; | ||||
|         switchStrip(true); | ||||
|       } | ||||
|         else if (bri != 0) { | ||||
|       else if (bri != 0) | ||||
|       { | ||||
|         // start switch off timer | ||||
|         m_offTimerStart = millis(); | ||||
|       } | ||||
| @@ -94,12 +133,15 @@ class PIRsensorSwitch : public Usermod { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|     /*  | ||||
|   /** | ||||
|    * switch off the strip if the delay has elapsed  | ||||
|    */ | ||||
|     bool handleOffTimer() { | ||||
|       if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) { | ||||
|         if (m_PIRenabled == true){ | ||||
|   bool handleOffTimer() | ||||
|   { | ||||
|     if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) | ||||
|     { | ||||
|       if (m_PIRenabled == true) | ||||
|       { | ||||
|         switchStrip(false); | ||||
|       } | ||||
|       m_offTimerStart = 0; | ||||
| @@ -111,37 +153,46 @@ class PIRsensorSwitch : public Usermod { | ||||
| public: | ||||
|   //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. | ||||
|    */ | ||||
|     void setup() { | ||||
|   void setup() | ||||
|   { | ||||
|     // PIR Sensor mode INPUT_PULLUP | ||||
|     pinMode(PIRsensorPin, INPUT_PULLUP); | ||||
|     if (m_PIRenabled) | ||||
|     { | ||||
|       // assign interrupt function and set CHANGE mode | ||||
|       attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * connected() is called every time the WiFi is (re)connected | ||||
|    * Use it to initialize network interfaces | ||||
|    */ | ||||
|     void connected() { | ||||
|  | ||||
|   void connected() | ||||
|   { | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * loop() is called continuously. Here you can check for events, read sensors, etc. | ||||
|    */ | ||||
|     void loop() { | ||||
|       if (!updatePIRsensorState()) { | ||||
|   void loop() | ||||
|   { | ||||
|     if (!updatePIRsensorState()) | ||||
|     { | ||||
|       handleOffTimer(); | ||||
|       if (m_updateConfig) | ||||
|       { | ||||
|         serializeConfig(); | ||||
|         m_updateConfig = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. | ||||
|    *  | ||||
|    * Add PIR sensor state and switch off timer duration to jsoninfo | ||||
| @@ -151,17 +202,21 @@ class PIRsensorSwitch : public Usermod { | ||||
|     //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object | ||||
|     // the value contains a button to toggle the sensor enabled/disabled | ||||
|     JsonObject user = root["u"]; | ||||
|       if (user.isNull()) user = root.createNestedObject("u"); | ||||
|     if (user.isNull()) | ||||
|       user = root.createNestedObject("u"); | ||||
|  | ||||
|     JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name | ||||
|     String uiDomString = "<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:"; | ||||
|     String sensorStateInfo; | ||||
|  | ||||
|     // PIR sensor state | ||||
|       if (m_PIRenabled) { | ||||
|     if (m_PIRenabled) | ||||
|     { | ||||
|       uiDomString += "false"; | ||||
|       sensorStateInfo = (m_PIRsensorPinState != LOW ? "active" : "inactive"); //value | ||||
|       } else { | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       uiDomString += "true"; | ||||
|       sensorStateInfo = "Disabled !"; | ||||
|     } | ||||
| @@ -171,35 +226,46 @@ class PIRsensorSwitch : public Usermod { | ||||
|     infoArr.add(uiDomString); //value | ||||
|  | ||||
|     //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object | ||||
|       infoArr = user.createNestedArray("⏲ switch off timer"); //name | ||||
|     uiDomString = "⏲ switch off timer<span style=\"display:block;padding-left:25px;\">\ | ||||
| after <input type=\"number\" min=\"1\" max=\"720\" value=\""; | ||||
|     uiDomString += (m_switchOffDelay / 60000); | ||||
|     uiDomString += "\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min</span>"; | ||||
|     infoArr = user.createNestedArray(uiDomString); //name | ||||
|  | ||||
|     // off timer | ||||
|       if (m_offTimerStart > 0) { | ||||
|     if (m_offTimerStart > 0) | ||||
|     { | ||||
|       uiDomString = ""; | ||||
|       unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000; | ||||
|         if (offSeconds >= 3600) { | ||||
|       if (offSeconds >= 3600) | ||||
|       { | ||||
|         uiDomString += (offSeconds / 3600); | ||||
|         uiDomString += " hours "; | ||||
|         offSeconds %= 3600; | ||||
|       } | ||||
|         if (offSeconds >= 60) { | ||||
|       if (offSeconds >= 60) | ||||
|       { | ||||
|         uiDomString += (offSeconds / 60); | ||||
|         offSeconds %= 60; | ||||
|         } else if (uiDomString.length() > 0){ | ||||
|       } | ||||
|       else if (uiDomString.length() > 0) | ||||
|       { | ||||
|         uiDomString += 0; | ||||
|       } | ||||
|         if (uiDomString.length() > 0){ | ||||
|       if (uiDomString.length() > 0) | ||||
|       { | ||||
|         uiDomString += " min "; | ||||
|       } | ||||
|       uiDomString += (offSeconds); | ||||
|       infoArr.add(uiDomString + " sec"); | ||||
|       } else { | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       infoArr.add("inactive"); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * 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 | ||||
|    * Add "PIRenabled" to json state. This can be used to disable/enable the sensor. | ||||
| @@ -211,8 +277,7 @@ class PIRsensorSwitch : public Usermod { | ||||
|     root["PIRoffSec"] = (m_switchOffDelay / 1000); | ||||
|   } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * 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 | ||||
|    * Read "PIRenabled" from json state and switch enable/disable the PIR sensor. | ||||
| @@ -220,24 +285,49 @@ class PIRsensorSwitch : public Usermod { | ||||
|    */ | ||||
|   void readFromJsonState(JsonObject &root) | ||||
|   { | ||||
|       if (root["PIRoffSec"] != nullptr) { | ||||
|     if (root["PIRoffSec"] != nullptr) | ||||
|     { | ||||
|       m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as<unsigned long>()))); | ||||
|       m_updateConfig = true; | ||||
|     } | ||||
|  | ||||
|       if (root["PIRenabled"] != nullptr) { | ||||
|         if (root["PIRenabled"] && !m_PIRenabled) { | ||||
|     if (root["PIRenabled"] != nullptr) | ||||
|     { | ||||
|       if (root["PIRenabled"] && !m_PIRenabled) | ||||
|       { | ||||
|         attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); | ||||
|         newPIRsensorState(true, true); | ||||
|       } | ||||
|         else if(m_PIRenabled) { | ||||
|       else if (m_PIRenabled) | ||||
|       { | ||||
|         detachInterrupt(PIRsensorPin); | ||||
|       } | ||||
|       m_PIRenabled = root["PIRenabled"]; | ||||
|       m_updateConfig = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * provide the changeable values | ||||
|    */ | ||||
|   void addToConfig(JsonObject &root) | ||||
|   { | ||||
|     JsonObject top = root.createNestedObject("PIRsensorSwitch"); | ||||
|     top["PIRenabled"] = m_PIRenabled; | ||||
|     top["PIRoffSec"] = m_switchOffDelay; | ||||
|   } | ||||
|  | ||||
|     /* | ||||
|   /** | ||||
|    * restore the changeable values | ||||
|    */ | ||||
|   void readFromConfig(JsonObject &root) | ||||
|   { | ||||
|     JsonObject top = root["PIRsensorSwitch"]; | ||||
|     m_PIRenabled = (top["PIRenabled"] != nullptr ? top["PIRenabled"] : true); | ||||
|     m_switchOffDelay = top["PIRoffSec"] | m_switchOffDelay; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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. | ||||
|    */ | ||||
| @@ -245,7 +335,32 @@ class PIRsensorSwitch : public Usermod { | ||||
|   { | ||||
|     return USERMOD_ID_PIRSWITCH; | ||||
|   } | ||||
|  | ||||
|    //More methods can be added in the future, this example will then be extended. | ||||
|    //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! | ||||
| }; | ||||
|  | ||||
| ////////////////////////////////////////////////////// | ||||
| // PIRsensorSwitch static method implementations | ||||
|  | ||||
| volatile bool PIRsensorSwitch::newPIRsensorState(bool changeState, bool newState) | ||||
| { | ||||
|   static volatile bool s_PIRsensorState = false; | ||||
|   if (changeState) | ||||
|   { | ||||
|     s_PIRsensorState = newState; | ||||
|   } | ||||
|   return s_PIRsensorState; | ||||
| } | ||||
|  | ||||
| void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange() | ||||
| { | ||||
|   newPIRsensorState(true, true); | ||||
| } | ||||
|  | ||||
| PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInstance, bool bRemoveInstance) | ||||
| { | ||||
|   static PIRsensorSwitch *s_pPIRsensorSwitch = nullptr; | ||||
|   if (pInstance != nullptr || bRemoveInstance) | ||||
|   { | ||||
|     s_pPIRsensorSwitch = pInstance; | ||||
|   } | ||||
|   return s_pPIRsensorSwitch; | ||||
| } | ||||
|   | ||||
							
								
								
									
										81
									
								
								usermods/buzzer/usermod_v2_buzzer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								usermods/buzzer/usermod_v2_buzzer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "wled.h" | ||||
| #include "Arduino.h" | ||||
|  | ||||
| #include <deque> | ||||
|  | ||||
| #define USERMOD_ID_BUZZER 900 | ||||
| #ifndef USERMOD_BUZZER_PIN | ||||
| #define USERMOD_BUZZER_PIN GPIO_NUM_32 | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Usermods allow you to add own functionality to WLED more easily | ||||
|  * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality | ||||
|  *  | ||||
|  * Using a usermod: | ||||
|  * 1. Copy the usermod into the sketch folder (same folder as wled00.ino) | ||||
|  * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp | ||||
|  */ | ||||
|  | ||||
| class BuzzerUsermod : public Usermod { | ||||
|   private: | ||||
|     unsigned long lastTime_ = 0; | ||||
|     unsigned long delay_ = 0; | ||||
|     std::deque<std::pair<uint8_t, unsigned long>> sequence_ {}; | ||||
|   public: | ||||
|     /* | ||||
|      * setup() is called once at boot. WiFi is not yet connected at this point. | ||||
|      * You can use it to initialize variables, sensors or similar. | ||||
|      */ | ||||
|     void setup() { | ||||
|       // Setup the pin, and default to LOW | ||||
|       pinMode(USERMOD_BUZZER_PIN, OUTPUT); | ||||
|       digitalWrite(USERMOD_BUZZER_PIN, LOW); | ||||
|  | ||||
|       // Beep on startup | ||||
|       sequence_.push_back({ HIGH, 50 }); | ||||
|       sequence_.push_back({ LOW, 0 }); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * connected() is called every time the WiFi is (re)connected | ||||
|      * Use it to initialize network interfaces | ||||
|      */ | ||||
|     void connected() { | ||||
|       // Double beep on WiFi | ||||
|       sequence_.push_back({ LOW, 100 }); | ||||
|       sequence_.push_back({ HIGH, 50 }); | ||||
|       sequence_.push_back({ LOW, 30 }); | ||||
|       sequence_.push_back({ HIGH, 50 }); | ||||
|       sequence_.push_back({ LOW, 0 }); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * loop() is called continuously. Here you can check for events, read sensors, etc. | ||||
|      */ | ||||
|     void loop() { | ||||
|       if (sequence_.size() < 1) return; // Wait until there is a sequence | ||||
|       if (millis() - lastTime_ <= delay_) return; // Wait until delay has elapsed | ||||
|  | ||||
|       auto event = sequence_.front(); | ||||
|       sequence_.pop_front(); | ||||
|  | ||||
|       digitalWrite(USERMOD_BUZZER_PIN, event.first); | ||||
|       delay_ = event.second; | ||||
|  | ||||
|       lastTime_ = millis(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * 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() | ||||
|     { | ||||
|       return USERMOD_ID_BUZZER; | ||||
|     } | ||||
| }; | ||||
| @@ -149,7 +149,6 @@ public: | ||||
|  | ||||
|   void Show() | ||||
|   { | ||||
|     byte b; | ||||
|     switch (_type) | ||||
|     { | ||||
|       case NeoPixelType_Grb: | ||||
| @@ -191,6 +190,51 @@ public: | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   bool CanShow() | ||||
|   { | ||||
|     bool canShow = true; | ||||
|     switch (_type) | ||||
|     { | ||||
|       case NeoPixelType_Grb: | ||||
|       { | ||||
|         for (uint8_t idx = 0; idx < numStrips; idx++) | ||||
|         { | ||||
|           switch (idx) | ||||
|           { | ||||
|             case 0: canShow &= pGrb0->CanShow(); break; | ||||
|             case 1: canShow &= pGrb1->CanShow(); break; | ||||
|             case 2: canShow &= pGrb2->CanShow(); break; | ||||
|             case 3: canShow &= pGrb3->CanShow(); break; | ||||
|             case 4: canShow &= pGrb4->CanShow(); break; | ||||
|             case 5: canShow &= pGrb5->CanShow(); break; | ||||
|             case 6: canShow &= pGrb6->CanShow(); break; | ||||
|             case 7: canShow &= pGrb7->CanShow(); break; | ||||
|           } | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|       case NeoPixelType_Grbw: | ||||
|       { | ||||
|         for (uint8_t idx = 0; idx < numStrips; idx++) | ||||
|         { | ||||
|           switch (idx) | ||||
|           { | ||||
|             case 0: canShow &= pGrbw0->CanShow(); break; | ||||
|             case 1: canShow &= pGrbw1->CanShow(); break; | ||||
|             case 2: canShow &= pGrbw2->CanShow(); break; | ||||
|             case 3: canShow &= pGrbw3->CanShow(); break; | ||||
|             case 4: canShow &= pGrbw4->CanShow(); break; | ||||
|             case 5: canShow &= pGrbw5->CanShow(); break; | ||||
|             case 6: canShow &= pGrbw6->CanShow(); break; | ||||
|             case 7: canShow &= pGrbw7->CanShow(); break; | ||||
|           } | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     return canShow; | ||||
|   } | ||||
|  | ||||
|   void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c) | ||||
|   { | ||||
|     // figure out which strip this pixel index is on | ||||
|   | ||||
							
								
								
									
										153
									
								
								wled00/FX.cpp
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								wled00/FX.cpp
									
									
									
									
									
								
							| @@ -25,6 +25,7 @@ | ||||
| */ | ||||
|  | ||||
| #include "FX.h" | ||||
| #include "tv_colors.h" | ||||
|  | ||||
| #define IBN 5100 | ||||
| #define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) | ||||
| @@ -233,9 +234,9 @@ uint16_t WS2812FX::mode_random_color(void) { | ||||
|  | ||||
| /* | ||||
|  * Lights every LED in a random color. Changes all LED at the same time | ||||
| // * to new random colors. | ||||
|  * to new random colors. | ||||
|  */ | ||||
| uint16_t WS2812FX::mode_dynamic(void) { | ||||
| uint16_t WS2812FX::dynamic(boolean smooth=false) { | ||||
|   if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed | ||||
|    | ||||
|   if(SEGENV.call == 0) { | ||||
| @@ -252,12 +253,31 @@ uint16_t WS2812FX::mode_dynamic(void) { | ||||
|     SEGENV.step = it; | ||||
|   } | ||||
|    | ||||
|   if (smooth) { | ||||
|     for (uint16_t i = 0; i < SEGLEN; i++) { | ||||
|       blendPixelColor(i, color_wheel(SEGENV.data[i]),16); | ||||
|     } | ||||
|   } else { | ||||
|     for (uint16_t i = 0; i < SEGLEN; i++) { | ||||
|       setPixelColor(i, color_wheel(SEGENV.data[i])); | ||||
|     } | ||||
|   }  | ||||
|   return FRAMETIME; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Original effect "Dynamic" | ||||
|  */ | ||||
| uint16_t WS2812FX::mode_dynamic(void) { | ||||
|   return dynamic(false); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * effect "Dynamic" with smoth color-fading | ||||
|  */ | ||||
| uint16_t WS2812FX::mode_dynamic_smooth(void) { | ||||
|   return dynamic(true); | ||||
|  } | ||||
|  | ||||
| /* | ||||
|  * Does the "standby-breathing" of well known i-Devices. | ||||
| @@ -990,6 +1010,12 @@ uint16_t WS2812FX::mode_merry_christmas(void) { | ||||
|   return running(RED, GREEN); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Alternating red/white pixels running. | ||||
|  */ | ||||
| uint16_t WS2812FX::mode_candy_cane(void) { | ||||
|   return running(RED, WHITE); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Alternating orange/purple pixels running. | ||||
| @@ -1754,19 +1780,22 @@ uint16_t WS2812FX::mode_fire_2012() | ||||
|  | ||||
|   if (it != SEGENV.step) | ||||
|   { | ||||
|     uint8_t ignition = max(7,SEGLEN/10);  // ignition area: 10% of segment length or minimum 7 pixels | ||||
|      | ||||
|     // Step 1.  Cool down every cell a little | ||||
|     for (uint16_t i = 0; i < SEGLEN; i++) { | ||||
|       SEGENV.data[i] = qsub8(heat[i],  random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); | ||||
|       uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); | ||||
|       heat[i] = (temp==0 && i<ignition) ? 2 : temp; // prevent ignition area from becoming black | ||||
|     } | ||||
|    | ||||
|     // Step 2.  Heat from each cell drifts 'up' and diffuses a little | ||||
|     for (uint16_t k= SEGLEN -1; k > 1; k--) { | ||||
|       heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; | ||||
|       heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3;  // heat[k-2] multiplied by 2 | ||||
|     } | ||||
|      | ||||
|     // Step 3.  Randomly ignite new 'sparks' of heat near the bottom | ||||
|     if (random8() <= SEGMENT.intensity) { | ||||
|       uint8_t y = random8(7); | ||||
|       uint8_t y = random8(ignition); | ||||
|       if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255)); | ||||
|     } | ||||
|     SEGENV.step = it; | ||||
| @@ -3727,3 +3756,117 @@ uint16_t WS2812FX::mode_washing_machine(void) { | ||||
|  | ||||
|   return FRAMETIME; | ||||
| } | ||||
|  | ||||
| /* | ||||
|   Blends random colors across palette | ||||
|   Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e | ||||
| */ | ||||
| uint16_t WS2812FX::mode_blends(void) { | ||||
|   uint16_t dataSize = sizeof(uint32_t) * SEGLEN; | ||||
|   if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed | ||||
|   uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data); | ||||
|   uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); | ||||
|     uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; | ||||
|  | ||||
|   for (int i = 0; i < SEGLEN; i++) { | ||||
|     pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); | ||||
|     setPixelColor(i, pixels[i]); | ||||
|     shift += 3; | ||||
|   } | ||||
|  | ||||
|   return FRAMETIME; | ||||
| } | ||||
|  | ||||
| #ifndef WLED_DISABLE_FX_HIGH_FLASH_USE | ||||
| typedef struct TvSim { | ||||
|   uint32_t totalTime = 0; | ||||
|   uint32_t fadeTime  = 0; | ||||
|   uint32_t startTime = 0; | ||||
|   uint32_t elapsed   = 0; | ||||
|   uint32_t pixelNum  = 0; | ||||
|   uint16_t pr = 0; // Prev R, G, B | ||||
|   uint16_t pg = 0; | ||||
|   uint16_t pb = 0; | ||||
| } tvSim; | ||||
|  | ||||
| #define  numTVPixels (sizeof(tv_colors) / 2)  // 2 bytes per Pixel (5/6/5) | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|   TV Simulator | ||||
|   Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch | ||||
| */ | ||||
| uint16_t WS2812FX::mode_tv_simulator(void) { | ||||
|   #ifdef WLED_DISABLE_FX_HIGH_FLASH_USE | ||||
|   return mode_static(); | ||||
|   #else | ||||
|   uint16_t nr, ng, nb, r, g, b, i; | ||||
|   uint8_t  hi, lo, r8, g8, b8; | ||||
|  | ||||
|   if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed | ||||
|   TvSim* tvSimulator = reinterpret_cast<TvSim*>(SEGENV.data); | ||||
|  | ||||
|   // initialize start of the TV-Colors | ||||
|   if (SEGENV.call == 0) {  | ||||
|     tvSimulator->pixelNum = ((uint8_t)random(18)) * numTVPixels / 18; // Begin at random movie (18 in total) | ||||
|   } | ||||
|  | ||||
|   // Read next 16-bit (5/6/5) color | ||||
|   hi = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2    ]); | ||||
|   lo = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 + 1]); | ||||
|  | ||||
|   // Expand to 24-bit (8/8/8) | ||||
|   r8 = (hi & 0xF8) | (hi >> 5); | ||||
|   g8 = ((hi << 5) & 0xff) | ((lo & 0xE0) >> 3) | ((hi & 0x06) >> 1); | ||||
|   b8 = ((lo << 3) & 0xff) | ((lo & 0x1F) >> 2); | ||||
|  | ||||
|   // Apply gamma correction, further expand to 16/16/16 | ||||
|   nr = (uint8_t)gamma8(r8) * 257; // New R/G/B | ||||
|   ng = (uint8_t)gamma8(g8) * 257; | ||||
|   nb = (uint8_t)gamma8(b8) * 257; | ||||
|  | ||||
|   if (SEGENV.aux0 == 0) {  // initialize next iteration  | ||||
|     SEGENV.aux0 = 1; | ||||
|      | ||||
|     // increase color-index for next loop | ||||
|     tvSimulator->pixelNum++; | ||||
|     if (tvSimulator->pixelNum >= numTVPixels) tvSimulator->pixelNum = 0; | ||||
|  | ||||
|     // randomize total duration and fade duration for the actual color | ||||
|     tvSimulator->totalTime = random(250, 2500);                   // Semi-random pixel-to-pixel time | ||||
|     tvSimulator->fadeTime  = random(0, tvSimulator->totalTime);   // Pixel-to-pixel transition time | ||||
|     if (random(10) < 3) tvSimulator->fadeTime = 0;                // Force scene cut 30% of time | ||||
|  | ||||
|     tvSimulator->startTime = millis(); | ||||
|   } // end of initialization | ||||
|  | ||||
|   // how much time is elapsed ? | ||||
|   tvSimulator->elapsed = millis() - tvSimulator->startTime; | ||||
|  | ||||
|   // fade from prev volor to next color | ||||
|   if (tvSimulator->elapsed < tvSimulator->fadeTime) { | ||||
|     r = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pr, nr);  | ||||
|     g = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pg, ng); | ||||
|     b = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pb, nb); | ||||
|   } else { // Avoid divide-by-zero in map() | ||||
|     r = nr; | ||||
|     g = ng; | ||||
|     b = nb; | ||||
|   } | ||||
|  | ||||
|   // set strip color | ||||
|   for (i = 0; i < SEGLEN; i++) { | ||||
|     setPixelColor(i, r >> 8, g >> 8, b >> 8);  // Quantize to 8-bit | ||||
|   } | ||||
|  | ||||
|   // if total duration has passed, remember last color and restart the loop | ||||
|   if ( tvSimulator->elapsed >= tvSimulator->totalTime) { | ||||
|     tvSimulator->pr = nr; // Prev RGB = new RGB | ||||
|     tvSimulator->pg = ng; | ||||
|     tvSimulator->pb = nb; | ||||
|     SEGENV.aux0 = 0; | ||||
|   } | ||||
|    | ||||
|   return FRAMETIME; | ||||
|   #endif | ||||
| } | ||||
|   | ||||
							
								
								
									
										51
									
								
								wled00/FX.h
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								wled00/FX.h
									
									
									
									
									
								
							| @@ -52,6 +52,9 @@ | ||||
| #define MAX(a,b) ((a)>(b)?(a):(b)) | ||||
| #endif | ||||
|  | ||||
| /* Disable effects with high flash memory usage (currently TV simulator) - saves 18.5kB */ | ||||
| //#define WLED_DISABLE_FX_HIGH_FLASH_USE | ||||
|  | ||||
| /* Not used in all effects yet */ | ||||
| #define WLED_FPS         42 | ||||
| #define FRAMETIME        (1000/WLED_FPS) | ||||
| @@ -116,7 +119,8 @@ | ||||
| #define IS_REVERSE      ((SEGMENT.options & REVERSE     ) == REVERSE     ) | ||||
| #define IS_SELECTED     ((SEGMENT.options & SELECTED    ) == SELECTED    ) | ||||
|  | ||||
| #define MODE_COUNT  114 | ||||
|  | ||||
| #define MODE_COUNT  118 | ||||
|  | ||||
| #define FX_MODE_STATIC                   0 | ||||
| #define FX_MODE_BLINK                    1 | ||||
| @@ -232,6 +236,10 @@ | ||||
| #define FX_MODE_CHUNCHUN               111 | ||||
| #define FX_MODE_DANCING_SHADOWS        112 | ||||
| #define FX_MODE_WASHING_MACHINE        113 | ||||
| #define FX_MODE_CANDY_CANE             114 | ||||
| #define FX_MODE_BLENDS                 115 | ||||
| #define FX_MODE_TV_SIMULATOR           116 | ||||
| #define FX_MODE_DYNAMIC_SMOOTH         117 | ||||
|  | ||||
| class WS2812FX { | ||||
|   typedef uint16_t (WS2812FX::*mode_ptr)(void); | ||||
| @@ -316,9 +324,31 @@ class WS2812FX { | ||||
|         WS2812FX::_usedSegmentData -= _dataLen; | ||||
|         _dataLen = 0; | ||||
|       } | ||||
|       void reset(){next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; deallocateData();} | ||||
|  | ||||
|       /**  | ||||
|        * If reset of this segment was request, clears runtime | ||||
|        * settings of this segment. | ||||
|        * Must not be called while an effect mode function is running | ||||
|        * because it could access the data buffer and this method  | ||||
|        * may free that data buffer. | ||||
|        */ | ||||
|       void resetIfRequired() { | ||||
|         if (_requiresReset) { | ||||
|           next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;  | ||||
|           deallocateData(); | ||||
|           _requiresReset = false; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       /**  | ||||
|        * Flags that before the next effect is calculated, | ||||
|        * the internal segment state should be reset.  | ||||
|        * Call resetIfRequired before calling the next effect function. | ||||
|        */ | ||||
|       void reset() { _requiresReset = true; } | ||||
|       private: | ||||
|         uint16_t _dataLen = 0; | ||||
|         bool _requiresReset = false; | ||||
|     } segment_runtime; | ||||
|  | ||||
|     WS2812FX() { | ||||
| @@ -437,6 +467,10 @@ class WS2812FX { | ||||
|       _mode[FX_MODE_CHUNCHUN]                = &WS2812FX::mode_chunchun; | ||||
|       _mode[FX_MODE_DANCING_SHADOWS]         = &WS2812FX::mode_dancing_shadows; | ||||
|       _mode[FX_MODE_WASHING_MACHINE]         = &WS2812FX::mode_washing_machine; | ||||
|       _mode[FX_MODE_CANDY_CANE]              = &WS2812FX::mode_candy_cane; | ||||
|       _mode[FX_MODE_BLENDS]                  = &WS2812FX::mode_blends; | ||||
|       _mode[FX_MODE_TV_SIMULATOR]            = &WS2812FX::mode_tv_simulator; | ||||
|       _mode[FX_MODE_DYNAMIC_SMOOTH]          = &WS2812FX::mode_dynamic_smooth; | ||||
|  | ||||
|       _brightness = DEFAULT_BRIGHTNESS; | ||||
|       currentPalette = CRGBPalette16(CRGB::Black); | ||||
| @@ -478,7 +512,9 @@ class WS2812FX { | ||||
|       gammaCorrectCol = true, | ||||
|       applyToAllSelected = true, | ||||
|       segmentsAreIdentical(Segment* a, Segment* b), | ||||
|       setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p); | ||||
|       setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), | ||||
|       // return true if the strip is being sent pixel updates | ||||
|       isUpdating(void); | ||||
|  | ||||
|     uint8_t | ||||
|       mainSegment = 0, | ||||
| @@ -643,7 +679,11 @@ class WS2812FX { | ||||
|       mode_flow(void), | ||||
|       mode_chunchun(void), | ||||
|       mode_dancing_shadows(void), | ||||
|       mode_washing_machine(void); | ||||
|       mode_washing_machine(void), | ||||
|       mode_candy_cane(void), | ||||
|       mode_blends(void), | ||||
|       mode_tv_simulator(void), | ||||
|       mode_dynamic_smooth(void); | ||||
|  | ||||
|   private: | ||||
|     NeoPixelWrapper *bus; | ||||
| @@ -676,6 +716,7 @@ class WS2812FX { | ||||
|       blink(uint32_t, uint32_t, bool strobe, bool), | ||||
|       candle(bool), | ||||
|       color_wipe(bool, bool), | ||||
|       dynamic(bool), | ||||
|       scan(bool), | ||||
|       theater_chase(uint32_t, uint32_t, bool), | ||||
|       running_base(bool), | ||||
| @@ -731,7 +772,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([ | ||||
| "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", | ||||
| "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", | ||||
| "Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", | ||||
| "Flow","Chunchun","Dancing Shadows","Washing Machine" | ||||
| "Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth" | ||||
| ])====="; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,10 @@ const uint16_t customMappingTable[] = { | ||||
| const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example | ||||
| #endif | ||||
|  | ||||
| #ifndef PWM_INDEX | ||||
| #define PWM_INDEX 0 | ||||
| #endif | ||||
|  | ||||
| void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst) | ||||
| { | ||||
|   if (supportWhite == _useRgbw && countPixels == _length && _skipFirstMode == skipFirst) return; | ||||
| @@ -76,6 +80,11 @@ void WS2812FX::service() { | ||||
|   for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++) | ||||
|   { | ||||
|     _segment_index = i; | ||||
|  | ||||
|     // reset the segment runtime data if needed, called before isActive to ensure deleted | ||||
|     // segment's buffers are cleared | ||||
|     SEGENV.resetIfRequired(); | ||||
|  | ||||
|     if (SEGMENT.isActive()) | ||||
|     { | ||||
|       if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary | ||||
| @@ -218,7 +227,10 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) | ||||
|                               //you can set it to 0 if the ESP is powered by USB and the LEDs by external | ||||
|  | ||||
| void WS2812FX::show(void) { | ||||
|   if (_callback) _callback(); | ||||
|  | ||||
|   // avoid race condition, caputre _callback value | ||||
|   show_callback callback = _callback; | ||||
|   if (callback) callback(); | ||||
|  | ||||
|   //power limit calculation | ||||
|   //each LED can draw up 195075 "power units" (approx. 53mA) | ||||
| @@ -291,10 +303,24 @@ void WS2812FX::show(void) { | ||||
|     bus->SetBrightness(_brightness); | ||||
|   } | ||||
|    | ||||
|   // 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 | ||||
|   bus->Show(); | ||||
|   _lastShow = millis(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a true value if any of the strips are still being updated. | ||||
|  * On some hardware (ESP32), strip updates are done asynchronously. | ||||
|  */ | ||||
| bool WS2812FX::isUpdating() { | ||||
|   return !bus->CanShow(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Forces the next frame to be computed on all active segments. | ||||
|  */ | ||||
| void WS2812FX::trigger() { | ||||
|   _triggered = true; | ||||
| } | ||||
| @@ -378,16 +404,16 @@ void WS2812FX::setColor(uint8_t slot, uint32_t c) { | ||||
| } | ||||
|  | ||||
| void WS2812FX::setBrightness(uint8_t b) { | ||||
|   if (gammaCorrectBri) b = gamma8(b); | ||||
|   if (_brightness == b) return; | ||||
|   _brightness = (gammaCorrectBri) ? gamma8(b) : b; | ||||
|   _brightness = b; | ||||
|   _segment_index = 0; | ||||
|   if (b == 0) { //unfreeze all segments on power off | ||||
|   if (_brightness == 0) { //unfreeze all segments on power off | ||||
|     for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) | ||||
|     { | ||||
|       _segments[i].setOption(SEG_OPTION_FREEZE, false); | ||||
|     } | ||||
|     #if LEDPIN == LED_BUILTIN | ||||
|       if (!shouldStartBus) | ||||
|       shouldStartBus = true; | ||||
|     #endif | ||||
|   } else { | ||||
| @@ -890,13 +916,24 @@ void WS2812FX::handle_palette(void) | ||||
|  */ | ||||
| uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) | ||||
| { | ||||
|   if (SEGMENT.palette == 0 && mcol < 3) return SEGCOLOR(mcol); //WS2812FX default | ||||
|   if (SEGMENT.palette == 0 && mcol < 3) { | ||||
|     uint32_t color = SEGCOLOR(mcol); | ||||
|     if (pbri != 255) { | ||||
|       CRGB crgb_color = col_to_crgb(color); | ||||
|       crgb_color.nscale8_video(pbri); | ||||
|       return crgb_to_col(crgb_color); | ||||
|     } else { | ||||
|       return color; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   uint8_t paletteIndex = i; | ||||
|   if (mapping) paletteIndex = (i*255)/(SEGLEN -1); | ||||
|   if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" | ||||
|   CRGB fastled_col; | ||||
|   fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); | ||||
|   return  fastled_col.r*65536 +  fastled_col.g*256 +  fastled_col.b; | ||||
|  | ||||
|   return crgb_to_col(fastled_col); | ||||
| } | ||||
|  | ||||
| //@returns `true` if color, mode, speed, intensity and palette match | ||||
| @@ -924,7 +961,7 @@ void WS2812FX::setRgbwPwm(void) { | ||||
|   _analogLastShow = nowUp; | ||||
|  | ||||
|   RgbwColor c; | ||||
|   uint32_t col = bus->GetPixelColorRgbw(0); | ||||
|   uint32_t col = bus->GetPixelColorRgbw(PWM_INDEX); | ||||
|   c.R = col >> 16; c.G = col >> 8; c.B = col; c.W = col >> 24; | ||||
|  | ||||
|   byte b = getBrightness(); | ||||
|   | ||||
| @@ -45,7 +45,7 @@ | ||||
| //This can be useful if you want to chain multiple strings with incompatible color order | ||||
| //#define COLOR_ORDER_OVERRIDE | ||||
| #define COO_MIN    0 | ||||
| #define COO_MAX   27 //not inclusive, this would set the override for LEDs 0-26 | ||||
| #define COO_MAX   35 //not inclusive, this would set the override for LEDs 0-26 | ||||
| #define COO_ORDER COL_ORDER_GRB | ||||
|  | ||||
| //END CONFIGURATION | ||||
| @@ -296,7 +296,6 @@ public: | ||||
|  | ||||
|   void Show() | ||||
|   { | ||||
|     byte b; | ||||
|     switch (_type) | ||||
|     { | ||||
|       case NeoPixelType_Grb:  _pGrb->Show();  break; | ||||
| @@ -304,6 +303,22 @@ public: | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /**  | ||||
|    * This will return true if enough time has passed since the last time Show() was called.  | ||||
|    * This also means that calling Show() will not cause any undue waiting. If the method for  | ||||
|    * the defined bus is hardware that sends asynchronously, then call CanShow() will let  | ||||
|    * you know if it has finished sending the data from the last Show(). | ||||
|    */ | ||||
|   bool CanShow() | ||||
|   { | ||||
|     switch (_type) | ||||
|     { | ||||
|       case NeoPixelType_Grb:  return _pGrb->CanShow(); | ||||
|       case NeoPixelType_Grbw: return _pGrbw->CanShow(); | ||||
|       default: return true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void SetPixelColor(uint16_t indexPixel, RgbwColor c) | ||||
|   { | ||||
|     RgbwColor col; | ||||
|   | ||||
| @@ -8,12 +8,12 @@ | ||||
| uint16_t blHue = 0; | ||||
| byte blSat = 255; | ||||
|  | ||||
| void initBlynk(const char* auth) | ||||
| void initBlynk(const char *auth, const char *host, uint16_t port) | ||||
| { | ||||
|   #ifndef WLED_DISABLE_BLYNK | ||||
|   if (!WLED_CONNECTED) return; | ||||
|   blynkEnabled = (auth[0] != 0); | ||||
|   if (blynkEnabled) Blynk.config(auth); | ||||
|   if (blynkEnabled) Blynk.config(auth, host, port); | ||||
|   #endif | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -91,6 +91,7 @@ void deserializeConfig() { | ||||
|   CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); | ||||
|   CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); | ||||
|   CJSON(strip.reverseMode, hw_led[F("rev")]); | ||||
|   CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); | ||||
|  | ||||
|   JsonObject hw_led_ins_0 = hw_led[F("ins")][0]; | ||||
|   //bool hw_led_ins_0_en = hw_led_ins_0[F("en")]; // true | ||||
| @@ -153,7 +154,6 @@ void deserializeConfig() { | ||||
|   CJSON(bootPreset, def[F("ps")]); | ||||
|   CJSON(turnOnAtBoot, def["on"]); // true | ||||
|   CJSON(briS, def["bri"]); // 128 | ||||
|   if (briS == 0) briS = 255; | ||||
|  | ||||
|   JsonObject def_cy = def[F("cy")]; | ||||
|   CJSON(presetCyclingEnabled, def_cy["on"]); | ||||
| @@ -162,7 +162,7 @@ void deserializeConfig() { | ||||
|   CJSON(presetCycleMax, def_cy[F("range")][1]); | ||||
|  | ||||
|   tdd = def_cy[F("dur")] | -1; | ||||
|   if (tdd >= 0) presetCycleTime = tdd * 100; | ||||
|   if (tdd > 0) presetCycleTime = tdd; | ||||
|  | ||||
|   JsonObject interfaces = doc["if"]; | ||||
|  | ||||
| @@ -212,6 +212,10 @@ void deserializeConfig() { | ||||
|   if (tdd > 20 || tdd == 0) | ||||
|     getStringFromJson(blynkApiKey, apikey, 36); //normally not present due to security | ||||
|  | ||||
|   JsonObject if_blynk = interfaces[F("blynk")]; | ||||
|   getStringFromJson(blynkHost, if_blynk[F("host")], 33); | ||||
|   CJSON(blynkPort, if_blynk[F("port")]); | ||||
|  | ||||
|   JsonObject if_mqtt = interfaces[F("mqtt")]; | ||||
|   CJSON(mqttEnabled, if_mqtt[F("en")]); | ||||
|   getStringFromJson(mqttServer, if_mqtt[F("broker")], 33); | ||||
| @@ -251,7 +255,11 @@ void deserializeConfig() { | ||||
|   CJSON(countdownMode, ol[F("cntdwn")]); | ||||
|   overlayCurrent = overlayDefault; | ||||
|  | ||||
|   JsonArray ol_cntdwn = ol[F("cntdwn")]; //[20,12,31,23,59,59] | ||||
|   CJSON(overlayMin, ol[F("min")]); | ||||
|   CJSON(overlayMax, ol[F("max")]); | ||||
|   CJSON(analogClock12pixel, ol[F("o12pix")]); | ||||
|   CJSON(analogClock5MinuteMarks, ol[F("o5m")]); | ||||
|   CJSON(analogClockSecondsTrail, ol[F("osec")]); | ||||
|  | ||||
|   //timed macro rules | ||||
|   JsonObject tm = doc[F("timers")]; | ||||
| @@ -274,11 +282,13 @@ void deserializeConfig() { | ||||
|     CJSON(timerMacro[it], timer[F("macro")]); | ||||
|  | ||||
|     byte dowPrev =  timerWeekday[it]; | ||||
|     bool actPrev = timerWeekday[it] & 0x01; | ||||
|     //note: act is currently only 0 or 1. | ||||
|     //the reason we are not using bool is that the on-disk type in 0.11.0 was already int | ||||
|     int actPrev = timerWeekday[it] & 0x01; | ||||
|     CJSON(timerWeekday[it], timer[F("dow")]); | ||||
|     if (timerWeekday[it] != dowPrev) { //present in JSON | ||||
|       timerWeekday[it] <<= 1; //add active bit | ||||
|       bool act = timer[F("en")] | actPrev; | ||||
|       int act = timer[F("en")] | actPrev; | ||||
|       if (act) timerWeekday[it]++; | ||||
|     } | ||||
|  | ||||
| @@ -305,11 +315,11 @@ void deserializeConfig() { | ||||
|   CJSON(DMXStart, dmx[F("start")]); | ||||
|   CJSON(DMXStartLED,dmx[F("start-led")]); | ||||
|  | ||||
|   JsonArray dmx_fixmap = dmx.createNestedArray("fixmap"); | ||||
|   JsonArray dmx_fixmap = dmx[F("fixmap")]; | ||||
|   it = 0; | ||||
|   for (int i : dmx_fixmap) { | ||||
|     if (it > 14) break; | ||||
|     DMXFixtureMap[i] = i; | ||||
|     CJSON(DMXFixtureMap[i],dmx_fixmap[i]); | ||||
|     it++; | ||||
|   } | ||||
|   #endif | ||||
| @@ -359,6 +369,7 @@ void serializeConfig() { | ||||
|   ap[F("ssid")] = apSSID; | ||||
|   ap[F("pskl")] = strlen(apPass); | ||||
|   ap[F("chan")] = apChannel; | ||||
|   ap[F("hide")] = apHide; | ||||
|   ap[F("behav")] = apBehavior; | ||||
|  | ||||
|   JsonArray ap_ip = ap.createNestedArray("ip"); | ||||
| @@ -378,6 +389,7 @@ void serializeConfig() { | ||||
|   hw_led[F("maxpwr")] = strip.ablMilliampsMax; | ||||
|   hw_led[F("ledma")] = strip.milliampsPerLed; | ||||
|   hw_led[F("rev")] = strip.reverseMode; | ||||
|   hw_led[F("rgbwm")] = strip.rgbwMode; | ||||
|  | ||||
|   JsonArray hw_led_ins = hw_led.createNestedArray("ins"); | ||||
|  | ||||
| @@ -478,7 +490,7 @@ void serializeConfig() { | ||||
|     JsonArray def_cy_range = def_cy.createNestedArray("range"); | ||||
|     def_cy_range.add(presetCycleMin); | ||||
|     def_cy_range.add(presetCycleMax); | ||||
|     def_cy[F("dur")] = presetCycleTime / 100; | ||||
|     def_cy[F("dur")] = presetCycleTime; | ||||
|   } | ||||
|  | ||||
|   JsonObject interfaces = doc.createNestedObject("if"); | ||||
| @@ -523,6 +535,8 @@ void serializeConfig() { | ||||
|   if_va_macros.add(macroAlexaOff); | ||||
|   JsonObject if_blynk = interfaces.createNestedObject("blynk"); | ||||
|   if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":""; | ||||
|   if_blynk[F("host")] = blynkHost; | ||||
|   if_blynk[F("port")] = blynkPort; | ||||
|  | ||||
|   JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); | ||||
|   if_mqtt[F("en")] = mqttEnabled; | ||||
| @@ -562,6 +576,12 @@ void serializeConfig() { | ||||
|   ol[F("clock")] = overlayDefault; | ||||
|   ol[F("cntdwn")] = countdownMode; | ||||
|  | ||||
|   ol[F("min")] = overlayMin; | ||||
|   ol[F("max")] = overlayMax; | ||||
|   ol[F("o12pix")] = analogClock12pixel; | ||||
|   ol[F("o5m")] = analogClock5MinuteMarks; | ||||
|   ol[F("osec")] = analogClockSecondsTrail; | ||||
|  | ||||
|   JsonObject timers = doc.createNestedObject("timers"); | ||||
|  | ||||
|   JsonObject cntdwn = timers.createNestedObject("cntdwn"); | ||||
|   | ||||
							
								
								
									
										937
									
								
								wled00/data/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										937
									
								
								wled00/data/index.css
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1414
									
								
								wled00/data/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1414
									
								
								wled00/data/index.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										7
									
								
								wled00/data/iro.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								wled00/data/iro.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								wled00/data/rangetouch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								wled00/data/rangetouch.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // ========================================================================== | ||||
| // rangetouch.js v2.0.1 | ||||
| // Making <input type="range"> work on touch devices | ||||
| // https://github.com/sampotts/rangetouch | ||||
| // License: The MIT License (MIT) | ||||
| // ========================================================================== | ||||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define("RangeTouch",t):(e=e||self).RangeTouch=t()}(this,(function(){"use strict";function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function t(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function n(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function r(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?n(Object(i),!0).forEach((function(n){t(e,n,i[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):n(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}var i={addCSS:!0,thumbWidth:15,watch:!0};function u(e,t){return function(){return Array.from(document.querySelectorAll(t)).includes(this)}.call(e,t)}var o=function(e){return null!=e?e.constructor:null},c=function(e,t){return!!(e&&t&&e instanceof t)},l=function(e){return null==e},a=function(e){return o(e)===Object},s=function(e){return o(e)===String},f=function(e){return Array.isArray(e)},h=function(e){return c(e,NodeList)},d=s,y=f,b=h,m=function(e){return c(e,Element)},g=function(e){return c(e,Event)},p=function(e){return l(e)||(s(e)||f(e)||h(e))&&!e.length||a(e)&&!Object.keys(e).length};function v(e,t){if(1>t){var n=function(e){var t="".concat(e).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return t?Math.max(0,(t[1]?t[1].length:0)-(t[2]?+t[2]:0)):0}(t);return parseFloat(e.toFixed(n))}return Math.round(e/t)*t}return function(){function t(e,n){(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")})(this,t),m(e)?this.element=e:d(e)&&(this.element=document.querySelector(e)),m(this.element)&&p(this.element.rangeTouch)&&(this.config=r({},i,{},n),this.init())}return n=t,c=[{key:"setup",value:function(e){var n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},o=null;if(p(e)||d(e)?o=Array.from(document.querySelectorAll(d(e)?e:'input[type="range"]')):m(e)?o=[e]:b(e)?o=Array.from(e):y(e)&&(o=e.filter(m)),p(o))return null;var c=r({},i,{},n);if(d(e)&&c.watch){var l=new MutationObserver((function(n){Array.from(n).forEach((function(n){Array.from(n.addedNodes).forEach((function(n){m(n)&&u(n,e)&&new t(n,c)}))}))}));l.observe(document.body,{childList:!0,subtree:!0})}return o.map((function(e){return new t(e,n)}))}},{key:"enabled",get:function(){return"ontouchstart"in document.documentElement}}],(o=[{key:"init",value:function(){t.enabled&&(this.config.addCSS&&(this.element.style.userSelect="none",this.element.style.webKitUserSelect="none",this.element.style.touchAction="manipulation"),this.listeners(!0),this.element.rangeTouch=this)}},{key:"destroy",value:function(){t.enabled&&(this.config.addCSS&&(this.element.style.userSelect="",this.element.style.webKitUserSelect="",this.element.style.touchAction=""),this.listeners(!1),this.element.rangeTouch=null)}},{key:"listeners",value:function(e){var t=this,n=e?"addEventListener":"removeEventListener";["touchstart","touchmove","touchend"].forEach((function(e){t.element[n](e,(function(e){return t.set(e)}),!1)}))}},{key:"get",value:function(e){if(!t.enabled||!g(e))return null;var n,r=e.target,i=e.changedTouches[0],u=parseFloat(r.getAttribute("min"))||0,o=parseFloat(r.getAttribute("max"))||100,c=parseFloat(r.getAttribute("step"))||1,l=r.getBoundingClientRect(),a=100/l.width*(this.config.thumbWidth/2)/100;return 0>(n=100/l.width*(i.clientX-l.left))?n=0:100<n&&(n=100),50>n?n-=(100-2*n)*a:50<n&&(n+=2*(n-50)*a),u+v(n/100*(o-u),c)}},{key:"set",value:function(e){t.enabled&&g(e)&&!e.target.disabled&&(e.preventDefault(),e.target.value=this.get(e),function(e,t){if(e&&t){var n=new Event(t,{bubbles:!0});e.dispatchEvent(n)}}(e.target,"touchend"===e.type?"change":"input"))}}])&&e(n.prototype,o),c&&e(n,c),t;var n,o,c}()})); | ||||
| //# sourceMappingURL=rangetouch.js.map | ||||
| @@ -1,12 +1,12 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"> | ||||
| <html lang="en"> | ||||
| <head><meta charset="UTF-8"> | ||||
| 	<title>WLED Settings</title> | ||||
| 	<style> | ||||
| 		body { | ||||
| 			text-align: center; | ||||
| 			background: #222; | ||||
| 			height: 100; | ||||
| 			height: 100px; | ||||
| 			margin: 0; | ||||
| 		} | ||||
| 		html { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>DMX Settings</title> | ||||
| <html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>DMX Settings</title> | ||||
| <script> | ||||
| function GCH(num) { | ||||
|   d=document; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=500"> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| 	<meta name="viewport" content="width=500"> | ||||
| 	<meta charset="utf-8"> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title> | ||||
| <html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title> | ||||
| <script>var d=document; | ||||
| function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");} | ||||
| function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;} | ||||
| @@ -34,7 +34,7 @@ UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required | ||||
| 2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br> | ||||
| Receive <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br> | ||||
| Send notifications on direct change: <input type="checkbox" name="SD"><br> | ||||
| Send notifications on button press: <input type="checkbox" name="SB"><br> | ||||
| Send notifications on button press or IR: <input type="checkbox" name="SB"><br> | ||||
| Send Alexa notifications: <input type="checkbox" name="SA"><br> | ||||
| Send Philips Hue change notifications: <input type="checkbox" name="SH"><br> | ||||
| Send Macro notifications: <input type="checkbox" name="SM"><br> | ||||
| @@ -79,6 +79,8 @@ Alexa invocation name: <input name="AI" maxlength="32"> | ||||
| This may impact the responsiveness of the ESP8266.</b><br> | ||||
| For best results, only use one of these services at a time.<br> | ||||
| (alternatively, connect a second ESP to them and use the UDP sync)<br><br> | ||||
| Host: <input name="BH" maxlength="32"> | ||||
| Port: <input name="BP" type="number" min="1" max="65535" value="80" class="d5"><br> | ||||
| Device Auth token: <input name="BK" maxlength="33"><br> | ||||
| <i>Clear the token field to disable. </i><a href="https://github.com/Aircoookie/WLED/wiki/Blynk" target="_blank">Setup info</a> | ||||
| <h3>MQTT</h3> | ||||
| @@ -88,7 +90,7 @@ Port: <input name="MQPORT" type="number" min="1" max="65535" class="d5"><br> | ||||
| <b>The MQTT credentials are sent over an unsecured connection.<br> | ||||
| Never use the MQTT password for another service!</b><br> | ||||
| Username: <input name="MQUSER" maxlength="40"><br> | ||||
| Password: <input type="password" input name="MQPASS" maxlength="40"><br> | ||||
| Password: <input type="password" name="MQPASS" maxlength="40"><br> | ||||
| Client ID: <input name="MQCID" maxlength="40"><br> | ||||
| Device Topic: <input name="MD" maxlength="32"><br> | ||||
| Group Topic: <input name="MG" maxlength="32"><br> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| 	<meta name="viewport" content="width=500"> | ||||
| 	<meta charset="utf-8"> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| <head lang="en"> | ||||
|   <meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=500"> | ||||
| 	<title>UI Settings</title> | ||||
| @@ -18,7 +18,8 @@ | ||||
| 				"quick": "Quick color selectors", | ||||
| 				"hex": "HEX color input" | ||||
| 			}, | ||||
| 			"pcmbot": "Show bottom tab bar in PC mode" | ||||
|       "pcmbot": "Show bottom tab bar in PC mode", | ||||
|       "pid": "Show preset IDs" | ||||
| 			}, | ||||
| 			"theme":{ | ||||
| 				"alpha": { | ||||
| @@ -26,7 +27,8 @@ | ||||
| 					"tab":"Button opacity" | ||||
| 				}, | ||||
| 				"bg":{ | ||||
| 					"url":"BG image URL" | ||||
| 					"url":"BG image URL", | ||||
| 					"random":"Random BG image" | ||||
| 				}, | ||||
| 				"color":{ | ||||
| 					"bg":"BG HEX color" | ||||
| @@ -162,6 +164,24 @@ | ||||
| 			var f = gId('theme_base'); | ||||
| 			if (f) f.value = (gId('dm').checked) ? 'light':'dark'; | ||||
| 		} | ||||
|  | ||||
| 		// random BG image | ||||
| 		function setRandomBg() { | ||||
| 			if (gId("theme_bg_random").checked) { | ||||
| 				gId("theme_bg_url").value = "https://picsum.photos/1920/1080"; | ||||
| 			} else { | ||||
| 				gId("theme_bg_url").value = ""; | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
| 		function checkRandomBg() { | ||||
| 			if (gId("theme_bg_url").value === "https://picsum.photos/1920/1080") { | ||||
| 				gId("theme_bg_random").checked = true; | ||||
| 			} else { | ||||
| 				gId("theme_bg_random").checked = false; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		function GetV() | ||||
| 		{ | ||||
| 		} | ||||
| @@ -190,12 +210,14 @@ | ||||
| 		<h3>UI Appearance</h3> | ||||
| 		<span class="l"></span>: <input type="checkbox" id="comp_labels" class="agi cb"><br> | ||||
|     <span class="l"></span>: <input type="checkbox" id="comp_pcmbot" class="agi cb"><br> | ||||
|     <span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br> | ||||
| 		I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br> | ||||
| 		<span id="idonthateyou" style="display:none"><i>Why would you? </i>🥺<br></span> | ||||
| 		<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br> | ||||
| 		<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_bg" class="agi"><br> | ||||
| 		<span class="l"></span>: <input id="theme_color_bg" maxlength="9" class="agi"><br> | ||||
| 		<span class="l">BG image URL</span>: <input id="theme_bg_url" class="agi"> | ||||
| 		<span class="l">BG image URL</span>: <input id="theme_bg_url" class="agi" oninput="checkRandomBg()"><br> | ||||
| 		<span class="l">Random BG image</span>: <input type="checkbox" id="theme_bg_random" class="agi cb" onchange="setRandomBg()"><br> | ||||
| 		<input id="theme_base" class="agi" style="display:none"> | ||||
| 		<hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button> | ||||
| 	</form> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=500"> | ||||
| @@ -51,7 +51,7 @@ | ||||
| 		<h3>Configure Access Point</h3> | ||||
| 		AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br> | ||||
| 		Hide AP name: <input type="checkbox" name="AH"><br> | ||||
| 		AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63"><br> | ||||
| 		AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br> | ||||
| 		Access Point WiFi channel: <input name="AC" type="number" min="1" max="13" required><br> | ||||
|     AP opens: | ||||
|     <select name="AB"> | ||||
|   | ||||
| @@ -4,7 +4,9 @@ | ||||
| <head> | ||||
|     <meta content='width=device-width' name='viewport'> | ||||
|     <title>WLED Update</title> | ||||
|     <script>function B() { window.history.back() }</script> | ||||
|     <script>function B() { window.history.back() } | ||||
|     function U() {document.getElementById("uf").style.display="none";document.getElementById("msg").style.display="block";} | ||||
|     </script> | ||||
|     <style> | ||||
|       .bt { | ||||
|         background: #333; | ||||
| @@ -28,16 +30,23 @@ | ||||
|         color: #fff; | ||||
|         line-height: 200% | ||||
|       } | ||||
|        | ||||
|       #msg { | ||||
|         display: none; | ||||
|       } | ||||
|     </style> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|     <h2>WLED Software Update</h2>Installed version: ##VERSION##<br>Download the latest binary: <a | ||||
|         href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img | ||||
|             src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br> | ||||
|     <form method='POST' action='/update' enctype='multipart/form-data'><input type='file' class="bt" name='update' | ||||
|             required><br><input type='submit' class="bt" value='Update!'></form><button type="button" class="bt" | ||||
|         onclick="B()">Back</button> | ||||
|     <h2>WLED Software Update</h2> | ||||
|     <form method='POST' action='/update' id='uf' enctype='multipart/form-data' onsubmit="U()"> | ||||
|       Installed version: ##VERSION##<br> | ||||
|       Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank"> | ||||
|       <img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br> | ||||
|       <input type='file' class="bt" name='update' accept=".bin" required><br> | ||||
|       <input type='submit' class="bt" value='Update!' ><br> | ||||
|       <button type="button" class="bt" onclick="B()">Back</button></form> | ||||
|     <div id="msg"><b>Updating...</b><br>Please do not close or refresh the page :)</div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
| @@ -15,7 +15,7 @@ void handleAlexa(); | ||||
| void onAlexaChange(EspalexaDevice* dev); | ||||
|  | ||||
| //blynk.cpp | ||||
| void initBlynk(const char* auth); | ||||
| void initBlynk(const char* auth, const char* host, uint16_t port); | ||||
| void handleBlynk(); | ||||
| void updateBlynk(); | ||||
|  | ||||
| @@ -240,6 +240,7 @@ void userLoop(); | ||||
| void applyMacro(byte index); | ||||
| void deEEP(); | ||||
| void deEEPSettings(); | ||||
| void clearEEPROM(); | ||||
|  | ||||
| //wled_serial.cpp | ||||
| void handleSerial(); | ||||
|   | ||||
| @@ -36,17 +36,19 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; | ||||
|  | ||||
| // Autogenerated from wled00/data/update.htm, do not edit!! | ||||
| const char PAGE_update[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta content="width=device-width" name="viewport"> | ||||
| <title>WLED Update</title><script>function B(){window.history.back()}</script> | ||||
| <style> | ||||
| .bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%} | ||||
| </style></head><body><h2>WLED Software Update</h2>Installed version: 0.11.0<br> | ||||
| Download the latest binary: <a  | ||||
| <title>WLED Update</title><script> | ||||
| function B(){window.history.back()}function U(){document.getElementById("uf").style.display="none",document.getElementById("msg").style.display="block"} | ||||
| </script><style> | ||||
| .bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none} | ||||
| </style></head><body><h2>WLED Software Update</h2><form method="POST"  | ||||
| action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()"> | ||||
| Installed version: 0.11.1<br>Download the latest binary: <a  | ||||
| href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img  | ||||
| src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"> | ||||
| </a><br><form method="POST" action="/update" enctype="multipart/form-data"> | ||||
| <input type="file" class="bt" name="update" required><br><input type="submit"  | ||||
| class="bt" value="Update!"></form><button type="button" class="bt"  | ||||
| onclick="B()">Back</button></body></html>)====="; | ||||
| </a><br><input type="file" class="bt" name="update" accept=".bin" required><br> | ||||
| <input type="submit" class="bt" value="Update!"><br><button type="button"  | ||||
| class="bt" onclick="B()">Back</button></form><div id="msg"><b>Updating...</b> | ||||
| <br>Please do not close or refresh the page :)</div></body></html>)====="; | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/welcome.htm, do not edit!! | ||||
|   | ||||
| @@ -10,9 +10,9 @@ const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana, | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings.htm, do not edit!! | ||||
| const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta http-equiv="Content-Type"  | ||||
| content="text/html; charset=windows-1252"><title>WLED Settings</title><style> | ||||
| body{text-align:center;background:#222;height:100;margin:0}html{--h:11.55vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;border:.3ch solid #333;display:inline-block;font-size:8vmin;height:var(--h);width:95%%;margin-top:2.4vh} | ||||
| const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>WLED Settings | ||||
| </title><style> | ||||
| body{text-align:center;background:#222;height:100px;margin:0}html{--h:11.55vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;border:.3ch solid #333;display:inline-block;font-size:8vmin;height:var(--h);width:95%%;margin-top:2.4vh} | ||||
| </style><script> | ||||
| function BB(){window.frameElement&&(document.getElementById("b").style.display="none",document.documentElement.style.setProperty("--h","13.86vh"))} | ||||
| </script></head><body onload="BB()"><form action="/"><button type="submit"  | ||||
| @@ -27,8 +27,8 @@ action="/settings/time"><button type="submit">Time & Macros</button></form><form | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_wifi.htm, do not edit!! | ||||
| const char PAGE_settings_wifi[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport"  | ||||
| content="width=500"><title>WiFi Settings</title><script> | ||||
| const char PAGE_settings_wifi[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta  | ||||
| name="viewport" content="width=500"><title>WiFi Settings</title><script> | ||||
| function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#wifi-settings")}function B(){window.open("/settings","_self")}function GetV() {var d=document; | ||||
| %CSS%%SCSS%</head><body onload="GetV()"> | ||||
| <form id="form_s" name="Sf" method="post"><div class="helpB"><button  | ||||
| @@ -53,8 +53,9 @@ maxlength="32"> .local<br>Client IP: <span class="sip">Not connected</span><br> | ||||
| <h3>Configure Access Point</h3>AP SSID (leave empty for no AP):<br><input  | ||||
| name="AS" maxlength="32"><br>Hide AP name: <input type="checkbox" name="AH"><br> | ||||
| AP password (leave empty for open):<br><input type="password" name="AP"  | ||||
| maxlength="63"><br>Access Point WiFi channel: <input name="AC" type="number"  | ||||
| min="1" max="13" required><br>AP opens: <select name="AB"><option value="0"> | ||||
| maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br> | ||||
| Access Point WiFi channel: <input name="AC" type="number" min="1" max="13"  | ||||
| required><br>AP opens: <select name="AB"><option value="0"> | ||||
| No connection after boot</option><option value="1">Disconnected</option><option  | ||||
| value="2">Always</option><option value="3">Never (not recommended)</option> | ||||
| </select><br>AP IP: <span class="sip">Not active</span><br><h3>Experimental</h3> | ||||
| @@ -66,8 +67,8 @@ Save & Connect</button></form></body></html>)====="; | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_leds.htm, do not edit!! | ||||
| const char PAGE_settings_leds[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport"  | ||||
| content="width=500"><title>LED Settings</title><script> | ||||
| const char PAGE_settings_leds[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta  | ||||
| name="viewport" content="width=500"><title>LED Settings</title><script> | ||||
| var d=document,laprev=55;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings")}function B(){window.open("/settings","_self")}function S(){GetV(),setABL()}function enABL(){var e=d.getElementById("able").checked;d.Sf.LA.value=e?laprev:0,d.getElementById("abl").style.display=e?"inline":"none",d.getElementById("psu2").style.display=e?"inline":"none",d.Sf.LA.value>0&&setABL()}function enLA(){var e=d.Sf.LAsel.value;d.Sf.LA.value=e,d.getElementById("LAdis").style.display=50==e?"inline":"none",UI()}function setABL(){switch(d.getElementById("able").checked=!0,d.Sf.LAsel.value=50,parseInt(d.Sf.LA.value)){case 0:d.getElementById("able").checked=!1,enABL();break;case 30:d.Sf.LAsel.value=30;break;case 35:d.Sf.LAsel.value=35;break;case 55:d.Sf.LAsel.value=55;break;case 255:d.Sf.LAsel.value=255;break;default:d.getElementById("LAdis").style.display="inline"}UI()}function UI(){var e=d.querySelectorAll(".wc"),l=e.length;for(i=0;i<l;i++)e[i].style.display=d.getElementById("rgbw").checked?"inline":"none";d.getElementById("ledwarning").style.display=d.Sf.LC.value>1e3?"inline":"none",d.getElementById("ampwarning").style.display=d.Sf.MA.value>7200?"inline":"none",255==d.Sf.LA.value?laprev=12:d.Sf.LA.value>0&&(laprev=d.Sf.LA.value);var n=Math.ceil((100+d.Sf.LC.value*laprev)/500)/2;n=n>5?Math.ceil(n):n;var t="",a=30==d.Sf.LAsel.value,s=255==d.Sf.LAsel.value;n<1.02&&!a&&!s?t="ESP 5V pin with 1A USB supply":(t+=a?"12V ":s?"WS2815 12V ":"5V ",t+=n,t+="A supply connected to LEDs");var u=Math.ceil((100+d.Sf.LC.value*laprev)/1500)/2,c="(for most effects, ~";c+=u=u>5?Math.ceil(u):u,c+="A is enough)<br>",d.getElementById("psu").innerHTML=t,d.getElementById("psu2").innerHTML=s?"":c}function GetV() {var d=document; | ||||
| %CSS%%SCSS%</head><body onload="S()"><form | ||||
|  id="form_s" name="Sf" method="post"><div class="helpB"><button type="button"  | ||||
| @@ -135,8 +136,8 @@ onclick="B()">Back</button><button type="submit">Save</button></form></body> | ||||
| #ifdef WLED_ENABLE_DMX | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_dmx.htm, do not edit!! | ||||
| const char PAGE_settings_dmx[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" content="width=500"><meta  | ||||
| charset="utf-8"><title>DMX Settings</title><script> | ||||
| const char PAGE_settings_dmx[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500"> | ||||
| <meta charset="utf-8"><title>DMX Settings</title><script> | ||||
| function GCH(n){for(d=document,d.getElementById("dmxchannels").innerHTML+="",i=0;i<n;i++)d.getElementById("dmxchannels").innerHTML+="<span id=CH"+(i+1)+"s >Channel "+(i+1)+": <select name=CH"+(i+1)+' id="CH'+(i+1)+'"><option value=0>Set to 0</option><option value=1>Red</option><option value=2>Green</option><option value=3>Blue</option><option value=4>White</option><option value=5>Shutter (Brightness)</option><option value=6>Set to 255</option></select></span><br />\n'}function mMap(){for(d=document,numCh=document.Sf.CN.value,numGap=document.Sf.CG.value,parseInt(numCh)>parseInt(numGap)?d.getElementById("gapwarning").style.display="block":d.getElementById("gapwarning").style.display="none",i=0;i<15;i++)i>=numCh?(d.getElementById("CH"+(i+1)+"s").style.opacity="0.5",d.getElementById("CH"+(i+1)).disabled=!0):(d.getElementById("CH"+(i+1)+"s").style.opacity="1",d.getElementById("CH"+(i+1)).disabled=!1)}function S(){GCH(15),GetV(),mMap()}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX")}function B(){window.history.back()}function GetV() {var d=document; | ||||
| %CSS%%SCSS%</head><body onload="S()"><form | ||||
|  id="form_s" name="Sf" method="post"><div class="helpB"><button type="button"  | ||||
| @@ -165,9 +166,9 @@ const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; | ||||
| #endif | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_ui.htm, do not edit!! | ||||
| const char PAGE_settings_ui[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport"  | ||||
| content="width=500"><title>UI Settings</title><script> | ||||
| var initial_ds,initial_st,d=document,sett=null,l={comp:{labels:"Show button labels",colors:{LABEL:"Color selection methods",picker:"Color Wheel",rgb:"RGB sliders",quick:"Quick color selectors",hex:"HEX color input"},pcmbot:"Show bottom tab bar in PC mode"},theme:{alpha:{bg:"Background opacity",tab:"Button opacity"},bg:{url:"BG image URL"},color:{bg:"BG HEX color"}}};function gId(e){return d.getElementById(e)}function isObject(e){return e&&"object"==typeof e&&!Array.isArray(e)}function set(e,i,t){for(var n=i,l=e.split("_"),s=l.length,o=0;o<s-1;o++){var a=l[o];n[a]||(n[a]={}),n=n[a]}n[l[s-1]]=t}function addRec(e,t="",n=null){var l="";for(i in e){var s=t+(t?"_":"")+i;if(isObject(e[i]))n&&n[i]&&n[i].LABEL&&(l+=`<h3>${n[i].LABEL}</h3>`),l+=addRec(e[i],s,n?n[i]:null);else{var o=s;if(n&&n[i]?o=n[i]:e[i+"LABEL"]&&(o=e[i+"LABEL"]),i.indexOf("LABEL")>0)continue;var a=typeof e[i];gId(s)?("boolean"===a?gId(s).checked=e[i]:gId(s).value=e[i],gId(s).previousElementSibling.matches(".l")&&(gId(s).previousElementSibling.innerHTML=o)):"boolean"===a?l+=`${o}: <input class="agi cb" type="checkbox" id=${s} ${e[i]?"checked":""}><br>`:"number"===a?l+=`${o}: <input class="agi" type="number" id=${s} value=${e[i]}><br>`:"string"===a&&(l+=`${o}:<br><input class="agi" id=${s} value=${e[i]}><br>`)}}return l}function genForm(e){var i;i=addRec(e,"",l),gId("gen").innerHTML=i}function GetLS(){(sett=localStorage.getItem("wledUiCfg"))||(gId("lserr").style.display="inline");try{sett=JSON.parse(sett)}catch(e){sett={},gId("lserr").style.display="inline",gId("lserr").innerHTML="⚠ Settings JSON parsing failed. ("+e+")"}genForm(sett),gId("dm").checked="light"===gId("theme_base").value}function SetLS(){for(var e=d.querySelectorAll(".agi"),i=0;i<e.length;i++){var t=e[i],n=t.classList.contains("cb")?t.checked:t.value;set(t.id,sett,n),console.log(`${t.id} set to ${n}`)}try{localStorage.setItem("wledUiCfg",JSON.stringify(sett)),gId("lssuc").style.display="inline"}catch(t){gId("lssuc").style.display="none",gId("lserr").style.display="inline",gId("lserr").innerHTML="⚠ Settings JSON saving failed. ("+t+")"}}function Save(){SetLS(),d.Sf.DS.value==initial_ds&&d.Sf.ST.checked==initial_st||d.Sf.submit()}function S(){GetV(),initial_ds=d.Sf.DS.value,initial_st=d.Sf.ST.checked,GetLS()}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#user-interface-settings")}function B(){window.open("/settings","_self")}function UI(){gId("idonthateyou").style.display=gId("dm").checked?"inline":"none";var e=gId("theme_base");e&&(e.value=gId("dm").checked?"light":"dark")}function GetV() {var d=document; | ||||
| const char PAGE_settings_ui[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta  | ||||
| name="viewport" content="width=500"><title>UI Settings</title><script> | ||||
| var initial_ds,initial_st,d=document,sett=null,l={comp:{labels:"Show button labels",colors:{LABEL:"Color selection methods",picker:"Color Wheel",rgb:"RGB sliders",quick:"Quick color selectors",hex:"HEX color input"},pcmbot:"Show bottom tab bar in PC mode",pid:"Show preset IDs"},theme:{alpha:{bg:"Background opacity",tab:"Button opacity"},bg:{url:"BG image URL",random:"Random BG image"},color:{bg:"BG HEX color"}}};function gId(e){return d.getElementById(e)}function isObject(e){return e&&"object"==typeof e&&!Array.isArray(e)}function set(e,t,i){for(var n=t,l=e.split("_"),o=l.length,s=0;s<o-1;s++){var d=l[s];n[d]||(n[d]={}),n=n[d]}n[l[o-1]]=i}function addRec(e,t="",n=null){var l="";for(i in e){var o=t+(t?"_":"")+i;if(isObject(e[i]))n&&n[i]&&n[i].LABEL&&(l+=`<h3>${n[i].LABEL}</h3>`),l+=addRec(e[i],o,n?n[i]:null);else{var s=o;if(n&&n[i]?s=n[i]:e[i+"LABEL"]&&(s=e[i+"LABEL"]),i.indexOf("LABEL")>0)continue;var d=typeof e[i];gId(o)?("boolean"===d?gId(o).checked=e[i]:gId(o).value=e[i],gId(o).previousElementSibling.matches(".l")&&(gId(o).previousElementSibling.innerHTML=s)):"boolean"===d?l+=`${s}: <input class="agi cb" type="checkbox" id=${o} ${e[i]?"checked":""}><br>`:"number"===d?l+=`${s}: <input class="agi" type="number" id=${o} value=${e[i]}><br>`:"string"===d&&(l+=`${s}:<br><input class="agi" id=${o} value=${e[i]}><br>`)}}return l}function genForm(e){var t;t=addRec(e,"",l),gId("gen").innerHTML=t}function GetLS(){(sett=localStorage.getItem("wledUiCfg"))||(gId("lserr").style.display="inline");try{sett=JSON.parse(sett)}catch(e){sett={},gId("lserr").style.display="inline",gId("lserr").innerHTML="⚠ Settings JSON parsing failed. ("+e+")"}genForm(sett),gId("dm").checked="light"===gId("theme_base").value}function SetLS(){for(var e=d.querySelectorAll(".agi"),t=0;t<e.length;t++){var i=e[t],n=i.classList.contains("cb")?i.checked:i.value;set(i.id,sett,n),console.log(`${i.id} set to ${n}`)}try{localStorage.setItem("wledUiCfg",JSON.stringify(sett)),gId("lssuc").style.display="inline"}catch(i){gId("lssuc").style.display="none",gId("lserr").style.display="inline",gId("lserr").innerHTML="⚠ Settings JSON saving failed. ("+i+")"}}function Save(){SetLS(),d.Sf.DS.value==initial_ds&&d.Sf.ST.checked==initial_st||d.Sf.submit()}function S(){GetV(),initial_ds=d.Sf.DS.value,initial_st=d.Sf.ST.checked,GetLS()}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#user-interface-settings")}function B(){window.open("/settings","_self")}function UI(){gId("idonthateyou").style.display=gId("dm").checked?"inline":"none";var e=gId("theme_base");e&&(e.value=gId("dm").checked?"light":"dark")}function setRandomBg(){gId("theme_bg_random").checked?gId("theme_bg_url").value="https://picsum.photos/1920/1080":gId("theme_bg_url").value=""}function checkRandomBg(){"https://picsum.photos/1920/1080"===gId("theme_bg_url").value?gId("theme_bg_random").checked=!0:gId("theme_bg_random").checked=!1}function GetV() {var d=document; | ||||
| %CSS%%SCSS%</head><body onload="S()"><form | ||||
|  id="form_s" name="Sf" method="post"><div  | ||||
| style="position:sticky;top:0;background-color:#222"><div class="helpB"><button  | ||||
| @@ -185,22 +186,25 @@ You will need to set them again if using a different browser, device or WLED IP | ||||
| <br>Refresh the main UI to apply changes.</i><br><div id="gen"> | ||||
| Loading settings...</div><h3>UI Appearance</h3><span class="l"></span>: <input  | ||||
| type="checkbox" id="comp_labels" class="agi cb"><br><span class="l"></span>:  | ||||
| <input type="checkbox" id="comp_pcmbot" class="agi cb"><br>I hate dark mode:  | ||||
| <input type="checkbox" id="dm" onchange="UI()"><br><span id="idonthateyou"  | ||||
| style="display:none"><i>Why would you? </i>🥺<br></span><span class="l"> | ||||
| </span>: <input type="number" min="0.0" max="1.0" step="0.01"  | ||||
| id="theme_alpha_tab" class="agi"><br><span class="l"></span>: <input  | ||||
| <input type="checkbox" id="comp_pcmbot" class="agi cb"><br><span class="l"> | ||||
| </span>: <input type="checkbox" id="comp_pid" class="agi cb"><br> | ||||
| I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br><span  | ||||
| id="idonthateyou" style="display:none"><i>Why would you? </i>🥺<br> | ||||
| </span><span class="l"></span>: <input type="number" min="0.0" max="1.0"  | ||||
| step="0.01" id="theme_alpha_tab" class="agi"><br><span class="l"></span>: <input | ||||
|  type="number" min="0.0" max="1.0" step="0.01" id="theme_alpha_bg" class="agi"> | ||||
| <br><span class="l"></span>: <input id="theme_color_bg" maxlength="9"  | ||||
| class="agi"><br><span class="l">BG image URL</span>: <input id="theme_bg_url"  | ||||
| class="agi"> <input id="theme_base" class="agi" style="display:none"><hr><button | ||||
|  type="button" onclick="B()">Back</button><button type="button"  | ||||
| onclick="Save()">Save</button></form></body></html>)====="; | ||||
| class="agi" oninput="checkRandomBg()"><br><span class="l">Random BG image</span> | ||||
| : <input type="checkbox" id="theme_bg_random" class="agi cb"  | ||||
| onchange="setRandomBg()"><br><input id="theme_base" class="agi"  | ||||
| style="display:none"><hr><button type="button" onclick="B()">Back</button> | ||||
| <button type="button" onclick="Save()">Save</button></form></body></html>)====="; | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_sync.htm, do not edit!! | ||||
| const char PAGE_settings_sync[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" content="width=500"><meta  | ||||
| charset="utf-8"><title>Sync Settings</title><script> | ||||
| const char PAGE_settings_sync[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500"> | ||||
| <meta charset="utf-8"><title>Sync Settings</title><script> | ||||
| var d=document;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings")}function B(){window.open("/settings","_self")}function adj(){6454==d.Sf.DI.value?(1==d.Sf.DA.value&&(d.Sf.DA.value=0),1==d.Sf.EU.value&&(d.Sf.EU.value=0)):5568==d.Sf.DI.value&&(0==d.Sf.DA.value&&(d.Sf.DA.value=1),0==d.Sf.EU.value&&(d.Sf.EU.value=1))}function SP(){var e=d.Sf.DI.value;d.getElementById("xp").style.display=e>0?"none":"block",e>0&&(d.Sf.EP.value=e)}function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568:d.Sf.DI.value=5568;break;case 6454:d.Sf.DI.value=6454;break;case 4048:d.Sf.DI.value=4048}SP()}function S(){GetV(),SetVal()}function GetV() { | ||||
| %CSS%%SCSS%</head><body onload="S()"><form | ||||
|  id="form_s" name="Sf" method="post"><div class="helpB"><button type="button"  | ||||
| @@ -219,7 +223,7 @@ type="number" min="1" max="65535" class="d5" required><br>Receive <input | ||||
| type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC"> | ||||
| Color, and <input type="checkbox" name="RX">Effects<br> | ||||
| Send notifications on direct change: <input type="checkbox" name="SD"><br> | ||||
| Send notifications on button press: <input type="checkbox" name="SB"><br> | ||||
| Send notifications on button press or IR: <input type="checkbox" name="SB"><br> | ||||
| Send Alexa notifications: <input type="checkbox" name="SA"><br> | ||||
| Send Philips Hue change notifications: <input type="checkbox" name="SH"><br> | ||||
| Send Macro notifications: <input type="checkbox" name="SM"><br> | ||||
| @@ -251,19 +255,20 @@ maxlength="32"><h3>Blynk</h3><b> | ||||
| Blynk, MQTT and Hue sync all connect to external hosts!<br> | ||||
| This may impact the responsiveness of the ESP8266.</b><br> | ||||
| For best results, only use one of these services at a time.<br> | ||||
| (alternatively, connect a second ESP to them and use the UDP sync)<br><br> | ||||
| Device Auth token: <input name="BK" maxlength="33"><br><i> | ||||
| Clear the token field to disable. </i><a  | ||||
| (alternatively, connect a second ESP to them and use the UDP sync)<br><br>Host:  | ||||
| <input name="BH" maxlength="32"> Port: <input name="BP" type="number" min="1"  | ||||
| max="65535" value="80" class="d5"><br>Device Auth token: <input name="BK"  | ||||
| maxlength="33"><br><i>Clear the token field to disable. </i><a  | ||||
| href="https://github.com/Aircoookie/WLED/wiki/Blynk" target="_blank">Setup info | ||||
| </a><h3>MQTT</h3>Enable MQTT: <input type="checkbox" name="MQ"><br>Broker:  | ||||
| <input name="MS" maxlength="32"> Port: <input name="MQPORT" type="number"  | ||||
| min="1" max="65535" class="d5"><br><b> | ||||
| The MQTT credentials are sent over an unsecured connection.<br> | ||||
| Never use the MQTT password for another service!</b><br>Username: <input  | ||||
| name="MQUSER" maxlength="40"><br>Password: <input type="password" input  | ||||
| name="MQPASS" maxlength="40"><br>Client ID: <input name="MQCID" maxlength="40"> | ||||
| <br>Device Topic: <input name="MD" maxlength="32"><br>Group Topic: <input  | ||||
| name="MG" maxlength="32"><br><i>Reboot required to apply changes. </i><a  | ||||
| name="MQUSER" maxlength="40"><br>Password: <input type="password" name="MQPASS"  | ||||
| maxlength="40"><br>Client ID: <input name="MQCID" maxlength="40"><br> | ||||
| Device Topic: <input name="MD" maxlength="32"><br>Group Topic: <input name="MG"  | ||||
| maxlength="32"><br><i>Reboot required to apply changes. </i><a  | ||||
| href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info | ||||
| </a><h3>Philips Hue</h3><i> | ||||
| You can find the bridge IP and the light number in the 'About' section of the hue app. | ||||
| @@ -282,8 +287,8 @@ type="submit">Save</button></form></body></html>)====="; | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_time.htm, do not edit!! | ||||
| const char PAGE_settings_time[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" content="width=500"><meta  | ||||
| charset="utf-8"><title>Time Settings</title><script> | ||||
| const char PAGE_settings_time[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500"> | ||||
| <meta charset="utf-8"><title>Time Settings</title><script> | ||||
| var d=document;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings")}function B(){window.open("/settings","_self")}function S(){BTa(),GetV(),Cs(),FC()}function gId(t){return d.getElementById(t)}function Cs(){gId("cac").style.display="none",gId("coc").style.display="block",gId("ccc").style.display="none",gId("ca").selected&&(gId("cac").style.display="block"),gId("cc").selected&&(gId("coc").style.display="none",gId("ccc").style.display="block"),gId("cn").selected&&(gId("coc").style.display="none")}function BTa(){var t="<tr><th>Active</th><th>Hour</th><th>Minute</th><th>Preset</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>";for(i=0;i<8;i++)for(t+='<tr><td><input name="W'+i+'" id="W'+i+'" type="number" style="display:none"><input id="W'+i+'0" type="checkbox"></td><td><input name="H'+i+'" type="number" min="0" max="24"></td><td><input name="N'+i+'" type="number" min="0" max="59"></td><td><input name="T'+i+'" type="number" min="0" max="250"></td>',j=1;j<8;j++)t+='<td><input id="W'+i+j+'" type="checkbox"></td>';gId("TMT").innerHTML=t}function FC(){for(j=0;j<8;j++)for(i=0;i<8;i++)gId("W"+i+j).checked=gId("W"+i).value>>j&1}function Wd(){for(a=[0,0,0,0,0,0,0,0],i=0;i<8;i++){for(m=1,j=0;j<8;j++)a[i]+=gId("W"+i+j).checked*m,m*=2;gId("W"+i).value=a[i]}}function GetV() { | ||||
| %CSS%%SCSS%</head><body onload="S()"><form | ||||
|  id="form_s" name="Sf" method="post" onsubmit="Wd()"><div class="helpB"><button  | ||||
| @@ -337,8 +342,8 @@ required><br><h3>Time-controlled presets</h3><div style="display:inline-block"> | ||||
|  | ||||
|  | ||||
| // Autogenerated from wled00/data/settings_sec.htm, do not edit!! | ||||
| const char PAGE_settings_sec[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" content="width=500"><meta  | ||||
| charset="utf-8"><title>Misc Settings</title><script> | ||||
| const char PAGE_settings_sec[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500"> | ||||
| <meta charset="utf-8"><title>Misc Settings</title><script> | ||||
| function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings")}function B(){window.open("/settings","_self")}function U(){window.open("/update","_self")}function GetV() {var d=document; | ||||
| %CSS%%SCSS%</head><body onload="GetV()"> | ||||
| <form id="form_s" name="Sf" method="post"><div class="helpB"><button  | ||||
| @@ -358,7 +363,7 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form | ||||
| <h3>Software Update</h3><button type="button" onclick="U()">Manual OTA Update | ||||
| </button><br>Enable ArduinoOTA: <input type="checkbox" name="AO"><br><h3>About | ||||
| </h3><a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> | ||||
|  version 0.11.0<br><br><a  | ||||
|  version 0.11.1<br><br><a  | ||||
| href="https://github.com/Aircoookie/WLED/wiki/Contributors-&-About"  | ||||
| target="_blank">Contributors, dependencies and special thanks</a><br> | ||||
| A huge thank you to everyone who helped me create WLED!<br><br> | ||||
|   | ||||
							
								
								
									
										4176
									
								
								wled00/html_ui.h
									
									
									
									
									
								
							
							
						
						
									
										4176
									
								
								wled00/html_ui.h
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -95,9 +95,21 @@ void colorUpdated(int callMode) | ||||
|       callMode != NOTIFIER_CALL_MODE_DIRECT_CHANGE &&  | ||||
|       callMode != NOTIFIER_CALL_MODE_NO_NOTIFY) strip.applyToAllSelected = true; //if not from JSON api, which directly sets segments | ||||
|  | ||||
|   bool someSel = false; | ||||
|  | ||||
|   if (callMode == NOTIFIER_CALL_MODE_NOTIFICATION) { | ||||
|     someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); | ||||
|   } | ||||
|    | ||||
|   //Notifier: apply received FX to selected segments only if actually receiving FX | ||||
|   if (someSel) strip.applyToAllSelected = receiveNotificationEffects; | ||||
|  | ||||
|   bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette); | ||||
|   bool colChanged = colorChanged(); | ||||
|  | ||||
|   //Notifier: apply received color to selected segments only if actually receiving color | ||||
|   if (someSel) strip.applyToAllSelected = receiveNotificationColor; | ||||
|  | ||||
|   if (fxChanged || colChanged) | ||||
|   { | ||||
|     if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0; | ||||
| @@ -107,7 +119,7 @@ void colorUpdated(int callMode) | ||||
|     notify(callMode); | ||||
|      | ||||
|     //set flag to update blynk and mqtt | ||||
|     if (callMode != NOTIFIER_CALL_MODE_PRESET_CYCLE) interfaceUpdateCallMode = callMode; | ||||
|     interfaceUpdateCallMode = callMode; | ||||
|   } else { | ||||
|     if (nightlightActive && !nightlightActiveOld &&  | ||||
|         callMode != NOTIFIER_CALL_MODE_NOTIFICATION &&  | ||||
| @@ -303,10 +315,10 @@ void handleNightlight() | ||||
|     if (bri == 0 || nightlightActive) return; | ||||
|  | ||||
|     if (presetCycCurr < presetCycleMin || presetCycCurr > presetCycleMax) presetCycCurr = presetCycleMin; | ||||
|     applyPreset(presetCycCurr); | ||||
|     applyPreset(presetCycCurr); //this handles colorUpdated() for us | ||||
|     presetCycCurr++; | ||||
|     if (presetCycCurr > 250) presetCycCurr = 1; | ||||
|     colorUpdated(NOTIFIER_CALL_MODE_PRESET_CYCLE); | ||||
|     interfaceUpdateCallMode = 0; //disable updates to MQTT and Blynk | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -163,8 +163,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) | ||||
|     alexaEnabled = request->hasArg(F("AL")); | ||||
|     strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33); | ||||
|  | ||||
|     strlcpy(blynkHost, request->arg("BH").c_str(), 33); | ||||
|     t = request->arg(F("BP")).toInt(); | ||||
|     if (t > 0) blynkPort = t; | ||||
|  | ||||
|     if (request->hasArg("BK") && !request->arg("BK").equals(F("Hidden"))) { | ||||
|       strlcpy(blynkApiKey, request->arg("BK").c_str(), 36); initBlynk(blynkApiKey); | ||||
|       strlcpy(blynkApiKey, request->arg("BK").c_str(), 36); initBlynk(blynkApiKey, blynkHost, blynkPort); | ||||
|     } | ||||
|  | ||||
|     #ifdef WLED_ENABLE_MQTT | ||||
| @@ -266,6 +270,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) | ||||
|     if (request->hasArg(F("RS"))) //complete factory reset | ||||
|     { | ||||
|       WLED_FS.format(); | ||||
|       clearEEPROM(); | ||||
|       serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255); | ||||
|       doReboot = true; | ||||
|     } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|  */ | ||||
| /* | ||||
|  * @title Espalexa library | ||||
|  * @version 2.4.6 | ||||
|  * @version 2.5.0 | ||||
|  * @author Christian Schwinne | ||||
|  * @license MIT | ||||
|  * @contributors d-999 | ||||
| @@ -50,7 +50,7 @@ | ||||
| #include "../network/Network.h" | ||||
|  | ||||
| #ifdef ESPALEXA_DEBUG | ||||
|  #pragma message "Espalexa 2.4.6 debug mode" | ||||
|  #pragma message "Espalexa 2.5.0 debug mode" | ||||
|  #define EA_DEBUG(x)  Serial.print (x) | ||||
|  #define EA_DEBUGLN(x) Serial.println (x) | ||||
| #else | ||||
| @@ -60,6 +60,7 @@ | ||||
|  | ||||
| #include "EspalexaDevice.h" | ||||
|  | ||||
| #define DEVICE_UNIQUE_ID_LENGTH 12 | ||||
|  | ||||
| class Espalexa { | ||||
| private: | ||||
| @@ -116,17 +117,24 @@ private: | ||||
|     return ""; | ||||
|   } | ||||
|    | ||||
|   //Workaround functions courtesy of Sonoff-Tasmota | ||||
|   uint32_t encodeLightId(uint8_t idx) | ||||
|   void encodeLightId(uint8_t idx, char* out) | ||||
|   { | ||||
|     //Unique id must be 12 character len | ||||
|     //use the last 10 characters of the MAC followed by the device id in hex value | ||||
|     //uniqueId: aabbccddeeii | ||||
|  | ||||
|     uint8_t mac[6]; | ||||
|     WiFi.macAddress(mac); | ||||
|     uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (idx & 0xF); | ||||
|     return id; | ||||
|   } | ||||
|  | ||||
|   uint32_t decodeLightId(uint32_t id) { | ||||
|     return id & 0xF; | ||||
|     //shift the mac address to the left (discard first byte) | ||||
|     for (uint8_t i = 0; i < 5; i++) { | ||||
|       mac[i] = mac[i+1]; | ||||
|     } | ||||
|     mac[5] = idx; | ||||
|  | ||||
|     for (uint8_t i = 0; i < 6; i++) { | ||||
|       sprintf(out + i*2, "%.2x", mac[i]); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   //device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001) | ||||
| @@ -136,10 +144,8 @@ private: | ||||
|     if (deviceId >= currentDeviceCount) {strcpy(buf,"{}"); return;} //error | ||||
|     EspalexaDevice* dev = devices[deviceId]; | ||||
|      | ||||
|     //char buf_bri[12] = ""; | ||||
|     //brightness support, add "bri" to JSON | ||||
|     //if (dev->getType() != EspalexaDeviceType::onoff)  | ||||
|     //  sprintf(buf_bri,",\"bri\":%u", dev->getLastValue()-1); | ||||
|     char buf_lightid[13]; | ||||
|     encodeLightId(deviceId + 1, buf_lightid); | ||||
|      | ||||
|     char buf_col[80] = ""; | ||||
|     //color support | ||||
| @@ -161,10 +167,10 @@ private: | ||||
|      | ||||
|     sprintf_P(buf, PSTR("{\"state\":{\"on\":%s,\"bri\":%u%s%s,\"alert\":\"none%s\",\"mode\":\"homeautomation\",\"reachable\":true}," | ||||
|                    "\"type\":\"%s\",\"name\":\"%s\",\"modelid\":\"%s\",\"manufacturername\":\"Philips\",\"productname\":\"E%u" | ||||
|                    "\",\"uniqueid\":\"%u\",\"swversion\":\"espalexa-2.4.6\"}") | ||||
|                    "\",\"uniqueid\":\"%s\",\"swversion\":\"espalexa-2.5.0\"}") | ||||
|                     | ||||
|     , (dev->getValue())?"true":"false", dev->getLastValue()-1, buf_col, buf_ct, buf_cm, typeString(dev->getType()), | ||||
|     dev->getName().c_str(), modelidString(dev->getType()), static_cast<uint8_t>(dev->getType()), encodeLightId(deviceId+1)); | ||||
|     dev->getName().c_str(), modelidString(dev->getType()), static_cast<uint8_t>(dev->getType()), buf_lightid); | ||||
|   } | ||||
|    | ||||
|   //Espalexa status page /espalexa | ||||
| @@ -186,7 +192,7 @@ private: | ||||
|     } | ||||
|     res += "\r\nFree Heap: " + (String)ESP.getFreeHeap(); | ||||
|     res += "\r\nUptime: " + (String)millis(); | ||||
|     res += "\r\n\r\nEspalexa library v2.4.6 by Christian Schwinne 2020"; | ||||
|     res += "\r\n\r\nEspalexa library v2.5.0 by Christian Schwinne 2020"; | ||||
|     server->send(200, "text/plain", res); | ||||
|   } | ||||
|   #endif | ||||
| @@ -222,7 +228,7 @@ private: | ||||
|         "<URLBase>http://%s:80/</URLBase>" | ||||
|         "<device>" | ||||
|           "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>" | ||||
|           "<friendlyName>Espalexa (%s)</friendlyName>" | ||||
|           "<friendlyName>Espalexa (%s:80)</friendlyName>" | ||||
|           "<manufacturer>Royal Philips Electronics</manufacturer>" | ||||
|           "<manufacturerURL>http://www.philips.com</manufacturerURL>" | ||||
|           "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>" | ||||
| @@ -237,8 +243,8 @@ private: | ||||
|            | ||||
|     server->send(200, "text/xml", buf); | ||||
|      | ||||
|     EA_DEBUG("Send setup.xml"); | ||||
|     //EA_DEBUGLN(setup_xml); | ||||
|     EA_DEBUGLN("Send setup.xml"); | ||||
|     EA_DEBUGLN(buf); | ||||
|   } | ||||
|    | ||||
|   //init the server | ||||
| @@ -298,7 +304,7 @@ private: | ||||
|       "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber | ||||
|       "hue-bridgeid: %s\r\n" | ||||
|       "ST: urn:schemas-upnp-org:device:basic:1\r\n"  // _deviceType | ||||
|       "USN: uuid:2f402f80-da50-11e1-9b23-%s::ssdp:all\r\n" // _uuid::_deviceType | ||||
|       "USN: uuid:2f402f80-da50-11e1-9b23-%s::upnp:rootdevice\r\n" // _uuid::_deviceType | ||||
|       "\r\n"),s,escapedMac.c_str(),escapedMac.c_str()); | ||||
|  | ||||
|     espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort()); | ||||
| @@ -359,26 +365,30 @@ public: | ||||
|      | ||||
|     if (!udpConnected) return;    | ||||
|     int packetSize = espalexaUdp.parsePacket();     | ||||
|     if (!packetSize) return; //no new udp packet | ||||
|     if (packetSize < 1) return; //no new udp packet | ||||
|      | ||||
|     EA_DEBUGLN("Got UDP!"); | ||||
|     char packetBuffer[255]; //buffer to hold incoming udp packet | ||||
|     uint16_t len = espalexaUdp.read(packetBuffer, 254); | ||||
|     if (len > 0) { | ||||
|       packetBuffer[len] = 0; | ||||
|     } | ||||
|  | ||||
|     unsigned char packetBuffer[packetSize+1]; //buffer to hold incoming udp packet | ||||
|     espalexaUdp.read(packetBuffer, packetSize); | ||||
|     packetBuffer[packetSize] = 0; | ||||
|    | ||||
|     espalexaUdp.flush(); | ||||
|     if (!discoverable) return; //do not reply to M-SEARCH if not discoverable | ||||
|    | ||||
|     String request = packetBuffer; | ||||
|     if(request.indexOf("M-SEARCH") >= 0) { | ||||
|     const char* request = (const char *) packetBuffer; | ||||
|     if (strstr(request, "M-SEARCH") == nullptr) return; | ||||
|  | ||||
|     EA_DEBUGLN(request); | ||||
|       if(request.indexOf("upnp:rootdevice") > 0 || request.indexOf("asic:1") > 0 || request.indexOf("ssdp:all") > 0) { | ||||
|     if (strstr(request, "ssdp:disc")  != nullptr &&  //short for "ssdp:discover" | ||||
|         (strstr(request, "upnp:rootd") != nullptr || //short for "upnp:rootdevice" | ||||
|          strstr(request, "ssdp:all")   != nullptr || | ||||
|          strstr(request, "asic:1")     != nullptr )) //short for "device:basic:1" | ||||
|     { | ||||
|       EA_DEBUGLN("Responding search req..."); | ||||
|       respondToSearch(); | ||||
|     } | ||||
|   } | ||||
|   } | ||||
|  | ||||
|   bool addDevice(EspalexaDevice* d) | ||||
|   { | ||||
| @@ -452,13 +462,12 @@ public: | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (req.indexOf("state") > 0) //client wants to control light | ||||
|     if ((req.indexOf("state") > 0) && (body.length() > 0)) //client wants to control light | ||||
|     { | ||||
|       server->send(200, "application/json", F("[{\"success\":{\"/lights/1/state/\": true}}]")); | ||||
|  | ||||
|       uint32_t devId = req.substring(req.indexOf("lights")+7).toInt(); | ||||
|       EA_DEBUG("ls"); EA_DEBUGLN(devId); | ||||
|       devId = decodeLightId(devId); | ||||
|       EA_DEBUGLN(devId); | ||||
|       devId--; //zero-based for devices array | ||||
|       if (devId >= currentDeviceCount) return true; //return if invalid ID | ||||
| @@ -530,7 +539,7 @@ public: | ||||
|         String jsonTemp = "{"; | ||||
|         for (int i = 0; i<currentDeviceCount; i++) | ||||
|         { | ||||
|           jsonTemp += "\"" + String(encodeLightId(i+1)) + "\":"; | ||||
|           jsonTemp += "\"" + String(i+1) + "\":"; | ||||
|           char buf[512]; | ||||
|           deviceJsonString(i+1, buf); | ||||
|           jsonTemp += buf; | ||||
| @@ -540,7 +549,6 @@ public: | ||||
|         server->send(200, "application/json", jsonTemp); | ||||
|       } else //client wants one light (devId) | ||||
|       { | ||||
|         devId = decodeLightId(devId); | ||||
|         EA_DEBUGLN(devId); | ||||
|         if (devId > currentDeviceCount) | ||||
|         { | ||||
|   | ||||
							
								
								
									
										1506
									
								
								wled00/tv_colors.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1506
									
								
								wled00/tv_colors.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -13,8 +13,18 @@ | ||||
| #ifdef USERMOD_DALLASTEMPERATURE | ||||
| #include "../usermods/Temperature/usermod_temperature.h" | ||||
| #endif | ||||
|  | ||||
| //#include "usermod_v2_empty.h" | ||||
|  | ||||
| #ifdef USERMOD_BUZZER | ||||
| #include "../usermods/buzzer/usermod_v2_buzzer.h" | ||||
| #endif | ||||
|  | ||||
| // BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h | ||||
| #ifdef USERMOD_BME280 | ||||
| #include "../usermods/BME280_v2/usermod_bme280.h" | ||||
| #endif | ||||
|  | ||||
| void registerUsermods() | ||||
| { | ||||
|   /* | ||||
| @@ -23,8 +33,18 @@ void registerUsermods() | ||||
|    * \/ \/ \/ | ||||
|    */ | ||||
|   //usermods.add(new MyExampleUsermod()); | ||||
|    | ||||
|   #ifdef USERMOD_DALLASTEMPERATURE | ||||
|   usermods.add(new UsermodTemperature()); | ||||
|   #endif | ||||
|    | ||||
|   //usermods.add(new UsermodRenameMe()); | ||||
|    | ||||
|   #ifdef USERMOD_BUZZER | ||||
|   usermods.add(new BuzzerUsermod()); | ||||
|   #endif | ||||
|    | ||||
|   #ifdef USERMOD_BME280 | ||||
|   usermods.add(new UsermodBME280()); | ||||
|   #endif | ||||
| } | ||||
| @@ -324,7 +324,8 @@ void WLED::beginStrip() | ||||
|  | ||||
|   if (bootPreset > 0) applyPreset(bootPreset); | ||||
|   if (turnOnAtBoot) { | ||||
|     bri = (briS > 0) ? briS : 128; | ||||
|     if (briS > 0) bri = briS; | ||||
|     else if (bri == 0) bri = 128; | ||||
|   } else { | ||||
|     briLast = briS; bri = 0; | ||||
|   } | ||||
| @@ -377,6 +378,7 @@ void WLED::initAP(bool resetAP) | ||||
|     if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { | ||||
|       udp2Connected = notifier2Udp.begin(udpPort2); | ||||
|     } | ||||
|     e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); | ||||
|    | ||||
|     dnsServer.setErrorReplyCode(DNSReplyCode::NoError); | ||||
|     dnsServer.start(53, "*", WiFi.softAPIP()); | ||||
| @@ -491,7 +493,7 @@ void WLED::initInterfaces() | ||||
|   if (ntpEnabled) | ||||
|     ntpConnected = ntpUdp.begin(ntpLocalPort); | ||||
|  | ||||
|   initBlynk(blynkApiKey); | ||||
|   initBlynk(blynkApiKey, blynkHost, blynkPort); | ||||
|   e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); | ||||
|   reconnectHue(); | ||||
|   initMqtt(); | ||||
|   | ||||
| @@ -3,12 +3,12 @@ | ||||
| /* | ||||
|    Main sketch, global variable declarations | ||||
|    @title WLED project sketch | ||||
|    @version 0.11.0 | ||||
|    @version 0.11.1 | ||||
|    @author Christian Schwinne | ||||
|  */ | ||||
|  | ||||
| // version code in format yymmddb (b = daily build) | ||||
| #define VERSION 2012020 | ||||
| #define VERSION 2012210 | ||||
|  | ||||
| //uncomment this if you have a "my_config.h" file you'd like to use | ||||
| //#define WLED_USE_MY_CONFIG | ||||
| @@ -22,10 +22,11 @@ | ||||
| // You are required to disable over-the-air updates: | ||||
| //#define WLED_DISABLE_OTA         // saves 14kb | ||||
|  | ||||
| // You need to choose some of these features to disable: | ||||
| // You can choose some of these features to disable: | ||||
| //#define WLED_DISABLE_ALEXA       // saves 11kb | ||||
| //#define WLED_DISABLE_BLYNK       // saves 6kb | ||||
| //#define WLED_DISABLE_CRONIXIE    // saves 3kb | ||||
| //WLED_DISABLE_FX_HIGH_FLASH_USE (need to enable in PIO config or FX.h, saves 18kb) | ||||
| //#define WLED_DISABLE_HUESYNC     // saves 4kb | ||||
| //#define WLED_DISABLE_INFRARED    // there is no pin left for this on ESP8266-01, saves 12kb | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
| @@ -173,7 +174,7 @@ | ||||
| #endif | ||||
|  | ||||
| // Global Variable definitions | ||||
| WLED_GLOBAL char versionString[] _INIT("0.11.0"); | ||||
| WLED_GLOBAL char versionString[] _INIT("0.11.1"); | ||||
| #define WLED_CODENAME "Mirai" | ||||
|  | ||||
| // AP and OTA default passwords (for maximum security change them!) | ||||
| @@ -249,6 +250,8 @@ WLED_GLOBAL bool alexaEnabled _INIT(false);                       // enable devi | ||||
| WLED_GLOBAL char alexaInvocationName[33] _INIT("Light");          // speech control name of device. Choose something voice-to-text can understand | ||||
|  | ||||
| WLED_GLOBAL char blynkApiKey[36] _INIT("");                       // Auth token for Blynk server. If empty, no connection will be made | ||||
| WLED_GLOBAL char blynkHost[33] _INIT("blynk-cloud.com");          // Default Blynk host | ||||
| WLED_GLOBAL uint16_t blynkPort _INIT(80);                         // Default Blynk port | ||||
|  | ||||
| WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500);               // ms timeout of realtime mode before returning to normal mode | ||||
| WLED_GLOBAL int arlsOffset _INIT(0);                              // realtime LED offset | ||||
|   | ||||
| @@ -36,6 +36,20 @@ | ||||
| //21-> 0.10.1p | ||||
| //22-> 2009260 | ||||
|  | ||||
| /* | ||||
|  * Erase all (pre 0.11) configuration data on factory reset | ||||
|  */ | ||||
| void clearEEPROM() | ||||
| { | ||||
|   EEPROM.begin(EEPSIZE); | ||||
|   for (int i = 0; i < EEPSIZE; i++) | ||||
|   { | ||||
|     EEPROM.write(i, 0); | ||||
|   } | ||||
|   EEPROM.end(); | ||||
| } | ||||
|  | ||||
|  | ||||
| void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len) | ||||
| { | ||||
|   for (int i = 0; i < len; ++i) | ||||
|   | ||||
| @@ -323,13 +323,14 @@ void getSettingsJS(byte subPage, char* dest) | ||||
|     sappends('s',SET_F("AI"),alexaInvocationName); | ||||
|     sappend('c',SET_F("SA"),notifyAlexa); | ||||
|     sappends('s',SET_F("BK"),(char*)((blynkEnabled)?SET_F("Hidden"):"")); | ||||
|     sappends('s',SET_F("BH"),blynkHost); | ||||
|     sappend('v',SET_F("BP"),blynkPort); | ||||
|  | ||||
|     #ifdef WLED_ENABLE_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); | ||||
|     sappends('s',SET_F("MQPASS"),mqttPass); | ||||
|     byte l = strlen(mqttPass); | ||||
|     char fpass[l+1]; //fill password field with *** | ||||
|     fpass[l] = 0; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Aircoookie
					Aircoookie