Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
cf9bc7a
First try at multimessage storage and display
HarukiToreda Sep 21, 2025
d779821
Nrf built issue fix
HarukiToreda Sep 21, 2025
abcc166
Message view mode
HarukiToreda Sep 22, 2025
3d8b4a6
Add channel name instead of channel slot
HarukiToreda Sep 23, 2025
8040bb2
trunk fix
HarukiToreda Sep 23, 2025
4e61016
Fix for DM threading
HarukiToreda Sep 23, 2025
6ead5c0
fix for message time
HarukiToreda Sep 23, 2025
3780290
rename of view mode to Conversations
HarukiToreda Sep 23, 2025
ea7638b
Reply in thread feature
HarukiToreda Sep 23, 2025
8333ceb
rename Select View Mode to Select Conversation
HarukiToreda Sep 23, 2025
91e1029
dismiss all live fix
HarukiToreda Sep 23, 2025
4d10026
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda Sep 23, 2025
46a8a9a
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda Sep 24, 2025
dd7a5cf
Messages from phone show on screen
HarukiToreda Sep 26, 2025
ebbb8a6
Decoupled message packets from screen.cpp and cleaned up
HarukiToreda Sep 26, 2025
07d3726
Cannedmessage cleanup and emotes fixed
HarukiToreda Sep 26, 2025
28502c9
Ack on messages sent
HarukiToreda Sep 26, 2025
3e4f654
Ack message cleanup
HarukiToreda Sep 28, 2025
e3553c4
Dismiss feature fixed
HarukiToreda Sep 28, 2025
4ae5e8a
removed legacy temporary messages
HarukiToreda Sep 28, 2025
34bb858
Emote picker fix
HarukiToreda Sep 28, 2025
7f1dc4e
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda Sep 28, 2025
526c7f8
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda Sep 28, 2025
7642d0c
Memory size debug
HarukiToreda Sep 30, 2025
0476eee
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda Oct 2, 2025
65bb78b
Build error fix
HarukiToreda Oct 2, 2025
4e84094
Sanity checks are okay sometimes
Xaositek Oct 5, 2025
13eb53f
Lengthen channel name and finalize cleanup removal of Broadcast
Xaositek Oct 5, 2025
bebb1e9
Change DM to @ in order to unify on a single method
Xaositek Oct 5, 2025
8860f61
Continue unifying display, also show message status on the "isMine" l…
Xaositek Oct 5, 2025
bbec517
Add context for incoming messages
Xaositek Oct 5, 2025
c9314c7
Better to say "in" vs "on"
Xaositek Oct 5, 2025
b8d33a3
crash fix for confirmation nodes
HarukiToreda Oct 6, 2025
0b96486
Fix outbound labels based to avoid creating delays
Xaositek Oct 6, 2025
9b57b21
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 6, 2025
383d95a
Eink autoscroll dissabled
HarukiToreda Oct 6, 2025
103f73e
gating for message storage when not using a screen
HarukiToreda Oct 6, 2025
b549786
revert
HarukiToreda Oct 6, 2025
e7cdc7c
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 6, 2025
67c0000
Build fail fix
HarukiToreda Oct 6, 2025
9f62007
Merge branch 'multi-message-Storage' of https://github.com/meshtastic…
HarukiToreda Oct 6, 2025
993e874
Don't error out with unset MAC address in unit tests
jp-bennett Oct 6, 2025
cb8a8a2
Merge branch 'develop' into multi-message-Storage
HarukiToreda Oct 6, 2025
d5ce469
Provide some extra spacing for low hanging characters in messages
Xaositek Oct 6, 2025
def5018
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 7, 2025
9e520d0
Reorder menu options and reword Respond
Xaositek Oct 7, 2025
974b3e6
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 7, 2025
541e050
Reword menus to better reflect actions
Xaositek Oct 7, 2025
20e9703
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 8, 2025
7970a32
Go to thread from favorite screen
HarukiToreda Oct 9, 2025
e93b657
Reorder Favorite Action Menu with simple word modifications
Xaositek Oct 9, 2025
334089a
Consolidate wording on "Chats"
Xaositek Oct 9, 2025
e609353
Mute channel fix
HarukiToreda Oct 10, 2025
6069dc2
trunk fix
HarukiToreda Oct 10, 2025
6d899c9
Clean up how muting works along with when we wake the screen
Xaositek Oct 10, 2025
84cef67
Fix builds for HELTEC_MESH_SOLAR
Xaositek Oct 10, 2025
784e71f
Signal bars for message ack
HarukiToreda Oct 10, 2025
163d8e0
fix for notification renderer
HarukiToreda Oct 10, 2025
e606d88
Remove duplicate code, fix more Chats, and fix C6L MessageRenderer
Xaositek Oct 10, 2025
97578fb
Fix to many warnings related to BaseUI
HarukiToreda Oct 10, 2025
abeeb12
preset aware signal strength display
HarukiToreda Oct 10, 2025
1d7fe20
More C6L fixes and clean up header lines
Xaositek Oct 10, 2025
e934f8f
Use text aligns for message layout where necessary
Xaositek Oct 11, 2025
9e9d2af
Attempt to fix memory usage of invalidLifetime
Xaositek Oct 11, 2025
5e5a449
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 11, 2025
544331d
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 12, 2025
6dfaf23
Update channel mute for adjusted protobuf
Xaositek Oct 12, 2025
f9e3155
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 12, 2025
65dcd82
Missed a comma in merge conflicts
Xaositek Oct 12, 2025
50a65a1
cleanup to get more space
HarukiToreda Oct 12, 2025
5510863
Trunk fixes
HarukiToreda Oct 12, 2025
e389258
Optimize Hi Rez Chirpy to save space
Xaositek Oct 13, 2025
0b11f93
more fixes
HarukiToreda Oct 13, 2025
f35f72e
More cleanup
HarukiToreda Oct 13, 2025
b0c0faa
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 13, 2025
8611175
Remove used getConversationWith
Xaositek Oct 13, 2025
2f53f3f
Remove unused dismissNewestMessage
Xaositek Oct 13, 2025
56656a4
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 13, 2025
5f8f3cf
Fix another build error on occassion
Xaositek Oct 13, 2025
835f130
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 13, 2025
7aa5b93
Dimiss key combo function deprecated
HarukiToreda Oct 13, 2025
3bb5a33
More cleanup
HarukiToreda Oct 13, 2025
4ca56ec
Fn symbol code removed
HarukiToreda Oct 13, 2025
8fb825e
Waypoint cleanup
HarukiToreda Oct 13, 2025
214aa8b
Trunk fix
HarukiToreda Oct 13, 2025
849a749
Fixup Waypoint screen with BaseUI code
Xaositek Oct 13, 2025
2f65721
Implement Haruki's ClockRenderer and broadcast decomposeTime across v…
Xaositek Oct 14, 2025
ee3c7f2
Revert "Implement Haruki's ClockRenderer and broadcast decomposeTime …
Xaositek Oct 14, 2025
c180f23
Implement Haruki's ClockRenderer and broadcast decomposeTime across v…
Xaositek Oct 14, 2025
aaf4a7e
remove memory usage debug
HarukiToreda Oct 14, 2025
a05936f
Revert only RangeTestModule.cpp change
Xaositek Oct 14, 2025
6cd64cc
Merge branch 'multi-message-Storage' of https://github.com/meshtastic…
Xaositek Oct 14, 2025
4bd5350
Switch from dynamic std::string storage to fixed-size char[]
HarukiToreda Oct 14, 2025
67c24c0
Removing old left over code
HarukiToreda Oct 14, 2025
62eaabc
More optimization
HarukiToreda Oct 15, 2025
c8f3cbb
Free Heap when not on Message screen
HarukiToreda Oct 15, 2025
7135387
build error fixes
HarukiToreda Oct 15, 2025
caff68f
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 15, 2025
3e3f03c
Restore ellipsis to end of long names
Xaositek Oct 15, 2025
04c7720
Remove legacy function renderMessageContent
Xaositek Oct 16, 2025
4aad908
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 16, 2025
4f79475
improved destination filtering
HarukiToreda Oct 16, 2025
fbf7ab0
force PKI
HarukiToreda Oct 17, 2025
f012f98
cleanup
HarukiToreda Oct 17, 2025
14dfce5
Merge branch 'develop' into multi-message-Storage
HarukiToreda Oct 17, 2025
13458d3
Shorten longNames to not exceed message popups
Xaositek Oct 17, 2025
c0361d2
log messages sent from apps
HarukiToreda Oct 17, 2025
f09ac16
Trunk fix
HarukiToreda Oct 17, 2025
b26c95d
Improve layout of messages screen
Xaositek Oct 18, 2025
69f489e
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 18, 2025
a635ee3
Fix potential crash for undefined variable
Xaositek Oct 19, 2025
dd95748
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 19, 2025
bc9c187
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 19, 2025
9a12304
Revert changes to RedirectablePrint.cpp
Xaositek Oct 19, 2025
d25110f
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 20, 2025
3cb4e0e
Merge branch 'develop' into multi-message-Storage
Xaositek Oct 20, 2025
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
394 changes: 394 additions & 0 deletions src/MessageStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,394 @@
#include "MessageStore.h"
#include "FSCommon.h"
#include "NodeDB.h"
#include "SPILock.h"
#include "SafeFile.h"
#include "configuration.h"
#include "gps/RTC.h"
#include "graphics/draw/MessageRenderer.h"

