Make ButtonThread an InputBroker

This commit is contained in:
Jonathan Bennett 2025-06-08 02:00:51 -05:00
parent 80af8e76f0
commit a115c78840
7 changed files with 67 additions and 135 deletions

View File

@ -47,7 +47,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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;

View File

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

View File

@ -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<const InputEvent *>, 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;

View File

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

View File

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

View File

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

View File

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