mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-07 21:52:05 +00:00

* Improved beeping booping and other buzzer based feedback * audible button feedback (#6949) * Refactor --------- Co-authored-by: todd-herbert <herbert.todd@gmail.com>
220 lines
7.2 KiB
C++
220 lines
7.2 KiB
C++
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
|
|
#include "./Events.h"
|
|
|
|
#include "RTC.h"
|
|
#include "buzz.h"
|
|
#include "modules/AdminModule.h"
|
|
#include "modules/TextMessageModule.h"
|
|
#include "sleep.h"
|
|
|
|
#include "./Applet.h"
|
|
#include "./SystemApplet.h"
|
|
#include "graphics/niche/FlashData.h"
|
|
|
|
using namespace NicheGraphics;
|
|
|
|
InkHUD::Events::Events()
|
|
{
|
|
// Get convenient references
|
|
inkhud = InkHUD::getInstance();
|
|
settings = &inkhud->persistence->settings;
|
|
}
|
|
|
|
void InkHUD::Events::begin()
|
|
{
|
|
// Register our callbacks for the various events
|
|
|
|
deepSleepObserver.observe(¬ifyDeepSleep);
|
|
rebootObserver.observe(¬ifyReboot);
|
|
textMessageObserver.observe(textMessageModule);
|
|
#if !MESHTASTIC_EXCLUDE_ADMIN
|
|
adminMessageObserver.observe(adminModule);
|
|
#endif
|
|
#ifdef ARCH_ESP32
|
|
lightSleepObserver.observe(¬ifyLightSleep);
|
|
#endif
|
|
}
|
|
|
|
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) {
|
|
if (sa->handleInput) {
|
|
consumer = sa;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no system applet is handling input, default behavior instead is to cycle applets
|
|
if (consumer)
|
|
consumer->onButtonShortPress();
|
|
else
|
|
inkhud->nextApplet();
|
|
}
|
|
|
|
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) {
|
|
if (sa->handleInput) {
|
|
consumer = sa;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no system applet is handling input, default behavior instead is to open the menu
|
|
if (consumer)
|
|
consumer->onButtonLongPress();
|
|
else
|
|
inkhud->openMenu();
|
|
}
|
|
|
|
// Callback for deepSleepObserver
|
|
// Returns 0 to signal that we agree to sleep now
|
|
int InkHUD::Events::beforeDeepSleep(void *unused)
|
|
{
|
|
// If a previous display update is in progress, wait for it to complete.
|
|
inkhud->awaitUpdate();
|
|
|
|
// Notify all applets that we're shutting down
|
|
for (Applet *ua : inkhud->userApplets) {
|
|
ua->onDeactivate();
|
|
ua->onShutdown();
|
|
}
|
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
// Note: no onDeactivate. System applets are always active.
|
|
sa->onShutdown();
|
|
}
|
|
|
|
// User has successful executed a safe shutdown
|
|
// We don't need to nag at boot anymore
|
|
settings->tips.safeShutdownSeen = true;
|
|
|
|
inkhud->persistence->saveSettings();
|
|
inkhud->persistence->saveLatestMessage();
|
|
|
|
// LogoApplet::onShutdown attempted to heal the display by drawing a "shutting down" screen twice,
|
|
// then prepared a final powered-off screen for us, which shows device shortname.
|
|
// We're updating to show that one now.
|
|
|
|
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
|
|
}
|
|
|
|
// Callback for rebootObserver
|
|
// Same as shutdown, without drawing the logoApplet
|
|
// Makes sure we don't lose message history / InkHUD config
|
|
int InkHUD::Events::beforeReboot(void *unused)
|
|
{
|
|
|
|
// Notify all applets that we're "shutting down"
|
|
// They don't need to know that it's really a reboot
|
|
for (Applet *a : inkhud->userApplets) {
|
|
a->onDeactivate();
|
|
a->onShutdown();
|
|
}
|
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
// Note: no onDeactivate. System applets are always active.
|
|
sa->onReboot();
|
|
}
|
|
|
|
// Save settings to flash, or erase if factory reset in progress
|
|
if (!eraseOnReboot) {
|
|
inkhud->persistence->saveSettings();
|
|
inkhud->persistence->saveLatestMessage();
|
|
} else {
|
|
NicheGraphics::clearFlashData();
|
|
}
|
|
|
|
// Note: no forceUpdate call here
|
|
// We don't have any final screen to draw, although LogoApplet::onReboot did already display a "rebooting" screen
|
|
|
|
return 0; // No special status to report. Ignored anyway by this Observable
|
|
}
|
|
|
|
// Callback when a new text message is received
|
|
// Caches the most recently received message, for use by applets
|
|
// Rx does not trigger a save to flash, however the data *will* be saved alongside other during shutdown, etc.
|
|
// Note: this is different from devicestate.rx_text_message, which may contain an *outgoing* message
|
|
int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
|
|
{
|
|
// Short circuit: don't store outgoing messages
|
|
if (getFrom(packet) == nodeDB->getNodeNum())
|
|
return 0;
|
|
|
|
// Determine whether the message is broadcast or a DM
|
|
// Store this info to prevent confusion after a reboot
|
|
// Avoids need to compare timestamps, because of situation where "future" messages block newly received, if time not set
|
|
inkhud->persistence->latestMessage.wasBroadcast = isBroadcast(packet->to);
|
|
|
|
// Pick the appropriate variable to store the message in
|
|
MessageStore::Message *storedMessage = inkhud->persistence->latestMessage.wasBroadcast
|
|
? &inkhud->persistence->latestMessage.broadcast
|
|
: &inkhud->persistence->latestMessage.dm;
|
|
|
|
// Store nodenum of the sender
|
|
// Applets can use this to fetch user data from nodedb, if they want
|
|
storedMessage->sender = packet->from;
|
|
|
|
// Store the time (epoch seconds) when message received
|
|
storedMessage->timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
|
|
|
// Store the channel
|
|
// - (potentially) used to determine whether notification shows
|
|
// - (potentially) used to determine which applet to focus
|
|
storedMessage->channelIndex = packet->channel;
|
|
|
|
// Store the text
|
|
// Need to specify manually how many bytes, because source not null-terminated
|
|
storedMessage->text =
|
|
std::string(&packet->decoded.payload.bytes[0], &packet->decoded.payload.bytes[packet->decoded.payload.size]);
|
|
|
|
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
|
}
|
|
|
|
int InkHUD::Events::onAdminMessage(const meshtastic_AdminMessage *message)
|
|
{
|
|
switch (message->which_payload_variant) {
|
|
// Factory reset
|
|
// Two possible messages. One preserves BLE bonds, other wipes. Both should clear InkHUD data.
|
|
case meshtastic_AdminMessage_factory_reset_device_tag:
|
|
case meshtastic_AdminMessage_factory_reset_config_tag:
|
|
eraseOnReboot = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
|
}
|
|
|
|
#ifdef ARCH_ESP32
|
|
// Callback for lightSleepObserver
|
|
// Make sure the display is not partway through an update when we begin light sleep
|
|
// This is because some displays require active input from us to terminate the update process, and protect the panel hardware
|
|
int InkHUD::Events::beforeLightSleep(void *unused)
|
|
{
|
|
inkhud->awaitUpdate();
|
|
return 0; // No special status to report. Ignored anyway by this Observable
|
|
}
|
|
#endif
|
|
|
|
#endif |