From 08d95cfb55b1fe531143909b6bc8af69ff7c9129 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:08:57 -0600 Subject: [PATCH 1/7] settings: clarify network menu --- src/ui/screens/LvSettingsScreen.cpp | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ui/screens/LvSettingsScreen.cpp b/src/ui/screens/LvSettingsScreen.cpp index e2c1b3a..7954998 100644 --- a/src/ui/screens/LvSettingsScreen.cpp +++ b/src/ui/screens/LvSettingsScreen.cpp @@ -176,9 +176,9 @@ bool LvSettingsScreen::settingNeedsReboot(const SettingItem& item) const { const auto& s = _cfg->settings(); if (labelEq(item.label, "WiFi Mode")) return s.wifiMode != _rebootSnap.wifiMode; if (labelEq(item.label, "LoRa Radio")) return loraSettingsChanged(); - if (labelEq(item.label, "Active WiFi")) return s.wifiSTASelected != _rebootSnap.wifiSTASelected; + if (labelEq(item.label, "WiFi Profile")) return s.wifiSTASelected != _rebootSnap.wifiSTASelected; if (isWiFiSSIDLabel(item.label) || isWiFiPasswordLabel(item.label)) return interfaceSettingsChanged(); - if (labelEq(item.label, "WiFi Scan") || labelEq(item.label, "Forget WiFi")) return interfaceSettingsChanged(); + if (labelEq(item.label, "Scan Networks") || labelEq(item.label, "Forget Network")) return interfaceSettingsChanged(); if (labelEq(item.label, "TCP Relay") || labelEq(item.label, "Relay Host") || labelEq(item.label, "Relay Port")) return tcpSettingsChanged(); if (labelEq(item.label, "LAN Discovery")) return s.autoIfaceEnabled != _rebootSnap.autoIfaceEnabled; @@ -191,7 +191,7 @@ bool LvSettingsScreen::categoryNeedsReboot(int catIdx) const { if (labelEq(_categories[catIdx].name, "LoRa")) { return loraSettingsChanged(); } - if (labelEq(_categories[catIdx].name, "Interfaces")) { + if (labelEq(_categories[catIdx].name, "Network")) { return interfaceSettingsChanged() || tcpSettingsChanged(); } if (labelEq(_categories[catIdx].name, "Storage & Maintenance")) { @@ -718,7 +718,7 @@ void LvSettingsScreen::buildItems() { return label; }}); - // Interfaces + // Network int netStart = idx; _items.push_back({"WiFi Mode", SettingType::ENUM_CHOICE, [&s]() { return (int)s.wifiMode; }, @@ -728,7 +728,19 @@ void LvSettingsScreen::buildItems() { }, nullptr, 0, 2, 1, {"Off", "Hotspot", "Client"}}); idx++; - _items.push_back({"Active WiFi", SettingType::INTEGER, + { + SettingItem scanItem; + scanItem.label = "Scan Networks"; + scanItem.type = SettingType::ACTION; + scanItem.formatter = [](int) { return String("[Enter]"); }; + scanItem.action = [this, &s]() { + _wifiTargetSlot = selectedWiFiSlot(s); + showWifiPicker(); + }; + _items.push_back(scanItem); + idx++; + } + _items.push_back({"WiFi Profile", SettingType::INTEGER, [&s]() { return (int)selectedWiFiSlot(s) + 1; }, [&s](int v) { s.wifiSTASelected = (uint8_t)constrain(v - 1, 0, (int)WIFI_STA_MAX_NETWORKS - 1); }, [&s](int v) { return wifiProfileValue(s, constrain(v - 1, 0, (int)WIFI_STA_MAX_NETWORKS - 1)); }, @@ -771,21 +783,9 @@ void LvSettingsScreen::buildItems() { _items.push_back(passItem); idx++; } - { - SettingItem scanItem; - scanItem.label = "WiFi Scan"; - scanItem.type = SettingType::ACTION; - scanItem.formatter = [](int) { return String("[Enter]"); }; - scanItem.action = [this, &s]() { - _wifiTargetSlot = selectedWiFiSlot(s); - showWifiPicker(); - }; - _items.push_back(scanItem); - idx++; - } { SettingItem forgetItem; - forgetItem.label = "Forget WiFi"; + forgetItem.label = "Forget Network"; forgetItem.type = SettingType::ACTION; forgetItem.formatter = [&s](int) { String ssid = selectedWiFiSSID(s); @@ -865,7 +865,7 @@ void LvSettingsScreen::buildItems() { [&s](int v) { s.autoIfaceEnabled = (v != 0); }, [](int v) { return String(onOff(v != 0)); }}); idx++; - _categories.push_back({"Interfaces", netStart, idx - netStart, + _categories.push_back({"Network", netStart, idx - netStart, [this, &s]() { if (interfaceSettingsChanged() || tcpSettingsChanged()) return String("Saved - reboot to apply"); String summary = wifiModeLabel(s.wifiMode); From e497de63b3fec9f09dd495eb3c838754abfe3f67 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:16:42 -0600 Subject: [PATCH 2/7] input: ignore wake key dispatch --- src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 0303cea..c92ab19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1869,7 +1869,9 @@ void loop() { handleSerialCommands(); // 1. Input polling + bool screenWasOn = powerMgr.isScreenOn(); inputManager.update(); + bool wakeOnlyInput = !screenWasOn && inputManager.hadStrongActivity(); if (inputManager.hadStrongActivity()) { powerMgr.activity(); // Keyboard/touch: wake from any state } else if (inputManager.hadActivity()) { @@ -1884,7 +1886,7 @@ void loop() { } // 3. Key event dispatch - if (inputManager.hasKeyEvent()) { + if (inputManager.hasKeyEvent() && !wakeOnlyInput) { const KeyEvent& evt = inputManager.getKeyEvent(); // Help overlay intercepts all keys when visible From a2aad03a39c03c246fc6abc35ba94928f632dc39 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:13:12 -0600 Subject: [PATCH 3/7] lxmf: avoid split LoRa opportunistic sends Gate rsDeck opportunistic LXMF sends by the final packed Reticulum raw size when the next hop is LoRa, instead of using the pre-encryption LXMF payload size as the 254-byte limit. Keep TCP paths on the normal Reticulum MDU, route LoRa packets that would exceed one RNode RF frame into link delivery, and log payload/raw/lora_raw sizing for field diagnosis. Tested with rsDeck/Ratdeck T-Deck and Heltec V3 RNode attached over USB to a MacBook Pro running Ratspeak, using the medium-fast LoRa preset. Short 1-5 character opportunistic sends worked bidirectionally. Direct link delivery worked from T-Deck to Ratspeak/Heltec; Ratspeak-to-T-Deck link delivery later queued during link establishment while opportunistic still worked. --- src/config/Config.h | 2 +- src/main.cpp | 2 +- src/reticulum/LXMFManager.cpp | 80 +++++++++++++++++++----------- src/reticulum/ReticulumManager.cpp | 6 +++ src/reticulum/ReticulumManager.h | 1 + 5 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/config/Config.h b/src/config/Config.h index 642fc6f..07c2cd3 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -68,7 +68,7 @@ #define RSDECK_MAX_MESSAGES_PER_CONV 100 #define FLASH_MSG_CACHE_LIMIT 20 #define RSDECK_MAX_OUTQUEUE 20 -#define RSDECK_LXMF_SINGLE_FRAME_MAX 254 // T-Deck-safe payload cap until resource transfers are fixed +#define RSDECK_RNODE_SINGLE_FRAME_RAW_MAX 254 // Raw Reticulum bytes per RNode LoRa RF frame #define PATH_PERSIST_INTERVAL_MS 60000 // --- Power Management --- diff --git a/src/main.cpp b/src/main.cpp index c92ab19..55227b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -711,7 +711,7 @@ static constexpr uint8_t LITE_TRANSPORT_ID[16] = { 'r', 's', 'l', 'i', 't', 'e', '-', 'h', 'e', 'l', 't', 'e', 'c', '-', 'v', '3' }; -static constexpr size_t RNODE_DIAG_SINGLE_MTU = 254; +static constexpr size_t RNODE_DIAG_SINGLE_MTU = RSDECK_RNODE_SINGLE_FRAME_RAW_MAX; static RNS::Bytes diagnosticLiteLinkId; static bool sendDiagnosticRawReticulum(const RNS::Bytes& raw, const char* label) { diff --git a/src/reticulum/LXMFManager.cpp b/src/reticulum/LXMFManager.cpp index 3c5f1ee..2c2d9ef 100644 --- a/src/reticulum/LXMFManager.cpp +++ b/src/reticulum/LXMFManager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include LXMFManager* LXMFManager::_instance = nullptr; std::map LXMFManager::_pendingProofs; @@ -131,13 +132,6 @@ bool LXMFManager::sendMessage(const RNS::Bytes& destHash, const std::string& con Serial.println("[LXMF] Message pack failed"); return false; } - if (payload.size() > RSDECK_LXMF_SINGLE_FRAME_MAX) { - msg.status = LXMFStatus::FAILED; - if (_store) _store->saveMessage(msg); - Serial.printf("[LXMF] Message too large for T-Deck safe path (%d > %d); resource transfer disabled\n", - (int)payload.size(), RSDECK_LXMF_SINGLE_FRAME_MAX); - return true; - } if (preference == DeliveryPreference::Link) { _linkRequiredIds.insert(msg.messageId.toHex()); Serial.printf("[LXMF] Message %s queued for link delivery\n", @@ -267,12 +261,6 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) { if (payload.empty()) { Serial.println("[LXMF] packFull returned empty!"); msg.status = LXMFStatus::FAILED; return true; } const std::string msgIdHex = msg.messageId.toHex(); const bool requireLink = _linkRequiredIds.count(msgIdHex) > 0; - if (payload.size() > RSDECK_LXMF_SINGLE_FRAME_MAX) { - Serial.printf("[LXMF] Refusing resource-sized message (%d bytes); T-Deck safe cap is %d\n", - (int)payload.size(), RSDECK_LXMF_SINGLE_FRAME_MAX); - msg.status = LXMFStatus::FAILED; - return true; - } msg.status = LXMFStatus::SENDING; bool sent = false; @@ -334,24 +322,60 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) { return false; } - // 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. - if (payloadBytes.size() <= RSDECK_LXMF_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; registerProofTracking(receipt, msg); } + // Use opportunistic only when the LXMF payload fits in a normal + // Reticulum packet and, on LoRa, the final raw packet fits in one + // RNode RF frame. The 254-byte RNode limit applies after encryption. + const bool opportunisticPayloadFits = payloadBytes.size() <= RNS::Type::Reticulum::MDU; + bool packetPacked = false; + size_t rawLen = 0; + size_t estimatedLoRaRawLen = 0; + const bool loraPath = _rns && _rns->isLoRaNextHop(msg.destHash); + const uint8_t hops = RNS::Transport::hops_to(msg.destHash); + + if (!opportunisticPayloadFits) { + Serial.printf("[LXMF] Opportunistic payload exceeds MDU: payload=%d mdu=%u\n", + (int)payloadBytes.size(), (unsigned)RNS::Type::Reticulum::MDU); } else { - // 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(), RSDECK_LXMF_SINGLE_FRAME_MAX, msg.retries); + RNS::Packet packet(outDest, payloadBytes); + const bool transportWrapsHeader2 = loraPath && hops > 1 + && packet.header_type() == RNS::Type::Packet::HEADER_1; + + try { + packet.pack(); + packetPacked = true; + rawLen = packet.raw().size(); + estimatedLoRaRawLen = rawLen + + (transportWrapsHeader2 ? RNS::Type::Reticulum::DESTINATION_LENGTH : 0); + } catch (const std::exception& e) { + Serial.printf("[LXMF] Opportunistic pack failed (%s); trying link delivery\n", e.what()); + } + + const bool singleFrameSafe = packetPacked && (!loraPath + || estimatedLoRaRawLen <= RSDECK_RNODE_SINGLE_FRAME_RAW_MAX); + + if (singleFrameSafe) { + Serial.printf("[LXMF] sending opportunistic: payload=%d raw=%u lora_raw=%u lora=%s hops=%u to %s\n", + (int)payloadBytes.size(), (unsigned)rawLen, + (unsigned)estimatedLoRaRawLen, + loraPath ? "yes" : "no", (unsigned)hops, + outDest.hash().toHex().substr(0, 12).c_str()); + RNS::PacketReceipt receipt = packet.send(); + if (receipt) { sent = true; registerProofTracking(receipt, msg); } + } + } + + if (!sent) { + // Too large for single-frame LoRa, or too large for an opportunistic + // Reticulum packet. Use link/resource delivery so packet loss is + // recoverable above the physical RNode split-frame layer. + Serial.printf("[LXMF] Message needs link delivery: payload=%d raw=%u lora_raw=%u limit=%u lora=%s hops=%u retry=%d\n", + (int)payloadBytes.size(), (unsigned)rawLen, + (unsigned)estimatedLoRaRawLen, + (unsigned)RSDECK_RNODE_SINGLE_FRAME_RAW_MAX, + loraPath ? "yes" : "no", (unsigned)hops, msg.retries); if (msg.retries % 3 == 0 && (!_outLink || _outLinkDestHash != msg.destHash || _outLink.status() != RNS::Type::Link::ACTIVE)) { - ensureOutboundLink(outDest, msg.destHash, "resource transfer"); + ensureOutboundLink(outDest, msg.destHash, loraPath ? "single-frame LoRa overflow" : "resource transfer"); } msg.retries++; if (msg.retries >= 30) { diff --git a/src/reticulum/ReticulumManager.cpp b/src/reticulum/ReticulumManager.cpp index 5c67b1c..7455191 100644 --- a/src/reticulum/ReticulumManager.cpp +++ b/src/reticulum/ReticulumManager.cpp @@ -274,6 +274,12 @@ bool ReticulumManager::begin(SX1262* radio, FlashStore* flash, bool loraEnabled) return true; } +bool ReticulumManager::isLoRaNextHop(const RNS::Bytes& destHash) const { + if (!_loraImpl) return false; + RNS::Interface nextHop = RNS::Transport::next_hop_interface(destHash); + return nextHop && nextHop.get() == _loraImpl; +} + bool ReticulumManager::loadOrCreateIdentity() { // Tier 1: Flash (LittleFS) if (_flash->exists(PATH_IDENTITY)) { diff --git a/src/reticulum/ReticulumManager.h b/src/reticulum/ReticulumManager.h index 780a117..9596c56 100644 --- a/src/reticulum/ReticulumManager.h +++ b/src/reticulum/ReticulumManager.h @@ -56,6 +56,7 @@ public: RNS::Destination& destination() { return _destination; } LoRaInterface* loraInterface() { return _loraImpl; } + bool isLoRaNextHop(const RNS::Bytes& destHash) const; private: bool loadOrCreateIdentity(); From b3d03e204a3ee228bc9de612d926724db2e9c929 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:14:24 -0600 Subject: [PATCH 4/7] ui: default send picker to opportunistic Normal SEND and Enter already use opportunistic LXMF delivery. Make the long-press send-mode picker open on Send normally instead of Send as link so LoRa users do not accidentally choose link delivery as the apparent default. This matches the field test result where short opportunistic LoRa messages worked bidirectionally, while link establishment from Ratspeak to rsDeck could queue after restarts/config changes. --- src/ui/screens/LvMessageView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/screens/LvMessageView.cpp b/src/ui/screens/LvMessageView.cpp index 3914178..622d393 100644 --- a/src/ui/screens/LvMessageView.cpp +++ b/src/ui/screens/LvMessageView.cpp @@ -714,7 +714,7 @@ bool LvMessageView::handleLongPress() { void LvMessageView::showSendModeMenu() { if (_inputText.empty()) return; hideSendModeMenu(); - _sendMenuIdx = 1; + _sendMenuIdx = 0; _sendOverlay = lv_obj_create(lv_layer_top()); lv_obj_set_size(_sendOverlay, 244, 118); From d53ae94f9346ba11ae25d90f071d6b5612a9698a Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:12:48 -0600 Subject: [PATCH 5/7] reticulum: default LoRa interface to roaming --- platformio.ini | 2 +- src/reticulum/ReticulumManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4241725..e029fb2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -52,7 +52,7 @@ build_unflags = -std=gnu++11 lib_deps = - https://github.com/ratspeak/microReticulum.git#3ddf3c362bdf05d1188ba2245c2ea06cb6514d40 + https://github.com/ratspeak/microReticulum.git#83b88146f4a49bb46ea6e0c22c560257c33815d7 bblanchon/ArduinoJson@^7.4.2 lovyan03/LovyanGFX@~1.1.16 lvgl/lvgl@~8.3.4 diff --git a/src/reticulum/ReticulumManager.cpp b/src/reticulum/ReticulumManager.cpp index 7455191..12f59b2 100644 --- a/src/reticulum/ReticulumManager.cpp +++ b/src/reticulum/ReticulumManager.cpp @@ -210,7 +210,7 @@ bool ReticulumManager::begin(SX1262* radio, FlashStore* flash, bool loraEnabled) if (loraEnabled) { _loraImpl = new LoRaInterface(radio, "LoRa"); _loraIface = _loraImpl; - _loraIface.mode(RNS::Type::Interface::MODE_GATEWAY); + _loraIface.mode(RNS::Type::Interface::MODE_ROAMING); RNS::Transport::register_interface(_loraIface); if (!_loraImpl->start()) { Serial.println("[RNS] WARNING: LoRa interface failed to start"); From c0d7d88427b9fcd136476904edbd2ad73a2250e3 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:58:24 -0600 Subject: [PATCH 6/7] reticulum: persist peer identity on announce --- src/reticulum/AnnounceManager.cpp | 26 ++++++++++++++++++++++++++ src/reticulum/AnnounceManager.h | 3 +++ src/reticulum/LXMFManager.cpp | 10 +++++----- src/reticulum/LXMFManager.h | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/reticulum/AnnounceManager.cpp b/src/reticulum/AnnounceManager.cpp index ae88001..e6bb272 100644 --- a/src/reticulum/AnnounceManager.cpp +++ b/src/reticulum/AnnounceManager.cpp @@ -152,6 +152,7 @@ void AnnounceManager::received_announce( auto& node = _nodes[it->second]; if (node.lastSeen != 0 && now >= node.lastSeen && now - node.lastSeen < ANNOUNCE_MIN_INTERVAL_MS) return; + bool identityChanged = !idHex.empty() && node.identityHex != idHex; // Saved contacts own their local alias. Incoming announce app_data is // still cached below, but must not reset the user-chosen contact name. if (!name.empty() && !node.saved) node.name = name; @@ -167,8 +168,15 @@ void AnnounceManager::received_announce( if (nc == _nameCache.end() || nc->second != name) { _nameCache[destHex] = name; _nameCacheDirty = true; + saveNameCache(); + _nameCacheDirty = false; } } + if (!idHex.empty()) { + persistKnownDestinationsAfterAnnounce( + identityChanged ? "identity update" : "repeat announce", + identityChanged); + } return; } @@ -192,6 +200,8 @@ void AnnounceManager::received_announce( } } } + saveNameCache(); + _nameCacheDirty = false; } } @@ -242,6 +252,9 @@ void AnnounceManager::received_announce( if (_loraIf) { node.rssi = _loraIf->lastRxRssi(); node.snr = _loraIf->lastRxSnr(); } _hashIndex[key] = (int)_nodes.size(); _nodes.push_back(node); + if (!idHex.empty()) { + persistKnownDestinationsAfterAnnounce("new peer", true); + } } void AnnounceManager::loop() { @@ -258,6 +271,19 @@ void AnnounceManager::loop() { } } +void AnnounceManager::persistKnownDestinationsAfterAnnounce(const char* reason, bool force) { + unsigned long now = millis(); + if (!force && _lastKnownDestinationsPersist != 0 && + now - _lastKnownDestinationsPersist < KNOWN_DESTINATION_PERSIST_MIN_INTERVAL_MS) { + return; + } + + _lastKnownDestinationsPersist = now; + RNS::Identity::persist_data(); + Serial.printf("[ANNOUNCE] Known destinations persisted after %s\n", + reason ? reason : "announce"); +} + int AnnounceManager::nodesOnlineSince(unsigned long maxAgeMs) const { unsigned long now = millis(); int count = 0; diff --git a/src/reticulum/AnnounceManager.h b/src/reticulum/AnnounceManager.h index e79b37e..637681b 100644 --- a/src/reticulum/AnnounceManager.h +++ b/src/reticulum/AnnounceManager.h @@ -60,6 +60,7 @@ public: private: void saveContact(const DiscoveredNode& node); void removeContact(const std::string& hexHash); + void persistKnownDestinationsAfterAnnounce(const char* reason, bool force); std::vector _nodes; SDStore* _sd = nullptr; @@ -77,7 +78,9 @@ private: static constexpr int MAX_NODES = 100; static constexpr int MAX_NAME_CACHE = 300; static constexpr unsigned long CONTACT_SAVE_INTERVAL_MS = 30000; + static constexpr unsigned long KNOWN_DESTINATION_PERSIST_MIN_INTERVAL_MS = 5000; static constexpr unsigned long ANNOUNCE_MIN_INTERVAL_MS = 200; // Rate-limit announce processing + unsigned long _lastKnownDestinationsPersist = 0; std::unordered_map _hashIndex; // raw hash bytes → _nodes index diff --git a/src/reticulum/LXMFManager.cpp b/src/reticulum/LXMFManager.cpp index 2c2d9ef..2965c9d 100644 --- a/src/reticulum/LXMFManager.cpp +++ b/src/reticulum/LXMFManager.cpp @@ -72,7 +72,7 @@ void LXMFManager::loop() { // Keep unresolved peers from churning the UI loop or LoRa airtime. // The first attempt is immediate; later path/identity retries happen - // every 10s and are capped in sendDirect(). + // every 10s and are capped in attemptOutboundDelivery(). if (msg.retries > 0 && (millis() - msg.lastRetryMs) < LXMF_DISCOVERY_RETRY_INTERVAL_MS) { ++it; continue; @@ -80,7 +80,7 @@ void LXMFManager::loop() { msg.lastRetryMs = millis(); - if (sendDirect(msg)) { + if (attemptOutboundDelivery(msg)) { processed++; Serial.printf("[LXMF] Queue drain: status=%s dest=%s\n", msg.statusStr(), msg.destHash.toHex().substr(0, 8).c_str()); @@ -99,7 +99,7 @@ void LXMFManager::loop() { clearDeliveryPreference(msg); it = _outQueue.erase(it); } else { - // sendDirect returned false — message stays in queue, try next + // attemptOutboundDelivery returned false — message stays in queue, try next ++it; } } @@ -187,8 +187,8 @@ bool LXMFManager::ensureOutboundLink(const RNS::Destination& dest, const RNS::By return false; } -bool LXMFManager::sendDirect(LXMFMessage& msg) { - Serial.printf("[LXMF] sendDirect: dest=%s link=%s pending=%s\n", +bool LXMFManager::attemptOutboundDelivery(LXMFMessage& msg) { + Serial.printf("[LXMF] attemptOutboundDelivery: dest=%s link=%s pending=%s\n", msg.destHash.toHex().substr(0, 12).c_str(), _outLink ? (_outLink.status() == RNS::Type::Link::ACTIVE ? "ACTIVE" : "INACTIVE") : "NONE", _outLinkPending ? "yes" : "no"); diff --git a/src/reticulum/LXMFManager.h b/src/reticulum/LXMFManager.h index 52be37a..d4c6978 100644 --- a/src/reticulum/LXMFManager.h +++ b/src/reticulum/LXMFManager.h @@ -41,7 +41,7 @@ public: const ConversationSummary* getConversationSummary(const std::string& peerHex) const; private: - bool sendDirect(LXMFMessage& msg); + bool attemptOutboundDelivery(LXMFMessage& msg); bool ensureOutboundLink(const RNS::Destination& dest, const RNS::Bytes& destHash, const char* reason); void clearDeliveryPreference(const LXMFMessage& msg); void processIncoming(const uint8_t* data, size_t len, const RNS::Bytes& destHash); From 0ee1b718603a284ecd7814f74e1cb7cc5e258311 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:10:51 -0600 Subject: [PATCH 7/7] release: bump rsDeck to v2.0.1 --- README.md | 2 +- src/config/Config.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f12630..d38d11b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Status](https://img.shields.io/badge/status-beta-yellow.svg)](#install) [![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-2.0.0-success.svg)](https://github.com/ratspeak/rsDeck/releases) +[![Version](https://img.shields.io/badge/version-2.0.1-success.svg)](https://github.com/ratspeak/rsDeck/releases) [Ratspeak](https://github.com/ratspeak/Ratspeak) | [Docs](https://ratspeak.org/docs.html) | diff --git a/src/config/Config.h b/src/config/Config.h index 07c2cd3..c40ca23 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -6,8 +6,8 @@ #define RSDECK_VERSION_MAJOR 2 #define RSDECK_VERSION_MINOR 0 -#define RSDECK_VERSION_PATCH 0 -#define RSDECK_VERSION_STRING "2.0.0" +#define RSDECK_VERSION_PATCH 1 +#define RSDECK_VERSION_STRING "2.0.1" // --- Feature Flags --- #define HAS_DISPLAY true