Add moar stored messages when oflfine as well. Also using psram.

This commit is contained in:
Clive Blackledge 2025-09-27 22:49:14 -07:00 committed by Tom Fifield
parent 344263de86
commit 2e4f1a0216
4 changed files with 132 additions and 7 deletions

View File

@ -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,69 @@ 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;
}
};
#endif

View File

@ -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;

View File

@ -43,9 +43,19 @@ 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)
// 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);
#else
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;
#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__));
@ -56,6 +66,14 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
*/ */
Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO) Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO)
{ {
#if defined(CONFIG_IDF_TARGET_ESP32S3)
if (!psramPool.isValid()) {
LOG_WARN("PSRAM packet pool unavailable, falling back to heap allocations");
}
if (!has_psram() && MAX_RX_TOPHONE > get_rx_tophone_limit()) {
LOG_WARN("Detected <2MB PSRAM, limiting phone queue to %d packets", get_rx_tophone_limit());
}
#endif
// This is called pre main(), don't touch anything here, the following code is not safe // This is called pre main(), don't touch anything here, the following code is not safe
/* LOG_DEBUG("Size of NodeInfo %d", sizeof(NodeInfo)); /* LOG_DEBUG("Size of NodeInfo %d", sizeof(NodeInfo));

View File

@ -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,42 @@
// 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;
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)
return has_psram() ? 200 : 32;
#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)
static constexpr int MAX_RX_TOPHONE_WITH_PSRAM = 200;
#define MAX_RX_TOPHONE MAX_RX_TOPHONE_WITH_PSRAM
#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
@ -51,9 +83,8 @@ static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increas
#include "Esp.h" #include "Esp.h"
static inline int get_max_num_nodes() static inline int get_max_num_nodes()
{ {
uint32_t psram_size = ESP.getPsramSize() / (1024 * 1024); // Convert Bytes to MB if (has_psram()) {
if (psram_size >= 2) { return 5000;
return 800;
} }
uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Fallback based on flash size uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Fallback based on flash size