using graphics::MessageRenderer::setThreadMode;
using graphics::MessageRenderer::ThreadMode;

static size_t getMessageSize(const StoredMessage &m)
{
// serialized size = fixed 16 bytes + text length (capped at MAX_MESSAGE_SIZE)
return 16 + std::min(static_cast<size_t>(MAX_MESSAGE_SIZE), m.text.size());
}

void MessageStore::logMemoryUsage(const char *context) const
{
size_t total = 0;
for (const auto &m : messages) {
total += getMessageSize(m);
}

LOG_DEBUG("MessageStore[%s]: %u messages, est %u bytes (~%u KB)", context, (unsigned)messages.size(), (unsigned)total,
(unsigned)(total / 1024));
}

MessageStore::MessageStore(const std::string &label)
{
filename = "/Messages_" + label + ".msgs";
}

// Live message handling (RAM only)
void MessageStore::addLiveMessage(const StoredMessage &msg)
{
if (liveMessages.size() >= MAX_MESSAGES_SAVED) {
liveMessages.pop_front(); // keep only most recent N
}
liveMessages.push_back(msg);
}

// Persistence queue (used only on shutdown/reboot)
void MessageStore::addMessage(const StoredMessage &msg)
{
if (messages.size() >= MAX_MESSAGES_SAVED) {
messages.pop_front();
}
messages.push_back(msg);
}
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{
StoredMessage sm;

// Always use our local time, ignore packet.rx_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) {
// 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;
}

