Merge branch 'dev' into feature/live-preview-websockets

This commit is contained in:
Aircoookie 2020-12-27 19:36:58 +01:00 committed by GitHub
commit e9c782bf9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 7459 additions and 5035 deletions

31
.github/workflows/wled-ci.yml vendored Normal file
View 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

View File

@ -1,6 +1,65 @@
## WLED changelog ## 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 #### Build 2012020
@ -11,6 +70,8 @@
- Fixed compilation for analog (PWM) LEDs - Fixed compilation for analog (PWM) LEDs
### WLED version 0.11.0
#### Build 2011290 #### Build 2011290
- Release of WLED 0.11.0 "Mirai" - Release of WLED 0.11.0 "Mirai"

5
images/Readme.md Normal file
View 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
View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.10.2", "version": "0.11.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -958,9 +958,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
}, },
"inliner": { "inliner": {
"version": "1.13.1", "version": "1.13.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.11.0", "version": "0.11.1",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {

View File

@ -179,7 +179,10 @@ extra_scripts = pio/name-firmware.py
framework = arduino framework = arduino
board_build.flash_mode = dout board_build.flash_mode = dout
monitor_speed = 115200 monitor_speed = 115200
# slow upload speed (comment this out with a ';' when building for development use)
upload_speed = 115200 upload_speed = 115200
# fast upload speed (remove ';' when building for development use)
; upload_speed = 921600
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# LIBRARIES: required dependencies # LIBRARIES: required dependencies
@ -361,6 +364,14 @@ board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} 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 # custom board configurations
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -69,6 +69,8 @@ function writeHtmlGzipped(sourceFile, resultFile) {
console.info("Reading " + sourceFile); console.info("Reading " + sourceFile);
new inliner(sourceFile, function (error, html) { new inliner(sourceFile, function (error, html) {
console.info("Inlined " + html.length + " characters"); console.info("Inlined " + html.length + " characters");
html = filter(html, "html-minify-ui");
console.info("Minified to " + html.length + " characters");
if (error) { if (error) {
console.warn(error); console.warn(error);
@ -123,6 +125,16 @@ function filter(str, type) {
continueOnParseError: false, continueOnParseError: false,
removeComments: true, 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 { } else {
console.warn("Unknown filter: " + type); console.warn("Unknown filter: " + type);
return str; return str;
@ -132,7 +144,7 @@ function filter(str, type) {
function specToChunk(srcDir, s) { function specToChunk(srcDir, s) {
if (s.method == "plaintext") { if (s.method == "plaintext") {
const buf = fs.readFileSync(srcDir + "/" + s.file); const buf = fs.readFileSync(srcDir + "/" + s.file);
const str = buf.toString("ascii"); const str = buf.toString("utf-8");
const chunk = ` const chunk = `
// Autogenerated from ${srcDir}/${s.file}, do not edit!! // Autogenerated from ${srcDir}/${s.file}, do not edit!!
const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${ const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${

View 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`

View 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;
}
}
}
};

View File

@ -1,17 +1,32 @@
# Fix unreachable net services V2 # 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. 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. 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:_ _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. 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. 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 ## Installation
1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory. 1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory.

View File

@ -1,6 +1,14 @@
#pragma once #pragma once
#include "wled.h" #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> #include <ping.h>
/* /*
@ -23,116 +31,138 @@
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp * 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 private:
unsigned long m_lastTime = 0; //Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long m_lastTime = 0;
// desclare required variables // declare required variables
const unsigned int PingDelayMs = 60000; unsigned long m_pingDelayMs = 60000;
unsigned long m_connectedWiFi = 0; unsigned long m_connectedWiFi = 0;
ping_option m_pingOpt; ping_option m_pingOpt;
unsigned int m_pingCount = 0; unsigned int m_pingCount = 0;
bool m_updateConfig = false;
public: public:
//Functions called by WLED //Functions called by WLED
/* /**
* setup() is called once at boot. WiFi is not yet connected at this point. * setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar. * You can use it to initialize variables, sensors or similar.
*/ */
void setup() { void setup()
//Serial.println("Hello from my usermod!"); {
} //Serial.println("Hello from my usermod!");
}
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
/* ++m_connectedWiFi;
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() {
//Serial.println("Connected to WiFi!");
++m_connectedWiFi; // initialize ping_options structure
memset(&m_pingOpt, 0, sizeof(struct ping_option));
// initialize ping_options structure m_pingOpt.count = 1;
memset(&m_pingOpt, 0, sizeof(struct ping_option)); m_pingOpt.ip = WiFi.localIP();
m_pingOpt.count = 1; }
m_pingOpt.ip = WiFi.localIP();
} /**
* loop
*/
/* void loop()
* loop() is called continuously. Here you can check for events, read sensors, etc. {
* if (m_connectedWiFi > 0 && millis() - m_lastTime > m_pingDelayMs)
* Tips:
* 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
* Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
*
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
* Instead, use a timer check as shown here.
*/
void loop() {
if (m_connectedWiFi > 0 && millis()-m_lastTime > PingDelayMs)
{
ping_start(&m_pingOpt);
m_lastTime = millis();
++m_pingCount;
}
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
void addToJsonInfo(JsonObject& root)
{ {
//this code adds "u":{"&#x26A1; Ping fix pings": m_pingCount} to the info object ping_start(&m_pingOpt);
JsonObject user = root["u"]; m_lastTime = millis();
if (user.isNull()) user = root.createNestedObject("u"); ++m_pingCount;
JsonArray infoArr = user.createNestedArray("&#x26A1; Ping fix pings"); //name
infoArr.add(m_pingCount); //value
//this code adds "u":{"&#x26A1; Reconnects": m_connectedWiFi - 1} to the info object
infoArr = user.createNestedArray("&#x26A1; Reconnects"); //name
infoArr.add(m_connectedWiFi - 1); //value
} }
if (m_updateConfig)
/*
* 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; 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
*/
void addToJsonInfo(JsonObject &root)
{
//this code adds "u":{"&#x26A1; Ping fix pings": m_pingCount} to the info object
JsonObject user = root["u"];
if (user.isNull())
user = root.createNestedObject("u");
/* String uiDomString = "&#x26A1; Ping fix pings<span style=\"display:block;padding-left:25px;\">\
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). Delay <input type=\"number\" min=\"5\" max=\"300\" value=\"";
* Values in the state object may be modified by connected clients uiDomString += (unsigned long)(m_pingDelayMs / 1000);
*/ uiDomString += "\" onchange=\"requestJson({PingDelay:parseInt(this.value)});\">sec</span>";
void readFromJsonState(JsonObject& root)
JsonArray infoArr = user.createNestedArray(uiDomString); //name
infoArr.add(m_pingCount); //value
//this code adds "u":{"&#x26A1; Reconnects": m_connectedWiFi - 1} to the info object
infoArr = user.createNestedArray("&#x26A1; Reconnects"); //name
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["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)
{
if (root["PingDelay"] != nullptr)
{ {
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value m_pingDelayMs = (1000 * max(1UL, min(300UL, root["PingDelay"].as<unsigned long>())));
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); m_updateConfig = true;
}
/*
* 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_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! * 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.
*/
uint16_t getId()
{
return USERMOD_ID_FIXNETSERVICES;
}
}; };
#endif

View File

@ -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.

View File

@ -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();
}
}

View File

@ -11,19 +11,21 @@ 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 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. - 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 installing an OTA update**. **I recommend to deactivate the sensor before an OTA update and activate it again afterwards**.
- the remaining time of the off timer. - the remaining time of the off timer.
## JSON API ## JSON API
The usermod supports the following state changes: The usermod supports the following state changes:
| JSON key | Value range | Description | | JSON key | Value range | Description |
|------------|-------------|---------------------------------| |------------|-------------|---------------------------------|
| PIRenabled | bool | Deactivdate/activate the sensor | | PIRenabled | bool | Deactivdate/activate the sensor |
| PIRoffSec | 60 to 43200 | Off timer seconds | | PIRoffSec | 60 to 43200 | Off timer seconds |
Changes also persist after a reboot.
## Sensor connection ## Sensor connection
My setup uses an HC-SR501 sensor, a HC-SR505 should also work. My setup uses an HC-SR501 sensor, a HC-SR505 should also work.
@ -55,7 +57,7 @@ Example **usermods_list.cpp**:
//#include "usermod_v2_example.h" //#include "usermod_v2_example.h"
//#include "usermod_temperature.h" //#include "usermod_temperature.h"
//#include "usermod_v2_empty.h" //#include "usermod_v2_empty.h"
#include "usermod_PIR_sensor_switch.h" #include "usermod_PIR_sensor_switch.h"
void registerUsermods() void registerUsermods()
{ {
@ -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. To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
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:
`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: or
`m_PIRenabled = true;`
2. Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it.
- To the on button:
`m_PIRenabled = false;` **Example usermod.h :**
```cpp
5. Edit line 40, on `usermod_PIR_sensor_switch.h` in the `wled00` directory: #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 Have fun - @gegu

View File

@ -24,228 +24,343 @@
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp * 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
private: {
// PIR sensor pin public:
const uint8_t PIRsensorPin = 13; // D7 on D1 mini /**
// notification mode for colorUpdated() * constructor
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE */
// delay before switch off after the sensor state goes LOW PIRsensorSwitch()
uint32_t m_switchOffDelay = 600000; {
// off timer start time // set static instance pointer
uint32_t m_offTimerStart = 0; PIRsensorSwitchInstance(this);
// current PIR sensor pin state }
byte m_PIRsensorPinState = LOW; /**
// PIR sensor enabled - ISR attached * desctructor
bool m_PIRenabled = true; */
~PIRsensorSwitch()
{
PIRsensorSwitchInstance(nullptr, true);
;
}
/* /**
* return or change if new PIR sensor state is available * return the instance pointer of the class
*/ */
static volatile bool newPIRsensorState(bool changeState = false, bool newState = false) { static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); }
static volatile bool s_PIRsensorState = false;
if (changeState) { /**
s_PIRsensorState = newState; * 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
// notification mode for colorUpdated()
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
// delay before switch off after the sensor state goes LOW
uint32_t m_switchOffDelay = 600000;
// off timer start time
uint32_t m_offTimerStart = 0;
// current PIR sensor pin state
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);
/**
* PIR sensor state has changed
*/
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)
{
bri = briLast;
colorUpdated(NotifyUpdateMode);
}
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())
{
m_PIRsensorPinState = digitalRead(PIRsensorPin);
if (m_PIRsensorPinState == HIGH)
{
m_offTimerStart = 0;
switchStrip(true);
} }
return s_PIRsensorState; else if (bri != 0)
} {
// start switch off timer
/* m_offTimerStart = millis();
* PIR sensor state has changed
*/
static void IRAM_ATTR ISR_PIRstateChange() {
newPIRsensorState(true, true);
}
/*
* switch strip on/off
*/
void switchStrip(bool switchOn) {
if (switchOn && bri == 0) {
bri = briLast;
colorUpdated(NotifyUpdateMode);
}
else if (!switchOn && bri != 0) {
briLast = bri;
bri = 0;
colorUpdated(NotifyUpdateMode);
} }
newPIRsensorState(true, false);
return true;
} }
return false;
}
/* /**
* Read and update PIR sensor state. * switch off the strip if the delay has elapsed
* Initilize/reset switch off timer */
*/ bool handleOffTimer()
bool updatePIRsensorState() { {
if (newPIRsensorState()) { if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
m_PIRsensorPinState = digitalRead(PIRsensorPin); {
if (m_PIRenabled == true)
if (m_PIRsensorPinState == HIGH) { {
m_offTimerStart = 0; switchStrip(false);
switchStrip(true);
}
else if (bri != 0) {
// start switch off timer
m_offTimerStart = millis();
}
newPIRsensorState(true, false);
return true;
} }
return false; m_offTimerStart = 0;
return true;
} }
return false;
}
/* public:
* switch off the strip if the delay has elapsed //Functions called by WLED
*/
bool handleOffTimer() {
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) {
if (m_PIRenabled == true){
switchStrip(false);
}
m_offTimerStart = 0;
return true;
}
return 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.
/* */
* setup() is called once at boot. WiFi is not yet connected at this point. void setup()
* You can use it to initialize variables, sensors or similar. {
*/ // PIR Sensor mode INPUT_PULLUP
void setup() { pinMode(PIRsensorPin, INPUT_PULLUP);
// PIR Sensor mode INPUT_PULLUP if (m_PIRenabled)
pinMode(PIRsensorPin, INPUT_PULLUP); {
// assign interrupt function and set CHANGE mode // assign interrupt function and set CHANGE mode
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
} }
}
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
}
/* /**
* connected() is called every time the WiFi is (re)connected * loop() is called continuously. Here you can check for events, read sensors, etc.
* Use it to initialize network interfaces */
*/ void loop()
void connected() { {
if (!updatePIRsensorState())
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
if (!updatePIRsensorState()) {
handleOffTimer();
}
}
/*
* 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
*/
void addToJsonInfo(JsonObject& root)
{ {
//this code adds "u":{"&#x23F2; PIR sensor state":uiDomString} to the info object handleOffTimer();
// the value contains a button to toggle the sensor enabled/disabled if (m_updateConfig)
JsonObject user = root["u"]; {
if (user.isNull()) user = root.createNestedObject("u"); serializeConfig();
m_updateConfig = false;
JsonArray infoArr = user.createNestedArray("&#x23F2; PIR sensor state"); //name
String uiDomString = "<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:";
String sensorStateInfo;
// PIR sensor state
if (m_PIRenabled) {
uiDomString += "false";
sensorStateInfo = (m_PIRsensorPinState != LOW ? "active" : "inactive"); //value
} else {
uiDomString += "true";
sensorStateInfo = "Disabled !";
}
uiDomString += "});return false;\">";
uiDomString += sensorStateInfo;
uiDomString += "</button>";
infoArr.add(uiDomString); //value
//this code adds "u":{"&#x23F2; switch off timer":uiDomString} to the info object
infoArr = user.createNestedArray("&#x23F2; switch off timer"); //name
// off timer
if (m_offTimerStart > 0) {
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600) {
uiDomString += (offSeconds / 3600);
uiDomString += " hours ";
offSeconds %= 3600;
}
if (offSeconds >= 60) {
uiDomString += (offSeconds / 60);
offSeconds %= 60;
} else if (uiDomString.length() > 0){
uiDomString += 0;
}
if (uiDomString.length() > 0){
uiDomString += " min ";
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec");
} else {
infoArr.add("inactive");
} }
} }
}
/**
* 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
*/
void addToJsonInfo(JsonObject &root)
{
//this code adds "u":{"&#x23F2; 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");
/* JsonArray infoArr = user.createNestedArray("&#x23F2; PIR sensor state"); //name
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). String uiDomString = "<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:";
* Values in the state object may be modified by connected clients String sensorStateInfo;
* Add "PIRenabled" to json state. This can be used to disable/enable the sensor.
* Add "PIRoffSec" to json state. This can be used to adjust <m_switchOffDelay> milliseconds . // PIR sensor state
*/ if (m_PIRenabled)
void addToJsonState(JsonObject& root)
{ {
root["PIRenabled"] = m_PIRenabled; uiDomString += "false";
root["PIRoffSec"] = (m_switchOffDelay / 1000); sensorStateInfo = (m_PIRsensorPinState != LOW ? "active" : "inactive"); //value
} }
else
/*
* 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.
* Read "PIRoffSec" from json state and adjust <m_switchOffDelay> milliseconds .
*/
void readFromJsonState(JsonObject& root)
{ {
if (root["PIRoffSec"] != nullptr) { uiDomString += "true";
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as<unsigned long>()))); sensorStateInfo = "Disabled !";
}
if (root["PIRenabled"] != nullptr) {
if (root["PIRenabled"] && !m_PIRenabled) {
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true);
}
else if(m_PIRenabled) {
detachInterrupt(PIRsensorPin);
}
m_PIRenabled = root["PIRenabled"];
}
} }
uiDomString += "});return false;\">";
uiDomString += sensorStateInfo;
/* uiDomString += "</button>";
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). infoArr.add(uiDomString); //value
* This could be used in the future for the system to determine whether your usermod is installed.
*/ //this code adds "u":{"&#x23F2; switch off timer":uiDomString} to the info object
uint16_t getId() uiDomString = "&#x23F2; 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)
{ {
return USERMOD_ID_PIRSWITCH; uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600)
{
uiDomString += (offSeconds / 3600);
uiDomString += " hours ";
offSeconds %= 3600;
}
if (offSeconds >= 60)
{
uiDomString += (offSeconds / 60);
offSeconds %= 60;
}
else if (uiDomString.length() > 0)
{
uiDomString += 0;
}
if (uiDomString.length() > 0)
{
uiDomString += " min ";
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec");
}
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.
* Add "PIRoffSec" to json state. This can be used to adjust <m_switchOffDelay> milliseconds.
*/
void addToJsonState(JsonObject &root)
{
root["PIRenabled"] = m_PIRenabled;
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.
* Read "PIRoffSec" from json state and adjust <m_switchOffDelay> milliseconds.
*/
void readFromJsonState(JsonObject &root)
{
if (root["PIRoffSec"] != nullptr)
{
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as<unsigned long>())));
m_updateConfig = true;
} }
//More methods can be added in the future, this example will then be extended. if (root["PIRenabled"] != nullptr)
//Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! {
if (root["PIRenabled"] && !m_PIRenabled)
{
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true);
}
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.
*/
uint16_t getId()
{
return USERMOD_ID_PIRSWITCH;
}
}; };
//////////////////////////////////////////////////////
// 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;
}

View 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;
}
};

View File

@ -149,7 +149,6 @@ public:
void Show() void Show()
{ {
byte b;
switch (_type) switch (_type)
{ {
case NeoPixelType_Grb: 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) void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c)
{ {
// figure out which strip this pixel index is on // figure out which strip this pixel index is on

View File

@ -25,6 +25,7 @@
*/ */
#include "FX.h" #include "FX.h"
#include "tv_colors.h"
#define IBN 5100 #define IBN 5100
#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) #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 * 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.allocateData(SEGLEN)) return mode_static(); //allocation failed
if(SEGENV.call == 0) { if(SEGENV.call == 0) {
@ -252,12 +253,31 @@ uint16_t WS2812FX::mode_dynamic(void) {
SEGENV.step = it; SEGENV.step = it;
} }
for (uint16_t i = 0; i < SEGLEN; i++) { if (smooth) {
setPixelColor(i, color_wheel(SEGENV.data[i])); 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; 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. * Does the "standby-breathing" of well known i-Devices.
@ -990,6 +1010,12 @@ uint16_t WS2812FX::mode_merry_christmas(void) {
return running(RED, GREEN); 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. * Alternating orange/purple pixels running.
@ -1754,19 +1780,22 @@ uint16_t WS2812FX::mode_fire_2012()
if (it != SEGENV.step) 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 // Step 1. Cool down every cell a little
for (uint16_t i = 0; i < SEGLEN; i++) { 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 // Step 2. Heat from each cell drifts 'up' and diffuses a little
for (uint16_t k= SEGLEN -1; k > 1; k--) { 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 // Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() <= SEGMENT.intensity) { 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)); if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255));
} }
SEGENV.step = it; SEGENV.step = it;
@ -3727,3 +3756,117 @@ uint16_t WS2812FX::mode_washing_machine(void) {
return FRAMETIME; 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
}

View File

@ -52,6 +52,9 @@
#define MAX(a,b) ((a)>(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b))
#endif #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 */ /* Not used in all effects yet */
#define WLED_FPS 42 #define WLED_FPS 42
#define FRAMETIME (1000/WLED_FPS) #define FRAMETIME (1000/WLED_FPS)
@ -116,7 +119,8 @@
#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) #define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE )
#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) #define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED )
#define MODE_COUNT 114
#define MODE_COUNT 118
#define FX_MODE_STATIC 0 #define FX_MODE_STATIC 0
#define FX_MODE_BLINK 1 #define FX_MODE_BLINK 1
@ -232,6 +236,10 @@
#define FX_MODE_CHUNCHUN 111 #define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113 #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 { class WS2812FX {
typedef uint16_t (WS2812FX::*mode_ptr)(void); typedef uint16_t (WS2812FX::*mode_ptr)(void);
@ -316,9 +324,31 @@ class WS2812FX {
WS2812FX::_usedSegmentData -= _dataLen; WS2812FX::_usedSegmentData -= _dataLen;
_dataLen = 0; _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: private:
uint16_t _dataLen = 0; uint16_t _dataLen = 0;
bool _requiresReset = false;
} segment_runtime; } segment_runtime;
WS2812FX() { WS2812FX() {
@ -437,6 +467,10 @@ class WS2812FX {
_mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun; _mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun;
_mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows; _mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows;
_mode[FX_MODE_WASHING_MACHINE] = &WS2812FX::mode_washing_machine; _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; _brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black); currentPalette = CRGBPalette16(CRGB::Black);
@ -478,7 +512,9 @@ class WS2812FX {
gammaCorrectCol = true, gammaCorrectCol = true,
applyToAllSelected = true, applyToAllSelected = true,
segmentsAreIdentical(Segment* a, Segment* b), 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 uint8_t
mainSegment = 0, mainSegment = 0,
@ -643,7 +679,11 @@ class WS2812FX {
mode_flow(void), mode_flow(void),
mode_chunchun(void), mode_chunchun(void),
mode_dancing_shadows(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: private:
NeoPixelWrapper *bus; NeoPixelWrapper *bus;
@ -676,6 +716,7 @@ class WS2812FX {
blink(uint32_t, uint32_t, bool strobe, bool), blink(uint32_t, uint32_t, bool strobe, bool),
candle(bool), candle(bool),
color_wipe(bool, bool), color_wipe(bool, bool),
dynamic(bool),
scan(bool), scan(bool),
theater_chase(uint32_t, uint32_t, bool), theater_chase(uint32_t, uint32_t, bool),
running_base(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", "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", "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", "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"
])====="; ])=====";

View File

@ -44,6 +44,10 @@ const uint16_t customMappingTable[] = {
const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example
#endif #endif
#ifndef PWM_INDEX
#define PWM_INDEX 0
#endif
void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst) void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst)
{ {
if (supportWhite == _useRgbw && countPixels == _length && _skipFirstMode == skipFirst) return; 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++) for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++)
{ {
_segment_index = 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 (SEGMENT.isActive())
{ {
if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
@ -218,8 +227,11 @@ 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 //you can set it to 0 if the ESP is powered by USB and the LEDs by external
void WS2812FX::show(void) { void WS2812FX::show(void) {
if (_callback) _callback();
// avoid race condition, caputre _callback value
show_callback callback = _callback;
if (callback) callback();
//power limit calculation //power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA) //each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step //one PU is the power it takes to have 1 channel 1 step brighter per brightness step
@ -291,10 +303,24 @@ void WS2812FX::show(void) {
bus->SetBrightness(_brightness); 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(); bus->Show();
_lastShow = millis(); _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() { void WS2812FX::trigger() {
_triggered = true; _triggered = true;
} }
@ -378,17 +404,17 @@ void WS2812FX::setColor(uint8_t slot, uint32_t c) {
} }
void WS2812FX::setBrightness(uint8_t b) { void WS2812FX::setBrightness(uint8_t b) {
if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return; if (_brightness == b) return;
_brightness = (gammaCorrectBri) ? gamma8(b) : b; _brightness = b;
_segment_index = 0; _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++) for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{ {
_segments[i].setOption(SEG_OPTION_FREEZE, false); _segments[i].setOption(SEG_OPTION_FREEZE, false);
} }
#if LEDPIN == LED_BUILTIN #if LEDPIN == LED_BUILTIN
if (!shouldStartBus) shouldStartBus = true;
shouldStartBus = true;
#endif #endif
} else { } else {
#if LEDPIN == LED_BUILTIN #if LEDPIN == LED_BUILTIN
@ -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) 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; uint8_t paletteIndex = i;
if (mapping) paletteIndex = (i*255)/(SEGLEN -1); if (mapping) paletteIndex = (i*255)/(SEGLEN -1);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col; CRGB fastled_col;
fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); 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 //@returns `true` if color, mode, speed, intensity and palette match
@ -924,7 +961,7 @@ void WS2812FX::setRgbwPwm(void) {
_analogLastShow = nowUp; _analogLastShow = nowUp;
RgbwColor c; 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; c.R = col >> 16; c.G = col >> 8; c.B = col; c.W = col >> 24;
byte b = getBrightness(); byte b = getBrightness();

View File

@ -45,7 +45,7 @@
//This can be useful if you want to chain multiple strings with incompatible color order //This can be useful if you want to chain multiple strings with incompatible color order
//#define COLOR_ORDER_OVERRIDE //#define COLOR_ORDER_OVERRIDE
#define COO_MIN 0 #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 #define COO_ORDER COL_ORDER_GRB
//END CONFIGURATION //END CONFIGURATION
@ -296,7 +296,6 @@ public:
void Show() void Show()
{ {
byte b;
switch (_type) switch (_type)
{ {
case NeoPixelType_Grb: _pGrb->Show(); break; 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) void SetPixelColor(uint16_t indexPixel, RgbwColor c)
{ {
RgbwColor col; RgbwColor col;

View File

@ -8,12 +8,12 @@
uint16_t blHue = 0; uint16_t blHue = 0;
byte blSat = 255; byte blSat = 255;
void initBlynk(const char* auth) void initBlynk(const char *auth, const char *host, uint16_t port)
{ {
#ifndef WLED_DISABLE_BLYNK #ifndef WLED_DISABLE_BLYNK
if (!WLED_CONNECTED) return; if (!WLED_CONNECTED) return;
blynkEnabled = (auth[0] != 0); blynkEnabled = (auth[0] != 0);
if (blynkEnabled) Blynk.config(auth); if (blynkEnabled) Blynk.config(auth, host, port);
#endif #endif
} }

View File

@ -91,6 +91,7 @@ void deserializeConfig() {
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
CJSON(strip.reverseMode, hw_led[F("rev")]); CJSON(strip.reverseMode, hw_led[F("rev")]);
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
JsonObject hw_led_ins_0 = hw_led[F("ins")][0]; JsonObject hw_led_ins_0 = hw_led[F("ins")][0];
//bool hw_led_ins_0_en = hw_led_ins_0[F("en")]; // true //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(bootPreset, def[F("ps")]);
CJSON(turnOnAtBoot, def["on"]); // true CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128 CJSON(briS, def["bri"]); // 128
if (briS == 0) briS = 255;
JsonObject def_cy = def[F("cy")]; JsonObject def_cy = def[F("cy")];
CJSON(presetCyclingEnabled, def_cy["on"]); CJSON(presetCyclingEnabled, def_cy["on"]);
@ -162,7 +162,7 @@ void deserializeConfig() {
CJSON(presetCycleMax, def_cy[F("range")][1]); CJSON(presetCycleMax, def_cy[F("range")][1]);
tdd = def_cy[F("dur")] | -1; tdd = def_cy[F("dur")] | -1;
if (tdd >= 0) presetCycleTime = tdd * 100; if (tdd > 0) presetCycleTime = tdd;
JsonObject interfaces = doc["if"]; JsonObject interfaces = doc["if"];
@ -212,6 +212,10 @@ void deserializeConfig() {
if (tdd > 20 || tdd == 0) if (tdd > 20 || tdd == 0)
getStringFromJson(blynkApiKey, apikey, 36); //normally not present due to security 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")]; JsonObject if_mqtt = interfaces[F("mqtt")];
CJSON(mqttEnabled, if_mqtt[F("en")]); CJSON(mqttEnabled, if_mqtt[F("en")]);
getStringFromJson(mqttServer, if_mqtt[F("broker")], 33); getStringFromJson(mqttServer, if_mqtt[F("broker")], 33);
@ -221,7 +225,7 @@ void deserializeConfig() {
getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41); getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41);
getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test" getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test"
getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // "" getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // ""
JsonObject if_hue = interfaces[F("hue")]; JsonObject if_hue = interfaces[F("hue")];
CJSON(huePollingEnabled, if_hue[F("en")]); CJSON(huePollingEnabled, if_hue[F("en")]);
@ -251,7 +255,11 @@ void deserializeConfig() {
CJSON(countdownMode, ol[F("cntdwn")]); CJSON(countdownMode, ol[F("cntdwn")]);
overlayCurrent = overlayDefault; 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 //timed macro rules
JsonObject tm = doc[F("timers")]; JsonObject tm = doc[F("timers")];
@ -274,11 +282,13 @@ void deserializeConfig() {
CJSON(timerMacro[it], timer[F("macro")]); CJSON(timerMacro[it], timer[F("macro")]);
byte dowPrev = timerWeekday[it]; 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")]); CJSON(timerWeekday[it], timer[F("dow")]);
if (timerWeekday[it] != dowPrev) { //present in JSON if (timerWeekday[it] != dowPrev) { //present in JSON
timerWeekday[it] <<= 1; //add active bit timerWeekday[it] <<= 1; //add active bit
bool act = timer[F("en")] | actPrev; int act = timer[F("en")] | actPrev;
if (act) timerWeekday[it]++; if (act) timerWeekday[it]++;
} }
@ -305,11 +315,11 @@ void deserializeConfig() {
CJSON(DMXStart, dmx[F("start")]); CJSON(DMXStart, dmx[F("start")]);
CJSON(DMXStartLED,dmx[F("start-led")]); CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx.createNestedArray("fixmap"); JsonArray dmx_fixmap = dmx[F("fixmap")];
it = 0; it = 0;
for (int i : dmx_fixmap) { for (int i : dmx_fixmap) {
if (it > 14) break; if (it > 14) break;
DMXFixtureMap[i] = i; CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
it++; it++;
} }
#endif #endif
@ -359,6 +369,7 @@ void serializeConfig() {
ap[F("ssid")] = apSSID; ap[F("ssid")] = apSSID;
ap[F("pskl")] = strlen(apPass); ap[F("pskl")] = strlen(apPass);
ap[F("chan")] = apChannel; ap[F("chan")] = apChannel;
ap[F("hide")] = apHide;
ap[F("behav")] = apBehavior; ap[F("behav")] = apBehavior;
JsonArray ap_ip = ap.createNestedArray("ip"); JsonArray ap_ip = ap.createNestedArray("ip");
@ -378,6 +389,7 @@ void serializeConfig() {
hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed; hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led[F("rev")] = strip.reverseMode; hw_led[F("rev")] = strip.reverseMode;
hw_led[F("rgbwm")] = strip.rgbwMode;
JsonArray hw_led_ins = hw_led.createNestedArray("ins"); JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@ -478,7 +490,7 @@ void serializeConfig() {
JsonArray def_cy_range = def_cy.createNestedArray("range"); JsonArray def_cy_range = def_cy.createNestedArray("range");
def_cy_range.add(presetCycleMin); def_cy_range.add(presetCycleMin);
def_cy_range.add(presetCycleMax); def_cy_range.add(presetCycleMax);
def_cy[F("dur")] = presetCycleTime / 100; def_cy[F("dur")] = presetCycleTime;
} }
JsonObject interfaces = doc.createNestedObject("if"); JsonObject interfaces = doc.createNestedObject("if");
@ -523,6 +535,8 @@ void serializeConfig() {
if_va_macros.add(macroAlexaOff); if_va_macros.add(macroAlexaOff);
JsonObject if_blynk = interfaces.createNestedObject("blynk"); JsonObject if_blynk = interfaces.createNestedObject("blynk");
if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":""; if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":"";
if_blynk[F("host")] = blynkHost;
if_blynk[F("port")] = blynkPort;
JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); JsonObject if_mqtt = interfaces.createNestedObject("mqtt");
if_mqtt[F("en")] = mqttEnabled; if_mqtt[F("en")] = mqttEnabled;
@ -562,6 +576,12 @@ void serializeConfig() {
ol[F("clock")] = overlayDefault; ol[F("clock")] = overlayDefault;
ol[F("cntdwn")] = countdownMode; 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 timers = doc.createNestedObject("timers");
JsonObject cntdwn = timers.createNestedObject("cntdwn"); JsonObject cntdwn = timers.createNestedObject("cntdwn");
@ -677,4 +697,4 @@ void serializeConfigSec() {
File f = WLED_FS.open("/wsec.json", "w"); File f = WLED_FS.open("/wsec.json", "w");
if (f) serializeJson(doc, f); if (f) serializeJson(doc, f);
f.close(); f.close();
} }

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

File diff suppressed because it is too large Load Diff

7
wled00/data/iro.js Normal file

File diff suppressed because one or more lines are too long

View 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

View File

@ -1,12 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"> <head><meta charset="UTF-8">
<title>WLED Settings</title> <title>WLED Settings</title>
<style> <style>
body { body {
text-align: center; text-align: center;
background: #222; background: #222;
height: 100; height: 100px;
margin: 0; margin: 0;
} }
html { html {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!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> <script>
function GCH(num) { function GCH(num) {
d=document; d=document;

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!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; <script>var d=document;
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");} 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;} 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> 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> 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 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 Alexa notifications: <input type="checkbox" name="SA"><br>
Send Philips Hue change notifications: <input type="checkbox" name="SH"><br> Send Philips Hue change notifications: <input type="checkbox" name="SH"><br>
Send Macro notifications: <input type="checkbox" name="SM"><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> This may impact the responsiveness of the ESP8266.</b><br>
For best results, only use one of these services at a time.<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> (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> 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> <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> <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> <b>The MQTT credentials are sent over an unsecured connection.<br>
Never use the MQTT password for another service!</b><br> Never use the MQTT password for another service!</b><br>
Username: <input name="MQUSER" maxlength="40"><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> Client ID: <input name="MQCID" maxlength="40"><br>
Device Topic: <input name="MD" maxlength="32"><br> Device Topic: <input name="MD" maxlength="32"><br>
Group Topic: <input name="MG" maxlength="32"><br> Group Topic: <input name="MG" maxlength="32"><br>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head lang="en">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<title>UI Settings</title> <title>UI Settings</title>
@ -18,7 +18,8 @@
"quick": "Quick color selectors", "quick": "Quick color selectors",
"hex": "HEX color input" "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":{ "theme":{
"alpha": { "alpha": {
@ -26,7 +27,8 @@
"tab":"Button opacity" "tab":"Button opacity"
}, },
"bg":{ "bg":{
"url":"BG image URL" "url":"BG image URL",
"random":"Random BG image"
}, },
"color":{ "color":{
"bg":"BG HEX color" "bg":"BG HEX color"
@ -162,6 +164,24 @@
var f = gId('theme_base'); var f = gId('theme_base');
if (f) f.value = (gId('dm').checked) ? 'light':'dark'; 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() function GetV()
{ {
} }
@ -189,13 +209,15 @@
<h3>UI Appearance</h3> <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_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_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> I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br>
<span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br></span> <span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<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_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 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"></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"> <input id="theme_base" class="agi" style="display:none">
<hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button> <hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button>
</form> </form>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
@ -51,7 +51,7 @@
<h3>Configure Access Point</h3> <h3>Configure Access Point</h3>
AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br> AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br>
Hide AP name: <input type="checkbox" name="AH"><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> Access Point WiFi channel: <input name="AC" type="number" min="1" max="13" required><br>
AP opens: AP opens:
<select name="AB"> <select name="AB">

View File

@ -4,40 +4,49 @@
<head> <head>
<meta content='width=device-width' name='viewport'> <meta content='width=device-width' name='viewport'>
<title>WLED Update</title> <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> <style>
.bt { .bt {
background: #333; background: #333;
color: #fff; color: #fff;
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: .3ch solid #333; border: .3ch solid #333;
display: inline-block; display: inline-block;
font-size: 20px; font-size: 20px;
margin: 8px; margin: 8px;
margin-top: 12px margin-top: 12px
} }
input[type=file] { input[type=file] {
font-size: 16px font-size: 16px
} }
body { body {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
text-align: center; text-align: center;
background: #222; background: #222;
color: #fff; color: #fff;
line-height: 200% line-height: 200%
} }
#msg {
display: none;
}
</style> </style>
</head> </head>
<body> <body>
<h2>WLED Software Update</h2>Installed version: ##VERSION##<br>Download the latest binary: <a <h2>WLED Software Update</h2>
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img <form method='POST' action='/update' id='uf' enctype='multipart/form-data' onsubmit="U()">
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br> Installed version: ##VERSION##<br>
<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' class="bt" name='update' Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank">
required><br><input type='submit' class="bt" value='Update!'></form><button type="button" class="bt" <img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
onclick="B()">Back</button> <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> </body>
</html> </html>

View File

@ -15,7 +15,7 @@ void handleAlexa();
void onAlexaChange(EspalexaDevice* dev); void onAlexaChange(EspalexaDevice* dev);
//blynk.cpp //blynk.cpp
void initBlynk(const char* auth); void initBlynk(const char* auth, const char* host, uint16_t port);
void handleBlynk(); void handleBlynk();
void updateBlynk(); void updateBlynk();
@ -240,6 +240,7 @@ void userLoop();
void applyMacro(byte index); void applyMacro(byte index);
void deEEP(); void deEEP();
void deEEPSettings(); void deEEPSettings();
void clearEEPROM();
//wled_serial.cpp //wled_serial.cpp
void handleSerial(); void handleSerial();

View File

@ -36,17 +36,19 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
// Autogenerated from wled00/data/update.htm, do not edit!! // 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"> 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> <title>WLED Update</title><script>
<style> function B(){window.history.back()}function U(){document.getElementById("uf").style.display="none",document.getElementById("msg").style.display="block"}
.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%} </script><style>
</style></head><body><h2>WLED Software Update</h2>Installed version: 0.11.0<br> .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}
Download the latest binary: <a </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 href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"> src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><form method="POST" action="/update" enctype="multipart/form-data"> </a><br><input type="file" class="bt" name="update" accept=".bin" required><br>
<input type="file" class="bt" name="update" required><br><input type="submit" <input type="submit" class="bt" value="Update!"><br><button type="button"
class="bt" value="Update!"></form><button type="button" class="bt" class="bt" onclick="B()">Back</button></form><div id="msg"><b>Updating...</b>
onclick="B()">Back</button></body></html>)====="; <br>Please do not close or refresh the page :)</div></body></html>)=====";
// Autogenerated from wled00/data/welcome.htm, do not edit!! // Autogenerated from wled00/data/welcome.htm, do not edit!!

View File

@ -10,9 +10,9 @@ const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana,
// Autogenerated from wled00/data/settings.htm, do not edit!! // Autogenerated from wled00/data/settings.htm, do not edit!!
const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta http-equiv="Content-Type" const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>WLED Settings
content="text/html; charset=windows-1252"><title>WLED Settings</title><style> </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} 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> </style><script>
function BB(){window.frameElement&&(document.getElementById("b").style.display="none",document.documentElement.style.setProperty("--h","13.86vh"))} 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" </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!! // 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" const char PAGE_settings_wifi[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta
content="width=500"><title>WiFi Settings</title><script> 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; 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()"> %CSS%%SCSS%</head><body onload="GetV()">
<form id="form_s" name="Sf" method="post"><div class="helpB"><button <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 <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> 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" AP password (leave empty for open):<br><input type="password" name="AP"
maxlength="63"><br>Access Point WiFi channel: <input name="AC" type="number" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
min="1" max="13" required><br>AP opens: <select name="AB"><option value="0"> 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 No connection after boot</option><option value="1">Disconnected</option><option
value="2">Always</option><option value="3">Never (not recommended)</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> </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!! // 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" const char PAGE_settings_leds[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta
content="width=500"><title>LED Settings</title><script> 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; 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 %CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post"><div class="helpB"><button type="button" 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 #ifdef WLED_ENABLE_DMX
// Autogenerated from wled00/data/settings_dmx.htm, do not edit!! // 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 const char PAGE_settings_dmx[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500">
charset="utf-8"><title>DMX Settings</title><script> <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; 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 %CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post"><div class="helpB"><button type="button" 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 #endif
// Autogenerated from wled00/data/settings_ui.htm, do not edit!! // 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" const char PAGE_settings_ui[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
content="width=500"><title>UI Settings</title><script> 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="&#9888; 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="&#9888; 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; 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="&#9888; 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="&#9888; 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 %CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post"><div id="form_s" name="Sf" method="post"><div
style="position:sticky;top:0;background-color:#222"><div class="helpB"><button 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"> <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 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>: 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="comp_pcmbot" class="agi cb"><br><span class="l">
<input type="checkbox" id="dm" onchange="UI()"><br><span id="idonthateyou" </span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
style="display:none"><i>Why would you? </i>&#x1F97A;<br></span><span class="l"> I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br><span
</span>: <input type="number" min="0.0" max="1.0" step="0.01" id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br>
id="theme_alpha_tab" class="agi"><br><span class="l"></span>: <input </span><span class="l"></span>: <input type="number" min="0.0" max="1.0"
type="number" min="0.0" max="1.0" step="0.01" id="theme_alpha_bg" class="agi"> 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" <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"><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 class="agi" oninput="checkRandomBg()"><br><span class="l">Random BG image</span>
type="button" onclick="B()">Back</button><button type="button" : <input type="checkbox" id="theme_bg_random" class="agi cb"
onclick="Save()">Save</button></form></body></html>)====="; 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!! // 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 const char PAGE_settings_sync[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500">
charset="utf-8"><title>Sync Settings</title><script> <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() { 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 %CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post"><div class="helpB"><button type="button" 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"> type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">
Color, and <input type="checkbox" name="RX">Effects<br> Color, and <input type="checkbox" name="RX">Effects<br>
Send notifications on direct change: <input type="checkbox" name="SD"><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 Alexa notifications: <input type="checkbox" name="SA"><br>
Send Philips Hue change notifications: <input type="checkbox" name="SH"><br> Send Philips Hue change notifications: <input type="checkbox" name="SH"><br>
Send Macro notifications: <input type="checkbox" name="SM"><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> Blynk, MQTT and Hue sync all connect to external hosts!<br>
This may impact the responsiveness of the ESP8266.</b><br> This may impact the responsiveness of the ESP8266.</b><br>
For best results, only use one of these services at a time.<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> (alternatively, connect a second ESP to them and use the UDP sync)<br><br>Host:
Device Auth token: <input name="BK" maxlength="33"><br><i> <input name="BH" maxlength="32"> Port: <input name="BP" type="number" min="1"
Clear the token field to disable. </i><a 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 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: </a><h3>MQTT</h3>Enable MQTT: <input type="checkbox" name="MQ"><br>Broker:
<input name="MS" maxlength="32"> Port: <input name="MQPORT" type="number" <input name="MS" maxlength="32"> Port: <input name="MQPORT" type="number"
min="1" max="65535" class="d5"><br><b> min="1" max="65535" class="d5"><br><b>
The MQTT credentials are sent over an unsecured connection.<br> The MQTT credentials are sent over an unsecured connection.<br>
Never use the MQTT password for another service!</b><br>Username: <input Never use the MQTT password for another service!</b><br>Username: <input
name="MQUSER" maxlength="40"><br>Password: <input type="password" input name="MQUSER" maxlength="40"><br>Password: <input type="password" name="MQPASS"
name="MQPASS" maxlength="40"><br>Client ID: <input name="MQCID" maxlength="40"> maxlength="40"><br>Client ID: <input name="MQCID" maxlength="40"><br>
<br>Device Topic: <input name="MD" maxlength="32"><br>Group Topic: <input Device Topic: <input name="MD" maxlength="32"><br>Group Topic: <input name="MG"
name="MG" maxlength="32"><br><i>Reboot required to apply changes. </i><a maxlength="32"><br><i>Reboot required to apply changes. </i><a
href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info
</a><h3>Philips Hue</h3><i> </a><h3>Philips Hue</h3><i>
You can find the bridge IP and the light number in the 'About' section of the hue app. 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!! // 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 const char PAGE_settings_time[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500">
charset="utf-8"><title>Time Settings</title><script> <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() { 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 %CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post" onsubmit="Wd()"><div class="helpB"><button 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!! // 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 const char PAGE_settings_sec[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=500">
charset="utf-8"><title>Misc Settings</title><script> <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; 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()"> %CSS%%SCSS%</head><body onload="GetV()">
<form id="form_s" name="Sf" method="post"><div class="helpB"><button <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 <h3>Software Update</h3><button type="button" onclick="U()">Manual OTA Update
</button><br>Enable ArduinoOTA: <input type="checkbox" name="AO"><br><h3>About </button><br>Enable ArduinoOTA: <input type="checkbox" name="AO"><br><h3>About
</h3><a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> </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" href="https://github.com/Aircoookie/WLED/wiki/Contributors-&-About"
target="_blank">Contributors, dependencies and special thanks</a><br> target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br> A huge thank you to everyone who helped me create WLED!<br><br>

File diff suppressed because it is too large Load Diff

View File

@ -94,10 +94,22 @@ void colorUpdated(int callMode)
if (callMode != NOTIFIER_CALL_MODE_INIT && if (callMode != NOTIFIER_CALL_MODE_INIT &&
callMode != NOTIFIER_CALL_MODE_DIRECT_CHANGE && callMode != NOTIFIER_CALL_MODE_DIRECT_CHANGE &&
callMode != NOTIFIER_CALL_MODE_NO_NOTIFY) strip.applyToAllSelected = true; //if not from JSON api, which directly sets segments 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 fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
bool colChanged = colorChanged(); bool colChanged = colorChanged();
//Notifier: apply received color to selected segments only if actually receiving color
if (someSel) strip.applyToAllSelected = receiveNotificationColor;
if (fxChanged || colChanged) if (fxChanged || colChanged)
{ {
if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0; if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0;
@ -107,7 +119,7 @@ void colorUpdated(int callMode)
notify(callMode); notify(callMode);
//set flag to update blynk and mqtt //set flag to update blynk and mqtt
if (callMode != NOTIFIER_CALL_MODE_PRESET_CYCLE) interfaceUpdateCallMode = callMode; interfaceUpdateCallMode = callMode;
} else { } else {
if (nightlightActive && !nightlightActiveOld && if (nightlightActive && !nightlightActiveOld &&
callMode != NOTIFIER_CALL_MODE_NOTIFICATION && callMode != NOTIFIER_CALL_MODE_NOTIFICATION &&
@ -303,10 +315,10 @@ void handleNightlight()
if (bri == 0 || nightlightActive) return; if (bri == 0 || nightlightActive) return;
if (presetCycCurr < presetCycleMin || presetCycCurr > presetCycleMax) presetCycCurr = presetCycleMin; if (presetCycCurr < presetCycleMin || presetCycCurr > presetCycleMax) presetCycCurr = presetCycleMin;
applyPreset(presetCycCurr); applyPreset(presetCycCurr); //this handles colorUpdated() for us
presetCycCurr++; presetCycCurr++;
if (presetCycCurr > 250) presetCycCurr = 1; if (presetCycCurr > 250) presetCycCurr = 1;
colorUpdated(NOTIFIER_CALL_MODE_PRESET_CYCLE); interfaceUpdateCallMode = 0; //disable updates to MQTT and Blynk
} }
} }

View File

@ -163,8 +163,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
alexaEnabled = request->hasArg(F("AL")); alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33); 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"))) { 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 #ifdef WLED_ENABLE_MQTT
@ -266,6 +270,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (request->hasArg(F("RS"))) //complete factory reset if (request->hasArg(F("RS"))) //complete factory reset
{ {
WLED_FS.format(); WLED_FS.format();
clearEEPROM();
serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255); serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255);
doReboot = true; doReboot = true;
} }

View File

@ -10,7 +10,7 @@
*/ */
/* /*
* @title Espalexa library * @title Espalexa library
* @version 2.4.6 * @version 2.5.0
* @author Christian Schwinne * @author Christian Schwinne
* @license MIT * @license MIT
* @contributors d-999 * @contributors d-999
@ -50,7 +50,7 @@
#include "../network/Network.h" #include "../network/Network.h"
#ifdef ESPALEXA_DEBUG #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_DEBUG(x) Serial.print (x)
#define EA_DEBUGLN(x) Serial.println (x) #define EA_DEBUGLN(x) Serial.println (x)
#else #else
@ -60,6 +60,7 @@
#include "EspalexaDevice.h" #include "EspalexaDevice.h"
#define DEVICE_UNIQUE_ID_LENGTH 12
class Espalexa { class Espalexa {
private: private:
@ -116,17 +117,24 @@ private:
return ""; return "";
} }
//Workaround functions courtesy of Sonoff-Tasmota void encodeLightId(uint8_t idx, char* out)
uint32_t encodeLightId(uint8_t idx)
{ {
//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]; uint8_t mac[6];
WiFi.macAddress(mac); 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) { //shift the mac address to the left (discard first byte)
return id & 0xF; 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) //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 if (deviceId >= currentDeviceCount) {strcpy(buf,"{}"); return;} //error
EspalexaDevice* dev = devices[deviceId]; EspalexaDevice* dev = devices[deviceId];
//char buf_bri[12] = ""; char buf_lightid[13];
//brightness support, add "bri" to JSON encodeLightId(deviceId + 1, buf_lightid);
//if (dev->getType() != EspalexaDeviceType::onoff)
// sprintf(buf_bri,",\"bri\":%u", dev->getLastValue()-1);
char buf_col[80] = ""; char buf_col[80] = "";
//color support //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}," 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" "\"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->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 //Espalexa status page /espalexa
@ -186,7 +192,7 @@ private:
} }
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap(); res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
res += "\r\nUptime: " + (String)millis(); 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); server->send(200, "text/plain", res);
} }
#endif #endif
@ -222,7 +228,7 @@ private:
"<URLBase>http://%s:80/</URLBase>" "<URLBase>http://%s:80/</URLBase>"
"<device>" "<device>"
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>" "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
"<friendlyName>Espalexa (%s)</friendlyName>" "<friendlyName>Espalexa (%s:80)</friendlyName>"
"<manufacturer>Royal Philips Electronics</manufacturer>" "<manufacturer>Royal Philips Electronics</manufacturer>"
"<manufacturerURL>http://www.philips.com</manufacturerURL>" "<manufacturerURL>http://www.philips.com</manufacturerURL>"
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>" "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
@ -237,8 +243,8 @@ private:
server->send(200, "text/xml", buf); server->send(200, "text/xml", buf);
EA_DEBUG("Send setup.xml"); EA_DEBUGLN("Send setup.xml");
//EA_DEBUGLN(setup_xml); EA_DEBUGLN(buf);
} }
//init the server //init the server
@ -290,7 +296,7 @@ private:
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
char buf[1024]; char buf[1024];
sprintf_P(buf,PSTR("HTTP/1.1 200 OK\r\n" sprintf_P(buf,PSTR("HTTP/1.1 200 OK\r\n"
"EXT:\r\n" "EXT:\r\n"
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL "CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
@ -298,7 +304,7 @@ private:
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
"hue-bridgeid: %s\r\n" "hue-bridgeid: %s\r\n"
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType "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()); "\r\n"),s,escapedMac.c_str(),escapedMac.c_str());
espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort()); espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort());
@ -359,24 +365,28 @@ public:
if (!udpConnected) return; if (!udpConnected) return;
int packetSize = espalexaUdp.parsePacket(); int packetSize = espalexaUdp.parsePacket();
if (!packetSize) return; //no new udp packet if (packetSize < 1) return; //no new udp packet
EA_DEBUGLN("Got UDP!"); EA_DEBUGLN("Got UDP!");
char packetBuffer[255]; //buffer to hold incoming udp packet
uint16_t len = espalexaUdp.read(packetBuffer, 254); unsigned char packetBuffer[packetSize+1]; //buffer to hold incoming udp packet
if (len > 0) { espalexaUdp.read(packetBuffer, packetSize);
packetBuffer[len] = 0; packetBuffer[packetSize] = 0;
}
espalexaUdp.flush(); espalexaUdp.flush();
if (!discoverable) return; //do not reply to M-SEARCH if not discoverable if (!discoverable) return; //do not reply to M-SEARCH if not discoverable
String request = packetBuffer; const char* request = (const char *) packetBuffer;
if(request.indexOf("M-SEARCH") >= 0) { 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) { EA_DEBUGLN(request);
EA_DEBUGLN("Responding search req..."); if (strstr(request, "ssdp:disc") != nullptr && //short for "ssdp:discover"
respondToSearch(); (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();
} }
} }
@ -452,13 +462,12 @@ public:
return true; 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}}]")); server->send(200, "application/json", F("[{\"success\":{\"/lights/1/state/\": true}}]"));
uint32_t devId = req.substring(req.indexOf("lights")+7).toInt(); uint32_t devId = req.substring(req.indexOf("lights")+7).toInt();
EA_DEBUG("ls"); EA_DEBUGLN(devId); EA_DEBUG("ls"); EA_DEBUGLN(devId);
devId = decodeLightId(devId);
EA_DEBUGLN(devId); EA_DEBUGLN(devId);
devId--; //zero-based for devices array devId--; //zero-based for devices array
if (devId >= currentDeviceCount) return true; //return if invalid ID if (devId >= currentDeviceCount) return true; //return if invalid ID
@ -530,7 +539,7 @@ public:
String jsonTemp = "{"; String jsonTemp = "{";
for (int i = 0; i<currentDeviceCount; i++) for (int i = 0; i<currentDeviceCount; i++)
{ {
jsonTemp += "\"" + String(encodeLightId(i+1)) + "\":"; jsonTemp += "\"" + String(i+1) + "\":";
char buf[512]; char buf[512];
deviceJsonString(i+1, buf); deviceJsonString(i+1, buf);
jsonTemp += buf; jsonTemp += buf;
@ -540,7 +549,6 @@ public:
server->send(200, "application/json", jsonTemp); server->send(200, "application/json", jsonTemp);
} else //client wants one light (devId) } else //client wants one light (devId)
{ {
devId = decodeLightId(devId);
EA_DEBUGLN(devId); EA_DEBUGLN(devId);
if (devId > currentDeviceCount) if (devId > currentDeviceCount)
{ {

1506
wled00/tv_colors.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,18 @@
#ifdef USERMOD_DALLASTEMPERATURE #ifdef USERMOD_DALLASTEMPERATURE
#include "../usermods/Temperature/usermod_temperature.h" #include "../usermods/Temperature/usermod_temperature.h"
#endif #endif
//#include "usermod_v2_empty.h" //#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() void registerUsermods()
{ {
/* /*
@ -23,8 +33,18 @@ void registerUsermods()
* \/ \/ \/ * \/ \/ \/
*/ */
//usermods.add(new MyExampleUsermod()); //usermods.add(new MyExampleUsermod());
#ifdef USERMOD_DALLASTEMPERATURE #ifdef USERMOD_DALLASTEMPERATURE
usermods.add(new UsermodTemperature()); usermods.add(new UsermodTemperature());
#endif #endif
//usermods.add(new UsermodRenameMe()); //usermods.add(new UsermodRenameMe());
#ifdef USERMOD_BUZZER
usermods.add(new BuzzerUsermod());
#endif
#ifdef USERMOD_BME280
usermods.add(new UsermodBME280());
#endif
} }

View File

@ -324,7 +324,8 @@ void WLED::beginStrip()
if (bootPreset > 0) applyPreset(bootPreset); if (bootPreset > 0) applyPreset(bootPreset);
if (turnOnAtBoot) { if (turnOnAtBoot) {
bri = (briS > 0) ? briS : 128; if (briS > 0) bri = briS;
else if (bri == 0) bri = 128;
} else { } else {
briLast = briS; bri = 0; briLast = briS; bri = 0;
} }
@ -377,6 +378,7 @@ void WLED::initAP(bool resetAP)
if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) {
udp2Connected = notifier2Udp.begin(udpPort2); udp2Connected = notifier2Udp.begin(udpPort2);
} }
e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(53, "*", WiFi.softAPIP()); dnsServer.start(53, "*", WiFi.softAPIP());
@ -491,7 +493,7 @@ void WLED::initInterfaces()
if (ntpEnabled) if (ntpEnabled)
ntpConnected = ntpUdp.begin(ntpLocalPort); ntpConnected = ntpUdp.begin(ntpLocalPort);
initBlynk(blynkApiKey); initBlynk(blynkApiKey, blynkHost, blynkPort);
e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
reconnectHue(); reconnectHue();
initMqtt(); initMqtt();

View File

@ -3,12 +3,12 @@
/* /*
Main sketch, global variable declarations Main sketch, global variable declarations
@title WLED project sketch @title WLED project sketch
@version 0.11.0 @version 0.11.1
@author Christian Schwinne @author Christian Schwinne
*/ */
// version code in format yymmddb (b = daily build) // 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 //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -22,10 +22,11 @@
// You are required to disable over-the-air updates: // You are required to disable over-the-air updates:
//#define WLED_DISABLE_OTA // saves 14kb //#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_ALEXA // saves 11kb
//#define WLED_DISABLE_BLYNK // saves 6kb //#define WLED_DISABLE_BLYNK // saves 6kb
//#define WLED_DISABLE_CRONIXIE // saves 3kb //#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_HUESYNC // saves 4kb
//#define WLED_DISABLE_INFRARED // there is no pin left for this on ESP8266-01, saves 12kb //#define WLED_DISABLE_INFRARED // there is no pin left for this on ESP8266-01, saves 12kb
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
@ -173,7 +174,7 @@
#endif #endif
// Global Variable definitions // Global Variable definitions
WLED_GLOBAL char versionString[] _INIT("0.11.0"); WLED_GLOBAL char versionString[] _INIT("0.11.1");
#define WLED_CODENAME "Mirai" #define WLED_CODENAME "Mirai"
// AP and OTA default passwords (for maximum security change them!) // 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 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 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 uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode
WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset

View File

@ -36,6 +36,20 @@
//21-> 0.10.1p //21-> 0.10.1p
//22-> 2009260 //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) void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len)
{ {
for (int i = 0; i < len; ++i) for (int i = 0; i < len; ++i)

View File

@ -323,13 +323,14 @@ void getSettingsJS(byte subPage, char* dest)
sappends('s',SET_F("AI"),alexaInvocationName); sappends('s',SET_F("AI"),alexaInvocationName);
sappend('c',SET_F("SA"),notifyAlexa); sappend('c',SET_F("SA"),notifyAlexa);
sappends('s',SET_F("BK"),(char*)((blynkEnabled)?SET_F("Hidden"):"")); 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 #ifdef WLED_ENABLE_MQTT
sappend('c',SET_F("MQ"),mqttEnabled); sappend('c',SET_F("MQ"),mqttEnabled);
sappends('s',SET_F("MS"),mqttServer); sappends('s',SET_F("MS"),mqttServer);
sappend('v',SET_F("MQPORT"),mqttPort); sappend('v',SET_F("MQPORT"),mqttPort);
sappends('s',SET_F("MQUSER"),mqttUser); sappends('s',SET_F("MQUSER"),mqttUser);
sappends('s',SET_F("MQPASS"),mqttPass);
byte l = strlen(mqttPass); byte l = strlen(mqttPass);
char fpass[l+1]; //fill password field with *** char fpass[l+1]; //fill password field with ***
fpass[l] = 0; fpass[l] = 0;