From a9511e6a298873898d22001de9cf4ac48735e239 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 24 Dec 2025 16:59:18 +0100 Subject: [PATCH] implements #2848 --- CHANGELOG_LATEST.md | 1 + lib/PButton/PButton.h | 19 ++-- lib_standalone/Arduino.h | 2 + src/ESP32React/NetworkStatus.cpp | 1 - src/core/analogsensor.cpp | 12 +-- src/core/system.cpp | 162 ++++++++++++++++++++----------- src/core/system.h | 35 +++++-- src/core/telegram.cpp | 2 +- 8 files changed, 149 insertions(+), 85 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index db8964bff..4f8736371 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -39,6 +39,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - added api/metrics endpoint for prometheus integration by @gr3enk [#2774](https://github.com/emsesp/EMS-ESP32/pull/2774) - added RTL8201 to eth phy list [#2800](https://github.com/emsesp/EMS-ESP32/issues/2800) - added partitions to Web UI Version page, so previous firmware versions can be installed [#2837](https://github.com/emsesp/EMS-ESP32/issues/2837) +- button pressures show LED. On a long press (10 seconds) the LED flashes for 5 seconds to indicate a factory reset is about to happen. [#2848](https://github.com/emsesp/EMS-ESP32/issues/2848) ## Fixed diff --git a/lib/PButton/PButton.h b/lib/PButton/PButton.h index d7be73447..a7f85f9a0 100644 --- a/lib/PButton/PButton.h +++ b/lib/PButton/PButton.h @@ -36,6 +36,9 @@ class PButton { void onVLongPress(uint16_t, buttonEventHandler handler); bool init(uint8_t pin, bool pullMode); bool check(void); + bool button_busy() { + return buttonBusy_; + } private: uint16_t Debounce_; // Debounce period to prevent flickering when pressing or releasing the button (in ms) @@ -47,21 +50,21 @@ class PButton { bool pullMode_; bool enabled_; - bool state_; // Value read from button - bool lastState_; // Last value of button state - bool dblClickWaiting_; // whether we're waiting for a double click (down) - bool dblClickOnNextUp_; // whether to register a double click on next release, or whether to wait and click - bool singleClickOK_; // whether it's OK to do a single click + bool state_; // Value read from button + bool lastState_; // Last value of button state + bool dblClickWaiting_; // whether we're waiting for a double click (down) + bool dblClickOnNextUp_; // whether to register a double click on next release, or whether to wait and click + bool singleClickOK_; // whether it's OK to do a single click - uint32_t downTime_; // time the button was pressed down - uint32_t upTime_; // time the button was released + uint32_t downTime_; // time the button was pressed down + uint32_t upTime_; // time the button was released bool ignoreUP_; // whether to ignore the button release because the click+hold was triggered bool waitForUP_; // when held, whether to wait for the up event bool longPressHappened_; // whether or not the hold event happened already bool vLongPressHappened_; // whether or not the long hold event happened already - bool buttonBusy_; // false if idle + bool buttonBusy_; // false if idle buttonEventHandler cb_onClick, cb_onDblClick, cb_onLongPress, cb_onVLongPress; }; diff --git a/lib_standalone/Arduino.h b/lib_standalone/Arduino.h index 9fe2f9ec1..1ac2aea3b 100644 --- a/lib_standalone/Arduino.h +++ b/lib_standalone/Arduino.h @@ -59,6 +59,8 @@ typedef double double_t; #define snprintf snprintf_P // to keep backwards compatibility +#define IRAM_ATTR + void pinMode(uint8_t pin, uint8_t mode); void digitalWrite(uint8_t pin, uint8_t value); int digitalRead(uint8_t pin); diff --git a/src/ESP32React/NetworkStatus.cpp b/src/ESP32React/NetworkStatus.cpp index 3d82cb57b..a67a356ba 100644 --- a/src/ESP32React/NetworkStatus.cpp +++ b/src/ESP32React/NetworkStatus.cpp @@ -55,7 +55,6 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { } } else if (wifi_status == WL_CONNECTED) { root["local_ip"] = WiFi.localIP().toString(); -// #if ESP_ARDUINO_VERSION_MAJOR < 3 #if ESP_IDF_VERSION_MAJOR < 5 root["local_ipv6"] = WiFi.localIPv6().toString(); #else diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 4e461b9c1..d62701ded 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -289,11 +289,7 @@ void AnalogSensor::reload(bool get_nvs) { uint8_t r = v / 10000; uint8_t g = (v - r * 10000) / 100; uint8_t b = v % 100; -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); -#else - rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); -#endif + EMSESP_RGB_WRITE(sensor.gpio(), 2 * r, 2 * g, 2 * b); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b); } else if (sensor.type() == AnalogType::DIGITAL_OUT) { LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio()); @@ -955,11 +951,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) { uint8_t r = v / 10000; uint8_t g = (v - r * 10000) / 100; uint8_t b = v % 100; -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); -#else - rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); -#endif + EMSESP_RGB_WRITE(sensor.gpio(), 2 * r, 2 * g, 2 * b); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b); } else if (sensor.type() == AnalogType::PULSE) { uint8_t v = val; diff --git a/src/core/system.cpp b/src/core/system.cpp index df1aa2255..5c7941c11 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -92,10 +92,20 @@ uuid::syslog::SyslogService System::syslog_; uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN}; // init statics -PButton System::myPButton_; -bool System::test_set_all_active_ = false; -uint32_t System::max_alloc_mem_; -uint32_t System::heap_mem_; +PButton System::myPButton_; +bool System::test_set_all_active_ = false; +uint32_t System::max_alloc_mem_; +uint32_t System::heap_mem_; + +// LED flash timer for factory reset +volatile uint8_t System::led_flash_count_ = 0; +volatile bool System::led_flash_state_ = false; +uint8_t System::led_flash_gpio_ = 0; +uint8_t System::led_flash_type_ = 0; +#ifndef EMSESP_STANDALONE +hw_timer_t * System::led_flash_timer_ = nullptr; +#endif + std::vector> System::valid_system_gpios_; std::vector> System::used_gpios_; std::vector> System::snapshot_used_gpios_; @@ -405,6 +415,11 @@ bool System::set_partition(const char * partitionname) { // app0 or app1 // on 16MB we have the additional boot and factory partitions void System::system_restart(const char * partitionname) { +#ifdef EMSESP_DEBUG + EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL); + return; +#endif + #ifndef EMSESP_STANDALONE // see if we are forcing a partition to use if (partitionname != nullptr) { @@ -449,8 +464,9 @@ void System::system_restart(const char * partitionname) { store_nvs_values(); // save any NVS values Shell::loop_all(); // flush log to output Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1 + EMSuart::stop(); // stop UART so there is no interference delay(1000); // wait 1 second - ESP.restart(); + ESP.restart(); // ka-boom! #else EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL); if (partitionname != nullptr) { @@ -640,15 +656,71 @@ void System::button_OnDblClick(PButton & b) { EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password } +// LED flash timer interrupt service routine +void IRAM_ATTR System::led_flash_timer_isr() { + led_flash_state_ = !led_flash_state_; // Toggle LED state + + if (led_flash_state_) { + led_flash_type_ ? EMSESP_RGB_WRITE(led_flash_gpio_, 255, 255, 255) : digitalWrite(led_flash_gpio_, LED_ON); // white, on + } else { + led_flash_type_ ? EMSESP_RGB_WRITE(led_flash_gpio_, 0, 0, 0) : digitalWrite(led_flash_gpio_, !LED_ON); // off + } + + // Increment flash count, 2 toggles = 1 flash cycle. And stop after 5 seconds (5000ms/100ms = 50) + if (led_flash_count_++ >= 50) { + stop_led_flash(); + } +} + +// Start the LED flash timer +void System::start_led_flash() { +#ifndef EMSESP_STANDALONE + // Don't start if already running + if (led_flash_timer_ != nullptr) { + return; + } + + // Get LED settings + EMSESP::webSettingsService.read([&](WebSettings & settings) { + led_flash_type_ = settings.led_type; + led_flash_gpio_ = settings.led_gpio; + }); + + // Reset counter and state + led_flash_count_ = 0; + led_flash_state_ = false; + + led_flash_timer_ = timerBegin(0, 80, true); // Create and start timer (prescaler 80 for 1us tick, counting up) + timerAttachInterrupt(led_flash_timer_, &led_flash_timer_isr, true); // Attach interrupt handler + timerAlarmWrite(led_flash_timer_, 100000, true); // Set alarm to trigger every 100ms + timerAlarmEnable(led_flash_timer_); // Enable the alarm +#endif +} + +// Stop the LED flash timer +void System::stop_led_flash() { +#ifndef EMSESP_STANDALONE + if (led_flash_timer_ != nullptr) { + // Stop and detach timer + timerAlarmDisable(led_flash_timer_); + timerDetachInterrupt(led_flash_timer_); + timerEnd(led_flash_timer_); + led_flash_timer_ = nullptr; + led_flash_type_ ? EMSESP_RGB_WRITE(led_flash_gpio_, 0, 0, 0) : digitalWrite(led_flash_gpio_, !LED_ON); // Turn off LED + } +#endif +} + // button long press void System::button_OnLongPress(PButton & b) { LOG_NOTICE("Button pressed - long press - perform factory reset"); + start_led_flash(); // Start the non-blocking LED flash timer for 5 seconds #ifndef EMSESP_STANDALONE System::command_format(nullptr, 0); #endif } -// button indefinite press - do nothing for now +// button indefinite press - boots to boot partition void System::button_OnVLongPress(PButton & b) { LOG_NOTICE("Button pressed - very long press - restart from factory/boot partition"); EMSESP::system_.system_restart("boot"); @@ -673,20 +745,12 @@ void System::button_init() { // set the LED to on or off when in normal operating mode void System::led_init() { // disabled old led port before setting new one -#if ESP_ARDUINO_VERSION_MAJOR < 3 - led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#else - led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#endif + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); if ((led_gpio_)) { // 0 means disabled if (led_type_) { // rgb LED WS2812B, use Neopixel -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(led_gpio_, 0, 0, 0); -#else - rgbLedWrite(led_gpio_, 0, 0, 0); -#endif + EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0); } else { pinMode(led_gpio_, OUTPUT); digitalWrite(led_gpio_, !LED_ON); // start with LED off @@ -945,20 +1009,12 @@ void System::system_check() { if (healthcheck_ == 0) { // everything is healthy, show LED permanently on or off depending on setting if (led_gpio_) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - led_type_ ? neopixelWrite(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); -#else - led_type_ ? rgbLedWrite(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); -#endif + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0) : digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); } } else { // turn off LED so we're ready to the flashes if (led_gpio_) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#else - led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#endif + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); } } } @@ -991,15 +1047,27 @@ void System::commands_init() { // 2 x flash = the network (wifi or ethernet) is not connected // 3 x flash = both EMS bus and network are failing. This is a critical error! void System::led_monitor() { + // check if button is busy or has been pressed - LED on to white + static bool button_busy_ = false; + if (button_busy_ != myPButton_.button_busy()) { + button_busy_ = myPButton_.button_busy(); + if (button_busy_) { + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 255, 255, 255) : digitalWrite(led_gpio_, LED_ON); // on + } else { + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // off + } + } + // we only need to run the LED healthcheck if there are errors - if (!healthcheck_ || !led_gpio_) { + if (!healthcheck_ || !led_gpio_ || button_busy_) { return; // all good } static uint32_t led_long_timer_ = 1; // 1 will kick it off immediately static uint32_t led_short_timer_ = 0; static uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer - auto current_time = uuid::get_uptime(); + + auto current_time = uuid::get_uptime(); // first long pause before we start flashing if (led_long_timer_ && (uint32_t)(current_time - led_long_timer_) >= HEALTHCHECK_LED_LONG_DUARATION) { @@ -1018,11 +1086,7 @@ void System::led_monitor() { // reset the whole sequence led_long_timer_ = uuid::get_uptime(); led_flash_step_ = 0; -#if ESP_ARDUINO_VERSION_MAJOR < 3 - led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // LED off -#else - led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // LED off -#endif + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // LED off } else if (led_flash_step_ % 2) { // handle the step events (on odd numbers 3,5,7,etc). see if we need to turn on a LED // 1 flash is the EMS bus is not connected @@ -1031,33 +1095,17 @@ void System::led_monitor() { if (led_type_) { if (led_flash_step_ == 3) { if ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red -#else - rgbLedWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red -#endif + EMSESP_RGB_WRITE(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red } else if ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue -#else - rgbLedWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue -#endif + EMSESP_RGB_WRITE(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue } } if (led_flash_step_ == 5 && (healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red -#else - rgbLedWrite(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red -#endif + EMSESP_RGB_WRITE(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red } if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) && ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - neopixelWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue -#else - rgbLedWrite(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue -#endif + EMSESP_RGB_WRITE(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue } } else { if ((led_flash_step_ == 3) @@ -1075,17 +1123,13 @@ void System::led_monitor() { } if (led_on_) { - digitalWrite(led_gpio_, LED_ON); // LED off + digitalWrite(led_gpio_, LED_ON); // LED on } } } else { // turn the led off after the flash, on even number count if (led_on_) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#else - led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); -#endif + led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); led_on_ = false; } } diff --git a/src/core/system.h b/src/core/system.h index 8ca6d0576..832fbfef6 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -37,6 +37,12 @@ #include #include +#if ESP_ARDUINO_VERSION_MAJOR < 3 +#define EMSESP_RGB_WRITE neopixelWrite +#else +#define EMSESP_RGB_WRITE rgbLedWrite +#endif + #if CONFIG_IDF_TARGET_ESP32 // there is no official API available on the original ESP32 extern "C" { @@ -56,7 +62,7 @@ using uuid::console::Shell; #define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json" -#define RGB_LED_BRIGHTNESS 20 +#define RGB_LED_BRIGHTNESS 20 // 255 is max brightness namespace emsesp { @@ -391,9 +397,26 @@ class System { static void button_OnDblClick(PButton & b); static void button_OnLongPress(PButton & b); static void button_OnVLongPress(PButton & b); - static constexpr uint32_t BUTTON_Debounce = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms) - static constexpr uint32_t BUTTON_DblClickDelay = 250; // Max period between clicks for a double click event (in ms) - static constexpr uint32_t BUTTON_LongPressDelay = 9500; // Hold period for a long press event (in ms) - 10 seconds + static constexpr uint32_t BUTTON_Debounce = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms) + static constexpr uint32_t BUTTON_DblClickDelay = 250; // Max period between clicks for a double click event (in ms) + +// LED flash timer for factory reset +#ifndef EMSESP_STANDALONE + static hw_timer_t * led_flash_timer_; +#endif + static void IRAM_ATTR led_flash_timer_isr(); + static volatile uint8_t led_flash_count_; + static volatile bool led_flash_state_; + static uint8_t led_flash_gpio_; + static uint8_t led_flash_type_; + static void start_led_flash(); + static void stop_led_flash(); + +#ifdef EMSESP_DEBUG + static constexpr uint32_t BUTTON_LongPressDelay = 2000; // Hold period for a long press event (in ms) - 2 seconds, for debugging +#else + static constexpr uint32_t BUTTON_LongPressDelay = 9500; // Hold period for a long press event (in ms) - ~10 seconds +#endif static constexpr uint32_t BUTTON_VLongPressDelay = 20000; // Hold period for a very long press event (in ms) - 20 seconds // healthcheck @@ -402,8 +425,8 @@ class System { #else static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds #endif - static constexpr uint32_t HEALTHCHECK_LED_LONG_DUARATION = 1500; - static constexpr uint32_t HEALTHCHECK_LED_FLASH_DUARATION = 150; + static constexpr uint32_t HEALTHCHECK_LED_LONG_DUARATION = 1500; // 1.5 seconds + static constexpr uint32_t HEALTHCHECK_LED_FLASH_DUARATION = 150; // 150ms static constexpr uint8_t HEALTHCHECK_NO_BUS = (1 << 0); // 1 static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2 static constexpr uint8_t LED_ON = HIGH; // LED on diff --git a/src/core/telegram.cpp b/src/core/telegram.cpp index 0f3298e46..ea9ba2b89 100644 --- a/src/core/telegram.cpp +++ b/src/core/telegram.cpp @@ -146,7 +146,7 @@ void RxService::add(uint8_t * data, uint8_t length) { } // ignore src==0, https://github.com/emsesp/EMS-ESP32/issues/2378 - if (!(data[0] & 0x7F)) { + if (!(data[0] & 0x7F) && bus_connected()) { LOG_WARNING("Invalid source: %s", Helpers::data_to_hex(data, length).c_str()); // include CRC return; }