From 38925dde650881dea77ae8ae3eb1ee675f28f671 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:32:54 +0200 Subject: [PATCH] fix(l2): a dot per DM send, lists that notice every change, the LED lit only on TX - A pending DM shows a dot per send, as on the L1. The thread is read from the SD archive, which a resend never updated: it does now, and the thread's signature and patch state count the attempt. - Nearby redraws when a node goes live, changes type or the units change; the scan popup when a name or the remote SNR arrives. - The user LED is active high: it was lit all the time and went dark while transmitting. - Sim hooks "dm" and "send" for testing a DM thread. Co-Authored-By: Claude Opus 5.5 --- .../companion_radio/ui-core/MessageHistory.h | 3 ++ examples/companion_radio/ui-lvgl/UITask.cpp | 30 +++++++++++++++---- examples/companion_radio/ui-lvgl/UITask.h | 2 ++ variants/wio-tracker-l2/WioTrackerL2Board.cpp | 8 ++--- variants/wio-tracker-l2/WioTrackerL2Board.h | 2 +- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/examples/companion_radio/ui-core/MessageHistory.h b/examples/companion_radio/ui-core/MessageHistory.h index 5e57e355..81bc1dee 100644 --- a/examples/companion_radio/ui-core/MessageHistory.h +++ b/examples/companion_radio/ui-core/MessageHistory.h @@ -460,6 +460,9 @@ public: e.ack_tag = expected_ack; // each attempt has a distinct ACK CRC e.ack_deadline_ms = now + est_timeout + 4000; e.resends_left--; +#ifdef HIST_ARCHIVE + if (_arc) _arc->dmUpdate(e); // the thread is read from it: a dot per send +#endif } else { failDm(e); // couldn't compose/send — give up } diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index d2a3c6eb..dbe91eef 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -3177,8 +3177,9 @@ void UITask::refreshScanPopup() { uint32_t sig = (uint32_t)n + (_scanning ? 0x10000u : 0); for (int i = 0; i < n; i++) { const NearbyModel::Entry& e = _scan->at(i); - sig = sig * 31 + e.rssi * 7 + e.snr_x4 + e.is_known; + sig = sig * 31 + e.rssi * 7 + e.snr_x4 + e.remote_snr_x4 * 3 + e.type * 5 + e.is_known; for (int k = 0; k < 4; k++) sig = sig * 31 + e.pub_key[k]; + for (const char* p = e.name; *p; p++) sig = sig * 31 + (uint8_t)*p; } if (sig == _scan_sig || vlist::pressed(_scan_list)) return; // held: once it's let go _scan_sig = sig; @@ -3224,10 +3225,12 @@ void UITask::openScanNode(int row) { // Changes whenever a rebuild would show something different. uint32_t UITask::nearbySignature() const { - uint32_t sig = (uint32_t)_nearby->count() * 2654435761u + _nearby->sortMode() + (_scanning ? 7 : 0); + uint32_t sig = (uint32_t)_nearby->count() * 2654435761u + _nearby->sortMode() + (_scanning ? 7 : 0) + + (_prefs && _prefs->units_imperial ? 13 : 0); for (int i = 0; i < _nearby->count(); i++) { const NearbyModel::Entry& e = _nearby->at(i); - sig = sig * 31 + e.contact_idx + (uint32_t)e.lastmod + (uint32_t)(e.dist_km * 100) + e.rssi + e.fav; + sig = sig * 31 + e.contact_idx + (uint32_t)e.lastmod + (uint32_t)(e.dist_km * 100) + e.rssi + e.fav + + e.type * 5 + (e.is_live ? 3 : 0); for (const char* p = e.name; *p; p++) sig = sig * 31 + (uint8_t)*p; } // Ages are shown in minutes, so let the list re-render once a minute anyway. @@ -3730,7 +3733,7 @@ uint32_t UITask::threadSignature() const { sig = n; for (int j = 0; j < n && j < 8; j++) { const DmHistEntry& e = h.dmAtPos(h.dmHistEntryForContact(_thread_key, j)); - sig = sig * 31 + e.timestamp + h.dmEffectiveStatus(e); + sig = sig * 31 + e.timestamp + h.dmEffectiveStatus(e) + e.attempt * 7; // attempt: a dot per resend } } return sig; @@ -4053,7 +4056,7 @@ void UITask::refreshThread() { }; auto stateOf = [&](int i) -> uint32_t { if (_thread_is_channel) return s_th_ch[i].relay_status | (uint32_t)s_th_ch[i].path_len << 8; - return s_th_dm[i].outgoing ? 0x100u | h.dmEffectiveStatus(s_th_dm[i]) : 0; + return s_th_dm[i].outgoing ? 0x100u | h.dmEffectiveStatus(s_th_dm[i]) | (uint32_t)s_th_dm[i].attempt << 16 : 0; }; // Entry i's bubble (at the list's end) and its menu / position slots. auto build = [&](int i) { @@ -4091,7 +4094,13 @@ void UITask::refreshThread() { switch (h.dmEffectiveStatus(e)) { case ACK_OK: st = LV_SYMBOL_OK; col = theme::OK; break; case ACK_FAIL: st = LV_SYMBOL_CLOSE; col = theme::FAIL; break; - case ACK_PENDING: st = "..."; break; + case ACK_PENDING: { // a dot per send, as ui-new: grows with each auto-resend + static char dots[9]; + int k = e.attempt + 1 < 8 ? e.attempt + 1 : 8; + memset(dots, '.', k); dots[k] = '\0'; + st = dots; + break; + } default: st = ""; break; } } @@ -4657,6 +4666,13 @@ void UITask::simCompose(const char* text) { lv_textarea_set_text(_compose_ta, text); } +void UITask::simDm() { + for (int i = 0; i < the_mesh.getNumContacts(); i++) { + ContactInfo c; + if (the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c) && c.type == ADV_TYPE_CHAT) { openDM(c.id.pub_key); return; } + } +} + // Sim tests: straight to a screen by name. extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) { if (!s_ui) return; @@ -4679,6 +4695,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) { if (!strncmp(name, "map@", 4)) { s_ui->simMapAt(name + 4); return; } // "map@lat,lon,z" if (!strncmp(name, "channel@", 8)) { s_ui->openChannel(atoi(name + 8)); return; } if (!strncmp(name, "compose@", 8)) { s_ui->simCompose(name + 8); return; } + if (!strcmp(name, "dm")) { s_ui->simDm(); return; } + if (!strcmp(name, "send")) { s_ui->simSend(); return; } if (!strcmp(name, "vector")) { s_ui->setVectorMap(true); return; } if (!strcmp(name, "maptools")) { s_ui->navToolsPopup(); return; } if (!strcmp(name, "areasel")) { s_ui->areaSelectBegin(); return; } diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index 5c85adfa..36314432 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -129,6 +129,8 @@ public: void setVectorMap(bool on); void simMapAt(const char* spec); // Map tools > Live tiles void simCompose(const char* text); // the open thread's field: this text, keyboard up + void simDm(); // the first chat contact's thread + void simSend() { sendFromCompose(); } // the field, sent // Navigation map (NavMap.h) void navTargetsPopup(); void navClosePopup(); diff --git a/variants/wio-tracker-l2/WioTrackerL2Board.cpp b/variants/wio-tracker-l2/WioTrackerL2Board.cpp index 02c09527..af3a6417 100644 --- a/variants/wio-tracker-l2/WioTrackerL2Board.cpp +++ b/variants/wio-tracker-l2/WioTrackerL2Board.cpp @@ -77,8 +77,9 @@ bool WioTrackerL2Board::initExpander() { expSetOutput(EXP_PIN_GNSS_RST, HIGH); delay(10); expWritePin(EXP_PIN_GNSS_RST, LOW); - // user LED is driven active-low through the expander; idle level HIGH = off - expSetOutput(EXP_PIN_USER_LED, HIGH); + // user LED: lit while the pin is HIGH (it read as active-low before, and + // stayed lit except while transmitting); idle LOW = off + expSetOutput(EXP_PIN_USER_LED, LOW); delay(10); expSetOutput(EXP_PIN_GROVE_EN, HIGH); delay(10); @@ -194,9 +195,8 @@ void WioTrackerL2Board::begin() { } void WioTrackerL2Board::setLed(bool on) { - // active low: HIGH = off if (expander_ok) { - expWritePin(EXP_PIN_USER_LED, !on); + expWritePin(EXP_PIN_USER_LED, on); } } diff --git a/variants/wio-tracker-l2/WioTrackerL2Board.h b/variants/wio-tracker-l2/WioTrackerL2Board.h index 6f8b72c1..d004e0c4 100644 --- a/variants/wio-tracker-l2/WioTrackerL2Board.h +++ b/variants/wio-tracker-l2/WioTrackerL2Board.h @@ -30,7 +30,7 @@ #define EXP_PIN_GROVE_EN 7 // output - Grove port power #define EXP_PIN_TP_RST 8 // output - touch panel reset #define EXP_PIN_GNSS_RST 9 // output - GNSS reset (active HIGH) -#define EXP_PIN_USER_LED 10 // output - mesh/user LED (active low) +#define EXP_PIN_USER_LED 10 // output - mesh/user LED (active high; also GNSS wakeup) #define EXP_PIN_OTG_EN 11 // output - USB OTG power #define EXP_PIN_PA_EN 12 // output - speaker amp power #define EXP_PIN_GNSS_EN 13 // output - GNSS power