Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 107 additions & 20 deletions src/MessageStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "SafeFile.h"
#include "gps/RTC.h"
#include "memory/MemAudit.h"
#include <cassert>
#include <cstring> // memcpy

#ifndef MESSAGE_TEXT_POOL_SIZE
Expand Down Expand Up @@ -65,6 +66,15 @@ static inline const char *getTextFromPool(uint16_t offset)
return &g_messagePool[offset];
}

static inline bool isIgnoredNodeNum(uint32_t nodeNum)
{
if (nodeNum == 0 || nodeNum == NODENUM_BROADCAST)
return false;

const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
return nodeInfoLiteIsIgnored(node);
}

// Helper: assign a timestamp (RTC if available, else boot-relative)
static inline void assignTimestamp(StoredMessage &sm)
{
Expand Down Expand Up @@ -163,9 +173,44 @@ static inline void autosaveTick(MessageStore *store)
}
#endif

bool MessageStore::shouldStorePacket(const meshtastic_MeshPacket &packet) const
{
const uint32_t localNode = nodeDB->getNodeNum();
const bool isDM = packet.to != 0 && packet.to != NODENUM_BROADCAST;
if (isDM) {
const bool outgoing = packet.from == 0 || packet.from == localNode;
const uint32_t peer = outgoing ? packet.to : packet.from;
return !isIgnoredNodeNum(peer);
}

if (packet.from != 0 && packet.from != localNode)
return !isIgnoredNodeNum(packet.from);

return true;
}

bool MessageStore::isMessageVisible(const StoredMessage &msg) const
{
const uint32_t localNode = nodeDB->getNodeNum();
if (msg.type == MessageType::DM_TO_US) {
const uint32_t peer = (msg.sender == localNode) ? msg.dest : msg.sender;
return !isIgnoredNodeNum(peer);
}

if (msg.sender != 0 && msg.sender != localNode)
return !isIgnoredNodeNum(msg.sender);

return true;
}

// Add from incoming/outgoing packet
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
const StoredMessage *MessageStore::tryAddFromPacket(const meshtastic_MeshPacket &packet)
{
if (!shouldStorePacket(packet)) {
LOG_DEBUG("Drop store 0x%08x", packet.from);
return nullptr;
}

StoredMessage sm;
assignTimestamp(sm);
sm.channelIndex = packet.channel;
Expand Down Expand Up @@ -196,7 +241,14 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
markMessageStoreUnsaved();
#endif

return liveMessages.back();
return &liveMessages.back();
}

const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{
const StoredMessage *stored = tryAddFromPacket(packet);
assert(stored);
return *stored;
}

// Outgoing/manual message
Expand Down Expand Up @@ -323,28 +375,33 @@ void MessageStore::loadFromFlash()
resetMessagePool(); // reset pool when loading

#ifdef FSCom
concurrency::LockGuard guard(spiLock);
{
concurrency::LockGuard guard(spiLock);

if (!FSCom.exists(filename.c_str()))
return;
if (!FSCom.exists(filename.c_str()))
return;

auto f = FSCom.open(filename.c_str(), FILE_O_READ);
if (!f)
return;
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
if (!f)
return;

uint8_t count = 0;
f.readBytes(reinterpret_cast<char *>(&count), 1);
if (count > MAX_MESSAGES_SAVED)
count = MAX_MESSAGES_SAVED;
uint8_t count = 0;
f.readBytes(reinterpret_cast<char *>(&count), 1);
if (count > MAX_MESSAGES_SAVED)
count = MAX_MESSAGES_SAVED;

for (uint8_t i = 0; i < count; ++i) {
StoredMessage m;
if (!readMessageRecord(f, m))
break;
liveMessages.push_back(m);
for (uint8_t i = 0; i < count; ++i) {
StoredMessage m;
if (!readMessageRecord(f, m))
break;
liveMessages.push_back(m);
}

f.close();
}

f.close();
if (pruneHiddenMessages())
saveToFlash();
#endif
// Loading messages does not trigger an autosave
g_messageStoreHasUnsavedChanges = false;
Expand Down Expand Up @@ -406,6 +463,13 @@ template <typename Predicate> static void eraseAllMatches(std::deque<StoredMessa
}
}

bool MessageStore::pruneHiddenMessages()
{
const size_t before = liveMessages.size();
eraseAllMatches(liveMessages, [&](const StoredMessage &m) { return !isMessageVisible(m); });
return liveMessages.size() != before;
}

// Delete oldest message (RAM + persisted queue)
void MessageStore::deleteOldestMessage()
{
Expand Down Expand Up @@ -443,6 +507,20 @@ void MessageStore::deleteAllMessagesWithPeer(uint32_t peer)
saveToFlash();
}

