mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-13 22:32:27 +00:00
More optimization
This commit is contained in:
@@ -9,6 +9,48 @@
|
||||
#include "graphics/draw/MessageRenderer.h"
|
||||
#include <cstring> // memcpy
|
||||
|
||||
#ifndef MESSAGE_TEXT_POOL_SIZE
|
||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
||||
#endif
|
||||
|
||||
// Global message text pool and state
|
||||
static char g_messagePool[MESSAGE_TEXT_POOL_SIZE];
|
||||
static size_t g_poolWritePos = 0;
|
||||
|
||||
// Reset pool (called on boot or clear)
|
||||
static inline void resetMessagePool()
|
||||
{
|
||||
g_poolWritePos = 0;
|
||||
memset(g_messagePool, 0, sizeof(g_messagePool));
|
||||
}
|
||||
|
||||
// Allocate text in pool and return offset
|
||||
// If not enough space remains, wrap around (ring buffer style)
|
||||
static inline uint16_t storeTextInPool(const char *src, size_t len)
|
||||
{
|
||||
if (len >= MAX_MESSAGE_SIZE)
|
||||
len = MAX_MESSAGE_SIZE - 1;
|
||||
|
||||
// Wrap pool if out of space
|
||||
if (g_poolWritePos + len + 1 >= MESSAGE_TEXT_POOL_SIZE) {
|
||||
g_poolWritePos = 0;
|
||||
}
|
||||
|
||||
uint16_t offset = g_poolWritePos;
|
||||
memcpy(&g_messagePool[g_poolWritePos], src, len);
|
||||
g_messagePool[g_poolWritePos + len] = '\0';
|
||||
g_poolWritePos += (len + 1);
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Retrieve a const pointer to message text by offset
|
||||
static inline const char *getTextFromPool(uint16_t offset)
|
||||
{
|
||||
if (offset >= MESSAGE_TEXT_POOL_SIZE)
|
||||
return "";
|
||||
return &g_messagePool[offset];
|
||||
}
|
||||
|
||||
// Helper: assign a timestamp (RTC if available, else boot-relative)
|
||||
static inline void assignTimestamp(StoredMessage &sm)
|
||||
{
|
||||
@@ -40,6 +82,7 @@ template <typename T> static inline void pushWithLimit(std::deque<T> &queue, T &
|
||||
MessageStore::MessageStore(const std::string &label)
|
||||
{
|
||||
filename = "/Messages_" + label + ".msgs";
|
||||
resetMessagePool(); // initialize text pool on boot
|
||||
}
|
||||
|
||||
// Live message handling (RAM only)
|
||||
@@ -56,24 +99,42 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
|
||||
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
||||
{
|
||||
StoredMessage sm;
|
||||
assignTimestamp(sm); // set timestamp (RTC or boot-relative)
|
||||
assignTimestamp(sm);
|
||||
sm.channelIndex = packet.channel;
|
||||
strncpy(sm.text, reinterpret_cast<const char *>(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1);
|
||||
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||
|
||||
const char *payload = reinterpret_cast<const char *>(packet.decoded.payload.bytes);
|
||||
size_t len = strnlen(payload, MAX_MESSAGE_SIZE - 1);
|
||||
sm.textOffset = storeTextInPool(payload, len);
|
||||
sm.textLength = len;
|
||||
|
||||
uint32_t localNode = nodeDB->getNodeNum();
|
||||
sm.sender = (packet.from == 0) ? localNode : packet.from;
|
||||
sm.dest = packet.decoded.dest;
|
||||
|
||||
// DM detection: use decoded.dest if valid, otherwise fallback to header 'to'
|
||||
bool isDM = false;
|
||||
uint32_t actualDest = sm.dest;
|
||||
|
||||
if (actualDest == 0 || actualDest == 0xffffffff) {
|
||||
actualDest = packet.to;
|
||||
}
|
||||
|
||||
if (actualDest != 0 && actualDest != NODENUM_BROADCAST && actualDest == localNode) {
|
||||
isDM = true;
|
||||
}
|
||||
|
||||
// Incoming vs outgoing classification
|
||||
if (packet.from == 0) {
|
||||
// Outgoing (phone-originated)
|
||||
sm.sender = nodeDB->getNodeNum();
|
||||
sm.dest = (packet.decoded.dest == 0) ? NODENUM_BROADCAST : packet.decoded.dest;
|
||||
sm.type = (sm.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST : MessageType::DM_TO_US;
|
||||
// Sent by us
|
||||
sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST;
|
||||
sm.ackStatus = AckStatus::NONE;
|
||||
} else {
|
||||
// Incoming
|
||||
sm.sender = packet.from;
|
||||
sm.dest = packet.decoded.dest;
|
||||
sm.type = (sm.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST
|
||||
: (sm.dest == nodeDB->getNodeNum()) ? MessageType::DM_TO_US
|
||||
: MessageType::BROADCAST;
|
||||
// Received from another node
|
||||
if (isDM) {
|
||||
sm.type = MessageType::DM_TO_US;
|
||||
} else {
|
||||
sm.type = MessageType::BROADCAST;
|
||||
}
|
||||
sm.ackStatus = AckStatus::ACKED;
|
||||
}
|
||||
|
||||
@@ -91,8 +152,8 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
||||
|
||||
sm.sender = sender;
|
||||
sm.channelIndex = channelIndex;
|
||||
strncpy(sm.text, text.c_str(), MAX_MESSAGE_SIZE - 1);
|
||||
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||
sm.textOffset = storeTextInPool(text.c_str(), text.size());
|
||||
sm.textLength = text.size();
|
||||
|
||||
// Default manual adds to broadcast
|
||||
sm.dest = NODENUM_BROADCAST;
|
||||
@@ -106,18 +167,17 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
|
||||
// Use a compile-time constant so the array bound can be used in the struct
|
||||
static constexpr size_t TEXT_LEN = MAX_MESSAGE_SIZE;
|
||||
|
||||
// Compact, fixed-size on-flash representation
|
||||
// Compact, fixed-size on-flash representation using offset + length
|
||||
struct __attribute__((packed)) StoredMessageRecord {
|
||||
uint32_t timestamp;
|
||||
uint32_t sender;
|
||||
uint8_t channelIndex;
|
||||
uint32_t dest;
|
||||
uint8_t isBootRelative;
|
||||
uint8_t ackStatus; // static_cast<uint8_t>(AckStatus)
|
||||
char text[TEXT_LEN]; // null-terminated
|
||||
uint8_t ackStatus; // static_cast<uint8_t>(AckStatus)
|
||||
uint8_t type; // static_cast<uint8_t>(MessageType)
|
||||
uint16_t textLength; // message length
|
||||
char text[MAX_MESSAGE_SIZE]; // <-- store actual text here
|
||||
};
|
||||
|
||||
// Serialize one StoredMessage to flash
|
||||
@@ -130,9 +190,14 @@ static inline void writeMessageRecord(SafeFile &f, const StoredMessage &m)
|
||||
rec.dest = m.dest;
|
||||
rec.isBootRelative = m.isBootRelative;
|
||||
rec.ackStatus = static_cast<uint8_t>(m.ackStatus);
|
||||
rec.type = static_cast<uint8_t>(m.type);
|
||||
rec.textLength = m.textLength;
|
||||
|
||||
// Copy the actual text into the record from RAM pool
|
||||
const char *txt = getTextFromPool(m.textOffset);
|
||||
strncpy(rec.text, txt, MAX_MESSAGE_SIZE - 1);
|
||||
rec.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||
|
||||
strncpy(rec.text, m.text, TEXT_LEN - 1);
|
||||
rec.text[TEXT_LEN - 1] = '\0';
|
||||
f.write(reinterpret_cast<const uint8_t *>(&rec), sizeof(rec));
|
||||
}
|
||||
|
||||
@@ -149,9 +214,13 @@ static inline bool readMessageRecord(File &f, StoredMessage &m)
|
||||
m.dest = rec.dest;
|
||||
m.isBootRelative = rec.isBootRelative;
|
||||
m.ackStatus = static_cast<AckStatus>(rec.ackStatus);
|
||||
strncpy(m.text, rec.text, MAX_MESSAGE_SIZE - 1);
|
||||
m.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||
m.type = (m.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST : MessageType::DM_TO_US;
|
||||
m.type = static_cast<MessageType>(rec.type);
|
||||
m.textLength = rec.textLength;
|
||||
|
||||
// 💡 Re-store text into pool and update offset
|
||||
m.textLength = strnlen(rec.text, MAX_MESSAGE_SIZE - 1);
|
||||
m.textOffset = storeTextInPool(rec.text, m.textLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,6 +252,8 @@ void MessageStore::saveToFlash()
|
||||
void MessageStore::loadFromFlash()
|
||||
{
|
||||
liveMessages.clear();
|
||||
resetMessagePool(); // reset pool when loading
|
||||
|
||||
#ifdef FSCom
|
||||
concurrency::LockGuard guard(spiLock);
|
||||
|
||||
@@ -219,6 +290,7 @@ void MessageStore::loadFromFlash() {}
|
||||
void MessageStore::clearAllMessages()
|
||||
{
|
||||
liveMessages.clear();
|
||||
resetMessagePool();
|
||||
|
||||
#ifdef FSCom
|
||||
SafeFile f(filename.c_str(), false);
|
||||
@@ -266,7 +338,7 @@ void MessageStore::dismissOldestMessageInChannel(uint8_t channel)
|
||||
saveToFlash();
|
||||
}
|
||||
|
||||
// Dismiss oldest message in a direct conversation with a peer
|
||||
// Dismiss oldest message in a direct chat with a node
|
||||
void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
|
||||
{
|
||||
auto pred = [peer](const StoredMessage &m) {
|
||||
@@ -279,7 +351,6 @@ void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
|
||||
saveToFlash();
|
||||
}
|
||||
|
||||
// Helper filters for future use
|
||||
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
|
||||
{
|
||||
std::deque<StoredMessage> result;
|
||||
@@ -320,12 +391,23 @@ void MessageStore::upgradeBootRelativeTimestamps()
|
||||
m.timestamp += bootOffset;
|
||||
m.isBootRelative = false;
|
||||
}
|
||||
// else: persisted from old boot → stays ??? forever
|
||||
}
|
||||
};
|
||||
fix(liveMessages);
|
||||
}
|
||||
|
||||
const char *MessageStore::getText(const StoredMessage &msg)
|
||||
{
|
||||
// Wrapper around the internal helper
|
||||
return getTextFromPool(msg.textOffset);
|
||||
}
|
||||
|
||||
uint16_t MessageStore::storeText(const char *src, size_t len)
|
||||
{
|
||||
// Wrapper around the internal helper
|
||||
return storeTextInPool(src, len);
|
||||
}
|
||||
|
||||
// Global definition
|
||||
MessageStore messageStore("default");
|
||||
#endif
|
||||
@@ -27,10 +27,16 @@
|
||||
// Internal alias used everywhere in code – do NOT redefine elsewhere.
|
||||
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
|
||||
|
||||
// Maximum text payload size per message in bytes (fixed).
|
||||
// All messages use the same size to simplify memory handling and avoid dynamic allocations.
|
||||
// Maximum text payload size per message in bytes.
|
||||
// This still defines the max message length, but we no longer reserve this space per message.
|
||||
#define MAX_MESSAGE_SIZE 220
|
||||
|
||||
// Total shared text pool size for all messages combined.
|
||||
// The text pool is RAM-only. Text is re-stored from flash into the pool on boot.
|
||||
#ifndef MESSAGE_TEXT_POOL_SIZE
|
||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
||||
#endif
|
||||
|
||||
// Explicit message classification
|
||||
enum class MessageType : uint8_t {
|
||||
BROADCAST = 0, // broadcast message
|
||||
@@ -46,25 +52,23 @@ enum class AckStatus : uint8_t {
|
||||
RELAYED = 4 // got an ACK from relay, not destination
|
||||
};
|
||||
|
||||
// A single stored message in RAM and/or flash
|
||||
struct StoredMessage {
|
||||
uint32_t timestamp; // When message was created (secs since boot or RTC)
|
||||
uint32_t sender; // NodeNum of sender
|
||||
uint8_t channelIndex; // Channel index used
|
||||
char text[MAX_MESSAGE_SIZE]; // Fixed-size buffer for message text (null-terminated)
|
||||
uint32_t timestamp; // When message was created (secs since boot or RTC)
|
||||
uint32_t sender; // NodeNum of sender
|
||||
uint8_t channelIndex; // Channel index used
|
||||
uint32_t dest; // Destination node (broadcast or direct)
|
||||
MessageType type; // Derived from dest (explicit classification)
|
||||
bool isBootRelative; // true = millis()/1000 fallback; false = epoch/RTC absolute
|
||||
AckStatus ackStatus; // Delivery status (only meaningful for our own sent messages)
|
||||
|
||||
uint32_t dest; // Destination node (broadcast or direct)
|
||||
|
||||
MessageType type; // Derived from dest (explicit classification)
|
||||
|
||||
bool isBootRelative; // true = millis()/1000 fallback; false = epoch/RTC absolute
|
||||
|
||||
AckStatus ackStatus; // Delivery status (only meaningful for our own sent messages)
|
||||
// Text storage metadata — rebuilt from flash at boot
|
||||
uint16_t textOffset; // Offset into global text pool (valid only after loadFromFlash())
|
||||
uint16_t textLength; // Length of text in bytes
|
||||
|
||||
// Default constructor initializes all fields safely
|
||||
StoredMessage()
|
||||
: timestamp(0), sender(0), channelIndex(0), text(""), dest(0xffffffff), type(MessageType::BROADCAST),
|
||||
isBootRelative(false), ackStatus(AckStatus::NONE) // start as NONE (waiting, no symbol)
|
||||
: timestamp(0), sender(0), channelIndex(0), dest(0xffffffff), type(MessageType::BROADCAST), isBootRelative(false),
|
||||
ackStatus(AckStatus::NONE), textOffset(0), textLength(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -87,7 +91,7 @@ class MessageStore
|
||||
void saveToFlash(); // Save messages to flash
|
||||
void loadFromFlash(); // Load messages from flash
|
||||
|
||||
// Clear all messages (RAM + persisted queue)
|
||||
// Clear all messages (RAM + persisted queue + text pool)
|
||||
void clearAllMessages();
|
||||
|
||||
// Dismiss helpers
|
||||
@@ -107,6 +111,15 @@ class MessageStore
|
||||
// Upgrade boot-relative timestamps once RTC is valid
|
||||
void upgradeBootRelativeTimestamps();
|
||||
|
||||
// Retrieve the C-string text for a stored message
|
||||
static const char *getText(const StoredMessage &msg);
|
||||
|
||||
// Allocate text into pool (used by sender-side code)
|
||||
static uint16_t storeText(const char *src, size_t len);
|
||||
|
||||
// Used when loading from flash to rebuild the text pool
|
||||
static uint16_t rebuildTextFromFlash(const char *src, size_t len);
|
||||
|
||||
private:
|
||||
std::deque<StoredMessage> liveMessages; // Single in-RAM message buffer (also used for persistence)
|
||||
std::string filename; // Flash filename for persistence
|
||||
|
||||
@@ -481,9 +481,6 @@ void menuHandler::messageResponseMenu()
|
||||
} else if (selected == DismissAll) {
|
||||
messageStore.clearAllMessages();
|
||||
graphics::MessageRenderer::clearThreadRegistries();
|
||||
|
||||
// Reset back to "View All"
|
||||
graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::ALL);
|
||||
} else if (selected == DismissOldest) {
|
||||
auto mode = graphics::MessageRenderer::getThreadMode();
|
||||
int ch = graphics::MessageRenderer::getThreadChannel();
|
||||
|
||||
@@ -416,6 +416,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
}
|
||||
|
||||
if (filtered.empty()) {
|
||||
// If current conversation is empty go back to ALL view
|
||||
if (currentMode != ThreadMode::ALL) {
|
||||
setThreadMode(ThreadMode::ALL);
|
||||
resetScrollState();
|
||||
return; // Next draw will rerun in ALL mode
|
||||
}
|
||||
|
||||
// Still in ALL mode and no messages at all → show placeholder
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
didReset = false;
|
||||
const char *messageString = "No messages";
|
||||
@@ -528,8 +536,9 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
isHeader.push_back(true);
|
||||
ackForLine.push_back(m.ackStatus);
|
||||
|
||||
// Split message text into wrapped lines
|
||||
std::vector<std::string> wrapped = generateLines(display, "", m.text, textWidth);
|
||||
const char *msgText = MessageStore::getText(m);
|
||||
|
||||
std::vector<std::string> wrapped = generateLines(display, "", msgText, textWidth);
|
||||
for (auto &ln : wrapped) {
|
||||
allLines.push_back(ln);
|
||||
isMine.push_back(mine);
|
||||
@@ -880,8 +889,11 @@ void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &pack
|
||||
screen->showSimpleBanner(banner, inThread ? 1000 : 3000);
|
||||
}
|
||||
|
||||
// Always focus into the correct conversation thread when a message arrives
|
||||
setThreadFor(sm, packet);
|
||||
// Always focus into the correct conversation thread when a message with real text arrives
|
||||
const char *msgText = MessageStore::getText(sm);
|
||||
if (msgText && msgText[0] != '\0') {
|
||||
setThreadFor(sm, packet);
|
||||
}
|
||||
|
||||
// Reset scroll for a clean start
|
||||
resetScrollState();
|
||||
@@ -889,10 +901,12 @@ void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &pack
|
||||
|
||||
void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet)
|
||||
{
|
||||
if (sm.type == MessageType::BROADCAST) {
|
||||
if (sm.dest == NODENUM_BROADCAST || sm.type == MessageType::BROADCAST) {
|
||||
// Broadcast
|
||||
setThreadMode(ThreadMode::CHANNEL, sm.channelIndex);
|
||||
} else if (sm.type == MessageType::DM_TO_US) {
|
||||
uint32_t peer = (packet.from == 0) ? sm.dest : sm.sender;
|
||||
} else {
|
||||
// Direct message
|
||||
uint32_t peer = (packet.from != 0) ? sm.sender : sm.dest;
|
||||
setThreadMode(ThreadMode::DIRECT, -1, peer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ int getThreadChannel();
|
||||
// Getter for current peer (valid if mode == DIRECT)
|
||||
uint32_t getThreadPeer();
|
||||
|
||||
// --- Registry accessors for menuHandler ---
|
||||
// Registry accessors for menuHandler
|
||||
const std::vector<int> &getSeenChannels();
|
||||
const std::vector<uint32_t> &getSeenPeers();
|
||||
|
||||
|
||||
@@ -1062,8 +1062,9 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
||||
|
||||
sm.sender = nodeDB->getNodeNum(); // us
|
||||
sm.channelIndex = channel;
|
||||
strncpy(sm.text, message, MAX_MESSAGE_SIZE - 1);
|
||||
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||
size_t len = strnlen(message, MAX_MESSAGE_SIZE - 1);
|
||||
sm.textOffset = MessageStore::storeText(message, len);
|
||||
sm.textLength = len;
|
||||
|
||||
// Classify broadcast vs DM
|
||||
if (dest == NODENUM_BROADCAST) {
|
||||
|
||||
Reference in New Issue
Block a user