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 "FSCommon.h"
#include "NodeDB.h" // for nodeDB->getNodeNum()
#include "NodeDB.h"
#include "SPILock.h"
#include "SafeFile.h"
#include "configuration.h" // for millis()
#include "configuration.h"
#include "gps/RTC.h"
#include "graphics/draw/MessageRenderer.h"
@ -15,7 +15,7 @@ MessageStore::MessageStore(const std::string &label)
filename = "/Messages_" + label + ".msgs";
}
// === Live message handling (RAM only) ===
// Live message handling (RAM only)
void MessageStore::addLiveMessage(const StoredMessage &msg)
{
if (liveMessages.size() >= MAX_MESSAGES_SAVED) {
@ -24,7 +24,7 @@ void MessageStore::addLiveMessage(const StoredMessage &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)
{
if (messages.size() >= MAX_MESSAGES_SAVED) {
@ -32,8 +32,7 @@ void MessageStore::addMessage(const StoredMessage &msg)
}
messages.push_back(msg);
}
void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{
StoredMessage sm;
@ -47,26 +46,41 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
sm.isBootRelative = true; // mark for later upgrade
}
sm.sender = packet.from;
sm.channelIndex = packet.channel;
sm.text = std::string(reinterpret_cast<const char *>(packet.decoded.payload.bytes));
// Classification logic
if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) {
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
} else if (packet.to == nodeDB->getNodeNum()) {
sm.dest = nodeDB->getNodeNum(); // DM to us
sm.type = MessageType::DM_TO_US;
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 {
sm.dest = NODENUM_BROADCAST; // fallback
sm.type = MessageType::BROADCAST;
// Normal incoming
sm.sender = packet.from;
if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) {
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
} else if (packet.to == nodeDB->getNodeNum()) {
sm.dest = nodeDB->getNodeNum(); // DM to us
sm.type = MessageType::DM_TO_US;
} else {
sm.dest = NODENUM_BROADCAST; // fallback
sm.type = MessageType::BROADCAST;
}
}
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)
{
StoredMessage sm;
@ -92,7 +106,7 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
addLiveMessage(sm);
}
// === Save RAM queue to flash (called on shutdown) ===
// Save RAM queue to flash (called on shutdown)
void MessageStore::saveToFlash()
{
#ifdef FSCom
@ -128,7 +142,7 @@ void MessageStore::saveToFlash()
#endif
}
// === Load persisted messages into RAM (called at boot) ===
// Load persisted messages into RAM (called at boot)
void MessageStore::loadFromFlash()
{
messages.clear();
@ -189,7 +203,7 @@ void MessageStore::loadFromFlash()
#endif
}
// === Clear all messages (RAM + persisted queue) ===
// Clear all messages (RAM + persisted queue)
void MessageStore::clearAllMessages()
{
liveMessages.clear();
@ -203,7 +217,7 @@ void MessageStore::clearAllMessages()
#endif
}
// === Dismiss oldest message (RAM + persisted queue) ===
// Dismiss oldest message (RAM + persisted queue)
void MessageStore::dismissOldestMessage()
{
if (!liveMessages.empty()) {
@ -215,7 +229,7 @@ void MessageStore::dismissOldestMessage()
saveToFlash();
}
// === Dismiss newest message (RAM + persisted queue) ===
// Dismiss newest message (RAM + persisted queue)
void MessageStore::dismissNewestMessage()
{
if (!liveMessages.empty()) {
@ -227,7 +241,7 @@ void MessageStore::dismissNewestMessage()
saveToFlash();
}
// === Helper filters for future use ===
// Helper filters for future use
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
{
std::deque<StoredMessage> result;
@ -250,7 +264,7 @@ std::deque<StoredMessage> MessageStore::getDirectMessages() const
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.
// Persisted boot-relative messages from old boots stay ??? forever.
void MessageStore::upgradeBootRelativeTimestamps()
@ -280,5 +294,5 @@ void MessageStore::upgradeBootRelativeTimestamps()
}
}
// === Global definition ===
// Global definition
MessageStore messageStore("default");

View File

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

View File

@ -283,12 +283,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
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.
*
@ -514,10 +508,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
void Screen::setup()
{
// === Enable display rendering ===
// Enable display rendering
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
if (uiconfig.screen_brightness == 0) {
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
@ -529,7 +523,7 @@ void Screen::setup()
brightness = uiconfig.screen_brightness;
}
// === Detect OLED subtype (if supported by board variant) ===
// Detect OLED subtype (if supported by board variant)
#ifdef AutoOLEDWire_h
if (isAUTOOled)
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
@ -544,7 +538,7 @@ void Screen::setup()
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
// === Initialize display and UI system ===
// Initialize display and UI system
ui->init();
displayWidth = dispdev->width();
displayHeight = dispdev->height();
@ -556,7 +550,7 @@ void Screen::setup()
ui->disableAllIndicators(); // Disable page indicator dots
ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance
// === Apply loaded brightness ===
// Apply loaded brightness
#if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
@ -564,20 +558,20 @@ void Screen::setup()
#endif
LOG_INFO("Applied screen brightness: %d", brightness);
// === Set custom overlay callbacks ===
// Set custom overlay callbacks
static OverlayCallback overlays[] = {
graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame
};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
// === Enable UTF-8 to display mapping ===
// Enable UTF-8 to display mapping
dispdev->setFontTableLookupFunction(customFontTableLookup);
#ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Give more time for branded boot logos
#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
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
#ifdef ARCH_ESP32
@ -593,10 +587,10 @@ void Screen::setup()
ui->setFrames(alertFrames, 1);
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);
// === 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
dispdev->mirrorScreen();
#else
@ -612,7 +606,7 @@ void Screen::setup()
}
#endif
// === Generate device ID from MAC address ===
// Generate device ID from MAC address
uint8_t dmac[6];
getMacAddr(dmac);
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
#endif
// === Turn on display and trigger first draw ===
// Turn on display and trigger first draw
handleSetOn(true);
determineResolution(dispdev->height(), dispdev->width());
ui->update();
@ -644,7 +638,7 @@ void Screen::setup()
touchScreenImpl1->init();
#endif
// === Subscribe to device status updates ===
// Subscribe to device status updates
powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
@ -652,16 +646,14 @@ void Screen::setup()
#if !MESHTASTIC_EXCLUDE_ADMIN
adminMessageObserver.observe(adminModule);
#endif
if (textMessageModule)
textMessageObserver.observe(textMessageModule);
if (inputBroker)
inputObserver.observe(inputBroker);
// === Load persisted messages into RAM ===
// Load persisted messages into RAM
messageStore.loadFromFlash();
LOG_INFO("MessageStore loaded from flash");
// === Notify modules that support UI events ===
// Notify modules that support UI events
MeshModule::observeUIEvents(&uiFrameEventObserver);
}
@ -1130,7 +1122,7 @@ void Screen::setFrames(FrameFocus focus)
}
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);
ui->setFrames(normalFrames, numframes);
@ -1150,10 +1142,6 @@ void Screen::setFrames(FrameFocus focus)
case FOCUS_FAULT:
ui->switchToFrame(fsi.positions.fault);
break;
case FOCUS_TEXTMESSAGE:
hasUnreadMessage = false; // ✅ Clear when message is *viewed*
ui->switchToFrame(fsi.positions.textMessage);
break;
case FOCUS_MODULE:
// Whichever frame was marked by MeshModule::requestFocus(), if any
// If no module requested focus, will show the first frame instead
@ -1438,120 +1426,6 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
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
int Screen::handleUIFrameEvent(const UIFrameEvent *event)

