mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 06:54:37 +00:00
Merge 3dcbadac67 into 799cf0e8b3
This commit is contained in:
commit
b1a6013919
@ -53,12 +53,42 @@ extern MemGet memGet;
|
||||
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||
#else
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
|
||||
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
|
||||
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
|
||||
#define LOG_ERROR(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__)
|
||||
#define LOG_CRIT(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__)
|
||||
#define LOG_TRACE(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__)
|
||||
#define LOG_DEBUG(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define LOG_INFO(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define LOG_WARN(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define LOG_ERROR(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define LOG_CRIT(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define LOG_TRACE(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_DEBUG(...)
|
||||
#define LOG_INFO(...)
|
||||
@ -70,7 +100,12 @@ extern MemGet memGet;
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG_HEAP)
|
||||
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
|
||||
#define LOG_HEAP(...) \
|
||||
do { \
|
||||
if (console) { \
|
||||
console->log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Macro-based heap debugging
|
||||
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
|
||||
@ -195,4 +230,4 @@ class Syslog
|
||||
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
|
||||
};
|
||||
|
||||
#endif // HAS_NETWORKING
|
||||
#endif // HAS_NETWORKING
|
||||
|
||||
@ -14,16 +14,16 @@ class NodeStatus : public Status
|
||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
||||
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||
|
||||
uint8_t numOnline = 0;
|
||||
uint8_t numTotal = 0;
|
||||
uint16_t numOnline = 0;
|
||||
uint16_t numTotal = 0;
|
||||
|
||||
uint8_t lastNumTotal = 0;
|
||||
uint16_t lastNumTotal = 0;
|
||||
|
||||
public:
|
||||
bool forceUpdate = false;
|
||||
|
||||
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
||||
NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
|
||||
NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
|
||||
{
|
||||
this->forceUpdate = forceUpdate;
|
||||
this->numOnline = numOnline;
|
||||
@ -34,11 +34,11 @@ class NodeStatus : public Status
|
||||
|
||||
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
uint8_t getNumOnline() const { return numOnline; }
|
||||
uint16_t getNumOnline() const { return numOnline; }
|
||||
|
||||
uint8_t getNumTotal() const { return numTotal; }
|
||||
uint16_t getNumTotal() const { return numTotal; }
|
||||
|
||||
uint8_t getLastNumTotal() const { return lastNumTotal; }
|
||||
uint16_t getLastNumTotal() const { return lastNumTotal; }
|
||||
|
||||
bool matches(const NodeStatus *newStatus) const
|
||||
{
|
||||
@ -65,4 +65,4 @@ class NodeStatus : public Status
|
||||
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::NodeStatus *nodeStatus;
|
||||
extern meshtastic::NodeStatus *nodeStatus;
|
||||
|
||||
@ -287,7 +287,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
float easternmost = lngCenter;
|
||||
float westernmost = lngCenter;
|
||||
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
@ -475,7 +475,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
bool InkHUD::MapApplet::enoughMarkers()
|
||||
{
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Count nodes
|
||||
@ -555,4 +555,4 @@ void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size)
|
||||
drawLine(x0, y1, x1, y0, BLACK);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -295,7 +295,7 @@ void printInfo()
|
||||
{
|
||||
LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
|
||||
}
|
||||
#ifndef PIO_UNIT_TESTING
|
||||
#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING)
|
||||
void setup()
|
||||
{
|
||||
#if defined(R1_NEO)
|
||||
@ -1573,7 +1573,7 @@ void scannerToSensorsMap(const std::unique_ptr<ScanI2CTwoWire> &i2cScanner, Scan
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef PIO_UNIT_TESTING
|
||||
#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING)
|
||||
void loop()
|
||||
{
|
||||
runASAP = false;
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
#include "PointerQueue.h"
|
||||
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
|
||||
|
||||
#if defined(ARCH_ESP32)
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
template <class T> class Allocator
|
||||
{
|
||||
|
||||
@ -159,3 +163,92 @@ template <class T, int MaxSize> class MemoryPool : public Allocator<T>
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(ARCH_ESP32)
|
||||
// Simple fixed-size allocator that uses PSRAM. Used on ESP32-S3 builds so the
|
||||
// large MeshPacket pool can live off-chip and free internal RAM.
|
||||
template <class T, int MaxSize> class PsramMemoryPool : public Allocator<T>
|
||||
{
|
||||
private:
|
||||
T *pool;
|
||||
bool used[MaxSize];
|
||||
|
||||
public:
|
||||
PsramMemoryPool() : pool(nullptr), used{}
|
||||
{
|
||||
pool = static_cast<T *>(heap_caps_malloc(sizeof(T) * MaxSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
if (pool) {
|
||||
memset(pool, 0, sizeof(T) * MaxSize);
|
||||
} else {
|
||||
LOG_WARN("Failed to allocate PSRAM pool of %d elements", MaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
~PsramMemoryPool() override
|
||||
{
|
||||
if (pool) {
|
||||
heap_caps_free(pool);
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid() const { return pool != nullptr; }
|
||||
|
||||
void release(T *p) override
|
||||
{
|
||||
if (!pool || !p) {
|
||||
LOG_DEBUG("Failed to release PSRAM memory, pointer is null or pool unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = static_cast<int>(p - pool);
|
||||
if (index >= 0 && index < MaxSize) {
|
||||
assert(used[index]);
|
||||
used[index] = false;
|
||||
LOG_HEAP("Released PSRAM pool item %d at 0x%x", index, p);
|
||||
} else {
|
||||
LOG_WARN("Pointer 0x%x not from PSRAM pool!", p);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
T *alloc(TickType_t maxWait) override
|
||||
{
|
||||
if (!pool)
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < MaxSize; i++) {
|
||||
if (!used[i]) {
|
||||
used[i] = true;
|
||||
LOG_HEAP("Allocated PSRAM pool item %d at 0x%x", i, &pool[i]);
|
||||
return &pool[i];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARN("No free slots available in PSRAM memory pool!");
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Utility helpers for PSRAM-backed array allocations on ESP32 targets.
|
||||
template <typename T> inline T *psramAllocArray(size_t count)
|
||||
{
|
||||
return static_cast<T *>(heap_caps_malloc(sizeof(T) * count, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
}
|
||||
|
||||
template <typename T> inline void psramFreeArray(T *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
heap_caps_free(ptr);
|
||||
}
|
||||
#else
|
||||
template <typename T> inline T *psramAllocArray(size_t count)
|
||||
{
|
||||
(void)count;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T> inline void psramFreeArray(T *ptr)
|
||||
{
|
||||
(void)ptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -305,15 +305,21 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (toPhoneQueue.numFree() == 0) {
|
||||
// MAX_RX_TOPHONE is sized for PSRAM-backed builds. Fall back to a smaller
|
||||
// runtime limit if the helper detects <2MB of PSRAM at boot.
|
||||
const int queueLimit = get_rx_tophone_limit();
|
||||
const bool runtimeLimitReached = queueLimit > 0 && toPhoneQueue.numUsed() >= queueLimit;
|
||||
|
||||
if (toPhoneQueue.numFree() == 0 || runtimeLimitReached) {
|
||||
const bool runtimeControlled = runtimeLimitReached && queueLimit < MAX_RX_TOPHONE;
|
||||
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||
p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
|
||||
LOG_WARN("ToPhone queue is full, discard oldest");
|
||||
LOG_WARN("ToPhone queue %s, discard oldest", runtimeControlled ? "reached runtime limit" : "is full");
|
||||
meshtastic_MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
||||
if (d)
|
||||
releaseToPool(d);
|
||||
} else {
|
||||
LOG_WARN("ToPhone queue is full, drop packet");
|
||||
LOG_WARN("ToPhone queue %s, drop packet", runtimeControlled ? "reached runtime limit" : "is full");
|
||||
releaseToPool(p);
|
||||
fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets
|
||||
return;
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include <ErriezCRC32.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <vector>
|
||||
@ -37,6 +38,12 @@
|
||||
#include <Preferences.h>
|
||||
#include <esp_efuse.h>
|
||||
#include <esp_efuse_table.h>
|
||||
#if __has_include(<esp_ptr.h>)
|
||||
#include <esp_ptr.h>
|
||||
#define NODEDB_HAS_ESP_PTR 1
|
||||
#else
|
||||
#define NODEDB_HAS_ESP_PTR 0
|
||||
#endif
|
||||
#include <nvs_flash.h>
|
||||
#include <soc/efuse_reg.h>
|
||||
#include <soc/soc.h>
|
||||
@ -67,6 +74,174 @@ meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 3
|
||||
meshtastic_LocalModuleConfig moduleConfig;
|
||||
meshtastic_ChannelFile channelFile;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Runtime instrumentation helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Log the pool headroom every 100 inserts (and when we hit MAX) so field logs
|
||||
// capture how close we are to exhausting heap/PSRAM on real hardware.
|
||||
|
||||
void logNodeInsertStats(size_t count, const char *poolLabel)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
if ((count % 100) != 0 && count != MAX_NUM_NODES)
|
||||
return;
|
||||
|
||||
LOG_INFO("NodeDB %s pool usage %u/%u nodes, heap free %u, psram free %u", poolLabel, static_cast<unsigned>(count),
|
||||
static_cast<unsigned>(MAX_NUM_NODES), memGet.getFreeHeap(), memGet.getFreePsram());
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
bool logPsramAllocationOnce(void *ptr, size_t capacity)
|
||||
{
|
||||
static bool logged = false;
|
||||
if (logged || !ptr)
|
||||
return logged;
|
||||
|
||||
#if NODEDB_HAS_ESP_PTR
|
||||
bool inPsram = esp_ptr_external_ram(ptr);
|
||||
#else
|
||||
bool inPsram = false;
|
||||
#endif
|
||||
LOG_INFO("NodeDB PSRAM backing at %p (%s) capacity %u entries (~%u bytes)", ptr, inPsram ? "PSRAM" : "DRAM",
|
||||
static_cast<unsigned>(capacity), static_cast<unsigned>(capacity * sizeof(meshtastic_NodeInfoLite)));
|
||||
logged = true;
|
||||
return logged;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
||||
void NodeDB::initHotCache()
|
||||
{
|
||||
// Pre-reserve the full cold store in PSRAM during boot so the high watermark
|
||||
// shows up immediately in PSRAM usage logs and we avoid fragmented
|
||||
// allocations later in the mission.
|
||||
psramMeshNodes.resize(MAX_NUM_NODES);
|
||||
hotNodes.resize(MAX_NUM_NODES);
|
||||
hotDirty.assign(MAX_NUM_NODES, true);
|
||||
meshNodes = &psramMeshNodes;
|
||||
logPsramAllocationOnce(psramMeshNodes.data(), psramMeshNodes.capacity());
|
||||
}
|
||||
|
||||
void NodeDB::refreshHotCache()
|
||||
{
|
||||
for (size_t i = 0; i < numMeshNodes; ++i) {
|
||||
if (hotDirty[i])
|
||||
syncHotFromCold(i);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeDB::syncHotFromCold(size_t index)
|
||||
{
|
||||
if (index >= psramMeshNodes.size())
|
||||
return;
|
||||
|
||||
const meshtastic_NodeInfoLite &node = psramMeshNodes[index];
|
||||
NodeHotEntry &hot = hotNodes[index];
|
||||
|
||||
hot.num = node.num;
|
||||
hot.last_heard = node.last_heard;
|
||||
hot.snr = node.snr;
|
||||
hot.channel = node.channel;
|
||||
hot.next_hop = node.next_hop;
|
||||
hot.role = static_cast<uint8_t>(node.user.role);
|
||||
hot.hops_away = node.hops_away;
|
||||
|
||||
uint8_t flags = 0;
|
||||
if (node.via_mqtt)
|
||||
flags |= HOT_FLAG_VIA_MQTT;
|
||||
if (node.is_favorite)
|
||||
flags |= HOT_FLAG_IS_FAVORITE;
|
||||
if (node.is_ignored)
|
||||
flags |= HOT_FLAG_IS_IGNORED;
|
||||
if (node.has_hops_away)
|
||||
flags |= HOT_FLAG_HAS_HOPS;
|
||||
if (node.bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK)
|
||||
flags |= HOT_FLAG_IS_KEY_VERIFIED;
|
||||
hot.flags = flags;
|
||||
|
||||
hotDirty[index] = false;
|
||||
}
|
||||
|
||||
void NodeDB::markHotDirty(size_t index)
|
||||
{
|
||||
if (index < hotDirty.size())
|
||||
hotDirty[index] = true;
|
||||
}
|
||||
|
||||
void NodeDB::markHotDirty(const meshtastic_NodeInfoLite *ptr)
|
||||
{
|
||||
size_t idx = indexOf(ptr);
|
||||
if (idx != std::numeric_limits<size_t>::max())
|
||||
markHotDirty(idx);
|
||||
}
|
||||
|
||||
void NodeDB::clearSlot(size_t index)
|
||||
{
|
||||
if (index >= psramMeshNodes.size())
|
||||
return;
|
||||
|
||||
psramMeshNodes[index] = {};
|
||||
hotNodes[index] = NodeHotEntry{};
|
||||
hotDirty[index] = false;
|
||||
}
|
||||
|
||||
void NodeDB::swapSlots(size_t a, size_t b)
|
||||
{
|
||||
if (a == b)
|
||||
return;
|
||||
|
||||
std::swap(psramMeshNodes[a], psramMeshNodes[b]);
|
||||
std::swap(hotNodes[a], hotNodes[b]);
|
||||
std::swap(hotDirty[a], hotDirty[b]);
|
||||
}
|
||||
|
||||
void NodeDB::copySlot(size_t src, size_t dst)
|
||||
{
|
||||
if (src == dst)
|
||||
return;
|
||||
|
||||
psramMeshNodes[dst] = psramMeshNodes[src];
|
||||
hotNodes[dst] = hotNodes[src];
|
||||
hotDirty[dst] = hotDirty[src];
|
||||
}
|
||||
|
||||
void NodeDB::moveSlot(size_t src, size_t dst)
|
||||
{
|
||||
if (src == dst)
|
||||
return;
|
||||
|
||||
copySlot(src, dst);
|
||||
clearSlot(src);
|
||||
}
|
||||
|
||||
bool NodeDB::isNodeEmpty(const meshtastic_NodeInfoLite &node) const
|
||||
{
|
||||
return node.num == 0 && !node.has_user && !node.has_position && !node.has_device_metrics && !node.is_favorite &&
|
||||
!node.is_ignored && node.last_heard == 0 && node.channel == 0 && node.next_hop == 0 && node.bitfield == 0;
|
||||
}
|
||||
|
||||
size_t NodeDB::indexOf(const meshtastic_NodeInfoLite *ptr) const
|
||||
{
|
||||
if (!ptr || psramMeshNodes.empty())
|
||||
return std::numeric_limits<size_t>::max();
|
||||
|
||||
const meshtastic_NodeInfoLite *base = psramMeshNodes.data();
|
||||
size_t idx = static_cast<size_t>(ptr - base);
|
||||
if (idx >= psramMeshNodes.size())
|
||||
return std::numeric_limits<size_t>::max();
|
||||
return idx;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_USE_ADMIN_KEY_0
|
||||
static unsigned char userprefs_admin_key_0[] = USERPREFS_USE_ADMIN_KEY_0;
|
||||
#endif
|
||||
@ -520,9 +695,16 @@ void NodeDB::installDefaultNodeDatabase()
|
||||
{
|
||||
LOG_DEBUG("Install default NodeDatabase");
|
||||
nodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
initHotCache();
|
||||
for (size_t i = 0; i < psramMeshNodes.size(); ++i)
|
||||
clearSlot(i);
|
||||
nodeDatabase.nodes.clear();
|
||||
#else
|
||||
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
|
||||
numMeshNodes = 0;
|
||||
meshNodes = &nodeDatabase.nodes;
|
||||
#endif
|
||||
numMeshNodes = 0;
|
||||
}
|
||||
|
||||
void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
@ -982,8 +1164,18 @@ void NodeDB::resetNodes()
|
||||
{
|
||||
if (!config.position.fixed_position)
|
||||
clearLocalPosition();
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if (psramMeshNodes.empty())
|
||||
initHotCache();
|
||||
numMeshNodes = std::min<pb_size_t>(numMeshNodes, MAX_NUM_NODES);
|
||||
if (numMeshNodes == 0)
|
||||
numMeshNodes = 1;
|
||||
for (size_t i = 1; i < psramMeshNodes.size(); ++i)
|
||||
clearSlot(i);
|
||||
#else
|
||||
numMeshNodes = 1;
|
||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||
#endif
|
||||
devicestate.has_rx_text_message = false;
|
||||
devicestate.has_rx_waypoint = false;
|
||||
saveNodeDatabaseToDisk();
|
||||
@ -994,7 +1186,25 @@ void NodeDB::resetNodes()
|
||||
|
||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
{
|
||||
int newPos = 0, removed = 0;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
int newPos = 0;
|
||||
int removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (hotNodes[i].num != nodeNum) {
|
||||
if (newPos != i)
|
||||
moveSlot(i, newPos);
|
||||
newPos++;
|
||||
} else {
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
for (int i = newPos; i < numMeshNodes; i++)
|
||||
clearSlot(i);
|
||||
numMeshNodes -= removed;
|
||||
#else
|
||||
int newPos = 0;
|
||||
int removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).num != nodeNum)
|
||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||
@ -1004,6 +1214,7 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
numMeshNodes -= removed;
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
||||
meshtastic_NodeInfoLite());
|
||||
#endif
|
||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
@ -1020,6 +1231,29 @@ void NodeDB::clearLocalPosition()
|
||||
|
||||
void NodeDB::cleanupMeshDB()
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
auto &node = psramMeshNodes[i];
|
||||
if (node.has_user) {
|
||||
if (node.user.public_key.size > 0) {
|
||||
if (memfll(node.user.public_key.bytes, 0, node.user.public_key.size)) {
|
||||
node.user.public_key.size = 0;
|
||||
markHotDirty(i);
|
||||
}
|
||||
}
|
||||
if (newPos != i)
|
||||
moveSlot(i, newPos);
|
||||
newPos++;
|
||||
} else {
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
for (int i = newPos; i < numMeshNodes; i++)
|
||||
clearSlot(i);
|
||||
numMeshNodes -= removed;
|
||||
#else
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).has_user) {
|
||||
@ -1039,6 +1273,7 @@ void NodeDB::cleanupMeshDB()
|
||||
numMeshNodes -= removed;
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
||||
meshtastic_NodeInfoLite());
|
||||
#endif
|
||||
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
||||
}
|
||||
|
||||
@ -1192,16 +1427,41 @@ void NodeDB::loadFromDisk()
|
||||
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
|
||||
installDefaultNodeDatabase();
|
||||
} else {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
initHotCache();
|
||||
size_t inserted = 0;
|
||||
for (const auto &n : nodeDatabase.nodes) {
|
||||
if (inserted >= MAX_NUM_NODES)
|
||||
break;
|
||||
if (isNodeEmpty(n))
|
||||
continue;
|
||||
psramMeshNodes[inserted] = n;
|
||||
hotDirty[inserted] = true;
|
||||
syncHotFromCold(inserted);
|
||||
++inserted;
|
||||
}
|
||||
for (size_t i = inserted; i < psramMeshNodes.size(); ++i)
|
||||
clearSlot(i);
|
||||
numMeshNodes = inserted;
|
||||
nodeDatabase.nodes.clear();
|
||||
LOG_INFO("Loaded saved nodedatabase version %d, with active nodes: %u", nodeDatabase.version, inserted);
|
||||
#else
|
||||
meshNodes = &nodeDatabase.nodes;
|
||||
numMeshNodes = nodeDatabase.nodes.size();
|
||||
LOG_INFO("Loaded saved nodedatabase version %d, with nodes count: %d", nodeDatabase.version, nodeDatabase.nodes.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if (numMeshNodes > MAX_NUM_NODES)
|
||||
numMeshNodes = MAX_NUM_NODES;
|
||||
#else
|
||||
if (numMeshNodes > MAX_NUM_NODES) {
|
||||
LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES);
|
||||
numMeshNodes = MAX_NUM_NODES;
|
||||
}
|
||||
meshNodes->resize(MAX_NUM_NODES);
|
||||
#endif
|
||||
|
||||
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
|
||||
state = loadProto(deviceStateFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState),
|
||||
@ -1407,10 +1667,21 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
||||
spiLock->lock();
|
||||
FSCom.mkdir("/prefs");
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
nodeDatabase.nodes.clear();
|
||||
nodeDatabase.nodes.reserve(numMeshNodes);
|
||||
for (size_t i = 0; i < numMeshNodes; ++i) {
|
||||
nodeDatabase.nodes.push_back(psramMeshNodes[i]);
|
||||
}
|
||||
#endif
|
||||
size_t nodeDatabaseSize;
|
||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &nodeDatabase);
|
||||
return saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false);
|
||||
bool success = saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
nodeDatabase.nodes.clear();
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
@ -1491,10 +1762,18 @@ bool NodeDB::saveToDisk(int saveWhat)
|
||||
|
||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if (readIndex < numMeshNodes) {
|
||||
markHotDirty(readIndex);
|
||||
return &psramMeshNodes[readIndex++];
|
||||
}
|
||||
return NULL;
|
||||
#else
|
||||
if (readIndex < numMeshNodes)
|
||||
return &meshNodes->at(readIndex++);
|
||||
else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
@ -1527,12 +1806,27 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
size_t numseen = 0;
|
||||
|
||||
// FIXME this implementation is kinda expensive
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
uint32_t now = getTime();
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
const NodeHotEntry &hot = hotNodes[i];
|
||||
if (localOnly && (hot.flags & HOT_FLAG_VIA_MQTT))
|
||||
continue;
|
||||
int delta = static_cast<int>(now - hot.last_heard);
|
||||
if (delta < 0)
|
||||
delta = 0;
|
||||
if (delta < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (localOnly && meshNodes->at(i).via_mqtt)
|
||||
continue;
|
||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
}
|
||||
#endif
|
||||
|
||||
return numseen;
|
||||
}
|
||||
@ -1648,6 +1942,13 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
sortMeshDB();
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
{
|
||||
size_t idx = indexOf(info);
|
||||
if (idx != std::numeric_limits<size_t>::max())
|
||||
syncHotFromCold(idx);
|
||||
}
|
||||
#endif
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
|
||||
@ -1710,6 +2011,14 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
info->channel);
|
||||
info->has_user = true;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
{
|
||||
size_t idx = indexOf(info);
|
||||
if (idx != std::numeric_limits<size_t>::max())
|
||||
syncHotFromCold(idx);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (changed) {
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
@ -1757,6 +2066,13 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
info->has_hops_away = true;
|
||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
||||
}
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
{
|
||||
size_t idx = indexOf(info);
|
||||
if (idx != std::numeric_limits<size_t>::max())
|
||||
syncHotFromCold(idx);
|
||||
}
|
||||
#endif
|
||||
sortMeshDB();
|
||||
}
|
||||
}
|
||||
@ -1766,6 +2082,11 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
if (lite && lite->is_favorite != is_favorite) {
|
||||
lite->is_favorite = is_favorite;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
size_t idx = indexOf(lite);
|
||||
if (idx != std::numeric_limits<size_t>::max())
|
||||
syncHotFromCold(idx);
|
||||
#endif
|
||||
sortMeshDB();
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
@ -1779,12 +2100,21 @@ bool NodeDB::isFavorite(uint32_t nodeId)
|
||||
if (nodeId == NODENUM_BROADCAST)
|
||||
return false;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
for (int i = 0; i < numMeshNodes; ++i) {
|
||||
if (hotNodes[i].num == nodeId)
|
||||
return (hotNodes[i].flags & HOT_FLAG_IS_FAVORITE) != 0;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
|
||||
if (lite) {
|
||||
return lite->is_favorite;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||
@ -1798,11 +2128,33 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||
if (p.to == NODENUM_BROADCAST)
|
||||
return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
|
||||
|
||||
meshtastic_NodeInfoLite *lite = NULL;
|
||||
|
||||
bool seenFrom = false;
|
||||
bool seenTo = false;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
const NodeHotEntry &hot = hotNodes[i];
|
||||
|
||||
if (hot.num == p.from) {
|
||||
if (hot.flags & HOT_FLAG_IS_FAVORITE)
|
||||
return true;
|
||||
|
||||
seenFrom = true;
|
||||
}
|
||||
|
||||
if (hot.num == p.to) {
|
||||
if (hot.flags & HOT_FLAG_IS_FAVORITE)
|
||||
return true;
|
||||
|
||||
seenTo = true;
|
||||
}
|
||||
|
||||
if (seenFrom && seenTo)
|
||||
return false; // we've seen both, and neither is a favorite, so we can stop searching early
|
||||
}
|
||||
#else
|
||||
meshtastic_NodeInfoLite *lite = NULL;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
lite = &meshNodes->at(i);
|
||||
|
||||
@ -1826,6 +2178,7 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
|
||||
// all favorited nodes first.
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -1842,6 +2195,27 @@ void NodeDB::sortMeshDB()
|
||||
bool changed = true;
|
||||
while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing
|
||||
changed = false;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
for (int i = numMeshNodes - 1; i > 0; i--) { // lowest case this should examine is i == 1
|
||||
NodeHotEntry &prev = hotNodes[i - 1];
|
||||
NodeHotEntry &curr = hotNodes[i];
|
||||
if (prev.num == getNodeNum()) {
|
||||
continue;
|
||||
} else if (curr.num == getNodeNum()) {
|
||||
swapSlots(i, i - 1);
|
||||
changed = true;
|
||||
} else if ((curr.flags & HOT_FLAG_IS_FAVORITE) && !(prev.flags & HOT_FLAG_IS_FAVORITE)) {
|
||||
swapSlots(i, i - 1);
|
||||
changed = true;
|
||||
} else if (!(curr.flags & HOT_FLAG_IS_FAVORITE) && (prev.flags & HOT_FLAG_IS_FAVORITE)) {
|
||||
continue;
|
||||
} else if (curr.last_heard > prev.last_heard) {
|
||||
swapSlots(i, i - 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int i = numMeshNodes - 1; i > 0; i--) { // lowest case this should examine is i == 1
|
||||
if (meshNodes->at(i - 1).num == getNodeNum()) {
|
||||
// noop
|
||||
@ -1860,6 +2234,7 @@ void NodeDB::sortMeshDB()
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
LOG_INFO("Sort took %u milliseconds", millis() - lastSort);
|
||||
}
|
||||
@ -1867,11 +2242,20 @@ void NodeDB::sortMeshDB()
|
||||
|
||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
refreshHotCache();
|
||||
for (int i = 0; i < numMeshNodes; ++i) {
|
||||
if (hotNodes[i].num == n)
|
||||
return hotNodes[i].channel;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
||||
if (!info) {
|
||||
return 0; // defaults to PRIMARY
|
||||
}
|
||||
return info->channel;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string NodeDB::getNodeId() const
|
||||
@ -1885,11 +2269,21 @@ std::string NodeDB::getNodeId() const
|
||||
/// NOTE: This function might be called from an ISR
|
||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (hotNodes[i].num == n) {
|
||||
markHotDirty(i);
|
||||
return &psramMeshNodes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
#else
|
||||
for (int i = 0; i < numMeshNodes; i++)
|
||||
if (meshNodes->at(i).num == n)
|
||||
return &meshNodes->at(i);
|
||||
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
@ -1901,6 +2295,60 @@ bool NodeDB::isFull()
|
||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||
|
||||
if (!lite) {
|
||||
if (isFull()) {
|
||||
LOG_INFO("Node database full with %i nodes and %u bytes free. Erasing oldest entry", numMeshNodes,
|
||||
memGet.getFreeHeap());
|
||||
refreshHotCache();
|
||||
uint32_t oldest = UINT32_MAX;
|
||||
uint32_t oldestBoring = UINT32_MAX;
|
||||
int oldestIndex = -1;
|
||||
int oldestBoringIndex = -1;
|
||||
for (int i = 1; i < numMeshNodes; i++) {
|
||||
const NodeHotEntry &hot = hotNodes[i];
|
||||
if (!(hot.flags & HOT_FLAG_IS_FAVORITE) && !(hot.flags & HOT_FLAG_IS_IGNORED) &&
|
||||
!(hot.flags & HOT_FLAG_IS_KEY_VERIFIED) && hot.last_heard < oldest) {
|
||||
oldest = hot.last_heard;
|
||||
oldestIndex = i;
|
||||
}
|
||||
const auto &coldNode = psramMeshNodes[i];
|
||||
if (!(hot.flags & HOT_FLAG_IS_FAVORITE) && !(hot.flags & HOT_FLAG_IS_IGNORED) &&
|
||||
coldNode.user.public_key.size == 0 && hot.last_heard < oldestBoring) {
|
||||
oldestBoring = hot.last_heard;
|
||||
oldestBoringIndex = i;
|
||||
}
|
||||
}
|
||||
if (oldestBoringIndex != -1)
|
||||
oldestIndex = oldestBoringIndex;
|
||||
|
||||
if (oldestIndex != -1) {
|
||||
for (int i = oldestIndex; i < numMeshNodes - 1; i++)
|
||||
copySlot(i + 1, i);
|
||||
clearSlot(numMeshNodes - 1);
|
||||
(numMeshNodes)--;
|
||||
}
|
||||
}
|
||||
|
||||
if (numMeshNodes >= MAX_NUM_NODES) {
|
||||
LOG_WARN("Unable to allocate new node %u, MAX_NUM_NODES reached", static_cast<unsigned>(n));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t index = numMeshNodes++;
|
||||
clearSlot(index);
|
||||
psramMeshNodes[index].num = n;
|
||||
syncHotFromCold(index);
|
||||
lite = &psramMeshNodes[index];
|
||||
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
||||
logNodeInsertStats(numMeshNodes, "PSRAM");
|
||||
}
|
||||
|
||||
markHotDirty(lite);
|
||||
return lite;
|
||||
#else
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||
|
||||
if (!lite) {
|
||||
@ -1947,9 +2395,11 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
memset(lite, 0, sizeof(*lite));
|
||||
lite->num = n;
|
||||
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
||||
logNodeInsertStats(numMeshNodes, "Heap");
|
||||
}
|
||||
|
||||
return lite;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Sometimes we will have Position objects that only have a time, so check for
|
||||
|
||||
@ -4,9 +4,13 @@
|
||||
#include <Arduino.h>
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <new>
|
||||
#include <pb_encode.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
#include "MeshTypes.h"
|
||||
#include "NodeStatus.h"
|
||||
@ -14,6 +18,53 @@
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
/**
|
||||
* Custom allocator that redirects NodeInfoLite storage into PSRAM so that the
|
||||
* heavy payload stays out of internal RAM on ESP32-S3 devices.
|
||||
*/
|
||||
template <class T> struct PsramAllocator {
|
||||
using value_type = T;
|
||||
|
||||
PsramAllocator() noexcept = default;
|
||||
|
||||
template <class U> PsramAllocator(const PsramAllocator<U> &) noexcept {}
|
||||
|
||||
[[nodiscard]] T *allocate(std::size_t n)
|
||||
{
|
||||
void *ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!ptr)
|
||||
throw std::bad_alloc();
|
||||
return static_cast<T *>(ptr);
|
||||
}
|
||||
|
||||
void deallocate(T *p, std::size_t) noexcept
|
||||
{
|
||||
if (p)
|
||||
heap_caps_free(p);
|
||||
}
|
||||
|
||||
template <class U> bool operator==(const PsramAllocator<U> &) const noexcept { return true; }
|
||||
template <class U> bool operator!=(const PsramAllocator<U> &) const noexcept { return false; }
|
||||
};
|
||||
|
||||
/** Lightweight DRAM copy of the latency-sensitive node fields. */
|
||||
struct NodeHotEntry {
|
||||
uint32_t num = 0;
|
||||
uint32_t last_heard = 0;
|
||||
float snr = 0.0f;
|
||||
uint8_t role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
uint8_t channel = 0;
|
||||
uint8_t next_hop = 0;
|
||||
uint8_t hops_away = 0;
|
||||
uint8_t flags = 0; // bitmask, see NodeDB::HotFlags
|
||||
};
|
||||
|
||||
using NodeInfoLiteVector = std::vector<meshtastic_NodeInfoLite, PsramAllocator<meshtastic_NodeInfoLite>>;
|
||||
#else
|
||||
using NodeInfoLiteVector = std::vector<meshtastic_NodeInfoLite>;
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
@ -135,7 +186,7 @@ class NodeDB
|
||||
// Note: these two references just point into our static array we serialize to/from disk
|
||||
|
||||
public:
|
||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||
NodeInfoLiteVector *meshNodes;
|
||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||
@ -245,7 +296,12 @@ class NodeDB
|
||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||
{
|
||||
assert(x < numMeshNodes);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
markHotDirty(x);
|
||||
return &psramMeshNodes[x];
|
||||
#else
|
||||
return &meshNodes->at(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
@ -330,6 +386,31 @@ class NodeDB
|
||||
bool saveDeviceStateToDisk();
|
||||
bool saveNodeDatabaseToDisk();
|
||||
void sortMeshDB();
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
enum HotFlags : uint8_t {
|
||||
HOT_FLAG_VIA_MQTT = 1 << 0,
|
||||
HOT_FLAG_IS_FAVORITE = 1 << 1,
|
||||
HOT_FLAG_IS_IGNORED = 1 << 2,
|
||||
HOT_FLAG_HAS_HOPS = 1 << 3,
|
||||
HOT_FLAG_IS_KEY_VERIFIED = 1 << 4
|
||||
};
|
||||
|
||||
void initHotCache();
|
||||
void refreshHotCache();
|
||||
void syncHotFromCold(size_t index);
|
||||
void markHotDirty(size_t index);
|
||||
void markHotDirty(const meshtastic_NodeInfoLite *ptr);
|
||||
void clearSlot(size_t index);
|
||||
void swapSlots(size_t a, size_t b);
|
||||
void copySlot(size_t src, size_t dst);
|
||||
void moveSlot(size_t src, size_t dst);
|
||||
bool isNodeEmpty(const meshtastic_NodeInfoLite &node) const;
|
||||
size_t indexOf(const meshtastic_NodeInfoLite *ptr) const;
|
||||
|
||||
NodeInfoLiteVector psramMeshNodes;
|
||||
std::vector<NodeHotEntry> hotNodes;
|
||||
std::vector<bool> hotDirty;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern NodeDB *nodeDB;
|
||||
@ -372,4 +453,4 @@ extern uint32_t error_address;
|
||||
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
||||
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
|
||||
|
||||
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|
||||
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "PacketHistory.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
|
||||
@ -16,32 +17,61 @@
|
||||
#define VERBOSE_PACKET_HISTORY 0 // Set to 1 for verbose logging, 2 for heavy debugging
|
||||
#define PACKET_HISTORY_TRACE_AGING 1 // Set to 1 to enable logging of the age of re/used history slots
|
||||
|
||||
PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPackets(NULL) // Initialize members
|
||||
PacketHistory::PacketHistory(uint32_t size)
|
||||
: recentPacketsCapacity(0), recentPackets(NULL), recentPacketsInPsram(false) // Initialize members
|
||||
{
|
||||
if (size < 4 || size > PACKETHISTORY_MAX) { // Copilot suggested - makes sense
|
||||
LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX);
|
||||
size = PACKETHISTORY_MAX; // Use default size if invalid
|
||||
}
|
||||
|
||||
LOG_DEBUG("Packet History - pre-alloc heap %u psram %u", memGet.getFreeHeap(), memGet.getFreePsram());
|
||||
|
||||
// Allocate memory for the recent packets array
|
||||
recentPacketsCapacity = size;
|
||||
recentPackets = new PacketRecord[recentPacketsCapacity];
|
||||
if (!recentPackets) { // No logging here, console/log probably uninitialized yet.
|
||||
LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size,
|
||||
sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
recentPacketsCapacity = 0; // mark allocation fail
|
||||
return; // return early
|
||||
if (has_psram()) {
|
||||
// Prefer PSRAM so the large history pool stays out of internal RAM on ESP32-S3 builds.
|
||||
recentPackets = psramAllocArray<PacketRecord>(recentPacketsCapacity);
|
||||
if (recentPackets) {
|
||||
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
recentPacketsInPsram = true;
|
||||
LOG_DEBUG("Packet History - allocated %u entries in PSRAM, free heap %u psram %u", recentPacketsCapacity,
|
||||
memGet.getFreeHeap(), memGet.getFreePsram());
|
||||
} else {
|
||||
LOG_WARN("Packet History - PSRAM allocation failed, falling back to DRAM");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the recent packets array to zero
|
||||
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
if (!recentPackets) {
|
||||
// Fall back to DRAM if PSRAM is unavailable or exhausted.
|
||||
recentPackets = new PacketRecord[recentPacketsCapacity];
|
||||
if (!recentPackets) { // No logging here, console/log probably uninitialized yet.
|
||||
LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size,
|
||||
sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
recentPacketsCapacity = 0; // mark allocation fail
|
||||
return; // return early
|
||||
}
|
||||
|
||||
// Initialize the recent packets array to zero
|
||||
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
LOG_DEBUG("Packet History - allocated %u entries in DRAM, free heap %u psram %u", recentPacketsCapacity,
|
||||
memGet.getFreeHeap(), memGet.getFreePsram());
|
||||
}
|
||||
}
|
||||
|
||||
PacketHistory::~PacketHistory()
|
||||
{
|
||||
recentPacketsCapacity = 0;
|
||||
delete[] recentPackets;
|
||||
if (recentPackets) {
|
||||
// Release via the allocator that produced the buffer.
|
||||
if (recentPacketsInPsram)
|
||||
psramFreeArray(recentPackets);
|
||||
else
|
||||
delete[] recentPackets;
|
||||
}
|
||||
|
||||
recentPackets = NULL;
|
||||
recentPacketsCapacity = 0;
|
||||
recentPacketsInPsram = false;
|
||||
}
|
||||
|
||||
/** Update recentPackets and return true if we have already seen this packet */
|
||||
@ -458,4 +488,4 @@ inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r)
|
||||
inline void PacketHistory::setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit)
|
||||
{
|
||||
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_OUR_TX_MASK) | ((hopLimit << HOP_LIMIT_OUR_TX_SHIFT) & HOP_LIMIT_OUR_TX_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ class PacketHistory
|
||||
uint32_t recentPacketsCapacity =
|
||||
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
|
||||
PacketRecord *recentPackets = NULL; // Simple and fixed in size. Debloat.
|
||||
bool recentPacketsInPsram = false; // Remember backing store so we free via the matching allocator.
|
||||
|
||||
/** Find a packet record in history.
|
||||
* @param sender NodeNum
|
||||
|
||||
@ -13,7 +13,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
const pb_msgdesc_t *fields;
|
||||
|
||||
public:
|
||||
uint8_t numOnlineNodes = 0;
|
||||
uint16_t numOnlineNodes = 0;
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
@ -120,4 +120,4 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
return alterReceivedProtobuf(mp, decoded);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -52,8 +52,21 @@ Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
||||
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
|
||||
// Try to put the heavy MeshPacket pool into PSRAM. If that fails we fall back to
|
||||
// heap allocation so the radio stays functional (at the cost of fewer packets).
|
||||
static PsramMemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> psramPool;
|
||||
static MemoryDynamic<meshtastic_MeshPacket> fallbackPool;
|
||||
Allocator<meshtastic_MeshPacket> &packetPool = psramPool.isValid()
|
||||
? static_cast<Allocator<meshtastic_MeshPacket> &>(psramPool)
|
||||
: static_cast<Allocator<meshtastic_MeshPacket> &>(fallbackPool);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
|
||||
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||
#else
|
||||
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
|
||||
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include "memGet.h"
|
||||
#include "mesh/generated/meshtastic/admin.pb.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
#include "mesh/generated/meshtastic/localonly.pb.h"
|
||||
@ -11,11 +12,58 @@
|
||||
// Tricky macro to let you find the sizeof a type member
|
||||
#define member_size(type, member) sizeof(((type *)0)->member)
|
||||
|
||||
// Minimum PSRAM the firmware expects before enabling the "expanded" queues that
|
||||
// rely on off-chip RAM instead of internal DRAM. Currently set to 2MB to
|
||||
// accommodate Heltec WiFi LoRa 32 V4 boards (and others)
|
||||
static constexpr size_t PSRAM_LARGE_THRESHOLD_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
// Default RX queue size for phone delivery when PSRAM is available
|
||||
// This is an arbitrary default bump from default, boards can override
|
||||
// this in board.h
|
||||
static constexpr int RX_TOPHONE_WITH_PSRAM_DEFAULT = 100;
|
||||
|
||||
inline bool has_psram(size_t minimumBytes = PSRAM_LARGE_THRESHOLD_BYTES)
|
||||
{
|
||||
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
|
||||
return memGet.getPsramSize() >= minimumBytes;
|
||||
#else
|
||||
(void)minimumBytes;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Runtime cap used to keep the BLE message queue from overflowing low-memory
|
||||
// S3 variants if PSRAM is smaller than expected or temporarily unavailable.
|
||||
inline int get_rx_tophone_limit()
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(BOARD_MAX_RX_TOPHONE)
|
||||
return BOARD_MAX_RX_TOPHONE;
|
||||
#elif defined(BOARD_HAS_PSRAM)
|
||||
return RX_TOPHONE_WITH_PSRAM_DEFAULT;
|
||||
#else
|
||||
return 32;
|
||||
#endif
|
||||
#elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
return 8;
|
||||
#else
|
||||
return 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// max number of packets which can be waiting for delivery to android - note, this value comes from mesh.options protobuf
|
||||
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
|
||||
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
|
||||
#ifndef MAX_RX_TOPHONE
|
||||
#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(BOARD_MAX_RX_TOPHONE)
|
||||
#define MAX_RX_TOPHONE BOARD_MAX_RX_TOPHONE
|
||||
#elif defined(BOARD_HAS_PSRAM)
|
||||
#define MAX_RX_TOPHONE RX_TOPHONE_WITH_PSRAM_DEFAULT
|
||||
#else
|
||||
#define MAX_RX_TOPHONE 32
|
||||
#endif
|
||||
#elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define MAX_RX_TOPHONE 8
|
||||
#else
|
||||
#define MAX_RX_TOPHONE 32
|
||||
@ -48,19 +96,24 @@ static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increas
|
||||
#elif defined(ARCH_NRF52)
|
||||
#define MAX_NUM_NODES 80
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(BOARD_MAX_NUM_NODES)
|
||||
#define MAX_NUM_NODES BOARD_MAX_NUM_NODES
|
||||
#elif defined(BOARD_HAS_PSRAM)
|
||||
#define MAX_NUM_NODES 3000
|
||||
#else
|
||||
#include "Esp.h"
|
||||
static inline int get_max_num_nodes()
|
||||
{
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Convert Bytes to MB
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Fallback based on flash size
|
||||
if (flash_size >= 15) {
|
||||
return 250;
|
||||
} else if (flash_size >= 7) {
|
||||
return 200;
|
||||
} else {
|
||||
return 100;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
#define MAX_NUM_NODES get_max_num_nodes()
|
||||
#endif
|
||||
#else
|
||||
#define MAX_NUM_NODES 100
|
||||
#endif
|
||||
@ -90,4 +143,4 @@ bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count);
|
||||
*/
|
||||
bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count);
|
||||
|
||||
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
|
||||
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
|
||||
|
||||
@ -327,9 +327,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
case meshtastic_AdminMessage_set_favorite_node_tag: {
|
||||
LOG_INFO("Client received set_favorite_node command");
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node);
|
||||
if (node != NULL) {
|
||||
node->is_favorite = true;
|
||||
if (nodeDB->getMeshNode(r->set_favorite_node) != NULL) {
|
||||
nodeDB->set_favorite(true, r->set_favorite_node);
|
||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||
if (screen)
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||
@ -338,9 +337,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
case meshtastic_AdminMessage_remove_favorite_node_tag: {
|
||||
LOG_INFO("Client received remove_favorite_node command");
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node);
|
||||
if (node != NULL) {
|
||||
node->is_favorite = false;
|
||||
if (nodeDB->getMeshNode(r->remove_favorite_node) != NULL) {
|
||||
nodeDB->set_favorite(false, r->remove_favorite_node);
|
||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||
if (screen)
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||
|
||||
Loading…
Reference in New Issue
Block a user