mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-24 10:47:20 +00:00
Merge branch 'develop'
This commit is contained in:
4
.github/workflows/build_firmware.yml
vendored
4
.github/workflows/build_firmware.yml
vendored
@@ -91,8 +91,8 @@ jobs:
|
|||||||
if [[ -f "$manifest" ]]; then
|
if [[ -f "$manifest" ]]; then
|
||||||
echo "Updating $manifest with $OTA_FILE (md5: $OTA_MD5, size: $OTA_SIZE)"
|
echo "Updating $manifest with $OTA_FILE (md5: $OTA_MD5, size: $OTA_SIZE)"
|
||||||
# Add OTA entry to files array if not already present
|
# Add OTA entry to files array if not already present
|
||||||
jq --arg name "$OTA_FILE" --arg md5 "$OTA_MD5" --argjson bytes "$OTA_SIZE" \
|
jq --arg name "$OTA_FILE" --arg md5 "$OTA_MD5" --argjson bytes "$OTA_SIZE" --arg part "app1" \
|
||||||
'if .files | map(select(.name == $name)) | length == 0 then .files += [{"name": $name, "md5": $md5, "bytes": $bytes}] else . end' \
|
'if .files | map(select(.name == $name)) | length == 0 then .files += [{"name": $name, "md5": $md5, "bytes": $bytes, "part_name": $part}] else . end' \
|
||||||
"$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
|
"$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ def manifest_gather(source, target, env):
|
|||||||
board_platform = env.BoardConfig().get("platform")
|
board_platform = env.BoardConfig().get("platform")
|
||||||
board_mcu = env.BoardConfig().get("build.mcu").lower()
|
board_mcu = env.BoardConfig().get("build.mcu").lower()
|
||||||
needs_ota_suffix = board_platform == "nordicnrf52"
|
needs_ota_suffix = board_platform == "nordicnrf52"
|
||||||
|
|
||||||
|
# Mapping of bin files to their target partition names
|
||||||
|
# Maps the filename pattern to the partition name where it should be flashed
|
||||||
|
partition_map = {
|
||||||
|
f"{progname}.bin": "app0", # primary application slot (app0 / OTA_0)
|
||||||
|
lfsbin: "spiffs", # filesystem image flashed to spiffs
|
||||||
|
}
|
||||||
|
|
||||||
check_paths = [
|
check_paths = [
|
||||||
progname,
|
progname,
|
||||||
f"{progname}.elf",
|
f"{progname}.elf",
|
||||||
@@ -85,6 +93,9 @@ def manifest_gather(source, target, env):
|
|||||||
"md5": f.get_content_hash(), # Returns MD5 hash
|
"md5": f.get_content_hash(), # Returns MD5 hash
|
||||||
"bytes": f.get_size() # Returns file size in bytes
|
"bytes": f.get_size() # Returns file size in bytes
|
||||||
}
|
}
|
||||||
|
# Add part_name if this file represents a partition that should be flashed
|
||||||
|
if p in partition_map:
|
||||||
|
d["part_name"] = partition_map[p]
|
||||||
out.append(d)
|
out.append(d)
|
||||||
print(d)
|
print(d)
|
||||||
manifest_write(out, env)
|
manifest_write(out, env)
|
||||||
|
|||||||
@@ -13,6 +13,11 @@
|
|||||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Default autosave interval 2 hours, override per device later with -DMESSAGE_AUTOSAVE_INTERVAL_SEC=300 (etc)
|
||||||
|
#ifndef MESSAGE_AUTOSAVE_INTERVAL_SEC
|
||||||
|
#define MESSAGE_AUTOSAVE_INTERVAL_SEC (2 * 60 * 60)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Global message text pool and state
|
// Global message text pool and state
|
||||||
static char *g_messagePool = nullptr;
|
static char *g_messagePool = nullptr;
|
||||||
static size_t g_poolWritePos = 0;
|
static size_t g_poolWritePos = 0;
|
||||||
@@ -102,6 +107,60 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
|
|||||||
pushWithLimit(liveMessages, msg);
|
pushWithLimit(liveMessages, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
static bool g_messageStoreHasUnsavedChanges = false;
|
||||||
|
static uint32_t g_lastAutoSaveMs = 0; // last time we actually saved
|
||||||
|
|
||||||
|
static inline uint32_t autosaveIntervalMs()
|
||||||
|
{
|
||||||
|
uint32_t sec = (uint32_t)MESSAGE_AUTOSAVE_INTERVAL_SEC;
|
||||||
|
if (sec < 60)
|
||||||
|
sec = 60;
|
||||||
|
return sec * 1000UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool reachedMs(uint32_t now, uint32_t target)
|
||||||
|
{
|
||||||
|
return (int32_t)(now - target) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark new messages in RAM that need to be saved later
|
||||||
|
static inline void markMessageStoreUnsaved()
|
||||||
|
{
|
||||||
|
g_messageStoreHasUnsavedChanges = true;
|
||||||
|
|
||||||
|
if (g_lastAutoSaveMs == 0) {
|
||||||
|
g_lastAutoSaveMs = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called periodically from the main loop in main.cpp
|
||||||
|
static inline void autosaveTick(MessageStore *store)
|
||||||
|
{
|
||||||
|
if (!store)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if (g_lastAutoSaveMs == 0) {
|
||||||
|
g_lastAutoSaveMs = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reachedMs(now, g_lastAutoSaveMs + autosaveIntervalMs()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Autosave interval reached, only save if there are unsaved messages.
|
||||||
|
if (g_messageStoreHasUnsavedChanges) {
|
||||||
|
LOG_INFO("Autosaving MessageStore to flash");
|
||||||
|
store->saveToFlash();
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Autosave skipped, no changes to save");
|
||||||
|
g_lastAutoSaveMs = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Add from incoming/outgoing packet
|
// Add from incoming/outgoing packet
|
||||||
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
||||||
{
|
{
|
||||||
@@ -131,6 +190,11 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLiveMessage(sm);
|
addLiveMessage(sm);
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
markMessageStoreUnsaved();
|
||||||
|
#endif
|
||||||
|
|
||||||
return liveMessages.back();
|
return liveMessages.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +219,10 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
|||||||
sm.ackStatus = AckStatus::NONE;
|
sm.ackStatus = AckStatus::NONE;
|
||||||
|
|
||||||
addLiveMessage(sm);
|
addLiveMessage(sm);
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
markMessageStoreUnsaved();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_MESSAGE_PERSISTENCE
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
@@ -239,6 +307,10 @@ void MessageStore::saveToFlash()
|
|||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Reset autosave state after any save
|
||||||
|
g_messageStoreHasUnsavedChanges = false;
|
||||||
|
g_lastAutoSaveMs = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageStore::loadFromFlash()
|
void MessageStore::loadFromFlash()
|
||||||
@@ -270,6 +342,9 @@ void MessageStore::loadFromFlash()
|
|||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
#endif
|
#endif
|
||||||
|
// Loading messages does not trigger an autosave
|
||||||
|
g_messageStoreHasUnsavedChanges = false;
|
||||||
|
g_lastAutoSaveMs = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@@ -290,6 +365,11 @@ void MessageStore::clearAllMessages()
|
|||||||
f.write(&count, 1); // write "0 messages"
|
f.write(&count, 1); // write "0 messages"
|
||||||
f.close();
|
f.close();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
g_messageStoreHasUnsavedChanges = false;
|
||||||
|
g_lastAutoSaveMs = millis();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper: erase first or last message matching a predicate
|
// Internal helper: erase first or last message matching a predicate
|
||||||
@@ -421,6 +501,14 @@ uint16_t MessageStore::storeText(const char *src, size_t len)
|
|||||||
return storeTextInPool(src, len);
|
return storeTextInPool(src, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
void messageStoreAutosaveTick()
|
||||||
|
{
|
||||||
|
// Called from the main loop to check autosave timing
|
||||||
|
autosaveTick(&messageStore);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Global definition
|
// Global definition
|
||||||
MessageStore messageStore("default");
|
MessageStore messageStore("default");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -125,6 +125,11 @@ class MessageStore
|
|||||||
std::string filename; // Flash filename for persistence
|
std::string filename; // Flash filename for persistence
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
// Called periodically from main loop to trigger time based autosave
|
||||||
|
void messageStoreAutosaveTick();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Global instance (defined in MessageStore.cpp)
|
// Global instance (defined in MessageStore.cpp)
|
||||||
extern MessageStore messageStore;
|
extern MessageStore messageStore;
|
||||||
|
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ void menuHandler::clockMenu()
|
|||||||
}
|
}
|
||||||
void menuHandler::messageResponseMenu()
|
void menuHandler::messageResponseMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back = 0, ViewMode, DeleteAll, DeleteOldest, ReplyMenu, MuteChannel, Aloud, enumEnd };
|
enum optionsNumbers { Back = 0, ViewMode, DeleteMenu, ReplyMenu, MuteChannel, Aloud, enumEnd };
|
||||||
|
|
||||||
static const char *optionsArray[enumEnd];
|
static const char *optionsArray[enumEnd];
|
||||||
static int optionsEnumArray[enumEnd];
|
static int optionsEnumArray[enumEnd];
|
||||||
@@ -479,7 +479,7 @@ void menuHandler::messageResponseMenu()
|
|||||||
|
|
||||||
// Delete submenu
|
// Delete submenu
|
||||||
optionsArray[options] = "Delete";
|
optionsArray[options] = "Delete";
|
||||||
optionsEnumArray[options++] = 900;
|
optionsEnumArray[options++] = DeleteMenu;
|
||||||
|
|
||||||
#ifdef HAS_I2S
|
#ifdef HAS_I2S
|
||||||
optionsArray[options] = "Read Aloud";
|
optionsArray[options] = "Read Aloud";
|
||||||
@@ -520,34 +520,10 @@ void menuHandler::messageResponseMenu()
|
|||||||
nodeDB->saveToDisk();
|
nodeDB->saveToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete submenu
|
} else if (selected == DeleteMenu) {
|
||||||
} else if (selected == 900) {
|
|
||||||
menuHandler::menuQueue = menuHandler::delete_messages_menu;
|
menuHandler::menuQueue = menuHandler::delete_messages_menu;
|
||||||
screen->runNow();
|
screen->runNow();
|
||||||
|
|
||||||
// Delete oldest FIRST (only change)
|
|
||||||
} else if (selected == DeleteOldest) {
|
|
||||||
auto mode = graphics::MessageRenderer::getThreadMode();
|
|
||||||
int ch = graphics::MessageRenderer::getThreadChannel();
|
|
||||||
uint32_t peer = graphics::MessageRenderer::getThreadPeer();
|
|
||||||
|
|
||||||
if (mode == graphics::MessageRenderer::ThreadMode::ALL) {
|
|
||||||
// Global oldest
|
|
||||||
messageStore.deleteOldestMessage();
|
|
||||||
} else if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) {
|
|
||||||
// Oldest in current channel
|
|
||||||
messageStore.deleteOldestMessageInChannel(ch);
|
|
||||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
|
||||||
// Oldest in current DM
|
|
||||||
messageStore.deleteOldestMessageWithPeer(peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all messages
|
|
||||||
} else if (selected == DeleteAll) {
|
|
||||||
messageStore.clearAllMessages();
|
|
||||||
graphics::MessageRenderer::clearThreadRegistries();
|
|
||||||
graphics::MessageRenderer::clearMessageCache();
|
|
||||||
|
|
||||||
#ifdef HAS_I2S
|
#ifdef HAS_I2S
|
||||||
} else if (selected == Aloud) {
|
} else if (selected == Aloud) {
|
||||||
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
|
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
|
||||||
@@ -716,7 +692,6 @@ void menuHandler::deleteMessagesMenu()
|
|||||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||||
messageStore.deleteOldestMessageWithPeer(peer);
|
messageStore.deleteOldestMessageWithPeer(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,7 +704,6 @@ void menuHandler::deleteMessagesMenu()
|
|||||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||||
messageStore.deleteAllMessagesWithPeer(peer);
|
messageStore.deleteAllMessagesWithPeer(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#if HAS_SCREEN
|
||||||
|
#include "MessageStore.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ELECROW_ThinkNode_M5
|
#ifdef ELECROW_ThinkNode_M5
|
||||||
PCA9557 io(0x18, &Wire);
|
PCA9557 io(0x18, &Wire);
|
||||||
@@ -1652,6 +1655,9 @@ void loop()
|
|||||||
if (dispdev)
|
if (dispdev)
|
||||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
||||||
|
messageStoreAutosaveTick();
|
||||||
#endif
|
#endif
|
||||||
long delayMsec = mainController.runOrDelay();
|
long delayMsec = mainController.runOrDelay();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user