-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Multi message storage #8182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
HarukiToreda
wants to merge
116
commits into
develop
Choose a base branch
from
multi-message-Storage
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,295
−1,336
Open
Multi message storage #8182
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 d779821
Nrf built issue fix
HarukiToreda abcc166
Message view mode
HarukiToreda 3d8b4a6
Add channel name instead of channel slot
HarukiToreda 8040bb2
trunk fix
HarukiToreda 4e61016
Fix for DM threading
HarukiToreda 6ead5c0
fix for message time
HarukiToreda 3780290
rename of view mode to Conversations
HarukiToreda ea7638b
Reply in thread feature
HarukiToreda 8333ceb
rename Select View Mode to Select Conversation
HarukiToreda 91e1029
dismiss all live fix
HarukiToreda 4d10026
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda 46a8a9a
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda dd7a5cf
Messages from phone show on screen
HarukiToreda ebbb8a6
Decoupled message packets from screen.cpp and cleaned up
HarukiToreda 07d3726
Cannedmessage cleanup and emotes fixed
HarukiToreda 28502c9
Ack on messages sent
HarukiToreda 3e4f654
Ack message cleanup
HarukiToreda e3553c4
Dismiss feature fixed
HarukiToreda 4ae5e8a
removed legacy temporary messages
HarukiToreda 34bb858
Emote picker fix
HarukiToreda 7f1dc4e
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda 526c7f8
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda 7642d0c
Memory size debug
HarukiToreda 0476eee
Merge remote-tracking branch 'upstream/develop' into multi-message-St…
HarukiToreda 65bb78b
Build error fix
HarukiToreda 4e84094
Sanity checks are okay sometimes
Xaositek 13eb53f
Lengthen channel name and finalize cleanup removal of Broadcast
Xaositek bebb1e9
Change DM to @ in order to unify on a single method
Xaositek 8860f61
Continue unifying display, also show message status on the "isMine" l…
Xaositek bbec517
Add context for incoming messages
Xaositek c9314c7
Better to say "in" vs "on"
Xaositek b8d33a3
crash fix for confirmation nodes
HarukiToreda 0b96486
Fix outbound labels based to avoid creating delays
Xaositek 9b57b21
Merge branch 'develop' into multi-message-Storage
Xaositek 383d95a
Eink autoscroll dissabled
HarukiToreda 103f73e
gating for message storage when not using a screen
HarukiToreda b549786
revert
HarukiToreda e7cdc7c
Merge branch 'develop' into multi-message-Storage
Xaositek 67c0000
Build fail fix
HarukiToreda 9f62007
Merge branch 'multi-message-Storage' of https://github.com/meshtastic…
HarukiToreda 993e874
Don't error out with unset MAC address in unit tests
jp-bennett cb8a8a2
Merge branch 'develop' into multi-message-Storage
HarukiToreda d5ce469
Provide some extra spacing for low hanging characters in messages
Xaositek def5018
Merge branch 'develop' into multi-message-Storage
Xaositek 9e520d0
Reorder menu options and reword Respond
Xaositek 974b3e6
Merge branch 'develop' into multi-message-Storage
Xaositek 541e050
Reword menus to better reflect actions
Xaositek 20e9703
Merge branch 'develop' into multi-message-Storage
Xaositek 7970a32
Go to thread from favorite screen
HarukiToreda e93b657
Reorder Favorite Action Menu with simple word modifications
Xaositek 334089a
Consolidate wording on "Chats"
Xaositek e609353
Mute channel fix
HarukiToreda 6069dc2
trunk fix
HarukiToreda 6d899c9
Clean up how muting works along with when we wake the screen
Xaositek 84cef67
Fix builds for HELTEC_MESH_SOLAR
Xaositek 784e71f
Signal bars for message ack
HarukiToreda 163d8e0
fix for notification renderer
HarukiToreda e606d88
Remove duplicate code, fix more Chats, and fix C6L MessageRenderer
Xaositek 97578fb
Fix to many warnings related to BaseUI
HarukiToreda abeeb12
preset aware signal strength display
HarukiToreda 1d7fe20
More C6L fixes and clean up header lines
Xaositek e934f8f
Use text aligns for message layout where necessary
Xaositek 9e9d2af
Attempt to fix memory usage of invalidLifetime
Xaositek 5e5a449
Merge branch 'develop' into multi-message-Storage
Xaositek 544331d
Merge branch 'develop' into multi-message-Storage
Xaositek 6dfaf23
Update channel mute for adjusted protobuf
Xaositek f9e3155
Merge branch 'develop' into multi-message-Storage
Xaositek 65dcd82
Missed a comma in merge conflicts
Xaositek 50a65a1
cleanup to get more space
HarukiToreda 5510863
Trunk fixes
HarukiToreda e389258
Optimize Hi Rez Chirpy to save space
Xaositek 0b11f93
more fixes
HarukiToreda f35f72e
More cleanup
HarukiToreda b0c0faa
Merge branch 'develop' into multi-message-Storage
Xaositek 8611175
Remove used getConversationWith
Xaositek 2f53f3f
Remove unused dismissNewestMessage
Xaositek 56656a4
Merge branch 'develop' into multi-message-Storage
Xaositek 5f8f3cf
Fix another build error on occassion
Xaositek 835f130
Merge branch 'develop' into multi-message-Storage
Xaositek 7aa5b93
Dimiss key combo function deprecated
HarukiToreda 3bb5a33
More cleanup
HarukiToreda 4ca56ec
Fn symbol code removed
HarukiToreda 8fb825e
Waypoint cleanup
HarukiToreda 214aa8b
Trunk fix
HarukiToreda 849a749
Fixup Waypoint screen with BaseUI code
Xaositek 2f65721
Implement Haruki's ClockRenderer and broadcast decomposeTime across v…
Xaositek ee3c7f2
Revert "Implement Haruki's ClockRenderer and broadcast decomposeTime …
Xaositek c180f23
Implement Haruki's ClockRenderer and broadcast decomposeTime across v…
Xaositek aaf4a7e
remove memory usage debug
HarukiToreda a05936f
Revert only RangeTestModule.cpp change
Xaositek 6cd64cc
Merge branch 'multi-message-Storage' of https://github.com/meshtastic…
Xaositek 4bd5350
Switch from dynamic std::string storage to fixed-size char[]
HarukiToreda 67c24c0
Removing old left over code
HarukiToreda 62eaabc
More optimization
HarukiToreda c8f3cbb
Free Heap when not on Message screen
HarukiToreda 7135387
build error fixes
HarukiToreda caff68f
Merge branch 'develop' into multi-message-Storage
Xaositek 3e3f03c
Restore ellipsis to end of long names
Xaositek 04c7720
Remove legacy function renderMessageContent
Xaositek 4aad908
Merge branch 'develop' into multi-message-Storage
Xaositek 4f79475
improved destination filtering
HarukiToreda fbf7ab0
force PKI
HarukiToreda f012f98
cleanup
HarukiToreda 14dfce5
Merge branch 'develop' into multi-message-Storage
HarukiToreda 13458d3
Shorten longNames to not exceed message popups
Xaositek c0361d2
log messages sent from apps
HarukiToreda f09ac16
Trunk fix
HarukiToreda b26c95d
Improve layout of messages screen
Xaositek 69f489e
Merge branch 'develop' into multi-message-Storage
Xaositek a635ee3
Fix potential crash for undefined variable
Xaositek dd95748
Merge branch 'develop' into multi-message-Storage
Xaositek bc9c187
Merge branch 'develop' into multi-message-Storage
Xaositek 9a12304
Revert changes to RedirectablePrint.cpp
Xaositek d25110f
Merge branch 'develop' into multi-message-Storage
Xaositek 3cb4e0e
Merge branch 'develop' into multi-message-Storage
Xaositek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.