Compare commits

..

1 Commits

Author SHA1 Message Date
coderabbitai[bot]
8cdfd2b55d 📝 Add docstrings to main
Docstrings generation was requested by @robsonek.

* https://github.com/wled/WLED/pull/5110#issuecomment-3577498568

The following files were modified:

* `usermods/stairway_wipe_basic/stairway_wipe_basic.cpp`
2025-11-25 20:40:56 +00:00
26 changed files with 442 additions and 722 deletions

View File

@@ -1,6 +1,10 @@
name: Usermod CI name: Usermod CI
on: on:
push:
paths:
- usermods/**
- .github/workflows/usermods.yml
pull_request: pull_request:
paths: paths:
- usermods/** - usermods/**
@@ -8,8 +12,6 @@ on:
jobs: jobs:
get_usermod_envs: get_usermod_envs:
# Only run for pull requests from forks (not from branches within wled/WLED)
if: github.event.pull_request.head.repo.full_name != github.repository
name: Gather Usermods name: Gather Usermods
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -29,8 +31,6 @@ jobs:
build: build:
# Only run for pull requests from forks (not from branches within wled/WLED)
if: github.event.pull_request.head.repo.full_name != github.repository
name: Build Enviornments name: Build Enviornments
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: get_usermod_envs needs: get_usermod_envs

View File

@@ -26,28 +26,9 @@ Github will pick up the changes so your PR stays up-to-date.
> It has many subtle and unexpected consequences on our github reposistory. > It has many subtle and unexpected consequences on our github reposistory.
> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. > For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push.
> [!TIP]
> use [cherry-picking](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop) to copy commits from one branch to another.
You can find a collection of very useful tips and tricks here: https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR You can find a collection of very useful tips and tricks here: https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR
### Source Code from an AI agent or bot
> [!IMPORTANT]
> Its OK if you took help from an AI for writing your source code.
>
> However, we expect a few things from you as the person making a contribution to WLED:
* Make sure you really understand the code suggested by the AI, and don't just accept it because it "seems to work".
* Don't let the AI change existing code without double-checking by you as the contributor. Often, the result will not be complete. For example, previous source code comments may be lost.
* Remember that AI are still "Often-Wrong" ;-)
* If you don't feel very confident using English, you can use AI for translating code comments and descriptions into English. AI bots are very good at understanding language. However, always check if the results is correct. The translation might still have wrong technical terms, or errors in some details.
#### best practice with AI:
* As the person who contributes source code to WLED, make sure you understand exactly what the AI generated code does
* best practice: add a comment like ``'// below section of my code was generated by an AI``, when larger parts of your source code were not written by you personally.
* always review translations and code comments for correctness
* always review AI generated source code
* If the AI has rewritten existing code, check that the change is necessary and that nothing has been lost or broken. Also check that previous code comments are still intact.
### Code style ### Code style

View File

@@ -2,7 +2,7 @@
"build": { "build": {
"arduino":{ "arduino":{
"ldscript": "esp32s3_out.ld", "ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv" "partitions": "partitions-8MB-tinyuf2.csv"
}, },
"core": "esp32", "core": "esp32",
"extra_flags": [ "extra_flags": [
@@ -43,8 +43,16 @@
"arduino", "arduino",
"espidf" "espidf"
], ],
"name": "Adafruit MatrixPortal ESP32-S3 for WLED", "name": "Adafruit MatrixPortal ESP32-S3",
"upload": { "upload": {
"arduino": {
"flash_extra_images": [
[
"0x410000",
"variants/adafruit_matrixportal_esp32s3/tinyuf2.bin"
]
]
},
"flash_size": "8MB", "flash_size": "8MB",
"maximum_ram_size": 327680, "maximum_ram_size": 327680,
"maximum_size": 8388608, "maximum_size": 8388608,

View File

@@ -14,14 +14,14 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/wled/WLED.git" "url": "git+https://github.com/wled-dev/WLED.git"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"bugs": { "bugs": {
"url": "https://github.com/wled/WLED/issues" "url": "https://github.com/wled-dev/WLED/issues"
}, },
"homepage": "https://github.com/wled/WLED#readme", "homepage": "https://github.com/wled-dev/WLED#readme",
"dependencies": { "dependencies": {
"clean-css": "^5.3.3", "clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
@@ -31,4 +31,4 @@
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
} }
} }

View File

@@ -160,7 +160,7 @@ lib_compat_mode = strict
lib_deps = lib_deps =
fastled/FastLED @ 3.6.0 fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2 IRremoteESP8266 @ 2.8.2
https://github.com/Makuna/NeoPixelBus.git#a0919d1c10696614625978dd6fb750a1317a14ce makuna/NeoPixelBus @ 2.8.3
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2 https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0 marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface # for I2C interface
@@ -256,7 +256,7 @@ lib_deps_compat =
lib_deps = lib_deps =
esp32async/AsyncTCP @ 3.4.7 esp32async/AsyncTCP @ 3.4.7
bitbank2/AnimatedGIF@^1.4.7 bitbank2/AnimatedGIF@^1.4.7
https://github.com/Aircoookie/GifDecoder.git#bc3af189b6b1e06946569f6b4287f0b79a860f8e https://github.com/Aircoookie/GifDecoder#bc3af18
build_flags = build_flags =
-D CONFIG_ASYNC_TCP_USE_WDT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192 -D CONFIG_ASYNC_TCP_STACK_SIZE=8192
@@ -300,7 +300,7 @@ build_flags = -g
-D WLED_ENABLE_DMX_INPUT -D WLED_ENABLE_DMX_INPUT
lib_deps = lib_deps =
${esp32_all_variants.lib_deps} ${esp32_all_variants.lib_deps}
https://github.com/someweisguy/esp_dmx.git#47db25d8c515e76fabcf5fc5ab0b786f98eeade0 https://github.com/someweisguy/esp_dmx.git#47db25d
${env.lib_deps} ${env.lib_deps}
[esp32s2] [esp32s2]

View File

@@ -580,7 +580,7 @@ build_flags = ${common.build_flags}
[env:adafruit_matrixportal_esp32s3] [env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 ; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
board = adafruit_matrixportal_esp32s3_wled ; modified board definition: removed flash section that causes FS erase on upload board = adafruit_matrixportal_esp32s3
;; adafruit recommends to use arduino-esp32 2.0.14 ;; adafruit recommends to use arduino-esp32 2.0.14
;;platform = espressif32@ ~6.5.0 ;;platform = espressif32@ ~6.5.0
;;platform_packages = platformio/framework-arduinoespressif32 @ 3.20014.231204 ;; arduino-esp32 2.0.14 ;;platform_packages = platformio/framework-arduinoespressif32 @ 3.20014.231204 ;; arduino-esp32 2.0.14

View File

@@ -38,11 +38,6 @@ const wledBanner = `
\t\t\x1b[36m build script for web UI \t\t\x1b[36m build script for web UI
\x1b[0m`; \x1b[0m`;
// Generate build timestamp as UNIX timestamp (seconds since epoch)
function generateBuildTime() {
return Math.floor(Date.now() / 1000);
}
const singleHeader = `/* const singleHeader = `/*
* Binary array for the Web UI. * Binary array for the Web UI.
* gzip is used for smaller size and improved speeds. * gzip is used for smaller size and improved speeds.
@@ -50,9 +45,6 @@ const singleHeader = `/*
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui * Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source! * to find out how to easily modify the web UI source!
*/ */
// Automatically generated build time for cache busting (UNIX timestamp)
#define WEB_BUILD_TIME ${generateBuildTime()}
`; `;

View File

