Decoupled message packets from screen.cpp and cleaned up

This commit is contained in:
HarukiToreda 2025-09-26 00:28:25 -04:00
parent dd7a5cf31f
commit ebbb8a6f9f
8 changed files with 169 additions and 230 deletions

View File

@ -1,9 +1,9 @@
#include "MessageStore.h" #include "MessageStore.h"
#include "FSCommon.h" #include "FSCommon.h"
#include "NodeDB.h" // for nodeDB->getNodeNum() #include "NodeDB.h"
#include "SPILock.h" #include "SPILock.h"
#include "SafeFile.h" #include "SafeFile.h"
#include "configuration.h" // for millis() #include "configuration.h"
#include "gps/RTC.h" #include "gps/RTC.h"
#include "graphics/draw/MessageRenderer.h" #include "graphics/draw/MessageRenderer.h"
@ -15,7 +15,7 @@ MessageStore::MessageStore(const std::string &label)
filename = "/Messages_" + label + ".msgs"; filename = "/Messages_" + label + ".msgs";
} }
// === Live message handling (RAM only) === // Live message handling (RAM only)
void MessageStore::addLiveMessage(const StoredMessage &msg) void MessageStore::addLiveMessage(const StoredMessage &msg)
{ {
if (liveMessages.size() >= MAX_MESSAGES_SAVED) { if (liveMessages.size() >= MAX_MESSAGES_SAVED) {
@ -24,7 +24,7 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
liveMessages.push_back(msg); liveMessages.push_back(msg);
} }
// === Persistence queue (used only on shutdown/reboot) === // Persistence queue (used only on shutdown/reboot)
void MessageStore::addMessage(const StoredMessage &msg) void MessageStore::addMessage(const StoredMessage &msg)
{ {
if (messages.size() >= MAX_MESSAGES_SAVED) { if (messages.size() >= MAX_MESSAGES_SAVED) {
@ -32,8 +32,7 @@ void MessageStore::addMessage(const StoredMessage &msg)
} }
messages.push_back(msg); messages.push_back(msg);
} }
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{ {
StoredMessage sm; StoredMessage sm;
@ -47,11 +46,22 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
sm.isBootRelative = true; // mark for later upgrade sm.isBootRelative = true; // mark for later upgrade
} }
sm.sender = packet.from;
sm.channelIndex = packet.channel; sm.channelIndex = packet.channel;
sm.text = std::string(reinterpret_cast<const char *>(packet.decoded.payload.bytes)); sm.text = std::string(reinterpret_cast<const char *>(packet.decoded.payload.bytes));
// Classification logic if (packet.from == 0) {
// Phone-originated (outgoing)
sm.sender = nodeDB->getNodeNum(); // our node ID
if (packet.decoded.dest == 0 || packet.decoded.dest == NODENUM_BROADCAST) {
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
} else {
sm.dest = packet.decoded.dest;
sm.type = MessageType::DM_TO_US;
}
} else {
// Normal incoming
sm.sender = packet.from;
if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) { if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) {
sm.dest = NODENUM_BROADCAST; sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST; sm.type = MessageType::BROADCAST;
@ -62,11 +72,15 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
sm.dest = NODENUM_BROADCAST; // fallback sm.dest = NODENUM_BROADCAST; // fallback
sm.type = MessageType::BROADCAST; sm.type = MessageType::BROADCAST;
} }
}
addLiveMessage(sm); addLiveMessage(sm);
// Return reference to the most recently stored message
return liveMessages.back();
} }
// === Outgoing/manual message === // Outgoing/manual message
void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text) void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text)
{ {
StoredMessage sm; StoredMessage sm;
@ -92,7 +106,7 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
addLiveMessage(sm); addLiveMessage(sm);
} }
// === Save RAM queue to flash (called on shutdown) === // Save RAM queue to flash (called on shutdown)
void MessageStore::saveToFlash() void MessageStore::saveToFlash()
{ {
#ifdef FSCom #ifdef FSCom
@ -128,7 +142,7 @@ void MessageStore::saveToFlash()
#endif #endif
} }
// === Load persisted messages into RAM (called at boot) === // Load persisted messages into RAM (called at boot)
void MessageStore::loadFromFlash() void MessageStore::loadFromFlash()
{ {
messages.clear(); messages.clear();
@ -189,7 +203,7 @@ void MessageStore::loadFromFlash()
#endif #endif
} }
// === Clear all messages (RAM + persisted queue) === // Clear all messages (RAM + persisted queue)
void MessageStore::clearAllMessages() void MessageStore::clearAllMessages()
{ {
liveMessages.clear(); liveMessages.clear();
@ -203,7 +217,7 @@ void MessageStore::clearAllMessages()
#endif #endif
} }
// === Dismiss oldest message (RAM + persisted queue) === // Dismiss oldest message (RAM + persisted queue)
void MessageStore::dismissOldestMessage() void MessageStore::dismissOldestMessage()
{ {
if (!liveMessages.empty()) { if (!liveMessages.empty()) {
@ -215,7 +229,7 @@ void MessageStore::dismissOldestMessage()
saveToFlash(); saveToFlash();
} }
// === Dismiss newest message (RAM + persisted queue) === // Dismiss newest message (RAM + persisted queue)
void MessageStore::dismissNewestMessage() void MessageStore::dismissNewestMessage()
{ {
if (!liveMessages.empty()) { if (!liveMessages.empty()) {
@ -227,7 +241,7 @@ void MessageStore::dismissNewestMessage()
saveToFlash(); saveToFlash();
} }
// === Helper filters for future use === // Helper filters for future use
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
{ {
std::deque<StoredMessage> result; std::deque<StoredMessage> result;
@ -250,7 +264,7 @@ std::deque<StoredMessage> MessageStore::getDirectMessages() const
return result; return result;
} }
// === Upgrade boot-relative timestamps once RTC is valid === // Upgrade boot-relative timestamps once RTC is valid
// Only same-boot boot-relative messages are healed. // Only same-boot boot-relative messages are healed.
// Persisted boot-relative messages from old boots stay ??? forever. // Persisted boot-relative messages from old boots stay ??? forever.
void MessageStore::upgradeBootRelativeTimestamps() void MessageStore::upgradeBootRelativeTimestamps()
@ -280,5 +294,5 @@ void MessageStore::upgradeBootRelativeTimestamps()
} }
} }
// === Global definition === // Global definition
MessageStore messageStore("default"); MessageStore messageStore("default");

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket #include "mesh/generated/meshtastic/mesh.pb.h"
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <string> #include <string>
@ -42,32 +42,31 @@ class MessageStore
public: public:
explicit MessageStore(const std::string &label); explicit MessageStore(const std::string &label);
// === Live RAM methods (always current, used by UI and runtime) === // Live RAM methods (always current, used by UI and runtime)
void addLiveMessage(const StoredMessage &msg); void addLiveMessage(const StoredMessage &msg);
const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; } const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; }
// === Persistence methods (used only on boot/shutdown) === // Persistence methods (used only on boot/shutdown)
void addMessage(const StoredMessage &msg); // Add to persistence queue void addMessage(const StoredMessage &msg);
void addFromPacket(const meshtastic_MeshPacket &mp); // Incoming → RAM only const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only
void addFromString(uint32_t sender, uint8_t channelIndex, void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text);
const std::string &text); // Outgoing/manual → RAM only void saveToFlash();
void saveToFlash(); // Persist RAM → flash void loadFromFlash();
void loadFromFlash(); // Restore flash → RAM
// === Clear all messages (RAM + persisted queue) === // Clear all messages (RAM + persisted queue)
void clearAllMessages(); void clearAllMessages();
// === Dismiss helpers === // Dismiss helpers
void dismissOldestMessage(); // Drop front() from history void dismissOldestMessage();
void dismissNewestMessage(); // Drop back() from history (useful for current screen) void dismissNewestMessage();
// === Unified accessor (for UI code, defaults to RAM buffer) === // Unified accessor (for UI code, defaults to RAM buffer)
const std::deque<StoredMessage> &getMessages() const { return liveMessages; } const std::deque<StoredMessage> &getMessages() const { return liveMessages; }
// Optional: direct access to persisted copy (mainly for debugging/inspection) // Optional: direct access to persisted copy (mainly for debugging/inspection)
const std::deque<StoredMessage> &getPersistedMessages() const { return messages; } const std::deque<StoredMessage> &getPersistedMessages() const { return messages; }
// === Helper filters for future use === // Helper filters for future use
std::deque<StoredMessage> getChannelMessages(uint8_t channel) const; std::deque<StoredMessage> getChannelMessages(uint8_t channel) const;
std::deque<StoredMessage> getDirectMessages() const; std::deque<StoredMessage> getDirectMessages() const;
std::deque<StoredMessage> getConversationWith(uint32_t peer) const; std::deque<StoredMessage> getConversationWith(uint32_t peer) const;
@ -76,14 +75,10 @@ class MessageStore
void upgradeBootRelativeTimestamps(); void upgradeBootRelativeTimestamps();
private: private:
// RAM buffer (always current, main source for UI)
std::deque<StoredMessage> liveMessages; std::deque<StoredMessage> liveMessages;
std::deque<StoredMessage> messages; // persisted copy
// Persisted storage (only updated on shutdown, loaded at boot)
std::deque<StoredMessage> messages;
std::string filename; std::string filename;
}; };
// === Global instance (defined in MessageStore.cpp) === // Global instance (defined in MessageStore.cpp)
extern MessageStore messageStore; extern MessageStore messageStore;

