mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 06:02:05 +00:00
Improved beeping booping and other buzzer based feedback (#6947)
* Improved beeping booping and other buzzer based feedback * audible button feedback (#6949) * Refactor --------- Co-authored-by: todd-herbert <herbert.todd@gmail.com>
This commit is contained in:
parent
2cc2002675
commit
25fbf58444
@ -172,6 +172,7 @@ void ButtonThread::sendAdHocPosition()
|
||||
screen->print("Sent ad-hoc nodeinfo\n");
|
||||
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
|
||||
}
|
||||
playComboTune();
|
||||
}
|
||||
|
||||
int32_t ButtonThread::runOnce()
|
||||
@ -197,10 +198,62 @@ int32_t ButtonThread::runOnce()
|
||||
canSleep &= userButtonTouch.isIdle();
|
||||
#endif
|
||||
|
||||
// Check for combination timeout
|
||||
if (waitingForLongPress && (millis() - shortPressTime) > BUTTON_COMBO_TIMEOUT_MS) {
|
||||
waitingForLongPress = false;
|
||||
}
|
||||
|
||||
// Check if we should play lead-up sound during long press
|
||||
// Play lead-up when button has been held for BUTTON_LEADUP_MS but before long press triggers
|
||||
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
bool buttonCurrentlyPressed = false;
|
||||
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN)
|
||||
// Read the actual physical state of the button pin
|
||||
#if !defined(USERPREFS_BUTTON_PIN)
|
||||
int buttonPin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
||||
#else
|
||||
int buttonPin = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
|
||||
#endif
|
||||
buttonCurrentlyPressed = isButtonPressed(buttonPin);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
|
||||
// For portduino, assume active low
|
||||
buttonCurrentlyPressed = isButtonPressed(settingsMap[user]);
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t buttonPressStartTime = 0;
|
||||
static bool buttonWasPressed = false;
|
||||
|
||||
// Detect start of button press
|
||||
if (buttonCurrentlyPressed && !buttonWasPressed) {
|
||||
buttonPressStartTime = millis();
|
||||
leadUpPlayed = false;
|
||||
}
|
||||
|
||||
// Check if we should play lead-up sound
|
||||
if (buttonCurrentlyPressed && !leadUpPlayed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS &&
|
||||
(millis() - buttonPressStartTime) < BUTTON_LONGPRESS_MS) {
|
||||
playLongPressLeadUp();
|
||||
leadUpPlayed = true;
|
||||
}
|
||||
|
||||
// Reset when button is released
|
||||
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
||||
leadUpPlayed = false;
|
||||
}
|
||||
|
||||
buttonWasPressed = buttonCurrentlyPressed;
|
||||
#endif
|
||||
|
||||
if (btnEvent != BUTTON_EVENT_NONE) {
|
||||
switch (btnEvent) {
|
||||
case BUTTON_EVENT_PRESSED: {
|
||||
LOG_BUTTON("press!");
|
||||
|
||||
// Play boop sound for every button press
|
||||
playBoop();
|
||||
|
||||
// If a nag notification is running, stop it and prevent other actions
|
||||
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
|
||||
externalNotificationModule->stopNow();
|
||||
@ -210,12 +263,24 @@ int32_t ButtonThread::runOnce()
|
||||
sendAdHocPosition();
|
||||
break;
|
||||
#endif
|
||||
|
||||
// Start tracking for potential combination
|
||||
waitingForLongPress = true;
|
||||
shortPressTime = millis();
|
||||
|
||||
switchPage();
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_PRESSED_SCREEN: {
|
||||
LOG_BUTTON("AltPress!");
|
||||
|
||||
// Play boop sound for every button press
|
||||
playBoop();
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
// If a nag notification is running, stop it and prevent other actions
|
||||
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
|
||||
@ -235,6 +300,12 @@ int32_t ButtonThread::runOnce()
|
||||
case BUTTON_EVENT_DOUBLE_PRESSED: {
|
||||
LOG_BUTTON("Double press!");
|
||||
|
||||
// Play boop sound for every button press
|
||||
playBoop();
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
@ -250,6 +321,13 @@ int32_t ButtonThread::runOnce()
|
||||
|
||||
case BUTTON_EVENT_MULTI_PRESSED: {
|
||||
LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
|
||||
|
||||
// Play boop sound for every button press
|
||||
playBoop();
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
switch (multipressClickCount) {
|
||||
#if HAS_GPS && !defined(ELECROW_ThinkNode_M1)
|
||||
// 3 clicks: toggle GPS
|
||||
@ -307,6 +385,18 @@ int32_t ButtonThread::runOnce()
|
||||
|
||||
case BUTTON_EVENT_LONG_PRESSED: {
|
||||
LOG_BUTTON("Long press!");
|
||||
|
||||
// Check if this is part of a short-press + long-press combination
|
||||
if (waitingForLongPress && (millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) {
|
||||
LOG_BUTTON("Combo detected: short-press + long-press!");
|
||||
btnEvent = BUTTON_EVENT_COMBO_SHORT_LONG;
|
||||
waitingForLongPress = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
|
||||
if (screen) {
|
||||
@ -314,6 +404,8 @@ int32_t ButtonThread::runOnce()
|
||||
screen->showOverlayBanner("Shutting Down..."); // Display for 3 seconds
|
||||
}
|
||||
|
||||
// Lead-up sound already played during button hold
|
||||
// Just a simple beep to confirm long press threshold reached
|
||||
playBeep();
|
||||
break;
|
||||
}
|
||||
@ -322,6 +414,10 @@ int32_t ButtonThread::runOnce()
|
||||
// may wake the board immediatedly.
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
LOG_INFO("Shutdown from long press");
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
playShutdownMelody();
|
||||
delay(3000);
|
||||
power->shutdown();
|
||||
@ -332,6 +428,13 @@ int32_t ButtonThread::runOnce()
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
|
||||
LOG_BUTTON("Touch press!");
|
||||
|
||||
// Play boop sound for every button press
|
||||
playBoop();
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
// Ignore if: no screen
|
||||
if (!screen)
|
||||
break;
|
||||
@ -353,6 +456,20 @@ int32_t ButtonThread::runOnce()
|
||||
}
|
||||
#endif // BUTTON_PIN_TOUCH
|
||||
|
||||
case BUTTON_EVENT_COMBO_SHORT_LONG: {
|
||||
// Placeholder for short-press + long-press combination
|
||||
LOG_BUTTON("Short-press + Long-press combination detected!");
|
||||
|
||||
// Play the combination tune
|
||||
playComboTune();
|
||||
|
||||
// Optionally show a message on screen
|
||||
if (screen) {
|
||||
screen->showOverlayBanner("Combo Tune Played", 2000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -16,6 +16,14 @@
|
||||
#define BUTTON_TOUCH_MS 400
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_COMBO_TIMEOUT_MS
|
||||
#define BUTTON_COMBO_TIMEOUT_MS 2000 // 2 seconds to complete the combination
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_LEADUP_MS
|
||||
#define BUTTON_LEADUP_MS 2500 // Play lead-up sound after 2.5 seconds of holding
|
||||
#endif
|
||||
|
||||
class ButtonThread : public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
@ -30,6 +38,7 @@ class ButtonThread : public concurrency::OSThread
|
||||
BUTTON_EVENT_LONG_PRESSED,
|
||||
BUTTON_EVENT_LONG_RELEASED,
|
||||
BUTTON_EVENT_TOUCH_LONG_PRESSED,
|
||||
BUTTON_EVENT_COMBO_SHORT_LONG,
|
||||
};
|
||||
|
||||
ButtonThread();
|
||||
@ -41,6 +50,14 @@ class ButtonThread : public concurrency::OSThread
|
||||
void setScreenFlag(bool flag) { screen_flag = flag; }
|
||||
bool getScreenFlag() { return screen_flag; }
|
||||
bool isInterceptingAndFocused();
|
||||
bool isButtonPressed(int buttonPin)
|
||||
{
|
||||
#ifdef BUTTON_ACTIVE_LOW
|
||||
return !digitalRead(buttonPin); // Active low: pressed = LOW
|
||||
#else
|
||||
return digitalRead(buttonPin); // Most buttons are active low by default
|
||||
#endif
|
||||
}
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
@ -74,6 +91,13 @@ class ButtonThread : public concurrency::OSThread
|
||||
// Store click count during callback, for later use
|
||||
volatile int multipressClickCount = 0;
|
||||
|
||||
// Combination tracking state
|
||||
bool waitingForLongPress = false;
|
||||
uint32_t shortPressTime = 0;
|
||||
|
||||
// Long press lead-up tracking
|
||||
bool leadUpPlayed = false;
|
||||
|
||||
static void wakeOnIrq(int irq, int mode);
|
||||
|
||||
static void sendAdHocPosition();
|
||||
|
@ -87,3 +87,36 @@ void playShutdownMelody()
|
||||
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playBoop()
|
||||
{
|
||||
// A short, friendly "boop" sound for button presses
|
||||
ToneDuration melody[] = {{NOTE_A3, 50}}; // Very short A3 note
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playLongPressLeadUp()
|
||||
{
|
||||
// An ascending lead-up sequence for long press - builds anticipation
|
||||
ToneDuration melody[] = {
|
||||
{NOTE_C3, 100}, // Start low
|
||||
{NOTE_E3, 100}, // Step up
|
||||
{NOTE_G3, 100}, // Keep climbing
|
||||
{NOTE_B3, 150} // Peak with longer note for emphasis
|
||||
};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playComboTune()
|
||||
{
|
||||
// Quick high-pitched notes with trills
|
||||
ToneDuration melody[] = {
|
||||
{NOTE_G3, 80}, // Quick chirp
|
||||
{NOTE_B3, 60}, // Higher chirp
|
||||
{NOTE_CS4, 80}, // Even higher
|
||||
{NOTE_G3, 60}, // Quick trill down
|
||||
{NOTE_CS4, 60}, // Quick trill up
|
||||
{NOTE_B3, 120} // Ending chirp
|
||||
};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
@ -6,3 +6,6 @@ void playStartMelody();
|
||||
void playShutdownMelody();
|
||||
void playGPSEnableBeep();
|
||||
void playGPSDisableBeep();
|
||||
void playComboTune();
|
||||
void playBoop();
|
||||
void playLongPressLeadUp();
|
@ -3,6 +3,7 @@
|
||||
#include "./Events.h"
|
||||
|
||||
#include "RTC.h"
|
||||
#include "buzz.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#include "sleep.h"
|
||||
@ -37,6 +38,10 @@ void InkHUD::Events::begin()
|
||||
|
||||
void InkHUD::Events::onButtonShort()
|
||||
{
|
||||
// Audio feedback (via buzzer)
|
||||
// Short low tone
|
||||
playBoop();
|
||||
|
||||
// Check which system applet wants to handle the button press (if any)
|
||||
SystemApplet *consumer = nullptr;
|
||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||
@ -55,6 +60,10 @@ void InkHUD::Events::onButtonShort()
|
||||
|
||||
void InkHUD::Events::onButtonLong()
|
||||
{
|
||||
// Audio feedback (via buzzer)
|
||||
// Low tone, longer than playBoop
|
||||
playBeep();
|
||||
|
||||
// Check which system applet wants to handle the button press (if any)
|
||||
SystemApplet *consumer = nullptr;
|
||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||
@ -102,6 +111,10 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
||||
delay(1000); // Cooldown, before potentially yanking display power
|
||||
|
||||
// InkHUD shutdown complete
|
||||
// Firmware shutdown continues for several seconds more; flash write still pending
|
||||
playShutdownMelody();
|
||||
|
||||
return 0; // We agree: deep sleep now
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h" // needed for button bypass
|
||||
#include "SPILock.h"
|
||||
#include "buzz.h"
|
||||
#include "detect/ScanI2C.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
@ -874,6 +875,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
||||
simulatedPacket.from = 0; // Local device
|
||||
screen->handleTextMessage(&simulatedPacket);
|
||||
}
|
||||
playComboTune();
|
||||
}
|
||||
int32_t CannedMessageModule::runOnce()
|
||||
{
|
||||
|
@ -22,6 +22,9 @@
|
||||
#include "graphics/niche/Drivers/EInk/GDEY0154D67.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
// Button feedback
|
||||
#include "buzz.h"
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
@ -98,8 +101,14 @@ void setupNicheGraphics()
|
||||
buttons->setWiring(1, PIN_BUTTON1);
|
||||
buttons->setTiming(1, 50, 500); // 500ms before latch
|
||||
buttons->setHandlerDown(1, [backlight]() { backlight->peek(); });
|
||||
buttons->setHandlerLongPress(1, [backlight]() { backlight->latch(); });
|
||||
buttons->setHandlerShortPress(1, [backlight]() { backlight->off(); });
|
||||
buttons->setHandlerLongPress(1, [backlight]() {
|
||||
backlight->latch();
|
||||
playBeep();
|
||||
});
|
||||
buttons->setHandlerShortPress(1, [backlight]() {
|
||||
backlight->off();
|
||||
playBoop();
|
||||
});
|
||||
|
||||
// Begin handling button events
|
||||
buttons->start();
|
||||
|
@ -21,6 +21,9 @@
|
||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
// Button feedback
|
||||
#include "buzz.h"
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
@ -85,7 +88,10 @@ void setupNicheGraphics()
|
||||
|
||||
// #1: Aux Button
|
||||
buttons->setWiring(1, BUTTON_PIN_SECONDARY);
|
||||
buttons->setHandlerShortPress(1, [inkhud]() { inkhud->nextTile(); });
|
||||
buttons->setHandlerShortPress(1, [inkhud]() {
|
||||
inkhud->nextTile();
|
||||
playBoop();
|
||||
});
|
||||
|
||||
// Begin handling button events
|
||||
buttons->start();
|
||||
|
@ -34,6 +34,9 @@ Different NicheGraphics UIs and different hardware variants will each have their
|
||||
#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
// Button feedback
|
||||
#include "buzz.h"
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
@ -98,7 +101,10 @@ void setupNicheGraphics()
|
||||
|
||||
// #1: Aux Button
|
||||
buttons->setWiring(1, BUTTON_PIN_SECONDARY);
|
||||
buttons->setHandlerShortPress(1, [inkhud]() { inkhud->nextTile(); });
|
||||
buttons->setHandlerShortPress(1, [inkhud]() {
|
||||
inkhud->nextTile();
|
||||
playBoop();
|
||||
});
|
||||
|
||||
// Begin handling button events
|
||||
buttons->start();
|
||||
|
Loading…
Reference in New Issue
Block a user