@@ -313,11 +313,11 @@ class MyExampleUsermod : public Usermod {
yield(); yield();
// ignore certain button types as they may have other consequences // ignore certain button types as they may have other consequences
if (!enabled if (!enabled
|| buttons[b].type == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false; return false;
} }

View File

@@ -1335,7 +1335,7 @@ class AudioReactive : public Usermod {
disableSoundProcessing = true; disableSoundProcessing = true;
} else { } else {
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG)
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource && audioSource->isInitialized()) { // we just switched to "enabled" if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled"
DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed."));
DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride));
} }
@@ -1347,7 +1347,7 @@ class AudioReactive : public Usermod {
if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode
if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (!audioSource || !audioSource->isInitialized()) disableSoundProcessing = true; // no audio source if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source
// Only run the sampling code IF we're not in Receive mode or realtime mode // Only run the sampling code IF we're not in Receive mode or realtime mode
@@ -1544,7 +1544,7 @@ class AudioReactive : public Usermod {
// better would be for AudioSource to implement getType() // better would be for AudioSource to implement getType()
if (enabled if (enabled
&& dmType == 0 && audioPin>=0 && dmType == 0 && audioPin>=0
&& (buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED) && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)
) { ) {
return true; return true;
} }

View File

@@ -562,11 +562,11 @@ void MultiRelay::loop() {
bool MultiRelay::handleButton(uint8_t b) { bool MultiRelay::handleButton(uint8_t b) {
yield(); yield();
if (!enabled if (!enabled
|| buttons[b].type == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false; return false;
} }
@@ -581,20 +581,20 @@ bool MultiRelay::handleButton(uint8_t b) {
unsigned long now = millis(); unsigned long now = millis();
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0) //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttons[b].type == BTN_TYPE_SWITCH) { if (buttonType[b] == BTN_TYPE_SWITCH) {
//handleSwitch(b); //handleSwitch(b);
if (buttons[b].pressedBefore != isButtonPressed(b)) { if (buttonPressedBefore[b] != isButtonPressed(b)) {
buttons[b].pressedTime = now; buttonPressedTime[b] = now;
buttons[b].pressedBefore = !buttons[b].pressedBefore; buttonPressedBefore[b] = !buttonPressedBefore[b];
} }
if (buttons[b].longPressed == buttons[b].pressedBefore) return handled; if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
if (now - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) { if (_relay[i].button == b) {
switchRelay(i, buttons[b].pressedBefore); switchRelay(i, buttonPressedBefore[b]);
buttons[b].longPressed = buttons[b].pressedBefore; //save the last "long term" switch state buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
} }
} }
} }
@@ -604,40 +604,40 @@ bool MultiRelay::handleButton(uint8_t b) {
//momentary button logic //momentary button logic
if (isButtonPressed(b)) { //pressed if (isButtonPressed(b)) { //pressed
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now; if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
buttons[b].pressedBefore = true; buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > 600) { //long press if (now - buttonPressedTime[b] > 600) { //long press
//longPressAction(b); //not exposed //longPressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour //handled = false; //use if you want to pass to default behaviour
buttons[b].longPressed = true; buttonLongPressed[b] = true;
} }
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = now - buttons[b].pressedTime; long dur = now - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) { if (dur < WLED_DEBOUNCE_THRESHOLD) {
buttons[b].pressedBefore = false; buttonPressedBefore[b] = false;
return handled; return handled;
} //too short "press", debounce } //too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have short press before? bool doublePress = buttonWaitTime[b]; //did we have short press before?
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
if (!buttons[b].longPressed) { //short press if (!buttonLongPressed[b]) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0) // if this is second release within 350ms it is a double press (buttonWaitTime!=0)
if (doublePress) { if (doublePress) {
//doublePressAction(b); //not exposed //doublePressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour //handled = false; //use if you want to pass to default behaviour
} else { } else {
buttons[b].waitTime = now; buttonWaitTime[b] = now;
} }
} }
buttons[b].pressedBefore = false; buttonPressedBefore[b] = false;
buttons[b].longPressed = false; buttonLongPressed[b] = false;
} }
// if 350ms elapsed since last press/release it is a short press // if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > 350 && !buttons[b].pressedBefore) { if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
//shortPressAction(b); //not exposed //shortPressAction(b); //not exposed
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) { if (_relay[i].button == b) {

View File

@@ -461,11 +461,11 @@ class PixelsDiceTrayUsermod : public Usermod {
#if USING_TFT_DISPLAY #if USING_TFT_DISPLAY
bool handleButton(uint8_t b) override { bool handleButton(uint8_t b) override {
if (!enabled || b > 1 // buttons 0,1 only if (!enabled || b > 1 // buttons 0,1 only
|| buttons[b].type == BTN_TYPE_SWITCH || buttons[b].type == BTN_TYPE_NONE || || buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_NONE ||
buttons[b].type == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_RESERVED ||
buttons[b].type == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_PIR_SENSOR ||
buttons[b].type == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG ||
buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false; return false;
} }
@@ -476,43 +476,43 @@ class PixelsDiceTrayUsermod : public Usermod {
static unsigned long buttonWaitTime[2] = {0}; static unsigned long buttonWaitTime[2] = {0};
//momentary button logic //momentary button logic
if (!buttons[b].longPressed && isButtonPressed(b)) { //pressed if (!buttonLongPressed[b] && isButtonPressed(b)) { //pressed
if (!buttons[b].pressedBefore) { if (!buttonPressedBefore[b]) {
buttons[b].pressedTime = now; buttonPressedTime[b] = now;
} }
buttons[b].pressedBefore = true; buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
menu_ctrl.HandleButton(ButtonType::LONG, b); menu_ctrl.HandleButton(ButtonType::LONG, b);
buttons[b].longPressed = true; buttonLongPressed[b] = true;
return true; return true;
} }
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = now - buttons[b].pressedTime; long dur = now - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) { if (dur < WLED_DEBOUNCE_THRESHOLD) {
buttons[b].pressedBefore = false; buttonPressedBefore[b] = false;
return true; return true;
} //too short "press", debounce } //too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have short press before? bool doublePress = buttonWaitTime[b]; //did we have short press before?
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
if (!buttons[b].longPressed) { //short press if (!buttonLongPressed[b]) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0) // if this is second release within 350ms it is a double press (buttonWaitTime!=0)
if (doublePress) { if (doublePress) {
menu_ctrl.HandleButton(ButtonType::DOUBLE, b); menu_ctrl.HandleButton(ButtonType::DOUBLE, b);
} else { } else {
buttons[b].waitTime = now; buttonWaitTime[b] = now;
} }
} }
buttons[b].pressedBefore = false; buttonPressedBefore[b] = false;
buttons[b].longPressed = false; buttonLongPressed[b] = false;
} }
// if 350ms elapsed since last press/release it is a short press // if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS && if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS &&
!buttons[b].pressedBefore) { !buttonPressedBefore[b]) {
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
menu_ctrl.HandleButton(ButtonType::SINGLE, b); menu_ctrl.HandleButton(ButtonType::SINGLE, b);
} }

View File

