Merge branch 'main' into touch

This commit is contained in:
DeFiDude
2026-04-02 23:06:43 -06:00
8 changed files with 588 additions and 27 deletions
+13 -1
View File
@@ -133,6 +133,9 @@ constexpr unsigned long TCP_GLOBAL_BUDGET_MS = 35; // Max cumulative TCP ti
bool wifiDeferredAnnounce = false;
unsigned long wifiConnectedAt = 0;
// LXMF diagnostic counters (reset each heartbeat)
static uint32_t diagTcpSkipEvents = 0;
// =============================================================================
// Timezone helper — returns POSIX TZ string for current config
// =============================================================================
@@ -1062,6 +1065,7 @@ void loop() {
// 8. WiFi + TCP loops (with global budget) — skip only if RNS severely overloaded
{
bool skipTcp = (rnsDuration > 500);
if (skipTcp) diagTcpSkipEvents++;
if (!skipTcp && wifiImpl) wifiImpl->loop();
if (!skipTcp) {
unsigned long tcpBudgetStart = millis();
@@ -1157,10 +1161,18 @@ void loop() {
{
auto& ifaces = RNS::Transport::get_interfaces();
int tcpUp = 0;
for (auto* tcp : tcpClients) { if (tcp && tcp->isConnected()) tcpUp++; }
int tcpRx = 0;
for (auto* tcp : tcpClients) {
if (tcp && tcp->isConnected()) tcpUp++;
if (tcp) tcpRx += tcp->hubRxCount();
}
Serial.printf("[HEART-DIAG] ifaces=%d tcp=%d/%d wifi=%s\n",
(int)ifaces.size(), tcpUp, (int)tcpClients.size(),
wifiSTAConnected ? "STA" : (wifiImpl ? "AP" : "OFF"));
Serial.printf("[LXMF-DIAG] tcp_rx=%d tcp_skip=%lu ann_filt=%lu\n",
tcpRx, (unsigned long)diagTcpSkipEvents,
(unsigned long)rns.announceFilterCount());
diagTcpSkipEvents = 0;
}
#if HAS_GPS
if (userConfig.settings().gpsTimeEnabled) {
+10 -5
View File
@@ -189,17 +189,22 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) {
// Fallback: opportunistic or queue for link-based resource transfer
if (!sent) {
RNS::Bytes payloadBytes(payload.data(), payload.size());
if (payloadBytes.size() <= RNS::Type::Reticulum::MDU) {
// Small enough for single opportunistic packet
// Use opportunistic only for packets that fit in a single LoRa frame (254 bytes).
// Larger packets require split-frame over LoRa, which is unreliable — any single
// frame loss (CRC error, collision, half-duplex timing) kills the entire transfer
// with no recovery. Link-based delivery handles retransmission at the protocol level.
static constexpr size_t SINGLE_FRAME_MAX = 254;
if (payloadBytes.size() <= SINGLE_FRAME_MAX) {
// Fits in single LoRa frame — send opportunistic
Serial.printf("[LXMF] sending opportunistic: %d bytes to %s\n",
(int)payloadBytes.size(), outDest.hash().toHex().substr(0, 12).c_str());
RNS::Packet packet(outDest, payloadBytes);
RNS::PacketReceipt receipt = packet.send();
if (receipt) { sent = true; }
} else {
// Too large for opportunistic — need link + resource transfer
Serial.printf("[LXMF] Message too large for opportunistic (%d bytes > MDU), needs link (retry %d)\n",
(int)payloadBytes.size(), msg.retries);
// Too large for single frame — need link + resource transfer
Serial.printf("[LXMF] Message needs link delivery (%d bytes > %d single-frame), retry %d\n",
(int)payloadBytes.size(), (int)SINGLE_FRAME_MAX, msg.retries);
if (msg.retries % 3 == 0 && (!_outLink || _outLinkDestHash != msg.destHash
|| _outLink.status() != RNS::Type::Link::ACTIVE)) {
_outLinkPendingHash = msg.destHash;
+12 -3
View File
@@ -6,6 +6,8 @@
#include <unordered_map>
#include <string>
uint32_t ReticulumManager::_announceFilterCount = 0;
bool LittleFSFileSystem::init() { return true; }
bool LittleFSFileSystem::file_exists(const char* p) { return LittleFS.exists(p); }
@@ -116,7 +118,7 @@ bool ReticulumManager::begin(SX1262* radio, FlashStore* flash) {
// Adaptive rate: tighter during first 60s boot flood, then normal
unsigned int maxRate = (now < 60000) ? 3 : RATDECK_MAX_ANNOUNCES_PER_SEC;
if (++count > maxRate) return false;
if (++count > maxRate) { ReticulumManager::_announceFilterCount++; return false; }
// Skip re-validation of known paths (saves ~100ms Ed25519 per announce)
// Allow through if better path discovered, or once per 5 min for name/ratchet updates.
@@ -131,7 +133,10 @@ bool ReticulumManager::begin(SX1262* radio, FlashStore* flash) {
packet.destination_hash().size());
auto it = lastRevalidate.find(key);
if (it != lastRevalidate.end() && (now - it->second) < 300000) return false;
if (it != lastRevalidate.end() && (now - it->second) < 300000) {
ReticulumManager::_announceFilterCount++;
return false;
}
lastRevalidate[key] = now;
if (lastRevalidate.size() > 300) lastRevalidate.clear();
@@ -288,7 +293,11 @@ void ReticulumManager::persistData() {
}
break;
}
Serial.printf("[PERSIST] Cycle %d done (%lums)\n", _persistCycle, millis() - start);
unsigned long dur = millis() - start;
Serial.printf("[PERSIST] Cycle %d done (%lums)\n", _persistCycle, dur);
if (dur > 500) {
Serial.printf("[PERSIST] WARNING: Cycle %d blocked for %lums!\n", _persistCycle, dur);
}
_persistCycle = (_persistCycle + 1) % 3;
}
+2
View File
@@ -52,6 +52,7 @@ public:
void announce(const RNS::Bytes& appData = {});
unsigned long lastAnnounceTime() const { return _lastAnnounceTime; }
static uint32_t announceFilterCount() { return _announceFilterCount; }
RNS::Destination& destination() { return _destination; }
LoRaInterface* loraInterface() { return _loraImpl; }
@@ -71,4 +72,5 @@ private:
unsigned long _lastPersist = 0;
unsigned long _lastAnnounceTime = 0;
uint8_t _persistCycle = 0; // Rotating: 0=Transport, 1=Identity, 2=SD backup
static uint32_t _announceFilterCount;
};
+34 -15
View File
@@ -50,10 +50,17 @@ void LoRaInterface::send_outgoing(const RNS::Bytes& data) {
return;
}
if (_txPending || _splitTxPending) {
// Queue TX when radio is busy OR when we're waiting for split frame 2.
// Transmitting during split RX would put the radio in TX mode, causing
// frame 2 to be lost (LoRa is half-duplex).
if (_txPending || _splitTxPending || _splitRxPending) {
if ((int)_txQueue.size() < TX_QUEUE_MAX) {
_txQueue.push_back(data);
Serial.printf("[LORA_IF] TX queued (%d in queue)\n", (int)_txQueue.size());
if (_splitRxPending) {
Serial.printf("[LORA_IF] TX deferred (split RX pending, %d in queue)\n", (int)_txQueue.size());
} else {
Serial.printf("[LORA_IF] TX queued (%d in queue)\n", (int)_txQueue.size());
}
} else {
Serial.println("[LORA_IF] TX queue full, dropping oldest");
_txQueue.pop_front();
@@ -132,19 +139,19 @@ void LoRaInterface::loop() {
if (_splitTxPending) {
_splitTxPending = false;
Serial.printf("[LORA_IF] TX SPLIT frame 2: %d+1 bytes\n",
(int)_splitTxRemaining.size());
size_t frame2Size = _splitTxRemaining.size();
Serial.printf("[LORA_IF] TX SPLIT frame 2: %d+1 bytes\n", (int)frame2Size);
_radio->beginPacket();
_radio->write(_splitTxHeader);
_radio->write(_splitTxRemaining.data(), _splitTxRemaining.size());
_radio->write(_splitTxRemaining.data(), frame2Size);
_radio->endPacket(true);
_txPending = true;
_splitTxRemaining = RNS::Bytes();
// Track airtime for second frame
float airtimeMs = _radio->getAirtime(_splitTxRemaining.size() + RNODE_HEADER_L);
// Track airtime for second frame (must use saved size before clear)
float airtimeMs = _radio->getAirtime(frame2Size + RNODE_HEADER_L);
_airtimeAccumMs += airtimeMs;
return;
}
@@ -162,11 +169,17 @@ void LoRaInterface::loop() {
return;
}
// Split RX timeout: discard stale partial packets
// Split RX timeout: discard stale partial packets and drain deferred TX
if (_splitRxPending && (millis() - _splitRxTimestamp > SPLIT_RX_TIMEOUT_MS)) {
Serial.println("[LORA_IF] RX SPLIT timeout, discarding partial");
_splitRxPending = false;
_splitRxBuffer = RNS::Bytes();
// Drain any TX that was deferred during split RX hold
if (!_txQueue.empty() && !_txPending) {
RNS::Bytes next = _txQueue.front();
_txQueue.pop_front();
transmitNow(next);
}
}
// Periodic RX debug
@@ -231,13 +244,19 @@ void LoRaInterface::loop() {
InterfaceImpl::handle_incoming(_splitRxBuffer);
_splitRxBuffer = RNS::Bytes();
if (!_txPending) {
// Drain any TX that was deferred during split RX hold
if (!_txQueue.empty() && !_txPending) {
RNS::Bytes next = _txQueue.front();
_txQueue.pop_front();
transmitNow(next);
} else if (!_txPending) {
_radio->receive();
}
return;
} else {
// Sequence mismatch — discard old, start new
Serial.printf("[LORA_IF] RX SPLIT seq mismatch (had 0x%02X, got 0x%02X), restarting\n",
// Different split packet's frame 1 arrived — the previous split is lost.
// This happens when frame 2 was missed (radio was busy, collision, etc.)
Serial.printf("[LORA_IF] RX SPLIT new seq (had 0x%02X, got 0x%02X), previous frame 2 lost\n",
_splitRxSeq, seq);
_splitRxSeq = seq;
_splitRxBuffer = RNS::Bytes(raw + RNODE_HEADER_L, payloadSize);
@@ -247,11 +266,11 @@ void LoRaInterface::loop() {
}
}
// Non-split packet — if we were waiting for split frame 2, discard the partial
// Non-split packet while waiting for split frame 2:
// Process the non-split packet normally but KEEP the split buffer.
// Frame 2 may still arrive after this interleaving packet.
if (_splitRxPending) {
Serial.println("[LORA_IF] RX non-split while waiting for split frame 2, discarding partial");
_splitRxPending = false;
_splitRxBuffer = RNS::Bytes();
Serial.printf("[LORA_IF] RX non-split %d bytes while awaiting split frame 2 (kept)\n", payloadSize);
}
Serial.printf("[LORA_IF] RX %d bytes (hdr=0x%02X, payload=%d), RSSI=%d, SNR=%.1f\n",
+3 -3
View File
@@ -83,9 +83,9 @@ void TCPClientInterface::loop() {
return; // Will reconnect on next loop iteration
}
// Drain incoming frames per loop (up to 10, time-boxed)
// Drain incoming frames per loop (up to 15, time-boxed)
unsigned long tcpStart = millis();
for (int i = 0; i < 10 && _client.available() && (millis() - tcpStart < TCP_LOOP_BUDGET_MS); i++) {
for (int i = 0; i < 15 && _client.available() && (millis() - tcpStart < TCP_LOOP_BUDGET_MS); i++) {
unsigned long rxStart = millis();
int len = readFrame();
if (len > 0) {
@@ -151,7 +151,7 @@ void TCPClientInterface::send_outgoing(const RNS::Bytes& data) {
Serial.printf("[TCP-DIAG] *** PROOF packet being sent via TCP! ***\n");
}
if (packet_type != 0x01) { // Not ANNOUNCE
if (packet_type != 0x01 && packet_type != 0x03) { // Not ANNOUNCE, not PROOF
if (header_type == 0) {
// Header1 → wrap as Header2 (handles hops==1, hops==0, unknown path)
uint8_t new_flags = flags | 0x50; // Set Header2 (bit 6) + Transport (bit 4)
+1
View File
@@ -65,4 +65,5 @@ private:
public:
unsigned long lastRxTime() const { return _lastRxTime; }
unsigned long hubRxCount() const { return _hubRxCount; }
};