-
Notifications
You must be signed in to change notification settings - Fork 4
Prevent boot buttons from hiding recoverable Tubes #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #!/usr/bin/env python3 | ||
| """Source contracts for boot rescue admission and button diagnostics.""" | ||
| from pathlib import Path | ||
|
|
||
| source = (Path(__file__).parents[1] / "wled00" / "wled.cpp").read_text() | ||
| assert "checkRescuePin" not in source | ||
| assert "checkRescueSerial" in source | ||
| assert "WLED_DISABLE_STUCK_BUTTON_DIAGNOSTICS" in source | ||
| assert source.count("diagnoseBootButtons();") >= 2 | ||
| assert "stuckButtonInterval = 10000UL" in source | ||
| assert "activeSince[b] = 0" in source | ||
| assert "activeInitialized[b] = false" in source | ||
| assert "if (!activeInitialized[b])" in source | ||
| assert "diagnosticPin[b] != pin" in source | ||
| assert "diagnosticType[b] != type" in source | ||
| assert "BTN_TYPE_PUSH && button.type != BTN_TYPE_PUSH_ACT_HIGH" in source | ||
| assert "active at boot" not in source | ||
| assert "sustained active" in source | ||
| assert "WLED button diagnostics: STUCK_BUTTON" in source | ||
| assert "WLED button diagnostics: AVAILABLE/INACTIVE" in source | ||
| assert "HEALTHY/AVAILABLE" not in source | ||
| assert "WLED button diagnostics: disabled" in source | ||
| assert 'DEBUG_PRINTF_P(PSTR("heap %u\\n")' in source | ||
| assert 'PSTR("heap %u\\\\n")' not in source | ||
| print("boot rescue admission and sustained stuck-button diagnostics: PASS") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,21 +37,6 @@ WLED::WLED() | |
| { | ||
| } | ||
|
|
||
| static bool checkRescuePin() | ||
| { | ||
| #if WLED_RESCUE_WINDOW_MS > 0 | ||
| constexpr int8_t rescuePins[] = {BTNPIN}; | ||
| constexpr size_t rescuePinCount = sizeof(rescuePins) / sizeof(rescuePins[0]); | ||
| if (rescuePinCount == 0 || rescuePins[0] < 0) return false; | ||
|
|
||
| pinMode(rescuePins[0], INPUT_PULLUP); | ||
| delay(2); | ||
| return digitalRead(rescuePins[0]) == LOW; | ||
| #else | ||
| return false; | ||
| #endif | ||
| } | ||
|
|
||
| static bool checkRescueSerial() | ||
| { | ||
| #if WLED_RESCUE_WINDOW_MS > 0 | ||
|
|
@@ -80,12 +65,6 @@ static bool checkRescueSerial() | |
| static void detectRescueMode() | ||
| { | ||
| #if WLED_RESCUE_WINDOW_MS > 0 | ||
| if (checkRescuePin()) { | ||
| wledRescueMode = true; | ||
| Serial.println(F("WLED rescue mode: startup button held")); | ||
| return; | ||
| } | ||
|
|
||
| Serial.print(F("WLED rescue window: send '")); | ||
| Serial.print(F(WLED_RESCUE_SERIAL_TOKEN)); | ||
| Serial.println(F("' to skip config, usermods, WiFi, and ESP-NOW")); | ||
|
|
@@ -96,6 +75,94 @@ static void detectRescueMode() | |
| #endif | ||
| } | ||
|
|
||
| // AI: below section was generated by an AI | ||
| // Report configured digital momentary inputs only after sustained activity. | ||
| static void diagnoseBootButtons() | ||
| { | ||
| #ifdef WLED_DISABLE_STUCK_BUTTON_DIAGNOSTICS | ||
| static bool reported = false; | ||
| if (!reported) { | ||
| Serial.println(F("WLED button diagnostics: disabled (WLED_DISABLE_STUCK_BUTTON_DIAGNOSTICS)")); | ||
| reported = true; | ||
| } | ||
| return; | ||
| #else | ||
| constexpr unsigned long stuckButtonInterval = 10000UL; | ||
| static unsigned long activeSince[WLED_MAX_BUTTONS] = {0}; | ||
| static bool activeInitialized[WLED_MAX_BUTTONS] = {false}; | ||
| static bool reportedStuck[WLED_MAX_BUTTONS] = {false}; | ||
| static int diagnosticPin[WLED_MAX_BUTTONS] = {0}; | ||
| static uint8_t diagnosticType[WLED_MAX_BUTTONS] = {0}; | ||
| static bool diagnosticIdentityInitialized[WLED_MAX_BUTTONS] = {false}; | ||
| bool available = false; | ||
| bool stuck = false; | ||
| const unsigned long now = millis(); | ||
| for (size_t b = 0; b < WLED_MAX_BUTTONS; b++) { | ||
| if (b >= buttons.size()) { | ||
| activeSince[b] = 0; | ||
| activeInitialized[b] = false; | ||
| reportedStuck[b] = false; | ||
| diagnosticIdentityInitialized[b] = false; | ||
| continue; | ||
| } | ||
| const auto &button = buttons[b]; | ||
| const int pin = button.pin; | ||
| const uint8_t type = (uint8_t)button.type; | ||
| if (!diagnosticIdentityInitialized[b] || diagnosticPin[b] != pin || diagnosticType[b] != type) { | ||
| diagnosticPin[b] = pin; | ||
| diagnosticType[b] = type; | ||
| diagnosticIdentityInitialized[b] = true; | ||
| activeSince[b] = 0; | ||
| activeInitialized[b] = false; | ||
| reportedStuck[b] = false; | ||
| } | ||
| if (pin < 0 || (button.type != BTN_TYPE_PUSH && button.type != BTN_TYPE_PUSH_ACT_HIGH)) continue; | ||
| available = true; | ||
| const bool active = (button.type == BTN_TYPE_PUSH_ACT_HIGH) ? | ||
| (digitalRead(button.pin) == HIGH) : (digitalRead(button.pin) == LOW); | ||
| if (!active) { | ||
| activeSince[b] = 0; | ||
| activeInitialized[b] = false; | ||
| reportedStuck[b] = false; | ||
| continue; | ||
| } | ||
| if (!activeInitialized[b]) { | ||
| activeSince[b] = now; | ||
| activeInitialized[b] = true; | ||
| } | ||
| if (now - activeSince[b] >= stuckButtonInterval) { | ||
| stuck = true; | ||
| if (!reportedStuck[b]) { | ||
| Serial.printf("WLED button diagnostic: sustained active index=%u gpio=%d type=%u\n", | ||
| (unsigned)b, button.pin, (unsigned)button.type); | ||
| reportedStuck[b] = true; | ||
| } | ||
| } | ||
| } | ||
| static bool reportedHealthy = false; | ||
| static bool reportedUnavailable = false; | ||
| static bool reportedStatus = false; | ||
| if (stuck) { | ||
| if (!reportedStatus) { | ||
| Serial.println(F("WLED button diagnostics: STUCK_BUTTON")); | ||
| reportedStatus = true; | ||
| } | ||
| return; | ||
| } | ||
| reportedStatus = false; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This resets reportedStatus after the button recovers, but reportedHealthy remains true from the initial AVAILABLE/INACTIVE report. The sequence AVAILABLE -> STUCK_BUTTON -> inactive therefore emits no recovered or healthy state, leaving the last reported state as STUCK_BUTTON. Please model the status as an explicit transition, or at minimum re-arm the healthy report when entering the stuck state, and add a behavioral test for the complete inactive -> stuck -> recovered sequence. |
||
| if (available && !reportedHealthy) { | ||
| Serial.println(F("WLED button diagnostics: AVAILABLE/INACTIVE")); | ||
| reportedHealthy = true; | ||
| reportedUnavailable = false; | ||
| } else if (!available && !reportedUnavailable) { | ||
| Serial.println(F("WLED button diagnostics: disabled (no configured digital momentary buttons)")); | ||
| reportedUnavailable = true; | ||
| reportedHealthy = false; | ||
| } | ||
| #endif | ||
| } | ||
| // AI: end | ||
|
|
||
| static void handleRescueMode() | ||
| { | ||
| static bool announced = false; | ||
|
|
@@ -178,6 +245,7 @@ void WLED::loop() | |
| return; | ||
| } | ||
|
|
||
| diagnoseBootButtons(); | ||
| handleTime(); | ||
| #ifndef WLED_DISABLE_INFRARED | ||
| handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too | ||
|
|
@@ -615,6 +683,7 @@ void WLED::setup() | |
| briLast = 0; | ||
| transitionDelay = 0; | ||
| } | ||
| diagnoseBootButtons(); | ||
| DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); | ||
|
|
||
| #if defined(STATUSLED) && STATUSLED>=0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This diagnostic is called on every main-loop iteration in every WLED build, and this loop scans all WLED_MAX_BUTTONS slots even when only one button is configured. The unused-slot branch also rewrites four static arrays on every pass, so ESP32 builds can do 31 unused iterations per loop while ESP8266 builds pay the RAM and flash cost even though their rescue window is disabled. Please compile-gate the diagnostic to builds where it applies, iterate only over configured buttons, and throttle sampling to a modest interval such as 100 ms; a ten-second detector does not need hot-loop polling.