ui fixes including discover screen; clock fix

This commit is contained in:
pelgraine
2026-03-12 04:32:58 +11:00
parent 7ae9c47006
commit 565c2a4c9b
10 changed files with 326 additions and 28 deletions
+32 -3
View File
@@ -614,6 +614,25 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
return KEY_ENTER; // file list: open
}
// Discovery screen: long press = rescan
if (ui_task.isOnDiscoveryScreen()) {
return 'f';
}
// Contacts screen: long press = open repeater admin (if on a repeater contact)
if (ui_task.isOnContactsScreen()) {
ContactsScreen* cs = (ContactsScreen*)ui_task.getContactsScreen();
if (cs) {
int idx = cs->getSelectedContactIdx();
ContactInfo ci;
if (the_mesh.getContactByIdx(idx, ci) && ci.type == ADV_TYPE_REPEATER) {
ui_task.gotoRepeaterAdmin(idx);
return 0;
}
}
return KEY_ENTER; // non-repeater: normal select
}
// Default: enter/select (settings toggle, etc.)
return KEY_ENTER;
}
@@ -959,9 +978,10 @@ void setup() {
#endif
// Initialize GT911 touch (T5S3 E-Paper Pro)
// Wire is already initialized by T5S3Board::begin(). The 4-arg begin() re-calls
// Wire.begin() which logs "bus already initialized" — cosmetic only, not harmful.
#if defined(LilyGo_T5S3_EPaper_Pro)
gt911Touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
pinMode(GT911_PIN_INT, INPUT_PULLUP); // Ensure INT pin has pullup for clean transitions
if (gt911Touch.begin(Wire, GT911_SLAVE_ADDRESS_L, GT911_PIN_SDA, GT911_PIN_SCL)) {
gt911Ready = true;
Serial.println("setup() - GT911 touch initialized");
@@ -970,8 +990,17 @@ void setup() {
}
#endif
// ---------------------------------------------------------------------------
// SD card is already initialized (early init above).
// RTC diagnostic — verify the auto-discovered RTC is working
#if defined(LilyGo_T5S3_EPaper_Pro)
{
uint32_t rtcTime = rtc_clock.getCurrentTime();
Serial.printf("setup() - RTC time: %lu (valid=%s)\n", rtcTime,
rtcTime > 1700000000 ? "YES" : "NO");
if (rtcTime < 1700000000) {
Serial.println("setup() - RTC has no valid time (will be set by companion app)");
}
}
#endif
// Now set up SD-dependent features: message history + text reader.
// ---------------------------------------------------------------------------
#if defined(LilyGo_TDeck_Pro) && defined(HAS_SDCARD)
@@ -957,7 +957,7 @@ public:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Boot: Home");
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select boot: home");
#else
// Left side: abbreviated controls
if (_replySelectMode) {
@@ -303,7 +303,7 @@ public:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Boot: Home");
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select boot: home");
#else
// Left: Q:Bk
display.setCursor(0, footerY);
@@ -79,7 +79,11 @@ public:
display.print(active ? "Listening for adverts..." : "No nodes found");
if (!active) {
display.setCursor(4, 38);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Long press: Rescan");
#else
display.print("F: Scan again Q: Back");
#endif
}
} else {
// Center visible window around selected item
@@ -158,6 +162,17 @@ public:
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Swipe:Scroll");
const char* mid = "Tap:Add";
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
display.print(mid);
const char* right = "Hold:Rescan";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#else
display.print("Q:Back");
const char* mid = "Ent:Add";
@@ -167,6 +182,7 @@ public:
const char* right = "F:Rescan";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#endif
// Faster refresh while actively scanning
return active ? 1000 : 5000;
@@ -1008,17 +1008,17 @@ public:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
if (_editMode == EDIT_NONE) {
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Hold: Edit Boot: Home");
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Hold: Edit boot: home");
} else if (_editMode == EDIT_NUMBER) {
display.drawTextCentered(display.width() / 2, footerY, "Swipe Up/Down: Adjust Tap: OK Boot: Cancel");
display.drawTextCentered(display.width() / 2, footerY, "Swipe Up/Down: Adjust Tap: OK boot: cancel");
} else if (_editMode == EDIT_PICKER) {
display.drawTextCentered(display.width() / 2, footerY, "Swipe Left/Right: Choose Tap: OK");
} else if (_editMode == EDIT_CONFIRM) {
display.drawTextCentered(display.width() / 2, footerY, "Tap: Confirm Boot: Cancel");
display.drawTextCentered(display.width() / 2, footerY, "Tap: Confirm boot: cancel");
#ifdef MECK_WIFI_COMPANION
} else if (_editMode == EDIT_WIFI) {
if (_wifiPhase == WIFI_PHASE_SELECT) {
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Pick Tap: Select Boot: Back");
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Pick Tap: Select boot: back");
} else {
display.drawTextCentered(display.width() / 2, footerY, "Please wait...");
}
@@ -926,7 +926,7 @@ private:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Open Boot: Home");
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Open boot: home");
#else
display.setCursor(0, footerY);
display.print("Q:Back W/S:Nav");
@@ -960,6 +960,7 @@ private:
display.setCursor(0, y);
// Print line with UTF-8 decoding: multi-byte sequences are decoded
// to Unicode codepoints, then mapped to CP437 for the built-in font.
bool lineHasContent = false;
char charStr[2] = {0, 0};
int j = pos;
while (j < wrap.lineEnd && j < _pageBufLen) {
@@ -975,6 +976,7 @@ private:
// Plain ASCII — print directly
charStr[0] = (char)b;
display.print(charStr);
lineHasContent = true;
j++;
} else if (b >= 0xC0) {
// UTF-8 lead byte — decode full sequence and map to CP437
@@ -984,6 +986,7 @@ private:
if (glyph) {
charStr[0] = (char)glyph;
display.print(charStr);
lineHasContent = true;
}
// If unmappable (glyph==0), just skip the character
} else {
@@ -991,11 +994,20 @@ private:
// Treat as CP437 pass-through (e.g. from EPUB numeric entity decoding).
charStr[0] = (char)b;
display.print(charStr);
lineHasContent = true;
j++;
}
}
y += _lineHeight;
// Blank lines (paragraph breaks) get reduced height for compact layout.
// Full _lineHeight for blank lines wastes too much space — on T5S3 each
// blank line is ~34px, making paragraph gaps 7-8× the normal line spacing.
// Using 40% height gives a visible paragraph break without wasting space.
if (lineHasContent) {
y += _lineHeight;
} else {
y += max(2, _lineHeight * 2 / 5); // ~40% height for blank lines
}
lineCount++;
pos = wrap.nextStart;
if (pos >= _pageBufLen) break;
+41 -5
View File
@@ -32,7 +32,7 @@
#if UI_HAS_JOYSTICK
#define PRESS_LABEL "press Enter"
#elif defined(LilyGo_T5S3_EPaper_Pro)
#define PRESS_LABEL "hold Boot btn"
#define PRESS_LABEL "hold boot btn"
#else
#define PRESS_LABEL "long press"
#endif
@@ -159,9 +159,11 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
int totalWidth = iconWidth + 2 + 2 + textWidth + 2;
int iconX = display.width() - totalWidth;
#if defined(LilyGo_T5S3_EPaper_Pro)
int iconY = 4; // Shift down to match header text offset
int iconY = 6; // Align with FreeSans12pt text baseline
int textY = 1; // Percentage text — setCursor adds +5 → baseline at (6)*scale_y
#else
int iconY = 0; // vertically align with node name text
int iconY = 0; // vertically align with node name text
int textY = iconY - 3; // offset up to vertically center with icon
#endif
if (outIconX) *outIconX = iconX;
@@ -179,7 +181,6 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
// draw percentage text after the battery cap, offset upward to center with icon
// (setCursor adds +5 internally for baseline, so compensate for the tiny font)
int textX = iconX + iconWidth + 2 + 2; // after cap + gap
int textY = iconY - 3; // offset up to vertically center with icon
display.setCursor(textX, textY);
display.print(pctStr);
display.setTextSize(1); // restore default text size
@@ -490,23 +491,39 @@ public:
#ifdef BLE_PIN_CODE
} else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::GREEN);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawXbm((display.width() - 32) / 2, 28,
#else
display.drawXbm((display.width() - 32) / 2, 18,
#endif
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
32, 32);
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, 64, "< Connected >");
#else
display.drawTextCentered(display.width() / 2, 53, "< Connected >");
#endif
} else if (_task->isSerialEnabled() && the_mesh.getBLEPin() != 0) {
display.setColor(DisplayDriver::RED);
display.setTextSize(2);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, 64, tmp);
#else
display.drawTextCentered(display.width() / 2, 53, tmp);
#endif
}
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, 80, "toggle: " PRESS_LABEL);
#else
display.drawTextCentered(display.width() / 2, 72, "toggle: " PRESS_LABEL);
#endif
#endif
#ifdef MECK_WIFI_COMPANION
} else if (_page == HomePage::WIFI_STATUS) {
display.setColor(DisplayDriver::GREEN);
@@ -547,8 +564,16 @@ public:
#endif
} else if (_page == HomePage::ADVERT) {
display.setColor(DisplayDriver::GREEN);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawXbm((display.width() - 32) / 2, 28, advert_icon, 32, 32);
#else
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
#endif
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, 64, "advert: " PRESS_LABEL);
#else
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
#endif
#if ENV_INCLUDE_GPS == 1
} else if (_page == HomePage::GPS) {
extern GPSStreamCounter gpsStream;
@@ -773,10 +798,21 @@ public:
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
if (_shutdown_init) {
#if defined(LilyGo_T5S3_EPaper_Pro)
board.setBacklight(false); // Turn off backlight on hibernate
#endif
display.drawTextCentered(display.width() / 2, 34, "hibernating...");
} else {
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawXbm((display.width() - 32) / 2, 28, power_icon, 32, 32);
#else
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
#endif
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, 64, "hibernate:" PRESS_LABEL);
#else
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
#endif
}
}
return _editing_utc ? 700 : 5000; // match e-ink refresh cycle while editing UTC
@@ -1490,7 +1526,7 @@ char UITask::handleTripleClick(char c) {
if (board.isBacklightOn()) {
board.setBacklight(false); // If already on, turn off
} else {
board.setBacklightBrightness(40);
board.setBacklightBrightness(4);
board.setBacklight(true);
}
#else
@@ -0,0 +1,209 @@
#pragma once
// =============================================================================
// PCF85063Clock — PCF8563/BM8563 RTC driver for T5S3 E-Paper Pro
//
// Time registers at 0x020x08 (PCF8563 layout):
// 0x02 Seconds, 0x03 Minutes, 0x04 Hours,
// 0x05 Days, 0x06 Weekdays, 0x07 Months, 0x08 Years
// =============================================================================
#include <Arduino.h>
#include <Wire.h>
#include <MeshCore.h>
#define PCF8563_ADDR 0x51
#define PCF8563_REG_SECONDS 0x02
// Reject timestamps outside 20242036 (blocks MeshCore contacts garbage)
#define EPOCH_MIN_SANE 1704067200UL
#define EPOCH_MAX_SANE 2082758400UL
class PCF85063Clock : public mesh::RTCClock {
public:
PCF85063Clock() : _wire(nullptr), _millis_offset(0),
_has_hw_time(false), _time_set_this_session(false) {}
bool begin(TwoWire& wire) {
_wire = &wire;
_wire->beginTransmission(PCF8563_ADDR);
if (_wire->endTransmission() != 0) {
Serial.println("[RTC] PCF8563 not found");
return false;
}
// Repair any corrupted registers from prior wrong-offset writes
repairRegisters();
uint32_t t = readHardwareTime();
if (t > EPOCH_MIN_SANE && t < EPOCH_MAX_SANE) {
_has_hw_time = true;
_millis_offset = t - (millis() / 1000);
Serial.printf("[RTC] PCF8563 OK, time=%lu\n", t);
} else {
_has_hw_time = false;
Serial.printf("[RTC] PCF8563 no valid time (%lu), awaiting BLE sync\n", t);
}
return true;
}
uint32_t getCurrentTime() override {
if (_time_set_this_session) {
return _millis_offset + (millis() / 1000);
}
if (_has_hw_time && _wire) {
uint32_t t = readHardwareTime();
if (t > EPOCH_MIN_SANE && t < EPOCH_MAX_SANE) {
_millis_offset = t - (millis() / 1000);
return t;
}
_has_hw_time = false;
}
return _millis_offset + (millis() / 1000);
}
void setCurrentTime(uint32_t time) override {
if (time < EPOCH_MIN_SANE || time > EPOCH_MAX_SANE) {
Serial.printf("[RTC] setCurrentTime(%lu) REJECTED\n", time);
return;
}
_millis_offset = time - (millis() / 1000);
_time_set_this_session = true;
Serial.printf("[RTC] setCurrentTime(%lu) OK\n", time);
if (_wire) writeHardwareTime(time);
}
private:
TwoWire* _wire;
uint32_t _millis_offset;
bool _has_hw_time;
bool _time_set_this_session;
// ---- Register helpers ----
void writeReg(uint8_t reg, uint8_t val) {
_wire->beginTransmission(PCF8563_ADDR);
_wire->write(reg);
_wire->write(val);
_wire->endTransmission();
}
uint8_t readReg(uint8_t reg) {
_wire->beginTransmission(PCF8563_ADDR);
_wire->write(reg);
if (_wire->endTransmission(false) != 0) return 0xFF;
if (_wire->requestFrom((uint8_t)PCF8563_ADDR, (uint8_t)1) != 1) return 0xFF;
return _wire->read();
}
// ---- Fix registers corrupted by prior PCF85063A-mode writes ----
void repairRegisters() {
uint8_t hours = readReg(0x04) & 0x3F;
if (bcd2dec(hours) > 23) {
Serial.printf("[RTC] Repairing hours (0x%02X→0x00)\n", hours);
writeReg(0x04, 0x00);
}
uint8_t days = readReg(0x05) & 0x3F;
if (bcd2dec(days) == 0 || bcd2dec(days) > 31) {
Serial.printf("[RTC] Repairing days (0x%02X→0x01)\n", days);
writeReg(0x05, 0x01);
}
uint8_t month = readReg(0x07) & 0x1F;
if (bcd2dec(month) == 0 || bcd2dec(month) > 12) {
Serial.printf("[RTC] Repairing month (0x%02X→0x01)\n", month);
writeReg(0x07, 0x01);
}
}
// ---- BCD ----
static uint8_t bcd2dec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F); }
static uint8_t dec2bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); }
// ---- Date helpers ----
static bool isLeap(int y) { return (y%4==0 && y%100!=0) || y%400==0; }
static int daysInMonth(int m, int y) {
static const uint8_t d[] = {31,28,31,30,31,30,31,31,30,31,30,31};
return (m==2 && isLeap(y)) ? 29 : d[m-1];
}
static uint32_t toEpoch(int yr, int mo, int dy, int h, int mi, int s) {
uint32_t days = 0;
for (int y = 1970; y < yr; y++) days += isLeap(y) ? 366 : 365;
for (int m = 1; m < mo; m++) days += daysInMonth(m, yr);
days += (dy - 1);
return days * 86400UL + h * 3600UL + mi * 60UL + s;
}
static void fromEpoch(uint32_t ep, int& yr, int& mo, int& dy, int& h, int& mi, int& s) {
s = ep % 60; ep /= 60;
mi = ep % 60; ep /= 60;
h = ep % 24; ep /= 24;
yr = 1970;
while (true) { int d = isLeap(yr)?366:365; if (ep<(uint32_t)d) break; ep-=d; yr++; }
mo = 1;
while (true) { int d = daysInMonth(mo,yr); if (ep<(uint32_t)d) break; ep-=d; mo++; }
dy = ep + 1;
}
// ---- Read time (burst from 0x02) ----
uint32_t readHardwareTime() {
_wire->beginTransmission(PCF8563_ADDR);
_wire->write(PCF8563_REG_SECONDS);
if (_wire->endTransmission(false) != 0) return 0;
if (_wire->requestFrom((uint8_t)PCF8563_ADDR, (uint8_t)7) != 7) return 0;
uint8_t raw[7];
for (int i = 0; i < 7; i++) raw[i] = _wire->read();
if (raw[0] & 0x80) {
Serial.println("[RTC] OS flag set — clearing");
writeReg(PCF8563_REG_SECONDS, raw[0] & 0x7F);
return 0;
}
int second = bcd2dec(raw[0] & 0x7F);
int minute = bcd2dec(raw[1] & 0x7F);
int hour = bcd2dec(raw[2] & 0x3F);
int day = bcd2dec(raw[3] & 0x3F);
int month = bcd2dec(raw[5] & 0x1F);
int year = 2000 + bcd2dec(raw[6]);
if (month<1 || month>12 || day<1 || day>31 || hour>23 || minute>59 || second>59)
return 0;
return toEpoch(year, month, day, hour, minute, second);
}
// ---- Write time (burst to 0x02) ----
void writeHardwareTime(uint32_t epoch) {
int year, month, day, hour, minute, second;
fromEpoch(epoch, year, month, day, hour, minute, second);
static const int dow[] = {0,3,2,5,0,3,5,1,4,6,2,4};
int y = year; if (month < 3) y--;
int wday = (y + y/4 - y/100 + y/400 + dow[month-1] + day) % 7;
int yr = year - 2000;
// Stop clock
writeReg(0x00, 0x20);
delay(5);
// Burst write
_wire->beginTransmission(PCF8563_ADDR);
_wire->write(PCF8563_REG_SECONDS);
_wire->write(dec2bcd(second) & 0x7F);
_wire->write(dec2bcd(minute));
_wire->write(dec2bcd(hour));
_wire->write(dec2bcd(day));
_wire->write(dec2bcd(wday));
_wire->write(dec2bcd(month));
_wire->write(dec2bcd(yr));
_wire->endTransmission();
delay(5);
// Restart clock
writeReg(0x00, 0x00);
Serial.printf("[RTC] Wrote %04d-%02d-%02d %02d:%02d:%02d\n",
year, month, day, hour, minute, second);
}
};
+5 -9
View File
@@ -15,8 +15,7 @@ T5S3Board board;
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
PCF85063Clock rtc_clock;
// No GPS on H752-B
#if HAS_GPS
@@ -39,13 +38,10 @@ bool radio_init() {
// NOTE: board.begin() is called by main.cpp setup() before radio_init()
// I2C is already initialized there with correct pins
fallback_clock.begin();
MESH_DEBUG_PRINTLN("radio_init() - fallback_clock started");
// Use existing Wire for RTC discovery
// AutoDiscoverRTCClock will find PCF85063 at 0x51 if present
// PCF85063 hardware RTC — reads correct registers (0x040x0A)
// Unlike AutoDiscoverRTCClock which uses RTClib's PCF8563 driver (wrong registers)
rtc_clock.begin(Wire);
MESH_DEBUG_PRINTLN("radio_init() - rtc_clock started");
MESH_DEBUG_PRINTLN("radio_init() - PCF85063 RTC started");
#if defined(P_LORA_SCLK)
MESH_DEBUG_PRINTLN("radio_init() - initializing LoRa SPI (SCLK=%d, MISO=%d, MOSI=%d, NSS=%d)...",
@@ -92,4 +88,4 @@ mesh::LocalIdentity radio_new_identity() {
void radio_reset_agc() {
radio.setRxBoostedGainMode(true);
}
}
+3 -3
View File
@@ -8,7 +8,7 @@
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <T5S3Board.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include "PCF85063Clock.h"
// Display support — FastEPDDisplay for parallel e-ink (not GxEPD2)
#ifdef DISPLAY_CLASS
@@ -28,7 +28,7 @@
extern T5S3Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern PCF85063Clock rtc_clock;
#if HAS_GPS
extern GPSStreamCounter gpsStream;
@@ -47,4 +47,4 @@ uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(uint8_t dbm);
mesh::LocalIdentity radio_new_identity();
void radio_reset_agc();
void radio_reset_agc();