diff --git a/README.md b/README.md index 20630dc..259ac1b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Ratdeck +# RatDeck -**v1.4.2** | [Ratspeak.org](https://ratspeak.org) +**v1.5.1** | [Ratspeak.org](https://ratspeak.org) Standalone [Reticulum](https://reticulum.network/) mesh node + [LXMF](https://github.com/markqvist/LXMF) encrypted messenger for the [LilyGo T-Deck Plus](https://www.lilygo.cc/products/t-deck-plus). @@ -42,7 +42,7 @@ Built on [microReticulum](https://github.com/attermann/microReticulum) with a re ## Quick Start ```bash -git clone https://github.com/defidude/Ratdeck.git +git clone https://github.com/ratspeak/ratdeck.git cd Ratdeck python3 -m platformio run # build python3 -m platformio run --target upload # flash diff --git a/src/config/BoardConfig.h b/src/config/BoardConfig.h index 961b090..f99f2f1 100644 --- a/src/config/BoardConfig.h +++ b/src/config/BoardConfig.h @@ -1,7 +1,7 @@ #pragma once // ============================================================================= -// Ratdeck — LilyGo T-Deck Plus Pin Definitions +// RatDeck — LilyGo T-Deck Plus Pin Definitions // ============================================================================= // --- Power Control --- diff --git a/src/config/Config.h b/src/config/Config.h index 4d3b4bf..33e2475 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -1,13 +1,13 @@ #pragma once // ============================================================================= -// Ratdeck — Compile-Time Configuration +// RatDeck — Compile-Time Configuration // ============================================================================= #define RATDECK_VERSION_MAJOR 1 -#define RATDECK_VERSION_MINOR 4 -#define RATDECK_VERSION_PATCH 2 -#define RATDECK_VERSION_STRING "1.4.2" +#define RATDECK_VERSION_MINOR 5 +#define RATDECK_VERSION_PATCH 1 +#define RATDECK_VERSION_STRING "1.5.1" // --- Feature Flags --- #define HAS_DISPLAY true diff --git a/src/main.cpp b/src/main.cpp index 2b00791..9e25849 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ // ============================================================================= -// Ratdeck v1.0 — Main Entry Point +// RatDeck v1.5 — Main Entry Point // LilyGo T-Deck Plus: LovyanGFX Direct UI + microReticulum + LXMF Messaging // ============================================================================= @@ -224,7 +224,7 @@ void onHotkeyAnnounce() { } void onHotkeyDiag() { Serial.println("=== DIAGNOSTIC DUMP ==="); - Serial.printf("Device: Ratdeck T-Deck Plus\n"); + Serial.printf("Device: RatDeck T-Deck Plus\n"); Serial.printf("Identity: %s\n", rns.identityHash().c_str()); Serial.printf("Transport: %s\n", rns.isTransportActive() ? "ACTIVE" : "OFFLINE"); Serial.printf("Paths: %d Links: %d\n", (int)rns.pathCount(), (int)rns.linkCount()); @@ -303,7 +303,7 @@ void setup() { delay(100); Serial.println(); Serial.println("================================="); - Serial.printf(" Ratdeck v%s\n", RATDECK_VERSION_STRING); + Serial.printf(" RatDeck v%s\n", RATDECK_VERSION_STRING); Serial.println(" LilyGo T-Deck Plus"); Serial.println("================================="); @@ -530,6 +530,16 @@ void setup() { // (LVGL boot renders via lv_timer_handler in setProgress) userConfig.load(sdStore, flash); + // Seed default Ratspeak TCP hub if no connections configured + if (userConfig.settings().tcpConnections.empty()) { + TCPEndpoint ep; + ep.host = "rns.ratspeak.org"; + ep.port = 4242; + ep.autoConnect = true; + userConfig.settings().tcpConnections.push_back(ep); + Serial.println("[CONFIG] Default TCP hub: rns.ratspeak.org:4242"); + } + // Sync display name between active identity slot and config. // The identity slot is the source of truth for the name. { @@ -660,8 +670,8 @@ void setup() { delay(400); bootComplete = true; - ui.statusBar().setTransportMode("Ratdeck"); - ui.lvStatusBar().setTransportMode("Ratdeck"); + ui.statusBar().setTransportMode("RatDeck"); + ui.lvStatusBar().setTransportMode("RatDeck"); // Keep UI alive during blocking radio TX (endPacket wait loop) // Re-entrancy guard prevents nested lv_timer_handler() calls @@ -730,7 +740,7 @@ void setup() { lvSettingsScreen.setRNS(&rns); lvSettingsScreen.setIdentityManager(&identityMgr); lvSettingsScreen.setUIManager(&ui); - lvSettingsScreen.setIdentityHash(rns.identityHash()); + lvSettingsScreen.setIdentityHash(rns.destinationHashStr()); lvSettingsScreen.setDestinationHash(rns.destinationHashHex()); lvSettingsScreen.setSaveCallback([]() -> bool { bool ok = userConfig.save(sdStore, flash); @@ -813,7 +823,7 @@ void setup() { } } - Serial.println("[BOOT] Ratdeck ready"); + Serial.println("[BOOT] RatDeck ready"); Serial.printf("[BOOT] Summary: radio=%s flash=%s sd=%s\n", radioOnline ? "ONLINE" : "OFFLINE", flash.isReady() ? "OK" : "FAIL", @@ -910,6 +920,10 @@ void loop() { ui.lvStatusBar().setWiFiActive(true); Serial.printf("[WIFI] STA connected: %s\n", WiFi.localIP().toString().c_str()); + // NTP time sync + configTzTime("UTC5", "pool.ntp.org", "time.nist.gov"); + Serial.println("[NTP] Time sync started (UTC-5)"); + // Create TCP clients (safe to call multiple times) if (tcpClients.empty()) { reloadTCPClients(); diff --git a/src/reticulum/LXMFManager.cpp b/src/reticulum/LXMFManager.cpp index 62f61be..d0df90a 100644 --- a/src/reticulum/LXMFManager.cpp +++ b/src/reticulum/LXMFManager.cpp @@ -18,12 +18,22 @@ bool LXMFManager::begin(ReticulumManager* rns, MessageStore* store) { void LXMFManager::loop() { if (_outQueue.empty()) return; LXMFMessage& msg = _outQueue.front(); + + // Throttle retries — wait 2 seconds between attempts + unsigned long now = millis(); + if (msg.retries > 0 && (now - _lastRetryMs) < 2000) return; + _lastRetryMs = now; + if (sendDirect(msg)) { Serial.printf("[LXMF] Queue drain: status=%s dest=%s\n", msg.statusStr(), msg.destHash.toHex().substr(0, 8).c_str()); - // Re-save with updated status (overwrite the QUEUED version) - msg.messageId = RNS::Bytes(); // Clear so saveMessage reuses timestamp key - if (_store) { _store->saveMessage(msg); } + + // Re-save with updated status (SENT/FAILED) using same timestamp key + RNS::Bytes savedId = msg.messageId; + msg.messageId = RNS::Bytes(); + if (_store) _store->saveMessage(msg); + msg.messageId = savedId; + // Fire status callback so UI can refresh if (_statusCb) { std::string peerHex = msg.destHash.toHex(); @@ -60,13 +70,13 @@ bool LXMFManager::sendDirect(LXMFMessage& msg) { RNS::Identity recipientId = RNS::Identity::recall(msg.destHash); if (!recipientId) { msg.retries++; - if (msg.retries >= 5) { + if (msg.retries >= 30) { Serial.printf("[LXMF] recall FAILED for %s after %d retries — marking FAILED\n", msg.destHash.toHex().substr(0, 8).c_str(), msg.retries); msg.status = LXMFStatus::FAILED; return true; } - Serial.printf("[LXMF] recall FAILED for %s (retry %d/5) — identity not known yet\n", + Serial.printf("[LXMF] recall pending for %s (retry %d/30) — identity not known yet\n", msg.destHash.toHex().substr(0, 8).c_str(), msg.retries); return false; // keep in queue, retry next loop } diff --git a/src/reticulum/LXMFManager.h b/src/reticulum/LXMFManager.h index 32bf375..d4628e4 100644 --- a/src/reticulum/LXMFManager.h +++ b/src/reticulum/LXMFManager.h @@ -48,5 +48,7 @@ private: std::set _seenMessageIds; static constexpr int MAX_SEEN_IDS = 100; + unsigned long _lastRetryMs = 0; + static LXMFManager* _instance; }; diff --git a/src/reticulum/ReticulumManager.cpp b/src/reticulum/ReticulumManager.cpp index ecc5e28..98b0f97 100644 --- a/src/reticulum/ReticulumManager.cpp +++ b/src/reticulum/ReticulumManager.cpp @@ -253,6 +253,15 @@ String ReticulumManager::destinationHashHex() const { return String(_destination.hash().toHex().c_str()); } +String ReticulumManager::destinationHashStr() const { + if (!_destination) return "unknown"; + std::string hex = _destination.hash().toHex(); + if (hex.length() >= 12) { + return String((hex.substr(0, 4) + ":" + hex.substr(4, 4) + ":" + hex.substr(8, 4)).c_str()); + } + return String(hex.c_str()); +} + size_t ReticulumManager::pathCount() const { return _reticulum.get_path_table().size(); } size_t ReticulumManager::linkCount() const { return _reticulum.get_link_count(); } diff --git a/src/reticulum/ReticulumManager.h b/src/reticulum/ReticulumManager.h index a684694..dd25c4b 100644 --- a/src/reticulum/ReticulumManager.h +++ b/src/reticulum/ReticulumManager.h @@ -46,6 +46,7 @@ public: const RNS::Identity& identity() const { return _identity; } String identityHash() const; String destinationHashHex() const; + String destinationHashStr() const; bool isTransportActive() const { return _transportActive; } size_t pathCount() const; diff --git a/src/storage/MessageStore.cpp b/src/storage/MessageStore.cpp index bfe4dc3..c02409d 100644 --- a/src/storage/MessageStore.cpp +++ b/src/storage/MessageStore.cpp @@ -299,8 +299,12 @@ std::vector MessageStore::loadConversation(const std::string& peerH } } + // Sort chronologically; non-epoch timestamps (uptime-based) sort before epoch std::sort(messages.begin(), messages.end(), [](const LXMFMessage& a, const LXMFMessage& b) { + bool aEpoch = a.timestamp > 1700000000; + bool bEpoch = b.timestamp > 1700000000; + if (aEpoch != bEpoch) return !aEpoch; // non-epoch sorts before epoch return a.timestamp < b.timestamp; }); diff --git a/src/ui/Theme.h b/src/ui/Theme.h index a33e8b2..77f7db2 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -3,7 +3,7 @@ #include // ============================================================================= -// Ratdeck — Cyberpunk Theme Constants +// RatDeck — Cyberpunk Theme Constants // ============================================================================= namespace Theme { diff --git a/src/ui/screens/HomeScreen.cpp b/src/ui/screens/HomeScreen.cpp index 8ec3b89..a11df0f 100644 --- a/src/ui/screens/HomeScreen.cpp +++ b/src/ui/screens/HomeScreen.cpp @@ -46,7 +46,7 @@ void HomeScreen::draw(LGFX_TDeck& gfx) { }; if (_rns) { - drawLine(Theme::PRIMARY, "ID: %s", _rns->identityHash().c_str()); + drawLine(Theme::PRIMARY, "LXMF: %s", _rns->destinationHashStr().c_str()); drawLine(Theme::PRIMARY, "Transport: %s", _rns->isTransportActive() ? "ACTIVE" : "OFFLINE"); drawLine(Theme::PRIMARY, "Paths: %d Links: %d", diff --git a/src/ui/screens/LvMessageView.cpp b/src/ui/screens/LvMessageView.cpp index 22e463c..76f9d45 100644 --- a/src/ui/screens/LvMessageView.cpp +++ b/src/ui/screens/LvMessageView.cpp @@ -237,7 +237,7 @@ void LvMessageView::rebuildMessages() { } // Timestamp below bubble - if (msg.timestamp > 1000000) { + if (msg.timestamp > 1700000000) { time_t t = (time_t)msg.timestamp; struct tm* tm = localtime(&t); if (tm) { diff --git a/src/ui/screens/LvMessagesScreen.cpp b/src/ui/screens/LvMessagesScreen.cpp index 36c976f..ab387fc 100644 --- a/src/ui/screens/LvMessagesScreen.cpp +++ b/src/ui/screens/LvMessagesScreen.cpp @@ -161,7 +161,7 @@ void LvMessagesScreen::rebuildList() { lv_obj_align(nameLbl, LV_ALIGN_TOP_LEFT, 14, 2); // Timestamp (line 1, right) - if (ci.lastTs > 1000000) { + if (ci.lastTs > 1700000000) { time_t t = (time_t)ci.lastTs; struct tm* tm = localtime(&t); if (tm) { @@ -257,7 +257,10 @@ bool LvMessagesScreen::handleKey(const KeyEvent& event) { extern MessageStore messageStore; messageStore.deleteConversation(peerHex); messageStore.refreshConversations(); - if (_ui) _ui->lvStatusBar().showToast("Chat deleted", 1200); + if (_ui) { + _ui->lvStatusBar().showToast("Chat deleted", 1200); + _ui->lvTabBar().setUnreadCount(LvTabBar::TAB_MSGS, _lxmf->unreadCount()); + } _selectedIdx = 0; _lastConvCount = -1; rebuildList(); diff --git a/src/ui/screens/MessageView.cpp b/src/ui/screens/MessageView.cpp index fd105f6..e00a205 100644 --- a/src/ui/screens/MessageView.cpp +++ b/src/ui/screens/MessageView.cpp @@ -71,7 +71,7 @@ std::vector MessageView::wordWrap(const std::string& text, int maxC } std::string MessageView::formatTime(double timestamp) { - if (timestamp < 1000000) return ""; + if (timestamp < 1700000000) return ""; time_t t = (time_t)timestamp; struct tm* tm = localtime(&t); if (!tm) return "";