View File

@ -283,12 +283,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
pi.drawFrame(display, state, x, y); pi.drawFrame(display, state, x, y);
} }
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
{
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
/** /**
* Given a recent lat/lon return a guess of the heading the user is walking on. * Given a recent lat/lon return a guess of the heading the user is walking on.
* *
@ -514,10 +508,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
void Screen::setup() void Screen::setup()
{ {
// === Enable display rendering === // Enable display rendering
useDisplay = true; useDisplay = true;
// === Load saved brightness from UI config === // Load saved brightness from UI config
// For OLED displays (SSD1306), default brightness is 255 if not set // For OLED displays (SSD1306), default brightness is 255 if not set
if (uiconfig.screen_brightness == 0) { if (uiconfig.screen_brightness == 0) {
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) #if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
@ -529,7 +523,7 @@ void Screen::setup()
brightness = uiconfig.screen_brightness; brightness = uiconfig.screen_brightness;
} }
// === Detect OLED subtype (if supported by board variant) === // Detect OLED subtype (if supported by board variant)
#ifdef AutoOLEDWire_h #ifdef AutoOLEDWire_h
if (isAUTOOled) if (isAUTOOled)
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model); static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
@ -544,7 +538,7 @@ void Screen::setup()
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH); static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif #endif
// === Initialize display and UI system === // Initialize display and UI system
ui->init(); ui->init();
displayWidth = dispdev->width(); displayWidth = dispdev->width();
displayHeight = dispdev->height(); displayHeight = dispdev->height();
@ -556,7 +550,7 @@ void Screen::setup()
ui->disableAllIndicators(); // Disable page indicator dots ui->disableAllIndicators(); // Disable page indicator dots
ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance
// === Apply loaded brightness === // Apply loaded brightness
#if defined(ST7789_CS) #if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness); static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306) #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
@ -564,20 +558,20 @@ void Screen::setup()
#endif #endif
LOG_INFO("Applied screen brightness: %d", brightness); LOG_INFO("Applied screen brightness: %d", brightness);
// === Set custom overlay callbacks === // Set custom overlay callbacks
static OverlayCallback overlays[] = { static OverlayCallback overlays[] = {
graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame
}; };
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
// === Enable UTF-8 to display mapping === // Enable UTF-8 to display mapping
dispdev->setFontTableLookupFunction(customFontTableLookup); dispdev->setFontTableLookupFunction(customFontTableLookup);
#ifdef USERPREFS_OEM_TEXT #ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Give more time for branded boot logos logo_timeout *= 2; // Give more time for branded boot logos
#endif #endif
// === Configure alert frames (e.g., "Resuming..." or region name) === // Configure alert frames (e.g., "Resuming..." or region name)
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip slow refresh EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip slow refresh
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -593,10 +587,10 @@ void Screen::setup()
ui->setFrames(alertFrames, 1); ui->setFrames(alertFrames, 1);
ui->disableAutoTransition(); // Require manual navigation between frames ui->disableAutoTransition(); // Require manual navigation between frames
// === Log buffer for on-screen logs (3 lines max) === // Log buffer for on-screen logs (3 lines max)
dispdev->setLogBuffer(3, 32); dispdev->setLogBuffer(3, 32);
// === Optional screen mirroring or flipping (e.g. for T-Beam orientation) === // Optional screen mirroring or flipping (e.g. for T-Beam orientation)
#ifdef SCREEN_MIRROR #ifdef SCREEN_MIRROR
dispdev->mirrorScreen(); dispdev->mirrorScreen();
#else #else
@ -612,7 +606,7 @@ void Screen::setup()
} }
#endif #endif
// === Generate device ID from MAC address === // Generate device ID from MAC address
uint8_t dmac[6]; uint8_t dmac[6];
getMacAddr(dmac); getMacAddr(dmac);
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]); snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
@ -621,7 +615,7 @@ void Screen::setup()
handleSetOn(false); // Ensure proper init for Arduino targets handleSetOn(false); // Ensure proper init for Arduino targets
#endif #endif
// === Turn on display and trigger first draw === // Turn on display and trigger first draw
handleSetOn(true); handleSetOn(true);
determineResolution(dispdev->height(), dispdev->width()); determineResolution(dispdev->height(), dispdev->width());
ui->update(); ui->update();
@ -644,7 +638,7 @@ void Screen::setup()
touchScreenImpl1->init(); touchScreenImpl1->init();
#endif #endif
// === Subscribe to device status updates === // Subscribe to device status updates
powerStatusObserver.observe(&powerStatus->onNewStatus); powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus);
@ -652,16 +646,14 @@ void Screen::setup()
#if !MESHTASTIC_EXCLUDE_ADMIN #if !MESHTASTIC_EXCLUDE_ADMIN
adminMessageObserver.observe(adminModule); adminMessageObserver.observe(adminModule);
#endif #endif
if (textMessageModule)
textMessageObserver.observe(textMessageModule);
if (inputBroker) if (inputBroker)
inputObserver.observe(inputBroker); inputObserver.observe(inputBroker);
// === Load persisted messages into RAM === // Load persisted messages into RAM
messageStore.loadFromFlash(); messageStore.loadFromFlash();
LOG_INFO("MessageStore loaded from flash"); LOG_INFO("MessageStore loaded from flash");
// === Notify modules that support UI events === // Notify modules that support UI events
MeshModule::observeUIEvents(&uiFrameEventObserver); MeshModule::observeUIEvents(&uiFrameEventObserver);
} }
@ -1130,7 +1122,7 @@ void Screen::setFrames(FrameFocus focus)
} }
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
this->frameCount = numframes; // Save frame count for use in custom overlay this->frameCount = numframes; // Save frame count for use in custom overlay
LOG_DEBUG("Finished build frames. numframes: %d", numframes); LOG_DEBUG("Finished build frames. numframes: %d", numframes);
ui->setFrames(normalFrames, numframes); ui->setFrames(normalFrames, numframes);
@ -1150,10 +1142,6 @@ void Screen::setFrames(FrameFocus focus)
case FOCUS_FAULT: case FOCUS_FAULT:
ui->switchToFrame(fsi.positions.fault); ui->switchToFrame(fsi.positions.fault);
break; break;
case FOCUS_TEXTMESSAGE:
hasUnreadMessage = false; // ✅ Clear when message is *viewed*
ui->switchToFrame(fsi.positions.textMessage);
break;
case FOCUS_MODULE: case FOCUS_MODULE:
// Whichever frame was marked by MeshModule::requestFocus(), if any // Whichever frame was marked by MeshModule::requestFocus(), if any
// If no module requested focus, will show the first frame instead // If no module requested focus, will show the first frame instead
@ -1438,120 +1426,6 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
return 0; return 0;
} }
// Handles when message is received; will jump to text message frame.
int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
{
if (!showingNormalScreen)
return 0;
// === Build stored message ===
StoredMessage sm;
// Always use our local time
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
if (nowSecs > 0) {
sm.timestamp = nowSecs;
sm.isBootRelative = false;
} else {
sm.timestamp = millis() / 1000;
sm.isBootRelative = true; // mark for later upgrade
}
sm.channelIndex = packet->channel;
sm.text = std::string(reinterpret_cast<const char *>(packet->decoded.payload.bytes));
if (packet->from == 0) {
// Outgoing message (sent by us, typically via phone)
sm.sender = nodeDB->getNodeNum(); // us
if (packet->decoded.dest == 0 || packet->decoded.dest == NODENUM_BROADCAST) {
// Fix: treat 0 as broadcast, not DM:00000000
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
} else {
sm.dest = packet->decoded.dest;
sm.type = MessageType::DM_TO_US;
}
} else {
// === Incoming message ===
sm.sender = packet->from;
if (packet->to == NODENUM_BROADCAST || packet->decoded.dest == NODENUM_BROADCAST) {
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
} else {
sm.dest = nodeDB->getNodeNum(); // our node (we are DM target)
sm.type = MessageType::DM_TO_US;
}
hasUnreadMessage = true; // only incoming triggers mail icon
// Wake/force display if configured
if (shouldWakeOnReceivedMessage()) {
setOn(true);
forceDisplay();
}
// Prepare banner
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
char banner[256];
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') { // bell
isAlert = true;
break;
}
}
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else {
strcpy(banner, "New Message");
}
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
// Save to store (both outgoing + incoming)
messageStore.addLiveMessage(sm);
// Keep frame set active
setFrames(FOCUS_PRESERVE);
// Auto-switch thread view
if (sm.type == MessageType::BROADCAST) {
graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::CHANNEL, sm.channelIndex);
} else if (sm.type == MessageType::DM_TO_US) {
uint32_t peer = (packet->from == 0) ? sm.dest : sm.sender;
graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::DIRECT, -1, peer);
}
// Reset scroll so newest message starts from the top
graphics::MessageRenderer::resetScrollState();
return 0;
}
// Triggered by MeshModules // Triggered by MeshModules
int Screen::handleUIFrameEvent(const UIFrameEvent *event) int Screen::handleUIFrameEvent(const UIFrameEvent *event)

View File

@ -40,7 +40,6 @@ class Screen
FOCUS_DEFAULT, // No specific frame FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT, FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK, FOCUS_CLOCK,
FOCUS_SYSTEM, FOCUS_SYSTEM,
@ -209,8 +208,6 @@ class Screen : public concurrency::OSThread
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate); CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver = CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate); CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleTextMessage);
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver = CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
CallbackObserver<Screen, const InputEvent *> inputObserver = CallbackObserver<Screen, const InputEvent *> inputObserver =
@ -229,7 +226,6 @@ class Screen : public concurrency::OSThread
FOCUS_DEFAULT, // No specific frame FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT, FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK, FOCUS_CLOCK,
FOCUS_SYSTEM, FOCUS_SYSTEM,
@ -575,7 +571,6 @@ class Screen : public concurrency::OSThread
// Handle observer events // Handle observer events
int handleStatusUpdate(const meshtastic::Status *arg); int handleStatusUpdate(const meshtastic::Status *arg);
int handleTextMessage(const meshtastic_MeshPacket *arg);
int handleUIFrameEvent(const UIFrameEvent *arg); int handleUIFrameEvent(const UIFrameEvent *arg);
int handleInputEvent(const InputEvent *arg); int handleInputEvent(const InputEvent *arg);
int handleAdminMessage(AdminModule_ObserverData *arg); int handleAdminMessage(AdminModule_ObserverData *arg);

View File

@ -1,26 +1,3 @@
/*
BaseUI
Developed and Maintained By:
- Ronald Garcia (HarukiToreda) Lead development and implementation.
- JasonP (Xaositek) Screen layout and icon design, UI improvements and testing.
- TonyG (Tropho) Project management, structural planning, and testing
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "configuration.h" #include "configuration.h"
#if HAS_SCREEN #if HAS_SCREEN
#include "MessageRenderer.h" #include "MessageRenderer.h"
@ -28,25 +5,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Core includes // Core includes
#include "MessageStore.h" #include "MessageStore.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h" #include "configuration.h"
#include "gps/RTC.h" #include "gps/RTC.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/emotes.h" #include "graphics/emotes.h"
#include "main.h" #include "main.h"
#include "meshUtils.h" #include "meshUtils.h"
// Additional includes for UI rendering
#include "UIRenderer.h"
#include "graphics/TimeFormatters.h"
// Additional includes for dependencies
#include <string> #include <string>
#include <vector> #include <vector>
// External declarations // External declarations
extern bool hasUnreadMessage; extern bool hasUnreadMessage;
extern meshtastic_DeviceState devicestate; extern meshtastic_DeviceState devicestate;
extern graphics::Screen *screen;
using graphics::Emote; using graphics::Emote;
using graphics::emotes; using graphics::emotes;
@ -65,7 +40,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
int cursorX = x; int cursorX = x;
const int fontHeight = FONT_HEIGHT_SMALL; const int fontHeight = FONT_HEIGHT_SMALL;
// === Step 1: Find tallest emote in the line === // Step 1: Find tallest emote in the line
int maxIconHeight = fontHeight; int maxIconHeight = fontHeight;
for (size_t i = 0; i < line.length();) { for (size_t i = 0; i < line.length();) {
bool matched = false; bool matched = false;
@ -248,7 +223,7 @@ uint32_t getThreadPeer()
return currentPeer; return currentPeer;
} }
// === Accessors for menuHandler === // Accessors for menuHandler
const std::vector<int> &getSeenChannels() const std::vector<int> &getSeenChannels()
{ {
return seenChannels; return seenChannels;
@ -656,6 +631,73 @@ void renderMessageContent(OLEDDisplay *display, const std::vector<std::string> &
} }
} }
void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &packet)
{
if (packet.from != 0) {
hasUnreadMessage = true;
if (shouldWakeOnReceivedMessage()) {
screen->setOn(true);
// screen->forceDisplay(); <-- remove, let Screen handle this
}
// Banner logic
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet.decoded.payload.bytes);
char banner[256];
bool isAlert = false;
for (size_t i = 0; i < packet.decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
if (isAlert) {
if (longName && longName[0])
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
else
strcpy(banner, "Alert Received");
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else
strcpy(banner, "New Message");
}
// Shorter banner if already in a conversation (Channel or Direct)
bool inThread = (getThreadMode() != ThreadMode::ALL);
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, inThread ? 1000 : 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, inThread ? 1000 : 3000);
#endif
}
// No setFrames() here anymore
setThreadFor(sm, packet);
resetScrollState();
}
void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet)
{
if (sm.type == MessageType::BROADCAST) {
setThreadMode(ThreadMode::CHANNEL, sm.channelIndex);
} else if (sm.type == MessageType::DM_TO_US) {
uint32_t peer = (packet.from == 0) ? sm.dest : sm.sender;
setThreadMode(ThreadMode::DIRECT, -1, peer);
}
}
} // namespace MessageRenderer } // namespace MessageRenderer
} // namespace graphics } // namespace graphics
#endif #endif

View File

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "MessageStore.h" // for StoredMessage
#include "OLEDDisplay.h" #include "OLEDDisplay.h"
#include "OLEDDisplayUi.h" #include "OLEDDisplayUi.h"
#include "graphics/emotes.h" #include "graphics/emotes.h"
#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket
#include <string> #include <string>
#include <vector> #include <vector>
@ -10,7 +12,7 @@ namespace graphics
namespace MessageRenderer namespace MessageRenderer
{ {
// === Thread filter modes === // Thread filter modes
enum class ThreadMode { ALL, CHANNEL, DIRECT }; enum class ThreadMode { ALL, CHANNEL, DIRECT };
// Setter for switching thread mode // Setter for switching thread mode
@ -50,5 +52,11 @@ void renderMessageContent(OLEDDisplay *display, const std::vector<std::string> &
// Reset scroll state when new messages arrive // Reset scroll state when new messages arrive
void resetScrollState(); void resetScrollState();
// Helper to auto-select the correct thread mode from a message
void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet);
// Handles a new incoming/outgoing message: banner, wake, thread select, scroll reset
void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &packet);
} // namespace MessageRenderer } // namespace MessageRenderer
} // namespace graphics } // namespace graphics

View File

@ -1,10 +1,13 @@
#include "TextMessageModule.h" #include "TextMessageModule.h"
#include "MeshService.h" #include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "PowerFSM.h" #include "PowerFSM.h"
#include "buzz.h" #include "buzz.h"
#include "configuration.h" #include "configuration.h"
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "graphics/draw/MessageRenderer.h"
TextMessageModule *textMessageModule; TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
@ -13,15 +16,23 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
auto &p = mp.decoded; auto &p = mp.decoded;
LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes); LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif #endif
// We only store/display messages destined for us. // We only store/display messages destined for us.
// Keep a copy of the most recent text message.
devicestate.rx_text_message = mp; devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true; devicestate.has_rx_text_message = true;
// Store in the central message history
const StoredMessage &sm = messageStore.addFromPacket(mp);
// Pass message to renderer (banner + thread switching + scroll reset)
graphics::MessageRenderer::handleNewMessage(sm, mp);
// Only trigger screen wake if configuration allows it // Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) { if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG); powerFSM.trigger(EVENT_RECEIVED_MSG);
} }
// Notify any observers (e.g. external modules that care about packets)
notifyObservers(&mp); notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want return ProcessMessage::CONTINUE; // Let others look at this message also if they want