From d4ed6df92ec8559a2c2e3f2a304b032cb5be0e6a Mon Sep 17 00:00:00 2001 From: "Michael A. Cojocari" Date: Sat, 5 Sep 2026 15:16:56 -0400 Subject: [PATCH] Work --- src/helpers/WadaNmeaLocationProvider.h | 84 +++++++++++++++++++++++++- src/ui-touch/UITask.cpp | 12 ++++ variants/lilygo_tdeck_pro/target.cpp | 3 +- variants/lilygo_tdeck_pro/target.h | 2 +- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/helpers/WadaNmeaLocationProvider.h b/src/helpers/WadaNmeaLocationProvider.h index 8ff4c4b5..a64342ab 100644 --- a/src/helpers/WadaNmeaLocationProvider.h +++ b/src/helpers/WadaNmeaLocationProvider.h @@ -11,9 +11,10 @@ // is exactly the build-fragile dependency patch this repo avoids. So the // provider is re-owned here: same behaviour line for line (claim/release, the // time-sync rules from #89's follow-up, GPS_EN/GPS_RESET polarity macros from -// the core header), plus motion(). Boards opt in by constructing this class in -// their target.cpp instead of the core one and defining HAS_GPS_MOTION; the -// wada.sys.gps() binding then reports speed_kmh/course on that board. +// the core header), plus motion() and optional checksum-driven UART baud +// recovery. Boards opt in by constructing this class in their target.cpp +// instead of the core one. HAS_GPS_MOTION exposes speed/course to Lua; passing +// the UART probe arguments enables baud recovery independently. // // Kept in lock-step with the core header it mirrors (meshcomod core-v1.17.4); // if the core provider gains behaviour, port it here too. ONE deliberate @@ -24,6 +25,7 @@ #include #include #include +#include "esp32/TouchPrefsStore.h" #include class WadaNmeaLocationProvider : public LocationProvider { @@ -38,7 +40,54 @@ class WadaNmeaLocationProvider : public LocationProvider { unsigned long next_check = 0; long time_valid = 0; unsigned long _last_time_sync = 0; + HardwareSerial* _probe_serial = nullptr; + int8_t _probe_rx = -1; + int8_t _probe_tx = -1; + uint8_t _probe_cursor = 0; + uint32_t _probe_default_baud = 0; + uint32_t _active_baud = 0; + uint32_t _probe_deadline_ms = 0; + bool _stream_locked = false; static const unsigned long TIME_SYNC_INTERVAL = 1800000; // re-sync every 30 minutes + static const unsigned long BAUD_PROBE_START_MS = 9000; + static const unsigned long BAUD_PROBE_INTERVAL_MS = 5000; + + void nudgeReceiver() { + if (!_probe_serial) return; + _probe_serial->write((uint8_t)'\r'); + _probe_serial->write((uint8_t)'\n'); + _probe_serial->flush(); + } + + void probeNextBaud() { + if (!_probe_serial || _probe_rx < 0) return; + const uint32_t candidates[] = { + _probe_default_baud, 9600u, 19200u, 38400u, 57600u, 115200u + }; + for (size_t attempt = 0; attempt < sizeof(candidates) / sizeof(candidates[0]); ++attempt) { + const uint32_t candidate = candidates[_probe_cursor]; + _probe_cursor = (uint8_t)((_probe_cursor + 1) % + (sizeof(candidates) / sizeof(candidates[0]))); + if (candidate == 0 || candidate == _active_baud) continue; + _probe_serial->end(); + _probe_serial->setPins(_probe_rx, _probe_tx); + _probe_serial->begin(candidate); + nmea.clear(); + time_valid = 0; + _active_baud = candidate; + _probe_deadline_ms = millis() + BAUD_PROBE_INTERVAL_MS; + nudgeReceiver(); + return; + } + } + + void noteValidSentence() { + if (!_probe_serial || _stream_locked) return; + _stream_locked = true; + _probe_deadline_ms = 0; + if (touchPrefsGetGpsBaud(_probe_default_baud) != _active_baud) + (void)touchPrefsSetGpsBaud(_active_baud); + } public: WadaNmeaLocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, @@ -66,6 +115,16 @@ public: } } + WadaNmeaLocationProvider(HardwareSerial& ser, mesh::RTCClock* clock, + int pin_reset, int pin_en, int serial_rx, int serial_tx, + uint32_t default_baud) + : WadaNmeaLocationProvider(static_cast(ser), clock, pin_reset, pin_en) { + _probe_serial = &ser; + _probe_rx = (int8_t)serial_rx; + _probe_tx = (int8_t)serial_tx; + _probe_default_baud = default_baud; + } + void claim() { _claims++; if (_peripher_power) _peripher_power->claim(); @@ -81,6 +140,13 @@ public: claim(); if (_pin_en != -1) digitalWrite(_pin_en, GPS_EN_ACTIVE); if (_pin_reset != -1) digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); + if (_probe_serial) { + _active_baud = touchPrefsGetGpsBaud(_probe_default_baud); + _probe_cursor = 0; + _stream_locked = false; + _probe_deadline_ms = millis() + BAUD_PROBE_START_MS; + nudgeReceiver(); + } } void reset() override { @@ -97,6 +163,8 @@ public: // circuits (see the constructor comment; on the M9 "asserted" = GPIO HIGH // through R46 into Q16's base = a constant drain while the GPS is off). if (_pin_reset != -1) digitalWrite(_pin_reset, LOW); + _probe_deadline_ms = 0; + _stream_locked = false; release(); } @@ -153,9 +221,19 @@ public: #ifdef GPS_NMEA_DEBUG Serial.print(c); #endif + const bool sentence_end = c == '\r' || c == '\n'; nmea.process(c); + if (sentence_end) { + const char* sentence = nmea.getSentence(); + if (sentence && sentence[0] == '$' && MicroNMEA::testChecksum(sentence)) + noteValidSentence(); + } } + if (_probe_serial && !_stream_locked && _probe_deadline_ms && + (int32_t)(millis() - _probe_deadline_ms) >= 0) + probeNextBaud(); + if (!isValid()) time_valid = 0; if ((long)(millis() - next_check) > 0) { diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index 80196d44..0213fcfb 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -36644,15 +36644,27 @@ static void refreshChatList(LvChatPanel& p) { // Avatar: same FNV-1a hue family as the chat-bubble colours (see // usernameBubbleColors), lifted in value so the disc reads on the dark panel. + #if !defined(HAS_TDECK_PRO) uint32_t hh = 2166136261u; for (const char* s2 = name; *s2; ++s2) { hh ^= (uint8_t)*s2; hh *= 16777619u; } + #endif lv_obj_t* av = lv_obj_create(btn); lv_obj_remove_style_all(av); lv_obj_add_flag(av, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_clear_flag(av, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); // taps fall through to the row lv_obj_set_size(av, kThreadAvatar, kThreadAvatar); lv_obj_set_style_radius(av, LV_RADIUS_CIRCLE, LV_PART_MAIN); + #if defined(HAS_TDECK_PRO) + // Every generated avatar hue falls below the e-paper luminance threshold, + // as does the black initials/emoji ink. Use an outlined paper-white disc + // so the glyph remains visible after RGB565 is reduced to one bit. + lv_obj_set_style_bg_color(av, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_border_color(av, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(av, 2, LV_PART_MAIN); + lv_obj_set_style_border_opa(av, LV_OPA_COVER, LV_PART_MAIN); + #else lv_obj_set_style_bg_color(av, lv_color_hsv_to_rgb((uint16_t)(hh % 360u), 55, 42), LV_PART_MAIN); + #endif lv_obj_set_style_bg_opa(av, LV_OPA_COVER, LV_PART_MAIN); lv_obj_align(av, LV_ALIGN_LEFT_MID, 8, 0); // Avatar content: a user-chosen emoji for channels (thread sheet -> Chat icon), diff --git a/variants/lilygo_tdeck_pro/target.cpp b/variants/lilygo_tdeck_pro/target.cpp index d34539a0..9082e7c9 100644 --- a/variants/lilygo_tdeck_pro/target.cpp +++ b/variants/lilygo_tdeck_pro/target.cpp @@ -8,7 +8,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; ClockFloorRTC rtc_clock(fallback_clock); -MicroNMEALocationProvider gps(Serial1, &rtc_clock); +WadaNmeaLocationProvider gps(Serial1, &rtc_clock, GPS_RESET, GPS_EN, + PIN_GPS_TX, PIN_GPS_RX, GPS_BAUD_RATE); EnvironmentSensorManager sensors(gps); TDeckProDisplay display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); diff --git a/variants/lilygo_tdeck_pro/target.h b/variants/lilygo_tdeck_pro/target.h index c03a1d6a..810791ae 100644 --- a/variants/lilygo_tdeck_pro/target.h +++ b/variants/lilygo_tdeck_pro/target.h @@ -12,8 +12,8 @@ #include "TDeckProBoard.h" #include "TDeckProDisplay.h" #include "../../src/helpers/ClockFloorRTC.h" +#include "../../src/helpers/WadaNmeaLocationProvider.h" #include "helpers/sensors/EnvironmentSensorManager.h" -#include "helpers/sensors/MicroNMEALocationProvider.h" extern TDeckProBoard board; extern WRAPPER_CLASS radio_driver;