@@ -25,11 +25,51 @@ class StairwayWipeUsermod : public Usermod {
public: public:
void setup() { void setup() {
} }
void loop() { /**
* @brief Drives the stairway wipe state machine and reacts to user variables.
*
* @details
* Reads userVar0 (U0) and userVar1 (U1) to control a directional stairway color wipe:
* - U0 = 0: off.
* - U0 = 1: start/keep wipe from local side.
* - U0 = 2: start/keep wipe from opposite side.
* - U0 = 3: toggle mode for direction 1 (becomes 1 when off, 0 when on).
* - U0 = 4: toggle mode for direction 2 (becomes 2 when off, 0 when on).
*
* Manages a small state machine:
* - State 0: idle, will start a wipe.
* - State 1: wiping; transitions to static when wipe completes.
* - State 2: static/hold; transitions to off after U1 seconds if U1 > 0.
* - State 3: prepare to wipe off (or immediately off if off-wipe is disabled).
* - State 4: wiping off; turns fully off when wipe-off completes.
*
* The wipe duration and wipe-off timing are derived from the current effectSpeed. A change
* in trigger side (previousUserVar0 differing from userVar0) forces the usermod to begin
* turning off. When turning on/off the code invokes startWipe() or turnOff() and issues
* color/state update notifications as appropriate.
*
* @note Defining STAIRCASE_WIPE_OFF enables a reverse color-wipe transition when turning off;
* without it the lights fade off immediately.
*/
void loop() {
//userVar0 (U0 in HTTP API): //userVar0 (U0 in HTTP API):
//has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266
//has to be set to 2 if movement is detected on the PIR that is the opposite side //has to be set to 2 if movement is detected on the PIR that is the opposite side
//can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds)
//U0 = 3: Toggle mode for direction 1 (if off, turn on with U0=1; if on, turn off with U0=0)
//U0 = 4: Toggle mode for direction 2 (if off, turn on with U0=2; if on, turn off with U0=0)
// Handle toggle modes U0=3 and U0=4
if (userVar0 == 3 || userVar0 == 4) {
if (wipeState == 0 || wipeState == 3 || wipeState == 4) {
// Lights are off or turning off, so turn them on
wipeState = 0; // Reset state so the state machine starts fresh
userVar0 = (userVar0 == 3) ? 1 : 2;
} else {
// Lights are on or turning on, so turn them off
userVar0 = 0;
}
}
if (userVar0 > 0) if (userVar0 > 0)
{ {

View File

@@ -749,12 +749,12 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) {
yield(); yield();
if (!enabled if (!enabled
|| b // button 0 only || b // button 0 only
|| buttons[b].type == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_SWITCH
|| buttons[b].type == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false; return false;
} }

View File

@@ -15,25 +15,14 @@
#include "fcn_declare.h" #include "fcn_declare.h"
#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) #if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
#include "FXparticleSystem.h" // include particle system code only if at least one system is enabled #include "FXparticleSystem.h"
#ifdef WLED_DISABLE_PARTICLESYSTEM2D
#define WLED_PS_DONT_REPLACE_2D_FX
#endif
#ifdef WLED_DISABLE_PARTICLESYSTEM1D
#define WLED_PS_DONT_REPLACE_1D_FX
#endif
#ifdef ESP8266 #ifdef ESP8266
#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) && !defined(WLED_DISABLE_PARTICLESYSTEM1D) #if !defined(WLED_DISABLE_PARTICLESYSTEM2D) && !defined(WLED_DISABLE_PARTICLESYSTEM1D)
#error ESP8266 does not support 1D and 2D particle systems simultaneously. Please disable one of them. #error ESP8266 does not support 1D and 2D particle systems simultaneously. Please disable one of them.
#endif #endif
#endif #endif
#else #else
#define WLED_PS_DONT_REPLACE_1D_FX #define WLED_PS_DONT_REPLACE_FX
#define WLED_PS_DONT_REPLACE_2D_FX
#endif
#ifdef WLED_PS_DONT_REPLACE_FX
#define WLED_PS_DONT_REPLACE_1D_FX
#define WLED_PS_DONT_REPLACE_2D_FX
#endif #endif
////////////// //////////////
@@ -724,7 +713,7 @@ uint16_t dissolve(uint32_t color) {
if (SEGENV.aux0) { //dissolve to primary/palette if (SEGENV.aux0) { //dissolve to primary/palette
if (pixels[i] == SEGCOLOR(1)) { if (pixels[i] == SEGCOLOR(1)) {
pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color; pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
break; //only spawn 1 new pixel per frame break; //only spawn 1 new pixel per frame per 50 LEDs
} }
} else { //dissolve to secondary } else { //dissolve to secondary
if (pixels[i] != SEGCOLOR(1)) { if (pixels[i] != SEGCOLOR(1)) {
@@ -735,27 +724,14 @@ uint16_t dissolve(uint32_t color) {
} }
} }
} }
unsigned incompletePixels = 0; // fix for #4401
for (unsigned i = 0; i < SEGLEN; i++) { for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, pixels[i]);
SEGMENT.setPixelColor(i, pixels[i]); // fix for #4401
if (SEGMENT.check2) {
if (SEGENV.aux0) {
if (pixels[i] == SEGCOLOR(1)) incompletePixels++;
} else {
if (pixels[i] != SEGCOLOR(1)) incompletePixels++;
}
}
}
if (SEGENV.step > (255 - SEGMENT.speed) + 15U) { if (SEGENV.step > (255 - SEGMENT.speed) + 15U) {
SEGENV.aux0 = !SEGENV.aux0; SEGENV.aux0 = !SEGENV.aux0;
SEGENV.step = 0; SEGENV.step = 0;
} else { } else {
if (SEGMENT.check2) { SEGENV.step++;
if (incompletePixels == 0)
SEGENV.step++; // only advance step once all pixels have changed
} else
SEGENV.step++;
} }
return FRAMETIME; return FRAMETIME;
@@ -768,7 +744,7 @@ uint16_t dissolve(uint32_t color) {
uint16_t mode_dissolve(void) { uint16_t mode_dissolve(void) {
return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(hw_random8()) : SEGCOLOR(0)); return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(hw_random8()) : SEGCOLOR(0));
} }
static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed,,,,Random,Complete;!,!;!"; static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed,,,,Random;!,!;!";
/* /*
@@ -779,6 +755,7 @@ uint16_t mode_dissolve_random(void) {
} }
static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!;!"; static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!;!";
/* /*
* Blinks one LED at a time. * Blinks one LED at a time.
* Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
@@ -800,6 +777,7 @@ uint16_t mode_sparkle(void) {
} }
static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!;!;;m12=0"; static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!;!;;m12=0";
/* /*
* Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark) * Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark)
* Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
@@ -1774,6 +1752,7 @@ uint16_t mode_tricolor_fade(void) {
} }
static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!";
#ifdef WLED_PS_DONT_REPLACE_FX
/* /*
* Creates random comets * Creates random comets
* Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h
@@ -1812,6 +1791,7 @@ uint16_t mode_multi_comet(void) {
} }
static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;!;1"; static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;!;1";
#undef MAX_COMETS #undef MAX_COMETS
#endif // WLED_PS_DONT_REPLACE_FX
/* /*
* Running random pixels ("Stream 2") * Running random pixels ("Stream 2")
@@ -2138,7 +2118,7 @@ uint16_t mode_palette() {
} }
static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;ix=112,c1=0,o1=1,o2=0,o3=1"; static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;ix=112,c1=0,o1=1,o2=0,o3=1";
#if defined(WLED_PS_DONT_REPLACE_1D_FX) || defined(WLED_PS_DONT_REPLACE_2D_FX) #ifdef WLED_PS_DONT_REPLACE_FX
// WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active
// Fire2012 by Mark Kriegsman, July 2012 // Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY // as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
@@ -2225,7 +2205,7 @@ uint16_t mode_fire_2012() {
return FRAMETIME; return FRAMETIME;
} }
static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars
#endif // WLED_PS_DONT_REPLACE_x_FX #endif // WLED_PS_DONT_REPLACE_FX
// colored stripes pulsing at a defined Beats-Per-Minute (BPM) // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint16_t mode_bpm() { uint16_t mode_bpm() {
@@ -3076,7 +3056,7 @@ uint16_t mode_bouncing_balls(void) {
} }
static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls,,,,,Overlay;!,!,!;!;1;m12=1"; //bar static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls,,,,,Overlay;!,!,!;!;1;m12=1"; //bar
#ifdef WLED_PS_DONT_REPLACE_1D_FX #ifdef WLED_PS_DONT_REPLACE_FX
/* /*
* bouncing balls on a track track Effect modified from Aircoookie's bouncing balls * bouncing balls on a track track Effect modified from Aircoookie's bouncing balls
* Courtesy of pjhatch (https://github.com/pjhatch) * Courtesy of pjhatch (https://github.com/pjhatch)
@@ -3176,7 +3156,7 @@ static uint16_t rolling_balls(void) {
return FRAMETIME; return FRAMETIME;
} }
static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar
#endif // WLED_PS_DONT_REPLACE_1D_FX #endif // WLED_PS_DONT_REPLACE_FX
/* /*
* Sinelon stolen from FASTLED examples * Sinelon stolen from FASTLED examples
@@ -3233,6 +3213,7 @@ uint16_t mode_sinelon_rainbow(void) {
} }
static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!"; static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!";
// utility function that will add random glitter to SEGMENT // utility function that will add random glitter to SEGMENT
void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) { void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) {
if (intensity > hw_random8()) SEGMENT.setPixelColor(hw_random16(SEGLEN), col); if (intensity > hw_random8()) SEGMENT.setPixelColor(hw_random16(SEGLEN), col);
@@ -3437,7 +3418,7 @@ uint16_t mode_candle_multi()
} }
static const char _data_FX_MODE_CANDLE_MULTI[] PROGMEM = "Candle Multi@!,!;!,!;!;;sx=96,ix=224,pal=0"; static const char _data_FX_MODE_CANDLE_MULTI[] PROGMEM = "Candle Multi@!,!;!,!;!;;sx=96,ix=224,pal=0";
#ifdef WLED_PS_DONT_REPLACE_1D_FX #ifdef WLED_PS_DONT_REPLACE_FX
/* /*
/ Fireworks in starburst effect / Fireworks in starburst effect
/ based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/ / based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/
@@ -3569,9 +3550,9 @@ uint16_t mode_starburst(void) {
} }
#undef STARBURST_MAX_FRAG #undef STARBURST_MAX_FRAG
static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments,,,,,Overlay;,!;!;;pal=11,m12=0"; static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments,,,,,Overlay;,!;!;;pal=11,m12=0";
#endif // WLED_PS_DONT_REPLACE_1DFX #endif // WLED_PS_DONT_REPLACE_FX
#if defined(WLED_PS_DONT_REPLACE_1D_FX) || defined(WLED_PS_DONT_REPLACE_2D_FX) #ifdef WLED_PS_DONT_REPLACE_FX
/* /*
* Exploding fireworks effect * Exploding fireworks effect
* adapted from: http://www.anirama.com/1000leds/1d-fireworks/ * adapted from: http://www.anirama.com/1000leds/1d-fireworks/
@@ -3709,7 +3690,7 @@ uint16_t mode_exploding_fireworks(void)
} }
#undef MAX_SPARKS #undef MAX_SPARKS
static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128"; static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128";
#endif // WLED_PS_DONT_REPLACE_x_FX #endif // WLED_PS_DONT_REPLACE_FX
/* /*
* Drip Effect * Drip Effect
@@ -4357,7 +4338,7 @@ static const char _data_FX_MODE_CHUNCHUN[] PROGMEM = "Chunchun@!,Gap size;!,!;!"
#define SPOT_MAX_COUNT 49 //Number of simultaneous waves #define SPOT_MAX_COUNT 49 //Number of simultaneous waves
#endif #endif
#ifdef WLED_PS_DONT_REPLACE_1D_FX #ifdef WLED_PS_DONT_REPLACE_FX
//13 bytes //13 bytes
typedef struct Spotlight { typedef struct Spotlight {
float speed; float speed;
@@ -4491,7 +4472,7 @@ uint16_t mode_dancing_shadows(void)
return FRAMETIME; return FRAMETIME;
} }
static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!;!"; static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!;!";
#endif // WLED_PS_DONT_REPLACE_1D_FX #endif // WLED_PS_DONT_REPLACE_FX
/* /*
Imitates a washing machine, rotating same waves forward, then pause, then backward. Imitates a washing machine, rotating same waves forward, then pause, then backward.
@@ -6052,7 +6033,7 @@ uint16_t mode_2Dcrazybees(void) {
static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur,,,,Smear;;!;2;pal=11,ix=0"; static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur,,,,Smear;;!;2;pal=11,ix=0";
#undef MAX_BEES #undef MAX_BEES
#ifdef WLED_PS_DONT_REPLACE_2D_FX #ifdef WLED_PS_DONT_REPLACE_FX
///////////////////////// /////////////////////////
// 2D Ghost Rider // // 2D Ghost Rider //
///////////////////////// /////////////////////////
@@ -6240,7 +6221,7 @@ uint16_t mode_2Dfloatingblobs(void) {
} }
static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8";
#undef MAX_BLOBS #undef MAX_BLOBS
#endif // WLED_PS_DONT_REPLACE_2D_FX #endif // WLED_PS_DONT_REPLACE_FX
//////////////////////////// ////////////////////////////
// 2D Scrolling text // // 2D Scrolling text //
@@ -10892,18 +10873,16 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_SPOTS, &mode_spots, _data_FX_MODE_SPOTS); addEffect(FX_MODE_SPOTS, &mode_spots, _data_FX_MODE_SPOTS);
addEffect(FX_MODE_SPOTS_FADE, &mode_spots_fade, _data_FX_MODE_SPOTS_FADE); addEffect(FX_MODE_SPOTS_FADE, &mode_spots_fade, _data_FX_MODE_SPOTS_FADE);
addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET); addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET);
#if defined(WLED_PS_DONT_REPLACE_1D_FX) || defined(WLED_PS_DONT_REPLACE_2D_FX) #ifdef WLED_PS_DONT_REPLACE_FX
addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET);
addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS); addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS);
#endif
addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE);
addEffect(FX_MODE_GLITTER, &mode_glitter, _data_FX_MODE_GLITTER); addEffect(FX_MODE_GLITTER, &mode_glitter, _data_FX_MODE_GLITTER);
addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER); addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER);
addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET);
#ifdef WLED_PS_DONT_REPLACE_1D_FX
addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS);
addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST); addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST);
addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS);
addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012);
addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS);
#endif #endif
addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE); addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE);
addEffect(FX_MODE_BOUNCINGBALLS, &mode_bouncing_balls, _data_FX_MODE_BOUNCINGBALLS); addEffect(FX_MODE_BOUNCINGBALLS, &mode_bouncing_balls, _data_FX_MODE_BOUNCINGBALLS);
@@ -10967,7 +10946,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS); addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS);
addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES);
#ifdef WLED_PS_DONT_REPLACE_2D_FX #ifdef WLED_PS_DONT_REPLACE_FX
addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER);
addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS);
#endif #endif

View File

@@ -17,13 +17,13 @@ static bool buttonBriDirection = false; // true: increase brightness, false: dec
void shortPressAction(uint8_t b) void shortPressAction(uint8_t b)
{ {
if (!buttons[b].macroButton) { if (!macroButton[b]) {
switch (b) { switch (b) {
case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break; case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break;
case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break;
} }
} else { } else {
applyPreset(buttons[b].macroButton, CALL_MODE_BUTTON_PRESET); applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
} }
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
@@ -38,7 +38,7 @@ void shortPressAction(uint8_t b)
void longPressAction(uint8_t b) void longPressAction(uint8_t b)
{ {
if (!buttons[b].macroLongPress) { if (!macroLongPress[b]) {
switch (b) { switch (b) {
case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break; case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break;
case 1: case 1:
@@ -52,11 +52,11 @@ void longPressAction(uint8_t b)
else bri -= WLED_LONG_BRI_STEPS; else bri -= WLED_LONG_BRI_STEPS;
} }
stateUpdated(CALL_MODE_BUTTON); stateUpdated(CALL_MODE_BUTTON);
buttons[b].pressedTime = millis(); buttonPressedTime[b] = millis();
break; // repeatable action break; // repeatable action
} }
} else { } else {
applyPreset(buttons[b].macroLongPress, CALL_MODE_BUTTON_PRESET); applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
} }
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
@@ -71,13 +71,13 @@ void longPressAction(uint8_t b)
void doublePressAction(uint8_t b) void doublePressAction(uint8_t b)
{ {
if (!buttons[b].macroDoublePress) { if (!macroDoublePress[b]) {
switch (b) { switch (b) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set //case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
} }
} else { } else {
applyPreset(buttons[b].macroDoublePress, CALL_MODE_BUTTON_PRESET); applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
} }
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
@@ -92,10 +92,10 @@ void doublePressAction(uint8_t b)
bool isButtonPressed(uint8_t b) bool isButtonPressed(uint8_t b)
{ {
if (buttons[b].pin < 0) return false; if (btnPin[b]<0) return false;
unsigned pin = buttons[b].pin; unsigned pin = btnPin[b];
switch (buttons[b].type) { switch (buttonType[b]) {
case BTN_TYPE_NONE: case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED: case BTN_TYPE_RESERVED:
break; break;
@@ -113,7 +113,7 @@ bool isButtonPressed(uint8_t b)
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt) #ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
if (touchInterruptGetLastStatus(pin)) return true; if (touchInterruptGetLastStatus(pin)) return true;
#else #else
if (digitalPinToTouchChannel(pin) >= 0 && touchRead(pin) <= touchThreshold) return true; if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true;
#endif #endif
#endif #endif
break; break;
@@ -124,25 +124,25 @@ bool isButtonPressed(uint8_t b)
void handleSwitch(uint8_t b) void handleSwitch(uint8_t b)
{ {
// isButtonPressed() handles inverted/noninverted logic // isButtonPressed() handles inverted/noninverted logic
if (buttons[b].pressedBefore != isButtonPressed(b)) { if (buttonPressedBefore[b] != isButtonPressed(b)) {
DEBUG_PRINTF_P(PSTR("Switch: State changed %u\n"), b); DEBUG_PRINTF_P(PSTR("Switch: State changed %u\n"), b);
buttons[b].pressedTime = millis(); buttonPressedTime[b] = millis();
buttons[b].pressedBefore = !buttons[b].pressedBefore; // toggle pressed state buttonPressedBefore[b] = !buttonPressedBefore[b];
} }
if (buttons[b].longPressed == buttons[b].pressedBefore) return; if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
if (millis() - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b); DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b);
if (!buttons[b].pressedBefore) { // on -> off if (!buttonPressedBefore[b]) { // on -> off
DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b); DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b);
if (buttons[b].macroButton) applyPreset(buttons[b].macroButton, CALL_MODE_BUTTON_PRESET); if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
else { //turn on else { //turn on
if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
} }
} else { // off -> on } else { // off -> on
DEBUG_PRINTF_P(PSTR("Switch: Off -> On (%u)\n"), b); DEBUG_PRINTF_P(PSTR("Switch: Off -> On (%u)\n"), b);
if (buttons[b].macroLongPress) applyPreset(buttons[b].macroLongPress, CALL_MODE_BUTTON_PRESET); if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
else { //turn off else { //turn off
if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
} }
@@ -152,13 +152,13 @@ void handleSwitch(uint8_t b)
// publish MQTT message // publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[MQTT_MAX_TOPIC_LEN + 32]; char subuf[MQTT_MAX_TOPIC_LEN + 32];
if (buttons[b].type == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, !buttons[b].pressedBefore ? "off" : "on"); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
} }
#endif #endif
buttons[b].longPressed = buttons[b].pressedBefore; //save the last "long term" switch state buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
} }
} }
@@ -178,17 +178,17 @@ void handleAnalog(uint8_t b)
#ifdef ESP8266 #ifdef ESP8266
rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit
#else #else
if ((buttons[b].pin < 0) /*|| (digitalPinToAnalogChannel(buttons[b].pin) < 0)*/) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise if ((btnPin[b] < 0) /*|| (digitalPinToAnalogChannel(btnPin[b]) < 0)*/) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise
rawReading = analogRead(buttons[b].pin); // collect at full 12bit resolution rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution
#endif #endif
yield(); // keep WiFi task running - analog read may take several millis on ESP8266 yield(); // keep WiFi task running - analog read may take several millis on ESP8266
filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255] filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255]
unsigned aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit unsigned aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit
if (aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used if(aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used
if (aRead >= 255-POT_SENSITIVITY) aRead = 255; if(aRead >= 255-POT_SENSITIVITY) aRead = 255;
if (buttons[b].type == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead; if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates // remove noise & reduce frequency of UI updates
if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading
@@ -206,10 +206,10 @@ void handleAnalog(uint8_t b)
oldRead[b] = aRead; oldRead[b] = aRead;
// if no macro for "short press" and "long press" is defined use brightness control // if no macro for "short press" and "long press" is defined use brightness control
if (!buttons[b].macroButton && !buttons[b].macroLongPress) { if (!macroButton[b] && !macroLongPress[b]) {
DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), buttons[b].macroDoublePress); DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), macroDoublePress[b]);
// if "double press" macro defines which option to change // if "double press" macro defines which option to change
if (buttons[b].macroDoublePress >= 250) { if (macroDoublePress[b] >= 250) {
// global brightness // global brightness
if (aRead == 0) { if (aRead == 0) {
briLast = bri; briLast = bri;
@@ -218,30 +218,27 @@ void handleAnalog(uint8_t b)
if (bri == 0) strip.restartRuntime(); if (bri == 0) strip.restartRuntime();
bri = aRead; bri = aRead;
} }
} else if (buttons[b].macroDoublePress == 249) { } else if (macroDoublePress[b] == 249) {
// effect speed // effect speed
effectSpeed = aRead; effectSpeed = aRead;
} else if (buttons[b].macroDoublePress == 248) { } else if (macroDoublePress[b] == 248) {
// effect intensity // effect intensity
effectIntensity = aRead; effectIntensity = aRead;
} else if (buttons[b].macroDoublePress == 247) { } else if (macroDoublePress[b] == 247) {
// selected palette // selected palette
effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1); effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (buttons[b].macroDoublePress == 200) { } else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation // primary color, hue, full saturation
colorHStoRGB(aRead*256, 255, colPri); colorHStoRGB(aRead*256,255,colPri);
} else { } else {
// otherwise use "double press" for segment selection // otherwise use "double press" for segment selection
Segment& seg = strip.getSegment(buttons[b].macroDoublePress); Segment& seg = strip.getSegment(macroDoublePress[b]);
if (aRead == 0) { if (aRead == 0) {
seg.on = false; // do not use transition seg.setOption(SEG_OPTION_ON, false); // off (use transition)
//seg.setOption(SEG_OPTION_ON, false); // off (use transition)
} else { } else {
seg.opacity = aRead; // set brightness (opacity) of segment seg.setOpacity(aRead);
seg.on = true; seg.setOption(SEG_OPTION_ON, true); // on (use transition)
//seg.setOpacity(aRead);
//seg.setOption(SEG_OPTION_ON, true); // on (use transition)
} }
// this will notify clients of update (websockets,mqtt,etc) // this will notify clients of update (websockets,mqtt,etc)
updateInterfaces(CALL_MODE_BUTTON); updateInterfaces(CALL_MODE_BUTTON);
@@ -264,16 +261,16 @@ void handleButton()
if (strip.isUpdating() && (now - lastRun < ANALOG_BTN_READ_CYCLE+1)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips) if (strip.isUpdating() && (now - lastRun < ANALOG_BTN_READ_CYCLE+1)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips)
lastRun = now; lastRun = now;
for (unsigned b = 0; b < buttons.size(); b++) { for (unsigned b=0; b<WLED_MAX_BUTTONS; b++) {
#ifdef ESP8266 #ifdef ESP8266
if ((buttons[b].pin < 0 && !(buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED)) || buttons[b].type == BTN_TYPE_NONE) continue; if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
#else #else
if (buttons[b].pin < 0 || buttons[b].type == BTN_TYPE_NONE) continue; if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
#endif #endif
if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons
if (buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer
if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) {
handleAnalog(b); handleAnalog(b);
} }
@@ -281,7 +278,7 @@ void handleButton()
} }
// button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0) // button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttons[b].type == BTN_TYPE_SWITCH || buttons[b].type == BTN_TYPE_TOUCH_SWITCH || buttons[b].type == BTN_TYPE_PIR_SENSOR) { if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_TOUCH_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) {
handleSwitch(b); handleSwitch(b);
continue; continue;
} }
@@ -290,39 +287,40 @@ void handleButton()
if (isButtonPressed(b)) { // pressed if (isButtonPressed(b)) { // pressed
// if all macros are the same, fire action immediately on rising edge // if all macros are the same, fire action immediately on rising edge
if (buttons[b].macroButton && buttons[b].macroButton == buttons[b].macroLongPress && buttons[b].macroButton == buttons[b].macroDoublePress) { if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) {
if (!buttons[b].pressedBefore) shortPressAction(b); if (!buttonPressedBefore[b])
buttons[b].pressedBefore = true; shortPressAction(b);
buttons[b].pressedTime = now; // continually update (for debouncing to work in release handler) buttonPressedBefore[b] = true;
buttonPressedTime[b] = now; // continually update (for debouncing to work in release handler)
continue; continue;
} }
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now; if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
buttons[b].pressedBefore = true; buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
if (!buttons[b].longPressed) { if (!buttonLongPressed[b]) {
buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press
longPressAction(b); longPressAction(b);
} else if (b) { //repeatable action (~5 times per s) on button > 0 } else if (b) { //repeatable action (~5 times per s) on button > 0
longPressAction(b); longPressAction(b);
buttons[b].pressedTime = now - WLED_LONG_REPEATED_ACTION; //200ms buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //200ms
} }
buttons[b].longPressed = true; buttonLongPressed[b] = true;
} }
} else if (buttons[b].pressedBefore) { //released } else if (buttonPressedBefore[b]) { //released
long dur = now - buttons[b].pressedTime; long dur = now - buttonPressedTime[b];
// released after rising-edge short press action // released after rising-edge short press action
if (buttons[b].macroButton && buttons[b].macroButton == buttons[b].macroLongPress && buttons[b].macroButton == buttons[b].macroDoublePress) { if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) {
if (dur > WLED_DEBOUNCE_THRESHOLD) buttons[b].pressedBefore = false; // debounce, blocks button for 50 ms once it has been released if (dur > WLED_DEBOUNCE_THRESHOLD) buttonPressedBefore[b] = false; // debounce, blocks button for 50 ms once it has been released
continue; continue;
} }
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttons[b].pressedBefore = false; continue;} // too short "press", debounce if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} // too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have a short press before? bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released)
if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds
@@ -334,25 +332,25 @@ void handleButton()
} else { } else {
WLED::instance().initAP(true); WLED::instance().initAP(true);
} }
} else if (!buttons[b].longPressed) { //short press } else if (!buttonLongPressed[b]) { //short press
//NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling
if (b != 1 && !buttons[b].macroDoublePress) { //don't wait for double press on buttons without a default action if no double press macro set if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set
shortPressAction(b); shortPressAction(b);
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
if (doublePress) { if (doublePress) {
doublePressAction(b); doublePressAction(b);
} else { } else {
buttons[b].waitTime = now; buttonWaitTime[b] = now;
} }
} }
} }
buttons[b].pressedBefore = false; buttonPressedBefore[b] = false;
buttons[b].longPressed = false; buttonLongPressed[b] = false;
} }
//if 350ms elapsed since last short press release it is a short press //if 350ms elapsed since last short press release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS && !buttons[b].pressedBefore) { if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
buttons[b].waitTime = 0; buttonWaitTime[b] = 0;
shortPressAction(b); shortPressAction(b);
} }
} }

