v1.0.5: Heltec V3 support, heap stability fix, TCP reconnection improvements

- Add Heltec WiFi LoRa 32 V3 board support (8MB flash, 8MB PSRAM)
  - New heltec_V3_boundary build environment in platformio.ini
  - Board auto-detection in flash.py (8MB=V3, 16MB=V4)
  - V3 board definition in Boards.h
- Fix heap exhaustion causing watchdog reboots every ~70 min
  - Lower boundary_mentioned_addresses cap from 512 to 200
  - Heap now stable at ~38KB free (was draining to 0)
- TCP reconnection improvements in TcpInterface.h
  - SO_LINGER(0) for clean socket teardown
  - 10-minute read timeout prevents zombie connections
  - Defensive client cleanup on accept
- Add heap telemetry instrumentation (HEAP-TEL) for monitoring
- Add level guards on TRACE/DEBUG macros in Log.h
- Update README for dual V3/V4 board support
This commit is contained in:
James L
2026-02-26 14:32:18 -05:00
parent 990649d810
commit 5e0e3f538a
8 changed files with 424 additions and 111 deletions

View File

@@ -25,10 +25,10 @@
#define VERBOSE(msg) (RNS::verbose(msg))
#define VERBOSEF(msg, ...) (RNS::verbosef(msg, __VA_ARGS__))
#ifndef NDEBUG
#define DEBUG(msg) (RNS::debug(msg))
#define DEBUGF(msg, ...) (RNS::debugf(msg, __VA_ARGS__))
#define TRACE(msg) (RNS::trace(msg))
#define TRACEF(msg, ...) (RNS::tracef(msg, __VA_ARGS__))
#define DEBUG(msg) do { if (RNS::loglevel() >= RNS::LOG_DEBUG) RNS::debug(msg); } while(0)
#define DEBUGF(msg, ...) do { if (RNS::loglevel() >= RNS::LOG_DEBUG) RNS::debugf(msg, __VA_ARGS__); } while(0)
#define TRACE(msg) do { if (RNS::loglevel() >= RNS::LOG_TRACE) RNS::trace(msg); } while(0)
#define TRACEF(msg, ...) do { if (RNS::loglevel() >= RNS::LOG_TRACE) RNS::tracef(msg, __VA_ARGS__); } while(0)
#if defined(RNS_MEM_LOG)
#define MEM(msg) (RNS::mem(msg))
#define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__))

View File

@@ -104,6 +104,7 @@ using namespace RNS::Utilities;
static std::set<Bytes> _boundary_local_addresses;
// BOUNDARY MODE Whitelist 2: addresses mentioned in packets from local devices
static std::set<Bytes> _boundary_mentioned_addresses;
static const uint16_t _boundary_maxsize = 200;
// BOUNDARY MODE: Check if an interface is the backbone
static bool is_backbone_interface(const Interface& iface) {
@@ -256,6 +257,9 @@ static bool is_backbone_interface(const Interface& iface) {
/*static*/ void Transport::jobs() {
//TRACE("Transport::jobs()");
// Heap telemetry: snapshot at jobs entry
size_t _jobs_heap_entry = OS::heap_available();
std::vector<Packet> outgoing;
std::set<Bytes> path_requests;
int count;
@@ -443,6 +447,15 @@ static bool is_backbone_interface(const Interface& iface) {
_packet_hashlist.erase(_packet_hashlist.begin(), iter);
}
#ifdef BOUNDARY_MODE
// Cull the boundary mentioned addresses if it has reached its max size
if (_boundary_mentioned_addresses.size() > _boundary_maxsize) {
std::set<Bytes>::iterator iter = _boundary_mentioned_addresses.begin();
std::advance(iter, _boundary_mentioned_addresses.size() - _boundary_maxsize);
_boundary_mentioned_addresses.erase(_boundary_mentioned_addresses.begin(), iter);
}
#endif
// Cull the path request tags list if it has reached its max size
if (_discovery_pr_tags.size() > _max_pr_tags) {
std::set<Bytes>::iterator iter = _discovery_pr_tags.begin();
@@ -656,6 +669,15 @@ static bool is_backbone_interface(const Interface& iface) {
_jobs_running = false;
// Heap telemetry: snapshot at jobs exit
{
size_t _jobs_heap_exit = OS::heap_available();
int _jobs_delta = (int)_jobs_heap_exit - (int)_jobs_heap_entry;
if (_jobs_delta < -64 || _jobs_delta > 64) {
VERBOSEF("[HEAP-TEL] jobs: %d bytes (heap=%u)", _jobs_delta, (uint32_t)_jobs_heap_exit);
}
}
// CBA send announce retransmission packets
for (auto& packet : outgoing) {
packet.send();
@@ -1201,6 +1223,9 @@ static bool is_backbone_interface(const Interface& iface) {
/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) {
TRACEF("Transport::inbound: received %d bytes", raw.size());
++_packets_received;
// Heap telemetry: snapshot at entry
size_t _heap_at_entry = OS::heap_available();
// CBA
if (_callbacks._receive_packet) {
try {
@@ -1440,7 +1465,16 @@ static bool is_backbone_interface(const Interface& iface) {
}
#endif
// CBA ACCUMULATES
// Heap telemetry: snapshot after boundary filter
{
size_t _heap_after_boundary = OS::heap_available();
int _boundary_delta = (int)_heap_after_boundary - (int)_heap_at_entry;
if (_boundary_delta < -64) {
VERBOSEF("[HEAP-TEL] boundary: %d bytes (bma=%u phl=%u)", _boundary_delta, _boundary_mentioned_addresses.size(), _packet_hashlist.size());
}
}
// CBA ACCUMULATES
_packet_hashlist.insert(packet.packet_hash());
cache_packet(packet);
@@ -2642,6 +2676,21 @@ static bool is_backbone_interface(const Interface& iface) {
}
}
// Heap telemetry: snapshot at exit
{
size_t _heap_at_exit = OS::heap_available();
int _inbound_delta = (int)_heap_at_exit - (int)_heap_at_entry;
// Log every 100th packet or when delta exceeds threshold
static uint32_t _tel_pkt_count = 0;
++_tel_pkt_count;
if (_inbound_delta < -64 || (_tel_pkt_count % 100 == 0)) {
VERBOSEF("[HEAP-TEL] inbound: %d bytes (heap=%u pin=%u bma=%u phl=%u lt=%u revr=%u)",
_inbound_delta, (uint32_t)_heap_at_exit, _packets_received,
_boundary_mentioned_addresses.size(), _packet_hashlist.size(),
_link_table.size(), _reverse_table.size());
}
}
_jobs_locked = false;
}
@@ -4249,6 +4298,7 @@ TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence::
interface_announces += interface.announce_queue().size();
}
VERBOSEF("phl: %u rcp: %u lt: %u pl: %u al: %u tun: %u", _packet_hashlist.size(), _receipts.size(), _link_table.size(), _pending_links.size(), _active_links.size(), _tunnels.size());
VERBOSEF("bla: %u bma: %u", _boundary_local_addresses.size(), _boundary_mentioned_addresses.size());
VERBOSEF("pin: %u pout: %u padd: %u dpr: %u ikd: %u ia: %u\r\n", _packets_received, _packets_sent, _destinations_added, destination_path_responses, Identity::_known_destinations.size(), interface_announces);
_last_memory = memory;