mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-07-30 15:39:21 +00:00
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:
@@ -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,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// Ratdeck — LilyGo T-Deck Plus Pin Definitions
|
||||
// RatDeck — LilyGo T-Deck Plus Pin Definitions
|
||||
// =============================================================================
|
||||
|
||||
// --- Power Control ---
|
||||
|
||||
+4
-4
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -48,5 +48,7 @@ private:
|
||||
std::set<std::string> _seenMessageIds;
|
||||
static constexpr int MAX_SEEN_IDS = 100;
|
||||
|
||||
unsigned long _lastRetryMs = 0;
|
||||
|
||||
static LXMFManager* _instance;
|
||||
};
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -3,7 +3,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
// =============================================================================
|
||||
// Ratdeck — Cyberpunk Theme Constants
|
||||
// RatDeck — Cyberpunk Theme Constants
|
||||
// =============================================================================
|
||||
|
||||
namespace Theme {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
Reference in New Issue
Block a user