View File

@@ -345,91 +345,97 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray hw_btn_ins = btn_obj["ins"]; JsonArray hw_btn_ins = btn_obj["ins"];
if (!hw_btn_ins.isNull()) { if (!hw_btn_ins.isNull()) {
// deallocate existing button pins // deallocate existing button pins
for (const auto &button : buttons) PinManager::deallocatePin(button.pin, PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) PinManager::deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
buttons.clear(); // clear existing buttons
unsigned s = 0; unsigned s = 0;
for (JsonObject btn : hw_btn_ins) { for (JsonObject btn : hw_btn_ins) {
uint8_t type = btn["type"] | BTN_TYPE_NONE; CJSON(buttonType[s], btn["type"]);
int8_t pin = btn["pin"][0] | -1; int8_t pin = btn["pin"][0] | -1;
if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) { if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32 btnPin[s] = pin;
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that analog button pin is a valid ADC gpio // ESP32 only: check that analog button pin is a valid ADC gpio
if ((type == BTN_TYPE_ANALOG) || (type == BTN_TYPE_ANALOG_INVERTED)) { if ((buttonType[s] == BTN_TYPE_ANALOG) || (buttonType[s] == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(pin) < 0) { if (digitalPinToAnalogChannel(btnPin[s]) < 0) {
// not an ADC analog pin // not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), pin, s); DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s);
PinManager::deallocatePin(pin, PinOwner::Button); btnPin[s] = -1;
pin = -1; PinManager::deallocatePin(pin,PinOwner::Button);
continue;
} else { } else {
analogReadResolution(12); // see #4040 analogReadResolution(12); // see #4040
} }
} else if ((type == BTN_TYPE_TOUCH || type == BTN_TYPE_TOUCH_SWITCH)) { }
if (digitalPinToTouchChannel(pin) < 0) { else if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH))
{
if (digitalPinToTouchChannel(btnPin[s]) < 0) {
// not a touch pin // not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), pin, s); DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s);
PinManager::deallocatePin(pin, PinOwner::Button); btnPin[s] = -1;
pin = -1; PinManager::deallocatePin(pin,PinOwner::Button);
continue; }
}
//if touch pin, enable the touch interrupt on ESP32 S2 & S3 //if touch pin, enable the touch interrupt on ESP32 S2 & S3
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) else
{
touchAttachInterrupt(btnPin[s], touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
}
#endif #endif
} else }
#endif else
#endif
{ {
// regular buttons and switches
if (disablePullUp) { if (disablePullUp) {
pinMode(pin, INPUT); pinMode(btnPin[s], INPUT);
} else { } else {
#ifdef ESP32 #ifdef ESP32
pinMode(pin, type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else #else
pinMode(pin, INPUT_PULLUP); pinMode(btnPin[s], INPUT_PULLUP);
#endif #endif
} }
} }
JsonArray hw_btn_ins_0_macros = btn["macros"]; } else {
uint8_t press = hw_btn_ins_0_macros[0] | 0; btnPin[s] = -1;
uint8_t longPress = hw_btn_ins_0_macros[1] | 0;
uint8_t doublePress = hw_btn_ins_0_macros[2] | 0;
buttons.emplace_back(pin, type, press, longPress, doublePress); // add button to vector
} }
JsonArray hw_btn_ins_0_macros = btn["macros"];
CJSON(macroButton[s], hw_btn_ins_0_macros[0]);
CJSON(macroLongPress[s],hw_btn_ins_0_macros[1]);
CJSON(macroDoublePress[s], hw_btn_ins_0_macros[2]);
if (++s >= WLED_MAX_BUTTONS) break; // max buttons reached if (++s >= WLED_MAX_BUTTONS) break; // max buttons reached
} }
// clear remaining buttons
for (; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
} else if (fromFS) { } else if (fromFS) {
// new install/missing configuration (button 0 has defaults) // new install/missing configuration (button 0 has defaults)
// relies upon only being called once with fromFS == true, which is currently true. // relies upon only being called once with fromFS == true, which is currently true.
constexpr uint8_t defTypes[] = {BTNTYPE}; for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) {
constexpr int8_t defPins[] = {BTNPIN}; if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) {
constexpr unsigned numTypes = (sizeof(defTypes) / sizeof(defTypes[0])); btnPin[s] = -1;
constexpr unsigned numPins = (sizeof(defPins) / sizeof(defPins[0])); buttonType[s] = BTN_TYPE_NONE;
// check if the number of pins and types are valid; count of pins must be greater than or equal to types
static_assert(numTypes <= numPins, "The default button pins defined in BTNPIN do not match the button types defined in BTNTYPE");
uint8_t type = BTN_TYPE_NONE;
buttons.clear(); // clear existing buttons (just in case)
for (size_t s = 0; s < WLED_MAX_BUTTONS && s < numPins; s++) {
type = defTypes[s < numTypes ? s : numTypes - 1]; // use last known type to set current type if types less than pins
if (type == BTN_TYPE_NONE || defPins[s] < 0 || !PinManager::allocatePin(defPins[s], false, PinOwner::Button)) {
if (buttons.size() == 0) buttons.emplace_back(-1, BTN_TYPE_NONE); // add disabled button to vector (so we have at least one button defined)
continue; // pin not available or invalid, skip configuring this GPIO
} }
if (disablePullUp) { if (btnPin[s] >= 0) {
pinMode(defPins[s], INPUT); if (disablePullUp) {
} else { pinMode(btnPin[s], INPUT);
#ifdef ESP32 } else {
pinMode(defPins[s], type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); #ifdef ESP32
#else pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
pinMode(defPins[s], INPUT_PULLUP); #else
#endif pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
} }
buttons.emplace_back(defPins[s], type); // add button to vector macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
} }
} }
CJSON(buttonPublishMqtt, btn_obj["mqtt"]); CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
#ifndef WLED_DISABLE_INFRARED #ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
@@ -1010,15 +1016,15 @@ void serializeConfig(JsonObject root) {
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins"); JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// configuration for all buttons // configuration for all buttons
for (const auto &button : buttons) { for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject(); JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = button.type; hw_btn_ins_0["type"] = buttonType[i];
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin"); JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(button.pin); hw_btn_ins_0_pin.add(btnPin[i]);
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros"); JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(button.macroButton); hw_btn_ins_0_macros.add(macroButton[i]);
hw_btn_ins_0_macros.add(button.macroLongPress); hw_btn_ins_0_macros.add(macroLongPress[i]);
hw_btn_ins_0_macros.add(button.macroDoublePress); hw_btn_ins_0_macros.add(macroDoublePress[i]);
} }
hw_btn[F("tt")] = touchThreshold; hw_btn[F("tt")] = touchThreshold;