View File

@ -40,7 +40,6 @@ class Screen
FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK,
FOCUS_SYSTEM,
@ -209,8 +208,6 @@ class Screen : public concurrency::OSThread
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
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 *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
CallbackObserver<Screen, const InputEvent *> inputObserver =
@ -229,7 +226,6 @@ class Screen : public concurrency::OSThread
FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK,
FOCUS_SYSTEM,
@ -575,7 +571,6 @@ class Screen : public concurrency::OSThread
// Handle observer events
int handleStatusUpdate(const meshtastic::Status *arg);
int handleTextMessage(const meshtastic_MeshPacket *arg);
int handleUIFrameEvent(const UIFrameEvent *arg);
int handleInputEvent(const InputEvent *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"
#if HAS_SCREEN
#include "MessageRenderer.h"
@ -28,25 +5,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Core includes
#include "MessageStore.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/RTC.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/emotes.h"
#include "main.h"
#include "meshUtils.h"
// Additional includes for UI rendering
#include "UIRenderer.h"
#include "graphics/TimeFormatters.h"
// Additional includes for dependencies
#include <string>
#include <vector>
// External declarations
extern bool hasUnreadMessage;
extern meshtastic_DeviceState devicestate;
extern graphics::Screen *screen;
using graphics::Emote;
using graphics::emotes;
@ -65,7 +40,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
int cursorX = x;
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;
for (size_t i = 0; i < line.length();) {
bool matched = false;
@ -248,7 +223,7 @@ uint32_t getThreadPeer()
return currentPeer;
}
// === Accessors for menuHandler ===
// Accessors for menuHandler
const std::vector<int> &getSeenChannels()
{
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 graphics
#endif

View File

@ -1,7 +1,9 @@
#pragma once
#include "MessageStore.h" // for StoredMessage
#include "OLEDDisplay.h"
#include "OLEDDisplayUi.h"
#include "graphics/emotes.h"
#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket
#include <string>
#include <vector>
@ -10,7 +12,7 @@ namespace graphics
namespace MessageRenderer
{
// === Thread filter modes ===
// Thread filter modes
enum class ThreadMode { ALL, CHANNEL, DIRECT };
// 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
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 graphics
} // namespace graphics

View File

@ -1,10 +1,13 @@
#include "TextMessageModule.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "buzz.h"
#include "configuration.h"
#include "graphics/Screen.h"
#include "graphics/draw/MessageRenderer.h"
TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
@ -13,15 +16,23 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
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);
#endif
// We only store/display messages destined for us.
// Keep a copy of the most recent text message.
devicestate.rx_text_message = mp;
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
if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG);
}
// Notify any observers (e.g. external modules that care about packets)
notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
@ -30,4 +41,4 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
bool TextMessageModule::wantPacket(const meshtastic_MeshPacket *p)
{
return MeshService::isTextPayload(p);
}
}

View File

@ -29,4 +29,4 @@ class TextMessageModule : public SinglePortModule, public Observable<const mesht
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
};
extern TextMessageModule *textMessageModule;
extern TextMessageModule *textMessageModule;