mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 23:12:39 +00:00
Decoupled message packets from screen.cpp and cleaned up
This commit is contained in:
parent
dd7a5cf31f
commit
ebbb8a6f9f
@ -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");
|
||||
|
||||
@ -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;
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user