View File

@@ -102,9 +102,9 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#ifndef WLED_MAX_BUTTONS #ifndef WLED_MAX_BUTTONS
#ifdef ESP8266 #ifdef ESP8266
#define WLED_MAX_BUTTONS 10 #define WLED_MAX_BUTTONS 2
#else #else
#define WLED_MAX_BUTTONS 32 #define WLED_MAX_BUTTONS 4
#endif #endif
#else #else
#if WLED_MAX_BUTTONS < 2 #if WLED_MAX_BUTTONS < 2

View File

@@ -794,7 +794,7 @@ input[type=range]::-moz-range-thumb {
/* buttons */ /* buttons */
.btn { .btn {
padding: 8px; padding: 8px;
margin: 10px 4px; /*margin: 10px 4px;*/
width: 230px; width: 230px;
font-size: 19px; font-size: 19px;
color: var(--c-d); color: var(--c-d);

View File

@@ -693,8 +693,6 @@ function parseInfo(i) {
// gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects // gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects
// gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects // gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects
// } // }
// Check for version upgrades on page load
checkVersionUpgrade(i);
} }
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml //https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
@@ -3306,209 +3304,6 @@ function simplifyUI() {
gId("btns").style.display = "none"; gId("btns").style.display = "none";
} }
// Version reporting feature
var versionCheckDone = false;
function checkVersionUpgrade(info) {
// Only check once per page load
if (versionCheckDone) return;
versionCheckDone = true;
// Suppress feature if in AP mode (no internet connection available)
if (info.wifi && info.wifi.ap) return;
// Fetch version-info.json using existing /edit endpoint
fetch(getURL('/edit?func=edit&path=/version-info.json'), {
method: 'get'
})
.then(res => {
if (res.status === 404) {
// File doesn't exist - first install, show install prompt
showVersionUpgradePrompt(info, null, info.ver);
return null;
}
if (!res.ok) {
throw new Error('Failed to fetch version-info.json');
}
return res.json();
})
.then(versionInfo => {
if (!versionInfo) return; // 404 case already handled
// Check if user opted out
if (versionInfo.neverAsk) return;
// Check if version has changed
const currentVersion = info.ver;
const storedVersion = versionInfo.version || '';
if (storedVersion && storedVersion !== currentVersion) {
// Version has changed
if (versionInfo.alwaysReport) {
// Automatically report if user opted in for always reporting
reportUpgradeEvent(info, storedVersion);
} else {
// Show upgrade prompt
showVersionUpgradePrompt(info, storedVersion, currentVersion);
}
} else if (!storedVersion) {
// Empty version in file, show install prompt
showVersionUpgradePrompt(info, null, currentVersion);
}
})
.catch(e => {
console.log('Failed to load version-info.json', e);
// On error, save current version for next time
if (info && info.ver) {
updateVersionInfo(info.ver, false, false);
}
});
}
function showVersionUpgradePrompt(info, oldVersion, newVersion) {
// Determine if this is an install or upgrade
const isInstall = !oldVersion;
// Create overlay and dialog
const overlay = d.createElement('div');
overlay.id = 'versionUpgradeOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;';
const dialog = d.createElement('div');
dialog.style.cssText = 'background:var(--c-1);border-radius:10px;padding:25px;max-width:500px;margin:20px;box-shadow:0 4px 6px rgba(0,0,0,0.3);';
// Build contextual message based on install vs upgrade
const title = isInstall
? '🎉 Thank you for installing WLED!'
: '🎉 WLED Upgrade Detected!';
const description = isInstall
? `You are now running WLED <strong style="text-wrap: nowrap">${newVersion}</strong>.`
: `Your WLED has been upgraded from <strong style="text-wrap: nowrap">${oldVersion}</strong> to <strong style="text-wrap: nowrap">${newVersion}</strong>.`;
const question = 'Help make WLED better by sharing hardware details like chip type and LED count? This helps us understand how WLED is used and prioritize features — we never collect personal data or your activities.'
dialog.innerHTML = `
<h2 style="margin-top:0;color:var(--c-f);">${title}</h2>
<p style="color:var(--c-f);">${description}</p>
<p style="color:var(--c-f);">${question}</p>
<p style="color:var(--c-f);font-size:0.9em;">
<a href="https://kno.wled.ge/about/privacy-policy/" target="_blank" style="color:var(--c-6);">Learn more about what data is collected and why</a>
</p>
<div style="margin-top:20px;display:flex;flex-wrap:wrap;gap:8px;">
<button id="versionReportYes" class="btn">Yes</button>
<button id="versionReportAlways" class="btn">Yes, Always</button>
<button id="versionReportNo" class="btn">Not Now</button>
<button id="versionReportNever" class="btn">Never Ask</button>
</div>
`;
overlay.appendChild(dialog);
d.body.appendChild(overlay);
// Add event listeners
gId('versionReportYes').addEventListener('click', () => {
reportUpgradeEvent(info, oldVersion);
d.body.removeChild(overlay);
});
gId('versionReportAlways').addEventListener('click', () => {
reportUpgradeEvent(info, oldVersion, true); // Pass true for alwaysReport
d.body.removeChild(overlay);
showToast('Thank you! Future upgrades will be reported automatically.');
});
gId('versionReportNo').addEventListener('click', () => {
// Don't update version, will ask again on next load
d.body.removeChild(overlay);
});
gId('versionReportNever').addEventListener('click', () => {
updateVersionInfo(newVersion, true, false);
d.body.removeChild(overlay);
showToast('You will not be asked again.');
});
}
function reportUpgradeEvent(info, oldVersion, alwaysReport) {
showToast('Reporting upgrade...');
// Fetch fresh data from /json/info endpoint as requested
fetch(getURL('/json/info'), {
method: 'get'
})
.then(res => res.json())
.then(infoData => {
// Map to UpgradeEventRequest structure per OpenAPI spec
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
const upgradeData = {
deviceId: infoData.deviceId, // Use anonymous unique device ID
version: infoData.ver || '', // Current version string
previousVersion: oldVersion || '', // Previous version from version-info.json
releaseName: infoData.release || '', // Release name (e.g., "WLED 0.15.0")
chip: infoData.arch || '', // Chip architecture (esp32, esp8266, etc)
ledCount: infoData.leds ? infoData.leds.count : 0, // Number of LEDs
isMatrix: !!(infoData.leds && infoData.leds.matrix), // Whether it's a 2D matrix setup
bootloaderSHA256: infoData.bootloaderSHA256 || '', // Bootloader SHA256 hash
brand: infoData.brand, // Device brand (always present)
product: infoData.product, // Product name (always present)
flashSize: infoData.flash // Flash size (always present)
};
// Add optional fields if available
if (infoData.psram !== undefined) upgradeData.psramSize = Math.round(infoData.psram / (1024 * 1024)); // convert bytes to MB
// Note: partitionSizes not currently available in /json/info endpoint
// Make AJAX call to postUpgradeEvent API
return fetch('https://usage.wled.me/api/usage/upgrade', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(upgradeData)
});
})
.then(res => {
if (res.ok) {
showToast('Thank you for reporting!');
updateVersionInfo(info.ver, false, !!alwaysReport);
} else {
showToast('Report failed. Please try again later.', true);
// Do NOT update version info on failure - user will be prompted again
}
})
.catch(e => {
console.log('Failed to report upgrade', e);
showToast('Report failed. Please try again later.', true);
// Do NOT update version info on error - user will be prompted again
});
}
function updateVersionInfo(version, neverAsk, alwaysReport) {
const versionInfo = {
version: version,
neverAsk: neverAsk,
alwaysReport: !!alwaysReport
};
// Create a Blob with JSON content and use /upload endpoint
const blob = new Blob([JSON.stringify(versionInfo)], {type: 'application/json'});
const formData = new FormData();
formData.append('data', blob, 'version-info.json');
fetch(getURL('/upload'), {
method: 'POST',
body: formData
})
.then(res => res.text())
.then(data => {
console.log('Version info updated', data);
})
.catch(e => {
console.log('Failed to update version-info.json', e);
});
}
size(); size();
_C.style.setProperty('--n', N); _C.style.setProperty('--n', N);

