mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-12 16:12:07 +00:00

* chore: todo.txt
* chore: InkHUD documentation
Word salad for maintainers
* refactor: don't init system applets using onActivate
System applets cannot be deactivated, so we will avoid using onActivate / onDeactivate methods entirely.
* chore: update the example applets
* fix: SSD16XX reset pulse
Allow time for controller IC to wake. Aligns with manufacturer's suggestions.
T-Echo button timing adjusted to prevent bouncing as a result(?) of slightly faster refreshes.
* fix: allow timeout if display update fails
Result is not graceful, but avoids total display lockup requiring power cycle.
Typical cause of failure is poor wiring / power supply.
* fix: improve display health on shutdown
Two extra full refreshes, masquerading as a "shutting down" screen. One is drawn white-on-black, to really shake the pixels up.
* feat: driver for display HINK_E042A87
As of Feb. 2025, these panels are used for "WeActStudio 4.2in B&W" display modules.
* fix: inkhud rotation should default to 0
* Revert "chore: todo.txt"
This reverts commit bea7df44a7
.
* fix: more generous timeout for display updates
Previously this was tied to the expected duration of the update, but this didn't account for any delay if our polling thread got held up by an unrelated firmware task.
* fix: don't use the full shutdown screen during reboot
* fix: cooldown period during the display shutdown display sequence
Observed to prevent border pixels from being locked in place with some residual charge?
185 lines
6.2 KiB
C++
185 lines
6.2 KiB
C++
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
|
|
#include "./Events.h"
|
|
|
|
#include "RTC.h"
|
|
#include "modules/TextMessageModule.h"
|
|
#include "sleep.h"
|
|
|
|
#include "./Applet.h"
|
|
#include "./SystemApplet.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);
|
|
#ifdef ARCH_ESP32
|
|
lightSleepObserver.observe(¬ifyLightSleep);
|
|
#endif
|
|
}
|
|
|
|
void InkHUD::Events::onButtonShort()
|
|
{
|
|
// 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()
|
|
{
|
|
// 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
|
|
|
|
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();
|
|
}
|
|
|
|
inkhud->persistence->saveSettings();
|
|
inkhud->persistence->saveLatestMessage();
|
|
|
|
// 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;
|
|
|
|
// Short circuit: don't store "emoji reactions"
|
|
// Possibly some implementation of this in future?
|
|
if (packet->decoded.emoji)
|
|
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)
|
|
}
|
|
|
|
#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 |