diff --git a/src/hal/Display.cpp b/src/hal/Display.cpp index 03dbfda..a1525ba 100644 --- a/src/hal/Display.cpp +++ b/src/hal/Display.cpp @@ -11,7 +11,8 @@ static void lvgl_flush_cb(lv_disp_drv_t* drv, const lv_area_t* area, lv_color_t* uint32_t h = area->y2 - area->y1 + 1; s_gfx->startWrite(); s_gfx->setAddrWindow(area->x1, area->y1, w, h); - s_gfx->pushPixelsDMA((lgfx::swap565_t*)&color_p->full, w * h); + // Blocking pushPixels prevents SPI bus contention with SX1262 radio on shared FSPI bus + s_gfx->pushPixels((lgfx::swap565_t*)&color_p->full, w * h); s_gfx->endWrite(); lv_disp_flush_ready(drv); } diff --git a/src/reticulum/LXMFManager.cpp b/src/reticulum/LXMFManager.cpp index 6aaccfa..d224352 100644 --- a/src/reticulum/LXMFManager.cpp +++ b/src/reticulum/LXMFManager.cpp @@ -135,25 +135,65 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) { Serial.printf("[DIAG] LXMF: outDest=%s msgDest=%s match=%s\n", outDest.hash().toHex().c_str(), msg.destHash.toHex().c_str(), (outDest.hash() == msg.destHash) ? "YES" : "NO"); + + // packFull returns opportunistic format: [src:16][sig:64][msgpack] std::vector payload = msg.packFull(_rns->identity()); if (payload.empty()) { Serial.println("[LXMF] packFull returned empty!"); msg.status = LXMFStatus::FAILED; return true; } - RNS::Bytes payloadBytes(payload.data(), payload.size()); - if (payloadBytes.size() > RNS::Type::Reticulum::MDU) { - Serial.printf("[LXMF] payload too large: %d > MDU\n", (int)payloadBytes.size()); - msg.status = LXMFStatus::FAILED; return true; - } + msg.status = LXMFStatus::SENDING; - Serial.printf("[LXMF] sending packet: %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) { + bool sent = false; + + // Try link-based delivery if we have an active link to this peer + if (_outLink && _outLinkDestHash == msg.destHash + && _outLink.status() == RNS::Type::Link::ACTIVE) { + // Link delivery: prepend dest_hash (Python DIRECT format) + std::vector linkPayload; + linkPayload.reserve(16 + payload.size()); + linkPayload.insert(linkPayload.end(), msg.destHash.data(), msg.destHash.data() + 16); + linkPayload.insert(linkPayload.end(), payload.begin(), payload.end()); + RNS::Bytes linkBytes(linkPayload.data(), linkPayload.size()); + if (linkBytes.size() <= RNS::Type::Reticulum::MDU) { + Serial.printf("[LXMF] sending via link: %d bytes to %s\n", + (int)linkBytes.size(), msg.destHash.toHex().substr(0, 8).c_str()); + RNS::Packet packet(_outLink, linkBytes); + RNS::PacketReceipt receipt = packet.send(); + if (receipt) { sent = true; } + } + } + + // Fallback: opportunistic delivery (always available, no delay) + if (!sent) { + RNS::Bytes payloadBytes(payload.data(), payload.size()); + if (payloadBytes.size() > RNS::Type::Reticulum::MDU) { + Serial.printf("[LXMF] payload too large: %d > MDU\n", (int)payloadBytes.size()); + msg.status = LXMFStatus::FAILED; return true; + } + 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; } + } + + if (sent) { msg.status = LXMFStatus::SENT; // messageId already computed by packFull() matching Python's LXMessage.pack() - Serial.printf("[LXMF] SENT OK: %d bytes, msgId=%s\n", (int)payloadBytes.size(), msg.messageId.toHex().substr(0, 8).c_str()); + Serial.printf("[LXMF] SENT OK: msgId=%s\n", msg.messageId.toHex().substr(0, 8).c_str()); } else { Serial.println("[LXMF] send FAILED: no receipt"); msg.status = LXMFStatus::FAILED; } + + // Background: establish link for future messages to this peer + if (!_outLinkPending && (!_outLink || _outLinkDestHash != msg.destHash + || _outLink.status() == RNS::Type::Link::CLOSED)) { + _outLinkDestHash = msg.destHash; + _outLinkPending = true; + Serial.printf("[LXMF] Establishing link to %s for future messages\n", + msg.destHash.toHex().substr(0, 8).c_str()); + RNS::Link newLink(outDest, onOutLinkEstablished, onOutLinkClosed); + } + return true; } @@ -169,6 +209,21 @@ void LXMFManager::onPacketReceived(const RNS::Bytes& data, const RNS::Packet& pa _instance->processIncoming(fullData.data(), fullData.size(), destHash); } +void LXMFManager::onOutLinkEstablished(RNS::Link& link) { + if (!_instance) return; + _instance->_outLink = link; + _instance->_outLinkPending = false; + Serial.printf("[LXMF] Outbound link established to %s\n", + _instance->_outLinkDestHash.toHex().substr(0, 8).c_str()); +} + +void LXMFManager::onOutLinkClosed(RNS::Link& link) { + if (!_instance) return; + _instance->_outLink = {RNS::Type::NONE}; + _instance->_outLinkPending = false; + Serial.println("[LXMF] Outbound link closed"); +} + void LXMFManager::onLinkEstablished(RNS::Link& link) { if (!_instance) return; link.set_packet_callback([](const RNS::Bytes& data, const RNS::Packet& packet) { diff --git a/src/reticulum/LXMFManager.h b/src/reticulum/LXMFManager.h index da139f4..39e8d8e 100644 --- a/src/reticulum/LXMFManager.h +++ b/src/reticulum/LXMFManager.h @@ -34,6 +34,8 @@ private: void processIncoming(const uint8_t* data, size_t len, const RNS::Bytes& destHash); static void onPacketReceived(const RNS::Bytes& data, const RNS::Packet& packet); static void onLinkEstablished(RNS::Link& link); + static void onOutLinkEstablished(RNS::Link& link); + static void onOutLinkClosed(RNS::Link& link); ReticulumManager* _rns = nullptr; MessageStore* _store = nullptr; @@ -41,6 +43,11 @@ private: StatusCallback _statusCb; std::deque _outQueue; + // Outbound link state (opportunistic-first, link upgrades in background) + RNS::Link _outLink; + RNS::Bytes _outLinkDestHash; + bool _outLinkPending = false; + // Deduplication: recently seen message IDs std::set _seenMessageIds; static constexpr int MAX_SEEN_IDS = 100;