View File

@@ -6,7 +6,7 @@
<title>LED Settings</title> <title>LED Settings</title>
<script src="common.js" type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[]; var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;} function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h // these functions correspond to C macros found in const.h
@@ -43,7 +43,7 @@
}); // If we set async false, file is loaded and executed, then next statement is processed }); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds'); if (loc) d.Sf.action = getURL('/settings/leds');
} }
function bLimits(b,v,p,m,l,o=5,d=2,a=6,n=4) { function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266 maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3) maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus maxPB = p; // maxPB - max LEDs per bus
@@ -52,7 +52,6 @@
maxCO = o; // maxCO - max Color Order mappings maxCO = o; // maxCO - max Color Order mappings
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266 maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266 maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxBT = n; // maxBT - max buttons
} }
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
@@ -268,10 +267,10 @@
} }
// enable/disable LED fields // enable/disable LED fields
updateTypeDropdowns(); // restrict bus types in dropdowns to max allowed digital/analog buses
let dC = 0; // count of digital buses (for parallel I2S) let dC = 0; // count of digital buses (for parallel I2S)
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
LTs.forEach((s,i)=>{ LTs.forEach((s,i)=>{
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
// is the field a LED type? // is the field a LED type?
var n = s.name.substring(2,3); // bus number (0-Z) var n = s.name.substring(2,3); // bus number (0-Z)
var t = parseInt(s.value); var t = parseInt(s.value);
@@ -448,8 +447,17 @@
{ {
var o = gEBCN("iST"); var o = gEBCN("iST");
var i = o.length; var i = o.length;
let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); }
var f = gId("mLC"); var f = gId("mLC");
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
f.querySelectorAll("select[name^=LT]").forEach((s)=>{
let t = s.value;
if (isDig(t) && !isD2P(t)) digitalB++;
if (isD2P(t)) twopinB++;
if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel
if (isVir(t)) virtB++;
});
if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
var s = chrID(i); var s = chrID(i);
@@ -459,7 +467,7 @@
var cn = `<div class="iST"> var cn = `<div class="iST">
<hr class="sml"> <hr class="sml">
${i+1}: ${i+1}:
<select name="LT${s}" onchange="updateTypeDropdowns();UI(true)"></select><br> <select name="LT${s}" onchange="UI(true)"></select><br>
<div id="abl${s}"> <div id="abl${s}">
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();"> mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<option value="55" selected>55mA (typ. 5V WS281x)</option> <option value="55" selected>55mA (typ. 5V WS281x)</option>
@@ -514,15 +522,18 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
} }
}); });
enLA(d.Sf["LAsel"+s],s); // update LED mA enLA(d.Sf["LAsel"+s],s); // update LED mA
// temporarily set to virtual (network) type to avoid "same type" exception during dropdown update // disable inappropriate LED types
let sel = d.getElementsByName("LT"+s)[0]; let sel = d.getElementsByName("LT"+s)[0];
sel.value = sel.querySelector('option[data-type="N"]').value; // 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
updateTypeDropdowns(); // update valid bus options including this new one let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
updateTypeDropdowns(); // update again for the newly selected type
} }
if (n==-1) { if (n==-1) {
o[--i].remove();--i; o[--i].remove();--i;
o[i].querySelector("[name^=LT]").disabled = false;
} }
gId("+").style.display = (i<35) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") gId("+").style.display = (i<35) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
@@ -589,9 +600,9 @@ Swap: <select id="xw${s}" name="XW${s}">
} }
function addBtn(i,p,t) { function addBtn(i,p,t) {
var b = gId("btns"); var c = gId("btns").innerHTML;
var s = chrID(i); var s = chrID(i);
var c = `<div id="btn${i}">#${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" min="-1" max="${d.max_gpio}" class="xs" value="${p}">`; c += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">` c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`; c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`; c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
@@ -603,24 +614,8 @@ Swap: <select id="xw${s}" name="XW${s}">
c += `<option value="8" ${t==8?"selected":""}>Analog inverted</option>`; c += `<option value="8" ${t==8?"selected":""}>Analog inverted</option>`;
c += `<option value="9" ${t==9?"selected":""}>Touch (switch)</option>`; c += `<option value="9" ${t==9?"selected":""}>Touch (switch)</option>`;
c += `</select>`; c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('BT${s}')">&nbsp;&#x2715;</span><br></div>`; c += `<span style="cursor: pointer;" onclick="off('BT${s}')">&nbsp;&#x2715;</span><br>`;
b.insertAdjacentHTML("beforeend", c); gId("btns").innerHTML = c;
btnBtn();
pinDropdowns();
UI();
}
function remBtn() {
var b = gId("btns");
if (b.children.length <= 1) return;
b.lastElementChild.remove();
btnBtn();
pinDropdowns();
UI();
}
function btnBtn() {
var b = gId("btns");
gId("btn_rem").style.display = (b.children.length > 1) ? "inline" : "none";
gId("btn_add").style.display = (b.children.length < maxBT) ? "inline" : "none";
} }
function tglSi(cs) { function tglSi(cs) {
customStarts = cs; customStarts = cs;
@@ -817,34 +812,6 @@ Swap: <select id="xw${s}" name="XW${s}">
} }
return opt; return opt;
} }
// dynamically enforce bus type availability based on current usage
function updateTypeDropdowns() {
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
// count currently used buses
LTs.forEach(sel => {
let t = parseInt(sel.value);
if (isDig(t) && !isD2P(t)) digitalB++;
if (isPWM(t)) analogB += numPins(t);
if (isD2P(t)) twopinB++;
if (isVir(t)) virtB++;
});
// enable/disable type options according to limits in dropdowns
LTs.forEach(sel => {
const curType = parseInt(sel.value);
const disable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = true);
const enable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = false);
enable('option'); // reset all first
// max digital buses: ESP32 & S2 support mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
// supported outputs using parallel I2S/mono I2S: S2: 12/5, S3: 12/4, ESP32: 16/9
let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
// disallow adding more of a type that has reached its limit but allow changing the current type
if (digitalB >= maxDB && !(isDig(curType) && !isD2P(curType))) disable('option[data-type="D"]');
if (twopinB >= 2 && !isD2P(curType)) disable('option[data-type="2P"]');
// Disable PWM types that need more pins than available (accounting for current type's pins if PWM)
disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(curType)?numPins(curType):0) + 1)}"]`);
});
}
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>
</head> </head>
@@ -900,16 +867,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<div id="com_entries"></div> <div id="com_entries"></div>
<hr class="sml"> <hr class="sml">
<button type="button" id="com_add" onclick="addCOM()">+</button> <button type="button" id="com_add" onclick="addCOM()">+</button>
<button type="button" id="com_rem" onclick="remCOM()">-</button> <button type="button" id="com_rem" onclick="remCOM()">-</button><br>
</div> </div>
<hr class="sml"> <hr class="sml">
<div id="btn_wrap"> <div id="btns"></div>
Buttons:
<div id="btns"></div>
<hr class="sml">
<button type="button" id="btn_add" onclick="addBtn(gId('btns').children.length,-1,0)">+</button>
<button type="button" id="btn_rem" onclick="remBtn()">-</button>
</div>
Disable internal pull-up/down: <input type="checkbox" name="IP"><br> Disable internal pull-up/down: <input type="checkbox" name="IP"><br>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br> Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
<hr class="sml"> <hr class="sml">