void MessageStore::deleteAllMessagesFromNode(uint32_t nodeNum)
{
const uint32_t local = nodeDB->getNodeNum();
auto pred = [&](const StoredMessage &m) {
if (m.sender == nodeNum)
return true;
if (m.type != MessageType::DM_TO_US)
return false;
return m.sender == local ? m.dest == nodeNum : m.sender == nodeNum;
};
eraseAllMatches(liveMessages, pred);
saveToFlash();
}

// Delete oldest message in a direct chat with a node
void MessageStore::deleteOldestMessageWithPeer(uint32_t peer)
{
Expand All @@ -460,7 +538,7 @@ std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) cons
{
std::deque<StoredMessage> result;
for (const auto &m : liveMessages) {
if (m.type == MessageType::BROADCAST && m.channelIndex == channel) {
if (isMessageVisible(m) && m.type == MessageType::BROADCAST && m.channelIndex == channel) {
result.push_back(m);
}
}
Expand All @@ -471,13 +549,22 @@ std::deque<StoredMessage> MessageStore::getDirectMessages() const
{
std::deque<StoredMessage> result;
for (const auto &m : liveMessages) {
if (m.type == MessageType::DM_TO_US) {
if (isMessageVisible(m) && m.type == MessageType::DM_TO_US) {
result.push_back(m);
}
}
return result;
}

bool MessageStore::hasVisibleMessages() const
{
for (const auto &m : liveMessages) {
if (isMessageVisible(m))
return true;
}
return false;
}

// 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.
Expand Down
8 changes: 6 additions & 2 deletions src/MessageStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class MessageStore
void addLiveMessage(StoredMessage &&msg);
void addLiveMessage(const StoredMessage &msg); // convenience overload
const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; }

const StoredMessage *tryAddFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing -> RAM only
// Add new messages from packets or manual input
const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only
void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text); // Manual add
Expand All @@ -111,13 +111,16 @@ class MessageStore
void deleteOldestMessageWithPeer(uint32_t peer);
void deleteAllMessagesInChannel(uint8_t channel);
void deleteAllMessagesWithPeer(uint32_t peer);

void deleteAllMessagesFromNode(uint32_t nodeNum);
// Unified accessor (for UI code, defaults to RAM buffer)
const std::deque<StoredMessage> &getMessages() const { return liveMessages; }
bool hasVisibleMessages() const;

// Helper filters for future use
std::deque<StoredMessage> getChannelMessages(uint8_t channel) const; // Only broadcast messages on a channel
std::deque<StoredMessage> getDirectMessages() const; // Only direct messages
bool shouldStorePacket(const meshtastic_MeshPacket &mp) const;
bool isMessageVisible(const StoredMessage &msg) const;

// Upgrade boot-relative timestamps once RTC is valid
void upgradeBootRelativeTimestamps();
Expand All @@ -129,6 +132,7 @@ class MessageStore
static uint16_t storeText(const char *src, size_t len);

private:
bool pruneHiddenMessages();
std::deque<StoredMessage> liveMessages; // Single in-RAM message buffer (also used for persistence)
std::string filename; // Flash filename for persistence
};
Expand Down
12 changes: 6 additions & 6 deletions src/graphics/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2110,7 +2110,7 @@ int Screen::handleInputEvent(const InputEvent *event)
if (ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {

if (event->inputEvent == INPUT_BROKER_UP) {
if (messageStore.getMessages().empty()) {
if (!messageStore.hasVisibleMessages()) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else {
graphics::MessageRenderer::scrollUp();
Expand All @@ -2120,7 +2120,7 @@ int Screen::handleInputEvent(const InputEvent *event)
}

if (event->inputEvent == INPUT_BROKER_DOWN) {
if (messageStore.getMessages().empty()) {
if (!messageStore.hasVisibleMessages()) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else {
graphics::MessageRenderer::scrollDown();
Expand Down Expand Up @@ -2169,9 +2169,9 @@ int Screen::handleInputEvent(const InputEvent *event)
if (!inputIntercepted) {
#if defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2
bool handledEncoderScroll = false;
const bool isTextMessageFrame = (framesetInfo.positions.textMessage != 255 &&
this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage &&
!messageStore.getMessages().empty());
const bool isTextMessageFrame =
(framesetInfo.positions.textMessage != 255 &&
this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage && messageStore.hasVisibleMessages());
if (isTextMessageFrame) {
if (event->inputEvent == INPUT_BROKER_UP_LONG) {
graphics::MessageRenderer::nudgeScroll(-1);
Expand Down Expand Up @@ -2254,7 +2254,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::loraMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (!messageStore.getMessages().empty()) {
if (messageStore.hasVisibleMessages()) {
menuHandler::messageResponseMenu();
} else {
if (currentResolution == ScreenResolution::UltraLow) {
Expand Down
11 changes: 7 additions & 4 deletions src/graphics/draw/MenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,15 @@ const StoredMessage *getNewestMessageForActiveThread()
const uint32_t peer = graphics::MessageRenderer::getThreadPeer();
const uint32_t localNode = nodeDB->getNodeNum();

if (mode == graphics::MessageRenderer::ThreadMode::ALL) {
return &messages.back();
}

for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
const StoredMessage &m = *it;
if (!messageStore.isMessageVisible(m)) {
continue;
}

if (mode == graphics::MessageRenderer::ThreadMode::ALL) {
return &m;
}

if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) {
if (m.type == MessageType::BROADCAST && static_cast<int>(m.channelIndex) == channel) {
Expand Down
2 changes: 2 additions & 0 deletions src/graphics/draw/MessageRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Filter messages based on thread mode
std::deque<StoredMessage> filtered;
for (const auto &m : messageStore.getLiveMessages()) {
if (!messageStore.isMessageVisible(m))
continue;
bool include = false;
switch (currentMode) {
case ThreadMode::ALL:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
// Pick source of message
const StoredMessage *message =
msgIsBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;

if (!message->sender || !messageStore.isMessageVisible(*message))
return parse(text);
// Find info about the sender
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);

Expand Down Expand Up @@ -270,4 +271,4 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
return parse(text);
}

#endif
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ void InkHUD::AllMessageApplet::onRender(bool full)
message = &latestMessage->dm;

// Short circuit: no text message
if (!message->sender) {
if (!message->sender || !messageStore.isMessageVisible(*message)) {
printAt(X(0.5), Y(0.5), "No Message", CENTER, MIDDLE);
return;
}
Expand Down Expand Up @@ -138,4 +138,4 @@ bool InkHUD::AllMessageApplet::approveNotification(Notification &n)
return true;
}

#endif
#endif
4 changes: 2 additions & 2 deletions src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
void InkHUD::DMApplet::onRender(bool full)
{
// Abort if no text message
if (!latestMessage->dm.sender) {
if (!latestMessage->dm.sender || !messageStore.isMessageVisible(latestMessage->dm)) {
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
return;
}
Expand Down Expand Up @@ -131,4 +131,4 @@ bool InkHUD::DMApplet::approveNotification(Notification &n)
return true;
}

#endif
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ ProcessMessage InkHUD::ThreadedMessageApplet::handleReceived(const meshtastic_Me
return ProcessMessage::CONTINUE;

// Store in the global messageStore - this handles sender, timestamp, channel, text, and ack status
messageStore.addFromPacket(mp);
if (!messageStore.tryAddFromPacket(mp))
return ProcessMessage::CONTINUE;

// If this was an incoming message, suggest that our applet becomes foreground, if permitted
if (getFrom(&mp) != nodeDB->getNodeNum())
Expand Down
8 changes: 7 additions & 1 deletion src/graphics/niche/InkHUD/Events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,13 +534,19 @@ int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
if (getFrom(packet) == nodeDB->getNodeNum())
return 0;

if (!messageStore.shouldStorePacket(*packet))
return 0;

bool isBroadcastMsg = isBroadcast(packet->to);
inkhud->persistence->latestMessage.wasBroadcast = isBroadcastMsg;

if (!isBroadcastMsg) {
// DMs never pass through ThreadedMessageApplet, so add them to the global store here
// so they survive reboots. Derive the latestMessage cache entry from the stored result.
inkhud->persistence->latestMessage.dm = messageStore.addFromPacket(*packet);
const StoredMessage *stored = messageStore.tryAddFromPacket(*packet);
if (!stored)
return 0;
inkhud->persistence->latestMessage.dm = *stored;
} else {
// Broadcasts are added to the global store by ThreadedMessageApplet::handleReceived().
// Here we only update the latestMessage cache used by AllMessageApplet / NotificationApplet.
Expand Down
4 changes: 4 additions & 0 deletions src/graphics/niche/InkHUD/Persistence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ void InkHUD::Persistence::loadLatestMessage()

int lastBroadcastPos = -1, lastDMPos = -1, pos = 0;
for (const StoredMessage &m : messageStore.getLiveMessages()) {
if (!messageStore.isMessageVisible(m)) {
pos++;
continue;
}
if (m.type == MessageType::BROADCAST) {
latestMessage.broadcast = m;
lastBroadcastPos = pos;
Expand Down
Loading
Loading