mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 15:02:41 +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__)
|
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||||
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
|
#define LOG_DEBUG(...) \
|
||||||
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
|
do { \
|
||||||
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
|
if (console) { \
|
||||||
#define LOG_ERROR(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__)
|
console->log(MESHTASTIC_LOG_LEVEL_DEBUG, __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__)
|
} 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
|
#else
|
||||||
#define LOG_DEBUG(...)
|
#define LOG_DEBUG(...)
|
||||||
#define LOG_INFO(...)
|
#define LOG_INFO(...)
|
||||||
@ -70,7 +100,12 @@ extern MemGet memGet;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DEBUG_HEAP)
|
#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
|
// Macro-based heap debugging
|
||||||
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
|
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
|
||||||
|
|||||||
@ -14,16 +14,16 @@ class NodeStatus : public Status
|
|||||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
||||||
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||||
|
|
||||||
uint8_t numOnline = 0;
|
uint16_t numOnline = 0;
|
||||||
uint8_t numTotal = 0;
|
uint16_t numTotal = 0;
|
||||||
|
|
||||||
uint8_t lastNumTotal = 0;
|
uint16_t lastNumTotal = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool forceUpdate = false;
|
bool forceUpdate = false;
|
||||||
|
|
||||||
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
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->forceUpdate = forceUpdate;
|
||||||
this->numOnline = numOnline;
|
this->numOnline = numOnline;
|
||||||
@ -34,11 +34,11 @@ class NodeStatus : public Status
|
|||||||
|
|
||||||
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
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
|
bool matches(const NodeStatus *newStatus) const
|
||||||
{
|
{
|
||||||
|
|||||||
@ -287,7 +287,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
float easternmost = lngCenter;
|
float easternmost = lngCenter;
|
||||||
float westernmost = 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);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Skip if no position
|
// Skip if no position
|
||||||
@ -475,7 +475,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
bool InkHUD::MapApplet::enoughMarkers()
|
bool InkHUD::MapApplet::enoughMarkers()
|
||||||
{
|
{
|
||||||
uint8_t count = 0;
|
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);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Count nodes
|
// Count nodes
|
||||||
|
|||||||
@ -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));
|
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()
|
void setup()
|
||||||
{
|
{
|
||||||
#if defined(R1_NEO)
|
#if defined(R1_NEO)
|
||||||
@ -1573,7 +1573,7 @@ void scannerToSensorsMap(const std::unique_ptr<ScanI2CTwoWire> &i2cScanner, Scan
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef PIO_UNIT_TESTING
|
#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING)
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
runASAP = false;
|
runASAP = false;
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
|
#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
|
template <class T> class Allocator
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -159,3 +163,92 @@ template <class T, int MaxSize> class MemoryPool : public Allocator<T>
|
|||||||
return nullptr;
|
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
|
||||||
#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 ||
|
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||||
p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_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);
|
meshtastic_MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
||||||
if (d)
|
if (d)
|
||||||
releaseToPool(d);
|
releaseToPool(d);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("ToPhone queue is full, drop packet");
|
LOG_WARN("ToPhone queue %s, drop packet", runtimeControlled ? "reached runtime limit" : "is full");
|
||||||
releaseToPool(p);
|
releaseToPool(p);
|
||||||
fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets
|
fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
#include "modules/NeighborInfoModule.h"
|
#include "modules/NeighborInfoModule.h"
|
||||||
#include <ErriezCRC32.h>
|
#include <ErriezCRC32.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -37,6 +38,12 @@
|
|||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include <esp_efuse.h>
|
#include <esp_efuse.h>
|
||||||
#include <esp_efuse_table.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 <nvs_flash.h>
|
||||||
#include <soc/efuse_reg.h>
|
#include <soc/efuse_reg.h>
|
||||||
#include <soc/soc.h>
|
#include <soc/soc.h>
|
||||||
@ -67,6 +74,174 @@ meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 3
|
|||||||
meshtastic_LocalModuleConfig moduleConfig;
|
meshtastic_LocalModuleConfig moduleConfig;
|
||||||
meshtastic_ChannelFile channelFile;
|
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
|
#ifdef USERPREFS_USE_ADMIN_KEY_0
|
||||||
static unsigned char userprefs_admin_key_0[] = USERPREFS_USE_ADMIN_KEY_0;
|
static unsigned char userprefs_admin_key_0[] = USERPREFS_USE_ADMIN_KEY_0;
|
||||||
#endif
|
#endif
|
||||||
@ -520,9 +695,16 @@ void NodeDB::installDefaultNodeDatabase()
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("Install default NodeDatabase");
|
LOG_DEBUG("Install default NodeDatabase");
|
||||||
nodeDatabase.version = DEVICESTATE_CUR_VER;
|
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);
|
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
|
||||||
numMeshNodes = 0;
|
|
||||||
meshNodes = &nodeDatabase.nodes;
|
meshNodes = &nodeDatabase.nodes;
|
||||||
|
#endif
|
||||||
|
numMeshNodes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::installDefaultConfig(bool preserveKey = false)
|
void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||||
@ -982,8 +1164,18 @@ void NodeDB::resetNodes()
|
|||||||
{
|
{
|
||||||
if (!config.position.fixed_position)
|
if (!config.position.fixed_position)
|
||||||
clearLocalPosition();
|
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;
|
numMeshNodes = 1;
|
||||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||||
|
#endif
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
devicestate.has_rx_waypoint = false;
|
devicestate.has_rx_waypoint = false;
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
@ -994,7 +1186,25 @@ void NodeDB::resetNodes()
|
|||||||
|
|
||||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
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++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (meshNodes->at(i).num != nodeNum)
|
if (meshNodes->at(i).num != nodeNum)
|
||||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||||
@ -1004,6 +1214,7 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
|||||||
numMeshNodes -= removed;
|
numMeshNodes -= removed;
|
||||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
||||||
meshtastic_NodeInfoLite());
|
meshtastic_NodeInfoLite());
|
||||||
|
#endif
|
||||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
@ -1020,6 +1231,29 @@ void NodeDB::clearLocalPosition()
|
|||||||
|
|
||||||
void NodeDB::cleanupMeshDB()
|
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;
|
int newPos = 0, removed = 0;
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (meshNodes->at(i).has_user) {
|
if (meshNodes->at(i).has_user) {
|
||||||
@ -1039,6 +1273,7 @@ void NodeDB::cleanupMeshDB()
|
|||||||
numMeshNodes -= removed;
|
numMeshNodes -= removed;
|
||||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
||||||
meshtastic_NodeInfoLite());
|
meshtastic_NodeInfoLite());
|
||||||
|
#endif
|
||||||
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1192,16 +1427,41 @@ void NodeDB::loadFromDisk()
|
|||||||
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
|
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
|
||||||
installDefaultNodeDatabase();
|
installDefaultNodeDatabase();
|
||||||
} else {
|
} 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;
|
meshNodes = &nodeDatabase.nodes;
|
||||||
numMeshNodes = nodeDatabase.nodes.size();
|
numMeshNodes = nodeDatabase.nodes.size();
|
||||||
LOG_INFO("Loaded saved nodedatabase version %d, with nodes count: %d", nodeDatabase.version, 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) {
|
if (numMeshNodes > MAX_NUM_NODES) {
|
||||||
LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES);
|
LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES);
|
||||||
numMeshNodes = MAX_NUM_NODES;
|
numMeshNodes = MAX_NUM_NODES;
|
||||||
}
|
}
|
||||||
meshNodes->resize(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
|
// 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),
|
state = loadProto(deviceStateFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState),
|
||||||
@ -1407,10 +1667,21 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
|||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
spiLock->unlock();
|
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
|
#endif
|
||||||
size_t nodeDatabaseSize;
|
size_t nodeDatabaseSize;
|
||||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &nodeDatabase);
|
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)
|
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||||
@ -1491,10 +1762,18 @@ bool NodeDB::saveToDisk(int saveWhat)
|
|||||||
|
|
||||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
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)
|
if (readIndex < numMeshNodes)
|
||||||
return &meshNodes->at(readIndex++);
|
return &meshNodes->at(readIndex++);
|
||||||
else
|
else
|
||||||
return NULL;
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
/// 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;
|
size_t numseen = 0;
|
||||||
|
|
||||||
// FIXME this implementation is kinda expensive
|
// 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++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (localOnly && meshNodes->at(i).via_mqtt)
|
if (localOnly && meshNodes->at(i).via_mqtt)
|
||||||
continue;
|
continue;
|
||||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
||||||
numseen++;
|
numseen++;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return numseen;
|
return numseen;
|
||||||
}
|
}
|
||||||
@ -1648,6 +1942,13 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
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();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1710,6 +2011,14 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
info->channel);
|
info->channel);
|
||||||
info->has_user = true;
|
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) {
|
if (changed) {
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
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->has_hops_away = true;
|
||||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
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();
|
sortMeshDB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1766,6 +2082,11 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
|||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||||
if (lite && lite->is_favorite != is_favorite) {
|
if (lite && lite->is_favorite != is_favorite) {
|
||||||
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();
|
sortMeshDB();
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
@ -1779,12 +2100,21 @@ bool NodeDB::isFavorite(uint32_t nodeId)
|
|||||||
if (nodeId == NODENUM_BROADCAST)
|
if (nodeId == NODENUM_BROADCAST)
|
||||||
return false;
|
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);
|
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||||
|
|
||||||
if (lite) {
|
if (lite) {
|
||||||
return lite->is_favorite;
|
return lite->is_favorite;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||||
@ -1798,11 +2128,33 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
|||||||
if (p.to == NODENUM_BROADCAST)
|
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
|
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 seenFrom = false;
|
||||||
bool seenTo = 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++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
lite = &meshNodes->at(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
|
// 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.
|
// all favorited nodes first.
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1842,6 +2195,27 @@ void NodeDB::sortMeshDB()
|
|||||||
bool changed = true;
|
bool changed = true;
|
||||||
while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing
|
while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing
|
||||||
changed = false;
|
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
|
for (int i = numMeshNodes - 1; i > 0; i--) { // lowest case this should examine is i == 1
|
||||||
if (meshNodes->at(i - 1).num == getNodeNum()) {
|
if (meshNodes->at(i - 1).num == getNodeNum()) {
|
||||||
// noop
|
// noop
|
||||||
@ -1860,6 +2234,7 @@ void NodeDB::sortMeshDB()
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
LOG_INFO("Sort took %u milliseconds", millis() - lastSort);
|
LOG_INFO("Sort took %u milliseconds", millis() - lastSort);
|
||||||
}
|
}
|
||||||
@ -1867,11 +2242,20 @@ void NodeDB::sortMeshDB()
|
|||||||
|
|
||||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
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);
|
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return 0; // defaults to PRIMARY
|
return 0; // defaults to PRIMARY
|
||||||
}
|
}
|
||||||
return info->channel;
|
return info->channel;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NodeDB::getNodeId() const
|
std::string NodeDB::getNodeId() const
|
||||||
@ -1885,11 +2269,21 @@ std::string NodeDB::getNodeId() const
|
|||||||
/// NOTE: This function might be called from an ISR
|
/// NOTE: This function might be called from an ISR
|
||||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
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++)
|
for (int i = 0; i < numMeshNodes; i++)
|
||||||
if (meshNodes->at(i).num == n)
|
if (meshNodes->at(i).num == n)
|
||||||
return &meshNodes->at(i);
|
return &meshNodes->at(i);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
// 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
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
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);
|
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||||
|
|
||||||
if (!lite) {
|
if (!lite) {
|
||||||
@ -1947,9 +2395,11 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
memset(lite, 0, sizeof(*lite));
|
memset(lite, 0, sizeof(*lite));
|
||||||
lite->num = n;
|
lite->num = n;
|
||||||
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
||||||
|
logNodeInsertStats(numMeshNodes, "Heap");
|
||||||
}
|
}
|
||||||
|
|
||||||
return lite;
|
return lite;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sometimes we will have Position objects that only have a time, so check for
|
/// Sometimes we will have Position objects that only have a time, so check for
|
||||||
|
|||||||
@ -4,9 +4,13 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <new>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "NodeStatus.h"
|
#include "NodeStatus.h"
|
||||||
@ -14,6 +18,53 @@
|
|||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
|
#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
|
#if ARCH_PORTDUINO
|
||||||
#include "PortduinoGlue.h"
|
#include "PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
@ -135,7 +186,7 @@ class NodeDB
|
|||||||
// Note: these two references just point into our static array we serialize to/from disk
|
// Note: these two references just point into our static array we serialize to/from disk
|
||||||
|
|
||||||
public:
|
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
|
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
|
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
@ -245,7 +296,12 @@ class NodeDB
|
|||||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||||
{
|
{
|
||||||
assert(x < numMeshNodes);
|
assert(x < numMeshNodes);
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
markHotDirty(x);
|
||||||
|
return &psramMeshNodes[x];
|
||||||
|
#else
|
||||||
return &meshNodes->at(x);
|
return &meshNodes->at(x);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||||
@ -330,6 +386,31 @@ class NodeDB
|
|||||||
bool saveDeviceStateToDisk();
|
bool saveDeviceStateToDisk();
|
||||||
bool saveNodeDatabaseToDisk();
|
bool saveNodeDatabaseToDisk();
|
||||||
void sortMeshDB();
|
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;
|
extern NodeDB *nodeDB;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
|
#include "MemoryPool.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.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 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
|
#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
|
if (size < 4 || size > PACKETHISTORY_MAX) { // Copilot suggested - makes sense
|
||||||
LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX);
|
LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX);
|
||||||
size = PACKETHISTORY_MAX; // Use default size if invalid
|
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
|
// Allocate memory for the recent packets array
|
||||||
recentPacketsCapacity = size;
|
recentPacketsCapacity = size;
|
||||||
recentPackets = new PacketRecord[recentPacketsCapacity];
|
if (has_psram()) {
|
||||||
if (!recentPackets) { // No logging here, console/log probably uninitialized yet.
|
// Prefer PSRAM so the large history pool stays out of internal RAM on ESP32-S3 builds.
|
||||||
LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size,
|
recentPackets = psramAllocArray<PacketRecord>(recentPacketsCapacity);
|
||||||
sizeof(PacketRecord) * recentPacketsCapacity);
|
if (recentPackets) {
|
||||||
recentPacketsCapacity = 0; // mark allocation fail
|
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
||||||
return; // return early
|
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
|
if (!recentPackets) {
|
||||||
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
// 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()
|
PacketHistory::~PacketHistory()
|
||||||
{
|
{
|
||||||
recentPacketsCapacity = 0;
|
if (recentPackets) {
|
||||||
delete[] recentPackets;
|
// Release via the allocator that produced the buffer.
|
||||||
|
if (recentPacketsInPsram)
|
||||||
|
psramFreeArray(recentPackets);
|
||||||
|
else
|
||||||
|
delete[] recentPackets;
|
||||||
|
}
|
||||||
|
|
||||||
recentPackets = NULL;
|
recentPackets = NULL;
|
||||||
|
recentPacketsCapacity = 0;
|
||||||
|
recentPacketsInPsram = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update recentPackets and return true if we have already seen this packet */
|
/** Update recentPackets and return true if we have already seen this packet */
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class PacketHistory
|
|||||||
uint32_t recentPacketsCapacity =
|
uint32_t recentPacketsCapacity =
|
||||||
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
|
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.
|
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.
|
/** Find a packet record in history.
|
||||||
* @param sender NodeNum
|
* @param sender NodeNum
|
||||||
|
|||||||
@ -13,7 +13,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
|||||||
const pb_msgdesc_t *fields;
|
const pb_msgdesc_t *fields;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint8_t numOnlineNodes = 0;
|
uint16_t numOnlineNodes = 0;
|
||||||
/** Constructor
|
/** Constructor
|
||||||
* name is for debugging output
|
* name is for debugging output
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -52,8 +52,21 @@ Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
|||||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
(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)
|
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;
|
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
|
||||||
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||||
|
#else
|
||||||
|
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
|
||||||
|
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "memGet.h"
|
||||||
#include "mesh/generated/meshtastic/admin.pb.h"
|
#include "mesh/generated/meshtastic/admin.pb.h"
|
||||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||||
#include "mesh/generated/meshtastic/localonly.pb.h"
|
#include "mesh/generated/meshtastic/localonly.pb.h"
|
||||||
@ -11,11 +12,58 @@
|
|||||||
// Tricky macro to let you find the sizeof a type member
|
// Tricky macro to let you find the sizeof a type member
|
||||||
#define member_size(type, member) sizeof(((type *)0)->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
|
/// 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
|
// 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]))
|
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
|
||||||
#ifndef MAX_RX_TOPHONE
|
#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
|
#define MAX_RX_TOPHONE 8
|
||||||
#else
|
#else
|
||||||
#define MAX_RX_TOPHONE 32
|
#define MAX_RX_TOPHONE 32
|
||||||
@ -48,19 +96,24 @@ static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increas
|
|||||||
#elif defined(ARCH_NRF52)
|
#elif defined(ARCH_NRF52)
|
||||||
#define MAX_NUM_NODES 80
|
#define MAX_NUM_NODES 80
|
||||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
#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"
|
#include "Esp.h"
|
||||||
static inline int get_max_num_nodes()
|
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) {
|
if (flash_size >= 15) {
|
||||||
return 250;
|
return 250;
|
||||||
} else if (flash_size >= 7) {
|
} else if (flash_size >= 7) {
|
||||||
return 200;
|
return 200;
|
||||||
} else {
|
|
||||||
return 100;
|
|
||||||
}
|
}
|
||||||
|
return 100;
|
||||||
}
|
}
|
||||||
#define MAX_NUM_NODES get_max_num_nodes()
|
#define MAX_NUM_NODES get_max_num_nodes()
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define MAX_NUM_NODES 100
|
#define MAX_NUM_NODES 100
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -327,9 +327,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
}
|
}
|
||||||
case meshtastic_AdminMessage_set_favorite_node_tag: {
|
case meshtastic_AdminMessage_set_favorite_node_tag: {
|
||||||
LOG_INFO("Client received set_favorite_node command");
|
LOG_INFO("Client received set_favorite_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node);
|
if (nodeDB->getMeshNode(r->set_favorite_node) != NULL) {
|
||||||
if (node != NULL) {
|
nodeDB->set_favorite(true, r->set_favorite_node);
|
||||||
node->is_favorite = true;
|
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
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: {
|
case meshtastic_AdminMessage_remove_favorite_node_tag: {
|
||||||
LOG_INFO("Client received remove_favorite_node command");
|
LOG_INFO("Client received remove_favorite_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node);
|
if (nodeDB->getMeshNode(r->remove_favorite_node) != NULL) {
|
||||||
if (node != NULL) {
|
nodeDB->set_favorite(false, r->remove_favorite_node);
|
||||||
node->is_favorite = false;
|
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user