View File

@@ -12,20 +12,7 @@
#ifdef ESP32 #ifdef ESP32
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
#define UPDATE_ERROR errorString #define UPDATE_ERROR errorString
const size_t BOOTLOADER_OFFSET = 0x1000;
// Bootloader is at fixed offset 0x1000 (4KB), 0x0000 (0KB), or 0x2000 (8KB), and is typically 32KB
// Bootloader offsets for different MCUs => see https://github.com/wled/WLED/issues/5064
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
constexpr size_t BOOTLOADER_OFFSET = 0x0000; // esp32-S3, esp32-C3 and (future support) esp32-c6
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#elif defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32C5)
constexpr size_t BOOTLOADER_OFFSET = 0x2000; // (future support) esp32-P4 and esp32-C5
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#else
constexpr size_t BOOTLOADER_OFFSET = 0x1000; // esp32 and esp32-s2
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#endif
#elif defined(ESP8266) #elif defined(ESP8266)
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString #define UPDATE_ERROR getErrorString
@@ -293,6 +280,9 @@ static String bootloaderSHA256HexCache = "";
void calculateBootloaderSHA256() { void calculateBootloaderSHA256() {
if (!bootloaderSHA256HexCache.isEmpty()) return; if (!bootloaderSHA256HexCache.isEmpty()) return;
// Bootloader is at fixed offset 0x1000 (4KB) and is typically 32KB
const uint32_t bootloaderSize = 0x8000; // 32KB, typical bootloader size
// Calculate SHA256 // Calculate SHA256
uint8_t sha256[32]; uint8_t sha256[32];
mbedtls_sha256_context ctx; mbedtls_sha256_context ctx;
@@ -302,8 +292,8 @@ void calculateBootloaderSHA256() {
const size_t chunkSize = 256; const size_t chunkSize = 256;
uint8_t buffer[chunkSize]; uint8_t buffer[chunkSize];
for (uint32_t offset = 0; offset < BOOTLOADER_SIZE; offset += chunkSize) { for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) {
size_t readSize = min((size_t)(BOOTLOADER_SIZE - offset), chunkSize); size_t readSize = min((size_t)(bootloaderSize - offset), chunkSize);
if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) { if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) {
mbedtls_sha256_update(&ctx, buffer, readSize); mbedtls_sha256_update(&ctx, buffer, readSize);
} }

View File

@@ -128,12 +128,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
PinManager::deallocatePin(irPin, PinOwner::IR); PinManager::deallocatePin(irPin, PinOwner::IR);
} }
#endif #endif
for (const auto &button : buttons) { for (unsigned s=0; s<WLED_MAX_BUTTONS; s++) {
if (button.pin >= 0 && PinManager::isPinAllocated(button.pin, PinOwner::Button)) { if (btnPin[s]>=0 && PinManager::isPinAllocated(btnPin[s], PinOwner::Button)) {
PinManager::deallocatePin(button.pin, PinOwner::Button); PinManager::deallocatePin(btnPin[s], PinOwner::Button);
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt
if (digitalPinToTouchChannel(button.pin) >= 0) // if touch capable pin if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin
touchDetachInterrupt(button.pin); // if not assigned previously, this will do nothing touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing
#endif #endif
} }
} }
@@ -280,56 +280,54 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10) char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10) char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->arg(bt).toInt(); int hw_btn_pin = request->arg(bt).toInt();
if (i >= buttons.size()) buttons.emplace_back(hw_btn_pin, request->arg(be).toInt()); // add button to vector if (hw_btn_pin >= 0 && PinManager::allocatePin(hw_btn_pin,false,PinOwner::Button)) {
else { btnPin[i] = hw_btn_pin;
buttons[i].pin = hw_btn_pin; buttonType[i] = request->arg(be).toInt();
buttons[i].type = request->arg(be).toInt(); #ifdef ARDUINO_ARCH_ESP32
}
if (buttons[i].pin >= 0 && PinManager::allocatePin(buttons[i].pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that button pin is a valid gpio // ESP32 only: check that button pin is a valid gpio
if ((buttons[i].type == BTN_TYPE_ANALOG) || (buttons[i].type == BTN_TYPE_ANALOG_INVERTED)) { if ((buttonType[i] == BTN_TYPE_ANALOG) || (buttonType[i] == BTN_TYPE_ANALOG_INVERTED))
if (digitalPinToAnalogChannel(buttons[i].pin) < 0) { {
if (digitalPinToAnalogChannel(btnPin[i]) < 0) {
// not an ADC analog pin // not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), buttons[i].pin, i); DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[i], i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button); btnPin[i] = -1;
buttons[i].type = BTN_TYPE_NONE; PinManager::deallocatePin(hw_btn_pin,PinOwner::Button);
} else { } else {
analogReadResolution(12); // see #4040 analogReadResolution(12); // see #4040
} }
} else if ((buttons[i].type == BTN_TYPE_TOUCH || buttons[i].type == BTN_TYPE_TOUCH_SWITCH)) { }
if (digitalPinToTouchChannel(buttons[i].pin) < 0) { else if ((buttonType[i] == BTN_TYPE_TOUCH || buttonType[i] == BTN_TYPE_TOUCH_SWITCH))
{
if (digitalPinToTouchChannel(btnPin[i]) < 0)
{
// not a touch pin // not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), buttons[i].pin, i); DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button); btnPin[i] = -1;
buttons[i].type = BTN_TYPE_NONE; PinManager::deallocatePin(hw_btn_pin,PinOwner::Button);
} }
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(buttons[i].pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) else
#endif {
} else touchAttachInterrupt(btnPin[i], touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
#endif }
#endif
}
else
#endif
{ {
// regular buttons and switches
if (disablePullUp) { if (disablePullUp) {
pinMode(buttons[i].pin, INPUT); pinMode(btnPin[i], INPUT);
} else { } else {
#ifdef ESP32 #ifdef ESP32
pinMode(buttons[i].pin, buttons[i].type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); pinMode(btnPin[i], buttonType[i]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else #else
pinMode(buttons[i].pin, INPUT_PULLUP); pinMode(btnPin[i], INPUT_PULLUP);
#endif #endif
} }
} }
} else { } else {
buttons[i].pin = -1; btnPin[i] = -1;
buttons[i].type = BTN_TYPE_NONE; buttonType[i] = BTN_TYPE_NONE;
}
}
// we should remove all unused buttons from the vector
for (int i = buttons.size()-1; i > 0; i--) {
if (buttons[i].pin < 0 && buttons[i].type == BTN_TYPE_NONE) {
buttons.erase(buttons.begin() + i); // remove button from vector
} }
} }
@@ -533,16 +531,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
macroAlexaOff = request->arg(F("A1")).toInt(); macroAlexaOff = request->arg(F("A1")).toInt();
macroCountdown = request->arg(F("MC")).toInt(); macroCountdown = request->arg(F("MC")).toInt();
macroNl = request->arg(F("MN")).toInt(); macroNl = request->arg(F("MN")).toInt();
int i = 0; for (unsigned i=0; i<WLED_MAX_BUTTONS; i++) {
for (auto &button : buttons) { char mp[4] = "MP"; mp[2] = (i<10?48:55)+i; mp[3] = 0; // short
char mp[4] = "MP"; mp[2] = (i<10?'0':'A'-10)+i; mp[3] = 0; // short char ml[4] = "ML"; ml[2] = (i<10?48:55)+i; ml[3] = 0; // long
char ml[4] = "ML"; ml[2] = (i<10?'0':'A'-10)+i; ml[3] = 0; // long char md[4] = "MD"; md[2] = (i<10?48:55)+i; md[3] = 0; // double
char md[4] = "MD"; md[2] = (i<10?'0':'A'-10)+i; md[3] = 0; // double
//if (!request->hasArg(mp)) break; //if (!request->hasArg(mp)) break;
button.macroButton = request->arg(mp).toInt(); // these will default to 0 if not present macroButton[i] = request->arg(mp).toInt(); // these will default to 0 if not present
button.macroLongPress = request->arg(ml).toInt(); macroLongPress[i] = request->arg(ml).toInt();
button.macroDoublePress = request->arg(md).toInt(); macroDoublePress[i] = request->arg(md).toInt();
i++;
} }
char k[3]; k[2] = 0; char k[3]; k[2] = 0;

View File

