v1.5.1: Fix messaging failures, stale timestamps, retry logic

- Add NTP time sync on WiFi STA connect (fixes --:-- timestamps)
- Throttle LXMF send retries to 2s intervals, increase max to 30 (~60s window)
- Restore message status re-save to disk after send completes (QUEUED→SENT/FAILED)
- Update GitHub org URLs to ratspeak
This commit is contained in:
DeFiDude
2026-03-07 21:05:19 -07:00
parent 255030955a
commit 9ec2984d4f
14 changed files with 69 additions and 26 deletions
+3 -3
View File
@@ -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
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
// =============================================================================
// Ratdeck — LilyGo T-Deck Plus Pin Definitions
// RatDeck — LilyGo T-Deck Plus Pin Definitions
// =============================================================================
// --- Power Control ---
+4 -4
View File
@@ -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
+21 -7
View File
@@ -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();
+15 -5
View File
@@ -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
}
+2
View File
@@ -48,5 +48,7 @@ private:
std::set<std::string> _seenMessageIds;
static constexpr int MAX_SEEN_IDS = 100;
unsigned long _lastRetryMs = 0;
static LXMFManager* _instance;
};
+9
View File
@@ -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(); }
+1
View File
@@ -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;
+4
View File
@@ -299,8 +299,12 @@ std::vector<LXMFMessage> 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;
});
+1 -1
View File
@@ -3,7 +3,7 @@
#include <cstdint>
// =============================================================================
// Ratdeck — Cyberpunk Theme Constants
// RatDeck — Cyberpunk Theme Constants
// =============================================================================
namespace Theme {
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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) {
+5 -2
View File
@@ -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();
+1 -1
View File
@@ -71,7 +71,7 @@ std::vector<std::string> 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 "";