// Outgoing messages start as NONE until ACK/NACK arrives
sm.ackStatus = AckStatus::NONE;
} else {
// 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;
}

// Received messages don’t wait for ACK mark as ACKED
sm.ackStatus = AckStatus::ACKED;
}

addLiveMessage(sm);

// Return reference to the most recently stored message
return liveMessages.back();
}

// Outgoing/manual message
void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text)
{
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.sender = sender;
sm.channelIndex = channelIndex;
sm.text = text;

// Default manual adds to broadcast
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;

// Outgoing messages start as NONE until ACK/NACK arrives
sm.ackStatus = AckStatus::NONE;

addLiveMessage(sm);
}

// Save RAM queue to flash (called on shutdown)
void MessageStore::saveToFlash()
{
#ifdef FSCom
// Copy live RAM buffer into persistence queue
messages = liveMessages;

spiLock->lock();
FSCom.mkdir("/"); // ensure root exists
spiLock->unlock();

SafeFile f(filename.c_str(), false);

spiLock->lock();
uint8_t count = messages.size();
f.write(&count, 1);

for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) {
const StoredMessage &m = messages.at(i);
f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp));
f.write((uint8_t *)&m.sender, sizeof(m.sender));
f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex));
f.write((uint8_t *)&m.dest, sizeof(m.dest));
f.write((uint8_t *)m.text.c_str(), std::min(static_cast<size_t>(MAX_MESSAGE_SIZE), m.text.size()));
f.write('\0'); // null terminator

uint8_t bootFlag = m.isBootRelative ? 1 : 0;
f.write(&bootFlag, 1); // persist boot-relative flag

uint8_t statusByte = static_cast<uint8_t>(m.ackStatus);
f.write(&statusByte, 1); // persist ackStatus
}
spiLock->unlock();

f.close();

// Debug after saving
logMemoryUsage("saveToFlash");
#else
// Filesystem not available, skip persistence
#endif
}

// Load persisted messages into RAM (called at boot)
void MessageStore::loadFromFlash()
{
messages.clear();
liveMessages.clear();
#ifdef FSCom
concurrency::LockGuard guard(spiLock);

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

uint8_t count = 0;
f.readBytes((char *)&count, 1);

for (uint8_t i = 0; i < count && i < MAX_MESSAGES_SAVED; i++) {
StoredMessage m;
f.readBytes((char *)&m.timestamp, sizeof(m.timestamp));
f.readBytes((char *)&m.sender, sizeof(m.sender));
f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex));
f.readBytes((char *)&m.dest, sizeof(m.dest));