@@ -1159,62 +1159,60 @@ String computeSHA1(const String& input) {
} }
#ifdef ESP32 #ifdef ESP32
#include "esp_adc_cal.h" static String dump_raw_block(esp_efuse_block_t block)
String generateDeviceFingerprint() { {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint const int WORDS = 8; // ESP32: 8×32-bit words per block i.e. 256bits
esp_chip_info_t chip_info; uint32_t buf[WORDS] = {0};
esp_chip_info(&chip_info);
esp_efuse_mac_get_default((uint8_t*)fp); const esp_efuse_desc_t d = {
fp[1] ^= ESP.getFlashChipSize(); .efuse_block = block,
fp[0] ^= chip_info.full_revision | (chip_info.model << 16); .bit_start = 0,
// mix in ADC calibration data: .bit_count = WORDS * 32
esp_adc_cal_characteristics_t ch; };
#if SOC_ADC_MAX_BITWIDTH == 13 // S2 has 13 bit ADC const esp_efuse_desc_t* field[2] = { &d, NULL };
constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_13;
#else esp_err_t err = esp_efuse_read_field_blob(field, buf, WORDS * 32);
constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_12; if (err != ESP_OK) {
#endif return "";
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, myBIT_WIDTH, 1100, &ch);
fp[0] ^= ch.coeff_a;
fp[1] ^= ch.coeff_b;
if (ch.low_curve) {
for (int i = 0; i < 8; i++) {
fp[0] ^= ch.low_curve[i];
}
} }
if (ch.high_curve) {
for (int i = 0; i < 8; i++) { String result = "";
fp[1] ^= ch.high_curve[i]; for (const unsigned int i : buf) {
} char line[32];
sprintf(line, "0x%08X", i);
result += line;
} }
char fp_string[17]; // 16 hex chars + null terminator return result;
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
return String(fp_string);
}
#else // ESP8266
String generateDeviceFingerprint() {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
WiFi.macAddress((uint8_t*)&fp); // use MAC address as fingerprint base
fp[0] ^= ESP.getFlashChipId();
fp[1] ^= ESP.getFlashChipSize() | ESP.getFlashChipVendorId() << 16;
char fp_string[17]; // 16 hex chars + null terminator
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
return String(fp_string);
} }
#endif #endif
// Generate a device ID based on SHA1 hash of MAC address salted with other unique device info
// Generate a device ID based on SHA1 hash of MAC address salted with "WLED"
// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total) // Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total)
String getDeviceId() { String getDeviceId() {
static String cachedDeviceId = ""; static String cachedDeviceId = "";
if (cachedDeviceId.length() > 0) return cachedDeviceId; if (cachedDeviceId.length() > 0) return cachedDeviceId;
uint8_t mac[6];
WiFi.macAddress(mac);
char macStr[18];
sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase // The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase
// MAC is salted with other consistent device info to avoid rainbow table attacks. // MAC is salted with other consistent device info to avoid rainbow table attacks.
// If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices, // If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices,
// but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable. // but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable.
// If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1 // If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1
#ifdef ESP8266
String firstHash = computeSHA1(generateDeviceFingerprint()); String deviceString = String(macStr) + "WLED" + ESP.getFlashChipId();
#else
String deviceString = String(macStr) + "WLED" + ESP.getChipModel() + ESP.getChipRevision();
deviceString += dump_raw_block(EFUSE_BLK0);
deviceString += dump_raw_block(EFUSE_BLK1);
deviceString += dump_raw_block(EFUSE_BLK2);
deviceString += dump_raw_block(EFUSE_BLK3);
#endif
String firstHash = computeSHA1(deviceString);
// Second hash: SHA1 of the first hash // Second hash: SHA1 of the first hash
String secondHash = computeSHA1(firstHash); String secondHash = computeSHA1(firstHash);

View File

@@ -286,10 +286,10 @@ WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS);
// Hardware and pin config // Hardware and pin config
#ifndef BTNPIN #ifndef BTNPIN
#define BTNPIN 0 #define BTNPIN 0,-1
#endif #endif
#ifndef BTNTYPE #ifndef BTNTYPE
#define BTNTYPE BTN_TYPE_PUSH #define BTNTYPE BTN_TYPE_PUSH,BTN_TYPE_NONE
#endif #endif
#ifndef RLYPIN #ifndef RLYPIN
WLED_GLOBAL int8_t rlyPin _INIT(-1); WLED_GLOBAL int8_t rlyPin _INIT(-1);
@@ -365,7 +365,7 @@ WLED_GLOBAL wifi_options_t wifiOpt _INIT_N(({0, 1, false, AP_BEHAVIOR_BOOT_NO_CO
#define force802_3g wifiOpt.force802_3g #define force802_3g wifiOpt.force802_3g
#else #else
WLED_GLOBAL int8_t selectedWiFi _INIT(0); WLED_GLOBAL int8_t selectedWiFi _INIT(0);
WLED_GLOBAL byte apChannel _INIT(6); // 2.4GHz WiFi AP channel (1-13) WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13)
WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID
WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@@ -571,6 +571,9 @@ WLED_GLOBAL byte countdownMin _INIT(0) , countdownSec _INIT(0);
WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over
WLED_GLOBAL byte macroCountdown _INIT(0); WLED_GLOBAL byte macroCountdown _INIT(0);
WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0); WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0);
WLED_GLOBAL byte macroButton[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroLongPress[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroDoublePress[WLED_MAX_BUTTONS] _INIT({0});
// Security CONFIG // Security CONFIG
#ifdef WLED_OTA_PASS #ifdef WLED_OTA_PASS
@@ -636,32 +639,13 @@ WLED_GLOBAL byte briLast _INIT(128); // brightness before
WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function in ir.cpp WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function in ir.cpp
// button // button
struct Button { WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({BTNPIN});
unsigned long pressedTime; // time button was pressed WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTNTYPE});
unsigned long waitTime; // time to wait for next button press
int8_t pin; // pin number
struct {
uint8_t type : 6; // button type (push, long, double, etc.)
bool pressedBefore : 1; // button was pressed before
bool longPressed : 1; // button was long pressed
};
uint8_t macroButton; // macro/preset to call on button press
uint8_t macroLongPress; // macro/preset to call on long press
uint8_t macroDoublePress; // macro/preset to call on double press
Button(int8_t p, uint8_t t, uint8_t mB = 0, uint8_t mLP = 0, uint8_t mDP = 0)
: pressedTime(0)
, waitTime(0)
, pin(p)
, type(t)
, pressedBefore(false)
, longPressed(false)
, macroButton(mB)
, macroLongPress(mLP)
, macroDoublePress(mDP) {}
};
WLED_GLOBAL std::vector<Button> buttons; // vector of button structs
WLED_GLOBAL bool buttonPublishMqtt _INIT(false); WLED_GLOBAL bool buttonPublishMqtt _INIT(false);
WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL unsigned long buttonPressedTime[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL unsigned long buttonWaitTime[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL bool disablePullUp _INIT(false); WLED_GLOBAL bool disablePullUp _INIT(false);
WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD); WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD);

View File

@@ -27,7 +27,6 @@ static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char s_not_found[] PROGMEM = "Not found"; static const char s_not_found[] PROGMEM = "Not found";
static const char s_wsec[] PROGMEM = "wsec.json"; static const char s_wsec[] PROGMEM = "wsec.json";
static const char s_func[] PROGMEM = "func"; static const char s_func[] PROGMEM = "func";
static const char s_list[] PROGMEM = "list";
static const char s_path[] PROGMEM = "path"; static const char s_path[] PROGMEM = "path";
static const char s_cache_control[] PROGMEM = "Cache-Control"; static const char s_cache_control[] PROGMEM = "Cache-Control";
static const char s_no_store[] PROGMEM = "no-store"; static const char s_no_store[] PROGMEM = "no-store";
@@ -67,7 +66,7 @@ static bool inLocalSubnet(const IPAddress &client) {
*/ */
static void generateEtag(char *etag, uint16_t eTagSuffix) { static void generateEtag(char *etag, uint16_t eTagSuffix) {
sprintf_P(etag, PSTR("%u-%02x-%04x"), WEB_BUILD_TIME, cacheInvalidate, eTagSuffix); sprintf_P(etag, PSTR("%7d-%02x-%04x"), VERSION, cacheInvalidate, eTagSuffix);
} }
static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix = 0) { static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix = 0) {
@@ -227,18 +226,14 @@ void createEditHandler() {
return; return;
} }
const String& func = request->arg(FPSTR(s_func)); const String& func = request->arg(FPSTR(s_func));
bool legacyList = false;
if (request->hasArg(FPSTR(s_list))) {
legacyList = true; // support for '?list=/'
}
if(func.length() == 0 && !legacyList) { if(func.length() == 0) {
// default: serve the editor page // default: serve the editor page
handleStaticContent(request, FPSTR(_edit_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_edit, PAGE_edit_length); handleStaticContent(request, FPSTR(_edit_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_edit, PAGE_edit_length);
return; return;
} }
if (func == FPSTR(s_list) || legacyList) { if (func == "list") {
bool first = true; bool first = true;
AsyncResponseStream* response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JSON)); AsyncResponseStream* response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JSON));
response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store)); response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store));
@@ -248,15 +243,15 @@ void createEditHandler() {
File rootdir = WLED_FS.open("/", "r"); File rootdir = WLED_FS.open("/", "r");
File rootfile = rootdir.openNextFile(); File rootfile = rootdir.openNextFile();
while (rootfile) { while (rootfile) {
String name = rootfile.name(); String name = rootfile.name();
if (name.indexOf(FPSTR(s_wsec)) >= 0) { if (name.indexOf(FPSTR(s_wsec)) >= 0) {
rootfile = rootdir.openNextFile(); // skip wsec.json rootfile = rootdir.openNextFile(); // skip wsec.json
continue; continue;
} }
if (!first) response->write(','); if (!first) response->write(',');
first = false; first = false;
response->printf_P(PSTR("{\"name\":\"%s\",\"type\":\"file\",\"size\":%u}"), name.c_str(), rootfile.size()); response->printf_P(PSTR("{\"name\":\"%s\",\"type\":\"file\",\"size\":%u}"), name.c_str(), rootfile.size());
rootfile = rootdir.openNextFile(); rootfile = rootdir.openNextFile();
} }
rootfile.close(); rootfile.close();
rootdir.close(); rootdir.close();

View File

@@ -291,7 +291,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str()); settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str());
// set limits // set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d);"), settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
WLED_MAX_BUSSES, WLED_MAX_BUSSES,
WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI
MAX_LEDS_PER_BUS, MAX_LEDS_PER_BUS,
@@ -299,8 +299,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
MAX_LEDS, MAX_LEDS,
WLED_MAX_COLOR_ORDER_MAPPINGS, WLED_MAX_COLOR_ORDER_MAPPINGS,
WLED_MAX_DIGITAL_CHANNELS, WLED_MAX_DIGITAL_CHANNELS,
WLED_MAX_ANALOG_CHANNELS, WLED_MAX_ANALOG_CHANNELS
WLED_MAX_BUTTONS
); );
printSetFormCheckbox(settingsScript,PSTR("MS"),strip.autoSegments); printSetFormCheckbox(settingsScript,PSTR("MS"),strip.autoSegments);
@@ -404,9 +403,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("RL"),rlyPin); printSetFormValue(settingsScript,PSTR("RL"),rlyPin);
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde); printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain); printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
int i = 0; for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
for (const auto &button : buttons) { settingsScript.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]);
settingsScript.printf_P(PSTR("addBtn(%d,%d,%d);"), i++, button.pin, button.type);
} }
printSetFormCheckbox(settingsScript,PSTR("IP"),disablePullUp); printSetFormCheckbox(settingsScript,PSTR("IP"),disablePullUp);
printSetFormValue(settingsScript,PSTR("TT"),touchThreshold); printSetFormValue(settingsScript,PSTR("TT"),touchThreshold);
@@ -580,9 +578,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("A1"),macroAlexaOff); printSetFormValue(settingsScript,PSTR("A1"),macroAlexaOff);
printSetFormValue(settingsScript,PSTR("MC"),macroCountdown); printSetFormValue(settingsScript,PSTR("MC"),macroCountdown);
printSetFormValue(settingsScript,PSTR("MN"),macroNl); printSetFormValue(settingsScript,PSTR("MN"),macroNl);
int i = 0; for (unsigned i=0; i<WLED_MAX_BUTTONS; i++) {
for (const auto &button : buttons) { settingsScript.printf_P(PSTR("addRow(%d,%d,%d,%d);"), i, macroButton[i], macroLongPress[i], macroDoublePress[i]);
settingsScript.printf_P(PSTR("addRow(%d,%d,%d,%d);"), i++, button.macroButton, button.macroLongPress, button.macroDoublePress);
} }
char k[4]; char k[4];