From a115c78840f72043c6f1c9e43def8ca5827061f4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 8 Jun 2025 02:00:51 -0500 Subject: [PATCH] Make ButtonThread an InputBroker --- src/graphics/Screen.cpp | 5 +- src/input/ButtonThread.cpp | 74 +++++++---------------------- src/input/ButtonThread.h | 12 ++--- src/input/ButtonThreadImpl.cpp | 17 +++++++ src/input/ButtonThreadImpl.h | 19 ++++++++ src/main.cpp | 73 ++++------------------------ src/modules/CannedMessageModule.cpp | 2 - 7 files changed, 67 insertions(+), 135 deletions(-) create mode 100644 src/input/ButtonThreadImpl.cpp create mode 100644 src/input/ButtonThreadImpl.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 6a6adaa26..8e2c1e341 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -47,7 +47,6 @@ along with this program. If not, see . #include "graphics/SharedUIDisplay.h" #include "graphics/emotes.h" #include "graphics/images.h" -#include "input/ButtonThread.h" #include "input/ScanAndSelect.h" #include "input/TouchScreenImpl1.h" #include "main.h" @@ -836,7 +835,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) if (on != screenOn) { if (on) { LOG_INFO("Turn on screen"); - buttonThread->setScreenFlag(true); powerMon->setState(meshtastic_PowerMon_State_Screen_On); #ifdef T_WATCH_S3 PMU->enablePowerOutput(XPOWERS_ALDO2); @@ -880,8 +878,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) // eInkScreensaver parameter is usually NULL (default argument), default frame used instead setScreensaverFrames(einkScreensaver); #endif - LOG_INFO("Turn off screen"); - buttonThread->setScreenFlag(false); #ifdef ELECROW_ThinkNode_M1 if (digitalRead(PIN_EINK_EN) == HIGH) { digitalWrite(PIN_EINK_EN, LOW); @@ -1761,6 +1757,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) int Screen::handleInputEvent(const InputEvent *event) { + LOG_WARN("event %u", event->inputEvent); if (NotificationRenderer::isOverlayBannerShowing()) { NotificationRenderer::inEvent = event->inputEvent; diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index 55a1aa77a..05e38b5ca 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -27,17 +27,14 @@ using namespace concurrency; -ButtonThread *buttonThread; // Declared extern in header -#if HAS_SCREEN -extern CannedMessageModule *cannedMessageModule; -#endif volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE; #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN) OneButton ButtonThread::userButton; // Get reference to static member #endif -ButtonThread::ButtonThread() : OSThread("Button") +ButtonThread::ButtonThread(const char *name) : OSThread(name) { + _originName = name; #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN) #if defined(ARCH_PORTDUINO) @@ -55,7 +52,7 @@ ButtonThread::ButtonThread() : OSThread("Button") #if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB) this->userButton = OneButton(pin, false, false); #elif defined(BUTTON_ACTIVE_LOW) - this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); + userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); #else this->userButton = OneButton(pin, true, true); #endif @@ -72,20 +69,20 @@ ButtonThread::ButtonThread() : OSThread("Button") #endif #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN) - userButton.attachClick(userButtonPressed); - userButton.setPressMs(BUTTON_LONGPRESS_MS); + userButton.attachClick([]() { btnEvent = BUTTON_EVENT_PRESSED; }); + userButton.setDebounceMs(1); if (screen) { userButton.setClickMs(20); + userButton.setPressMs(500); } else { + userButton.setPressMs(BUTTON_LONGPRESS_MS); userButton.setClickMs(BUTTON_CLICK_MS); userButton.attachDoubleClick(userButtonDoublePressed); userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton } -#if !defined(T_DECK) && \ - !defined( \ - ELECROW_ThinkNode_M2) // T-Deck immediately wakes up after shutdown, Thinknode M2 has this on the smaller ALT button +#if !defined(ELECROW_ThinkNode_M2) // T-Deck immediately wakes up after shutdown, Thinknode M2 has this on the smaller ALT button userButton.attachLongPressStart(userButtonPressedLongStart); userButton.attachLongPressStop(userButtonPressedLongStop); #endif @@ -230,10 +227,13 @@ int32_t ButtonThread::runOnce() playBoop(); // Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event) - if (inputBroker) { - InputEvent evt = {"button", INPUT_BROKER_USER_PRESS, 0, 0, 0}; - inputBroker->injectInputEvent(&evt); - } + InputEvent evt; + evt.source = _originName; + evt.kbchar = 0; + evt.touchX = 0; + evt.touchY = 0; + evt.inputEvent = INPUT_BROKER_USER_PRESS; + this->notifyObservers(&evt); break; } case BUTTON_EVENT_LONG_PRESSED: { @@ -245,7 +245,7 @@ int32_t ButtonThread::runOnce() // Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event) if (inputBroker) { InputEvent evt = {"button", INPUT_BROKER_SELECT, 0, 0, 0}; - inputBroker->injectInputEvent(&evt); + this->notifyObservers(&evt); } break; } @@ -269,10 +269,6 @@ int32_t ButtonThread::runOnce() externalNotificationModule->stopNow(); break; } -#ifdef ELECROW_ThinkNode_M1 - sendAdHocPosition(); - break; -#endif // Start tracking for potential combination waitingForLongPress = true; @@ -300,10 +296,6 @@ int32_t ButtonThread::runOnce() powerFSM.trigger(EVENT_PRESS); break; #endif - // turn screen on or off - screen_flag = !screen_flag; - if (screen) - screen->setOn(screen_flag); break; } @@ -325,7 +317,8 @@ int32_t ButtonThread::runOnce() sendAdHocPosition(); // Show temporary on-screen confirmation banner for 3 seconds - screen->showOverlayBanner("Ad-hoc Ping Sent", 3000); + if (screen) + screen->showOverlayBanner("Ad-hoc Ping Sent", 3000); break; } @@ -344,25 +337,8 @@ int32_t ButtonThread::runOnce() case 3: if (!config.device.disable_triple_click && (gps != nullptr)) { gps->toggleGpsMode(); - - const char *statusMsg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - ? "GPS Enabled" - : "GPS Disabled"; - - if (screen) { - screen->forceDisplay(true); // Force a new UI frame, then force an EInk update - screen->showOverlayBanner(statusMsg, 3000); - } } break; -#elif defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) - case 3: - LOG_INFO("3 clicks: toggle buzzer"); - buzzer_flag = !buzzer_flag; - if (!buzzer_flag) - noTone(PIN_BUZZER); - break; - #endif #if defined(USE_EINK) && defined(PIN_EINK_EN) && !defined(ELECROW_ThinkNode_M1) // i.e. T-Echo @@ -370,20 +346,6 @@ int32_t ButtonThread::runOnce() case 4: digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); break; -#endif -#if !MESHTASTIC_EXCLUDE_SCREEN && HAS_SCREEN - // 5 clicks: start accelerometer/magenetometer calibration for 30 seconds - case 5: - if (accelerometerThread) { - accelerometerThread->calibrate(30); - } - break; - // 6 clicks: start accelerometer/magenetometer calibration for 60 seconds - case 6: - if (accelerometerThread) { - accelerometerThread->calibrate(60); - } - break; #endif // No valid multipress action default: diff --git a/src/input/ButtonThread.h b/src/input/ButtonThread.h index fe42e9f74..937bb93e4 100644 --- a/src/input/ButtonThread.h +++ b/src/input/ButtonThread.h @@ -1,5 +1,6 @@ #pragma once +#include "InputBroker.h" #include "OneButton.h" #include "concurrency/OSThread.h" #include "configuration.h" @@ -29,9 +30,10 @@ #define BUTTON_LEADUP_MS 2200 // Play lead-up sound after 2.5 seconds of holding #endif -class ButtonThread : public concurrency::OSThread +class ButtonThread : public Observable, public concurrency::OSThread { public: + const char *_originName; static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot enum ButtonEventType { @@ -46,15 +48,11 @@ class ButtonThread : public concurrency::OSThread BUTTON_EVENT_COMBO_SHORT_LONG, }; - ButtonThread(); + ButtonThread(const char *name); int32_t runOnce() override; void attachButtonInterrupts(); void detachButtonInterrupts(); void storeClickCount(); - bool isBuzzing() { return buzzer_flag; } - void setScreenFlag(bool flag) { screen_flag = flag; } - bool getScreenFlag() { return screen_flag; } - bool isInterceptingAndFocused(); bool isButtonPressed(int buttonPin) { #ifdef BUTTON_ACTIVE_LOW @@ -90,8 +88,6 @@ class ButtonThread : public concurrency::OSThread // set during IRQ static volatile ButtonEventType btnEvent; - bool buzzer_flag = false; - bool screen_flag = true; // Store click count during callback, for later use volatile int multipressClickCount = 0; diff --git a/src/input/ButtonThreadImpl.cpp b/src/input/ButtonThreadImpl.cpp new file mode 100644 index 000000000..c57f1c8f0 --- /dev/null +++ b/src/input/ButtonThreadImpl.cpp @@ -0,0 +1,17 @@ +#include "ButtonThreadImpl.h" +#include "InputBroker.h" +#include "configuration.h" + +#if defined(BUTTON_PIN) + +ButtonThreadImpl *aButtonThreadImpl; + +ButtonThreadImpl::ButtonThreadImpl() : ButtonThread("UserButton") {} + +void ButtonThreadImpl::init() // init should give the pin number and the action to perform and whether to do the legacy actions +{ + if (inputBroker) + inputBroker->registerSource(this); +} + +#endif // INPUTBROKER_SERIAL_TYPE \ No newline at end of file diff --git a/src/input/ButtonThreadImpl.h b/src/input/ButtonThreadImpl.h new file mode 100644 index 000000000..470dd46d4 --- /dev/null +++ b/src/input/ButtonThreadImpl.h @@ -0,0 +1,19 @@ +#pragma once +#include "ButtonThread.h" +#include "main.h" + +/** + * @brief The idea behind this class to have static methods for the event handlers. + * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp + * Technically you can have as many rotary encoders hardver attached + * to your device as you wish, but you always need to have separate event + * handlers, thus you need to have a RotaryEncoderInterrupt implementation. + */ +class ButtonThreadImpl : public ButtonThread +{ + public: + ButtonThreadImpl(); + void init(); +}; + +extern ButtonThreadImpl *aButtonThreadImpl; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a8bd4971a..976eefcfc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #endif #if HAS_BUTTON || defined(ARCH_PORTDUINO) -#include "input/ButtonThread.h" +#include "input/ButtonThreadImpl.h" #endif #include "AmbientLightingThread.h" @@ -220,64 +220,6 @@ const char *getDeviceName() return name; } -#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) -static int32_t ledBlinkCount = 0; - -static int32_t elecrowLedBlinker() -{ - // are we in alert buzzer mode? -#if HAS_BUTTON - if (buttonThread->isBuzzing()) { - // blink LED three times for 3 seconds, then 3 times for a second, with one second pause - if (ledBlinkCount % 2) { // odd means LED OFF - ledBlink.set(false); - ledBlinkCount++; - if (ledBlinkCount >= 12) - ledBlinkCount = 0; - noTone(PIN_BUZZER); - return 1000; - } else { - if (ledBlinkCount < 6) { - ledBlink.set(true); - tone(PIN_BUZZER, 4000, 3000); - ledBlinkCount++; - return 3000; - } else { - ledBlink.set(true); - tone(PIN_BUZZER, 4000, 1000); - ledBlinkCount++; - return 1000; - } - } - } else { -#endif - ledBlinkCount = 0; - if (config.device.led_heartbeat_disabled) - return 1000; - - static bool ledOn; - // remain on when fully charged or discharging above 10% - if ((powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() >= 100) || - (!powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() >= 10)) { - ledOn = true; - } else { - ledOn ^= 1; - } - ledBlink.set(ledOn); - // when charging, blink 0.5Hz square wave rate to indicate that - if (powerStatus->getIsCharging()) { - return 500; - } - // Blink rapidly when almost empty or if battery is not connected - if ((!powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() < 10) || !powerStatus->getHasBattery()) { - return 250; - } -#if HAS_BUTTON - } -#endif - return 1000; -} -#else static int32_t ledBlinker() { // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if @@ -293,7 +235,6 @@ static int32_t ledBlinker() // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); } -#endif uint32_t timeLastPowered = 0; @@ -861,11 +802,6 @@ void setup() } #endif // HAS_SCREEN -#if HAS_BUTTON || defined(ARCH_PORTDUINO) - // Buttons. Moved here cause we need NodeDB to be initialized - buttonThread = new ButtonThread(); -#endif - // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string @@ -932,6 +868,13 @@ void setup() // Now that the mesh service is created, create any modules setupModules(); +// buttons are now inputBroker, so have to come after setupModules +#if HAS_BUTTON || defined(ARCH_PORTDUINO) + // Buttons. Moved here cause we need NodeDB to be initialized + aButtonThreadImpl = new ButtonThreadImpl(); + aButtonThreadImpl->init(); +#endif + #ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS // After modules are setup, so we can observe modules setupNicheGraphics(); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 0eb68470c..2cddb42cd 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -310,8 +310,6 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) case CANNED_MESSAGE_RUN_STATE_INACTIVE: if (isSelect) { - // When inactive, call the onebutton shortpress instead. Activate module only on up/down - powerFSM.trigger(EVENT_PRESS); return 0; // Main button press no longer runs through powerFSM } // Let LEFT/RIGHT pass through so frame navigation works