char c;
while (m.text.size() < MAX_MESSAGE_SIZE) {
if (f.readBytes(&c, 1) <= 0)
break;
if (c == '\0')
break;
m.text.push_back(c);
}

// Try to read boot-relative flag (new format)
uint8_t bootFlag = 0;
if (f.available() > 0) {
if (f.readBytes((char *)&bootFlag, 1) == 1) {
m.isBootRelative = (bootFlag != 0);
} else {
// Old format, fallback heuristic
m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u);
}
} else {
// Old format, fallback heuristic
m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u);
}

// Try to read ackStatus (newer format)
if (f.available() > 0) {
uint8_t statusByte = 0;
if (f.readBytes((char *)&statusByte, 1) == 1) {
m.ackStatus = static_cast<AckStatus>(statusByte);
} else {
m.ackStatus = AckStatus::NONE;
}
} else {
m.ackStatus = AckStatus::NONE;
}

// Recompute type from dest
if (m.dest == NODENUM_BROADCAST) {
m.type = MessageType::BROADCAST;
} else {
m.type = MessageType::DM_TO_US;
}

messages.push_back(m);
liveMessages.push_back(m); // restore into RAM buffer
}
f.close();

// Debug after loading
logMemoryUsage("loadFromFlash");
#endif
}

// Clear all messages (RAM + persisted queue)
void MessageStore::clearAllMessages()
{
liveMessages.clear();
messages.clear();

#ifdef FSCom
SafeFile f(filename.c_str(), false);
uint8_t count = 0;
f.write(&count, 1); // write "0 messages"
f.close();
#endif
}

// Dismiss oldest message (RAM + persisted queue)
void MessageStore::dismissOldestMessage()
{
if (!liveMessages.empty()) {
liveMessages.pop_front();
}
if (!messages.empty()) {
messages.pop_front();
}
saveToFlash();
}

// Dismiss oldest message in a specific channel
void MessageStore::dismissOldestMessageInChannel(uint8_t channel)
{
auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [channel](const StoredMessage &m) {
return m.type == MessageType::BROADCAST && m.channelIndex == channel;
});
if (it != liveMessages.end()) {
liveMessages.erase(it);
}

auto it2 = std::find_if(messages.begin(), messages.end(), [channel](const StoredMessage &m) {
return m.type == MessageType::BROADCAST && m.channelIndex == channel;
});
if (it2 != messages.end()) {
messages.erase(it2);
}

saveToFlash();
}

// Dismiss oldest message in a direct conversation with a peer
void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
{
auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [peer](const StoredMessage &m) {
if (m.type == MessageType::DM_TO_US) {
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
return other == peer;
}
return false;
});
if (it != liveMessages.end()) {
liveMessages.erase(it);
}

auto it2 = std::find_if(messages.begin(), messages.end(), [peer](const StoredMessage &m) {
if (m.type == MessageType::DM_TO_US) {
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
return other == peer;
}
return false;
});
if (it2 != messages.end()) {
messages.erase(it2);
}

saveToFlash();
}

// Dismiss newest message (RAM + persisted queue)
void MessageStore::dismissNewestMessage()
{
if (!liveMessages.empty()) {
liveMessages.pop_back();
}
if (!messages.empty()) {
messages.pop_back();
}
saveToFlash();
}

// Helper filters for future use
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
{
std::deque<StoredMessage> result;
for (const auto &m : liveMessages) {
if (m.type == MessageType::BROADCAST && m.channelIndex == channel) {
result.push_back(m);
}
}
return result;
}

std::deque<StoredMessage> MessageStore::getDirectMessages() const
{
std::deque<StoredMessage> result;
for (const auto &m : liveMessages) {
if (m.type == MessageType::DM_TO_US) {
result.push_back(m);
}
}
return result;
}

// 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()
{
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
if (nowSecs == 0)
return; // Still no valid RTC

uint32_t bootNow = millis() / 1000;

for (auto &m : liveMessages) {
if (m.isBootRelative && m.timestamp <= bootNow) {
uint32_t bootOffset = nowSecs - bootNow;
m.timestamp += bootOffset;
m.isBootRelative = false;
}
// else: persisted from old boot → stays ??? forever
}

for (auto &m : messages) {
if (m.isBootRelative && m.timestamp <= bootNow) {
uint32_t bootOffset = nowSecs - bootNow;
m.timestamp += bootOffset;
m.isBootRelative = false;
}
// else: persisted from old boot → stays ??? forever
}
}

// Global definition
MessageStore messageStore("default");
Loading
Loading