mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
Compare commits
7 Commits
v1.0
...
tdpro-v1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4004acf15d | ||
|
|
0b9402b530 | ||
|
|
e55799f8a5 | ||
|
|
0549efa627 | ||
|
|
a52cf166cb | ||
|
|
facffe9f07 | ||
|
|
148fb7f001 |
26
README.md
26
README.md
@@ -266,8 +266,8 @@ Press **S** from the home screen to open settings. On first boot (when the devic
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| W / S | Navigate up / down through settings |
|
||||
| Enter | Edit selected setting |
|
||||
| Q | Back to home screen |
|
||||
| Enter | Edit selected setting, or enter a sub-screen |
|
||||
| Q | Back one level (sub-screen → top level → home screen) |
|
||||
|
||||
**Available settings:**
|
||||
|
||||
@@ -275,17 +275,25 @@ Press **S** from the home screen to open settings. On first boot (when the devic
|
||||
|---------|-------------|
|
||||
| Device Name | Text entry — type a name, Enter to confirm |
|
||||
| Radio Preset | A / D to cycle presets (MeshCore Default, Long Range, Fast/Short, EU Default), Enter to apply |
|
||||
| Frequency | W / S to adjust, Enter to confirm |
|
||||
| Frequency | Text entry — type exact value (e.g. 916.575), Enter to confirm |
|
||||
| Bandwidth | W / S to cycle standard values (31.25 / 62.5 / 125 / 250 / 500 kHz), Enter to confirm |
|
||||
| Spreading Factor | W / S to adjust (5–12), Enter to confirm |
|
||||
| Coding Rate | W / S to adjust (5–8), Enter to confirm |
|
||||
| TX Power | W / S to adjust (1–20 dBm), Enter to confirm |
|
||||
| UTC Offset | W / S to adjust (-12 to +14), Enter to confirm |
|
||||
| Path Hash Mode | A / D to cycle (0 = 1-byte, 1 = 2-byte, 2 = 3-byte), Enter to confirm |
|
||||
| Channels | View existing channels, add hashtag channels, or delete non-primary channels (X) |
|
||||
| Msg Rcvd LED Pulse | Toggle keyboard backlight flash on new message (Enter to toggle) |
|
||||
| GPS Baud Rate | A / D to cycle (Default 38400 / 4800 / 9600 / 19200 / 38400 / 57600 / 115200), Enter to confirm. **Requires reboot to take effect.** |
|
||||
| Path Hash Mode | W / S to cycle (1-byte / 2-byte / 3-byte), Enter to confirm |
|
||||
| Dark Mode | Toggle inverted display — white text on black background (Enter to toggle) |
|
||||
| Contacts >> | Opens the Contacts sub-screen (see below) |
|
||||
| Channels >> | Opens the Channels sub-screen (see below) |
|
||||
| Device Info | Public key and firmware version (read-only) |
|
||||
|
||||
The bottom of the settings screen also displays your node ID and firmware version. On the 4G variant, IMEI, carrier name, and APN details are shown here as well.
|
||||
**Contacts sub-screen** — press Enter on the `Contacts >>` row to open. Contains the contact auto-add mode picker (Auto All / Custom / Manual) and, when set to Custom, per-type toggles for Chat, Repeater, Room Server, Sensor, and an Overwrite Oldest option. Press Q to return to the top-level settings list.
|
||||
|
||||
**Channels sub-screen** — press Enter on the `Channels >>` row to open. Lists all current channels, with an option to add hashtag channels or delete non-primary channels (X). Press Q to return to the top-level settings list.
|
||||
|
||||
The top-level settings screen also displays your node ID and firmware version. On the 4G variant, IMEI, carrier name, and APN details are shown here as well.
|
||||
|
||||
When adding a hashtag channel, type the channel name and press Enter. The channel secret is automatically derived from the name via SHA-256, matching the standard MeshCore hashtag convention.
|
||||
|
||||
@@ -437,12 +445,12 @@ Tap keys to type. Tap **Enter** to submit, or press the **Boot button** to cance
|
||||
|
||||
### Display Settings
|
||||
|
||||
The T5S3 Settings screen includes two additional options not available on the T-Deck Pro:
|
||||
The T5S3 Settings screen includes one additional display option not available on the T-Deck Pro:
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| **Dark Mode** | Inverts the display — white text on black background. Tap to toggle on/off. |
|
||||
| **Portrait Mode** | Rotates the display 90° from landscape (960×540) to portrait (540×960). Touch coordinates are automatically remapped. Text reader layout recalculates on orientation change. |
|
||||
| **Dark Mode** | Inverts the display — white text on black background. Tap to toggle on/off. Available on both T-Deck Pro and T5S3. |
|
||||
| **Portrait Mode** | Rotates the display 90° from landscape (960×540) to portrait (540×960). Touch coordinates are automatically remapped. Text reader layout recalculates on orientation change. T5S3 only. |
|
||||
|
||||
These settings are persisted and survive reboots.
|
||||
|
||||
|
||||
@@ -1193,12 +1193,24 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.gps_baudrate != 9600 && _prefs.gps_baudrate != 19200 &&
|
||||
_prefs.gps_baudrate != 38400 && _prefs.gps_baudrate != 57600 &&
|
||||
_prefs.gps_baudrate != 115200) {
|
||||
Serial.printf("PREFS: invalid gps_baudrate=%lu — reset to 0 (default)\n",
|
||||
(unsigned long)_prefs.gps_baudrate);
|
||||
_prefs.gps_baudrate = 0; // reset to default if invalid
|
||||
}
|
||||
// interference_threshold: 0 = disabled, minimum functional value is 14
|
||||
// interference_threshold: 0 = disabled, minimum functional value is 14, max sane ~30
|
||||
if (_prefs.interference_threshold > 0 && _prefs.interference_threshold < 14) {
|
||||
_prefs.interference_threshold = 0;
|
||||
}
|
||||
if (_prefs.interference_threshold > 50) {
|
||||
Serial.printf("PREFS: invalid interference_threshold=%d — reset to 0 (disabled)\n",
|
||||
_prefs.interference_threshold);
|
||||
_prefs.interference_threshold = 0; // garbage from prefs upgrade — disable
|
||||
}
|
||||
// Clamp remaining v1.0 fields that may contain garbage after upgrade from older firmware
|
||||
if (_prefs.path_hash_mode > 2) _prefs.path_hash_mode = 0;
|
||||
if (_prefs.autoadd_max_hops > 64) _prefs.autoadd_max_hops = 0;
|
||||
if (_prefs.dark_mode > 1) _prefs.dark_mode = 0;
|
||||
if (_prefs.portrait_mode > 1) _prefs.portrait_mode = 0;
|
||||
|
||||
#ifdef BLE_PIN_CODE // 123456 by default
|
||||
if (_prefs.ble_pin == 0) {
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#define FIRMWARE_VER_CODE 10
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "13 March 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 March 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "Meck v1.0"
|
||||
#define FIRMWARE_VERSION "Meck v1.1"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
|
||||
@@ -832,7 +832,27 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
|
||||
}
|
||||
}
|
||||
|
||||
// Default: enter/select (settings toggle, etc.)
|
||||
// Settings screen: context-dependent long press
|
||||
if (ui_task.isOnSettingsScreen()) {
|
||||
SettingsScreen* ss = (SettingsScreen*)ui_task.getSettingsScreen();
|
||||
if (ss) {
|
||||
if (ss->isEditingText()) {
|
||||
// Open VKB pre-populated with current edit buffer
|
||||
ui_task.showVirtualKeyboard(VKB_SETTINGS_TEXT, ss->getEditLabel(),
|
||||
ss->getEditBuf(), SETTINGS_TEXT_BUF - 1);
|
||||
return 0;
|
||||
}
|
||||
if (ss->isEditingNumOrPicker()) {
|
||||
return 0; // Consume — don't confirm prematurely
|
||||
}
|
||||
if (ss->isCursorOnDeletableChannel()) {
|
||||
return 'x'; // Triggers existing X key → EDIT_CONFIRM delete flow
|
||||
}
|
||||
}
|
||||
return KEY_ENTER; // All other settings rows: toggle/edit as normal
|
||||
}
|
||||
|
||||
// Default: enter/select
|
||||
return KEY_ENTER;
|
||||
}
|
||||
#endif
|
||||
@@ -1144,15 +1164,18 @@ void setup() {
|
||||
sensors.begin();
|
||||
MESH_DEBUG_PRINTLN("setup() - sensors.begin() done");
|
||||
|
||||
// IMPORTANT: sensors.begin() calls initBasicGPS() which steals the GPS pins for Serial1
|
||||
// We need to reinitialize Serial2 to reclaim them
|
||||
// IMPORTANT: sensors.begin() calls initBasicGPS() which steals the GPS pins for Serial1.
|
||||
// We must end Serial1 first, then reclaim the pins for Serial2 (which feeds gpsStream).
|
||||
#if HAS_GPS
|
||||
Serial2.end(); // Close any existing Serial2
|
||||
Serial1.end(); // Release GPS pins from Serial1's UART + ISR
|
||||
Serial2.end(); // Close any existing Serial2
|
||||
{
|
||||
uint32_t gps_baud = the_mesh.getNodePrefs()->gps_baudrate;
|
||||
Serial.printf("GPS: prefs gps_baudrate=%lu (0=use default)\n", (unsigned long)gps_baud);
|
||||
if (gps_baud == 0) gps_baud = GPS_BAUDRATE;
|
||||
Serial2.begin(gps_baud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS at %lu baud", (unsigned long)gps_baud);
|
||||
Serial.printf("GPS: Serial2 started at %lu baud (RX=%d TX=%d)\n",
|
||||
(unsigned long)gps_baud, GPS_RX_PIN, GPS_TX_PIN);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1353,10 +1376,12 @@ void setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// GPS power — honour saved pref, default to enabled on first boot
|
||||
// GPS power — honour saved pref, default to enabled on first boot.
|
||||
// GPS is critical for timesync on standalone variants without 4G.
|
||||
#if HAS_GPS
|
||||
{
|
||||
bool gps_wanted = the_mesh.getNodePrefs()->gps_enabled;
|
||||
Serial.printf("GPS: pref gps_enabled=%d\n", (int)gps_wanted);
|
||||
if (gps_wanted) {
|
||||
#ifdef PIN_GPS_EN
|
||||
digitalWrite(PIN_GPS_EN, GPS_EN_ACTIVE);
|
||||
@@ -1368,7 +1393,7 @@ void setup() {
|
||||
#endif
|
||||
sensors.setSettingValue("gps", "0");
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("setup() - GPS power %s", gps_wanted ? "on" : "off");
|
||||
Serial.printf("GPS: power %s, PIN_GPS_EN=%d\n", gps_wanted ? "ON" : "OFF", PIN_GPS_EN);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1390,6 +1415,21 @@ void loop() {
|
||||
|
||||
sensors.loop();
|
||||
|
||||
// GPS diagnostic — print sentence count every 30s so we can tell if Serial2 is receiving data
|
||||
#if HAS_GPS
|
||||
{
|
||||
static unsigned long lastGpsDiag = 0;
|
||||
if (millis() - lastGpsDiag >= 30000) {
|
||||
lastGpsDiag = millis();
|
||||
uint32_t sentences = gpsStream.getSentenceCount();
|
||||
uint16_t perSec = gpsStream.getSentencesPerSec();
|
||||
Serial.printf("GPS diag: %lu sentences total, %u/sec, Serial2.available=%d, lat=%.6f lon=%.6f\n",
|
||||
(unsigned long)sentences, perSec, Serial2.available(),
|
||||
sensors.node_lat, sensors.node_lon);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Map screen: periodically update own GPS position and contact markers
|
||||
#if HAS_GPS
|
||||
if (ui_task.isOnMapScreen()) {
|
||||
|
||||
@@ -51,6 +51,25 @@ extern MyMesh the_mesh;
|
||||
// ---------------------------------------------------------------------------
|
||||
#include "RadioPresets.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GPS baud rate options (shared with UITask GPS home page overlay)
|
||||
// ---------------------------------------------------------------------------
|
||||
static const uint32_t GPS_BAUD_OPTIONS[] = { 0, 4800, 9600, 19200, 38400, 57600, 115200 };
|
||||
#define GPS_BAUD_OPTION_COUNT 7
|
||||
|
||||
static inline const char* gpsBaudLabel(uint32_t baud, char* buf, int bufLen) {
|
||||
if (baud == 0) return "Default (38400)";
|
||||
snprintf(buf, bufLen, "%lu", (unsigned long)baud);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline int findGpsBaudIndex(uint32_t baud) {
|
||||
for (int i = 0; i < GPS_BAUD_OPTION_COUNT; i++) {
|
||||
if (GPS_BAUD_OPTIONS[i] == baud) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings row types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -68,6 +87,7 @@ enum SettingsRowType : uint8_t {
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
ROW_PORTRAIT_MODE, // Portrait orientation toggle
|
||||
#endif
|
||||
ROW_GPS_BAUD, // GPS baud rate picker (requires reboot)
|
||||
ROW_PATH_HASH_SIZE, // Path hash size (1, 2, or 3 bytes per hop)
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
ROW_WIFI_SETUP, // WiFi SSID/password configuration
|
||||
@@ -84,6 +104,8 @@ enum SettingsRowType : uint8_t {
|
||||
ROW_AUTOADD_ROOM, // Toggle: auto-add Room Servers
|
||||
ROW_AUTOADD_SENSOR, // Toggle: auto-add Sensors
|
||||
ROW_AUTOADD_OVERWRITE, // Toggle: overwrite oldest non-favourite when full
|
||||
ROW_CONTACTS_SUBMENU, // Folder row → enters Contacts sub-screen
|
||||
ROW_CHANNELS_SUBMENU, // Folder row → enters Channels sub-screen
|
||||
ROW_CH_HEADER, // "--- Channels ---" separator
|
||||
ROW_CHANNEL, // A channel entry (dynamic, index stored separately)
|
||||
ROW_ADD_CHANNEL, // "+ Add Hashtag Channel"
|
||||
@@ -111,6 +133,15 @@ enum EditMode : uint8_t {
|
||||
#endif
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings sub-screens (collapsible sections)
|
||||
// ---------------------------------------------------------------------------
|
||||
enum SubScreen : uint8_t {
|
||||
SUB_NONE, // Top-level settings list
|
||||
SUB_CONTACTS, // Contacts settings sub-screen
|
||||
SUB_CHANNELS, // Channels management sub-screen
|
||||
};
|
||||
|
||||
// Max rows in the settings list (increased for contact sub-toggles + WiFi)
|
||||
#if defined(HAS_4G_MODEM) && defined(MECK_WIFI_COMPANION)
|
||||
#define SETTINGS_MAX_ROWS 56 // Extra rows for IMEI, Carrier, APN, contacts, WiFi
|
||||
@@ -153,6 +184,10 @@ private:
|
||||
// Onboarding mode
|
||||
bool _onboarding;
|
||||
|
||||
// Sub-screen navigation
|
||||
SubScreen _subScreen;
|
||||
int _savedTopCursor; // cursor position to restore when leaving sub-screen
|
||||
|
||||
// Dirty flag for radio params  prompt to apply
|
||||
bool _radioChanged;
|
||||
|
||||
@@ -240,66 +275,68 @@ private:
|
||||
void rebuildRows() {
|
||||
_numRows = 0;
|
||||
|
||||
addRow(ROW_NAME);
|
||||
addRow(ROW_RADIO_PRESET);
|
||||
addRow(ROW_FREQ);
|
||||
addRow(ROW_BW);
|
||||
addRow(ROW_SF);
|
||||
addRow(ROW_CR);
|
||||
addRow(ROW_TX_POWER);
|
||||
addRow(ROW_UTC_OFFSET);
|
||||
addRow(ROW_MSG_NOTIFY);
|
||||
addRow(ROW_PATH_HASH_SIZE);
|
||||
addRow(ROW_DARK_MODE);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
addRow(ROW_PORTRAIT_MODE);
|
||||
#endif
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
addRow(ROW_WIFI_SETUP);
|
||||
addRow(ROW_WIFI_TOGGLE);
|
||||
#endif
|
||||
#ifdef HAS_4G_MODEM
|
||||
addRow(ROW_MODEM_TOGGLE);
|
||||
// addRow(ROW_RINGTONE);
|
||||
#endif
|
||||
|
||||
// --- Contacts section ---
|
||||
addRow(ROW_CONTACT_HEADER);
|
||||
addRow(ROW_CONTACT_MODE);
|
||||
|
||||
// Show per-type sub-toggles only in Custom mode
|
||||
if (getContactMode() == CONTACT_MODE_CUSTOM) {
|
||||
addRow(ROW_AUTOADD_CHAT);
|
||||
addRow(ROW_AUTOADD_REPEATER);
|
||||
addRow(ROW_AUTOADD_ROOM);
|
||||
addRow(ROW_AUTOADD_SENSOR);
|
||||
addRow(ROW_AUTOADD_OVERWRITE);
|
||||
}
|
||||
|
||||
// --- Channels section ---
|
||||
addRow(ROW_CH_HEADER);
|
||||
|
||||
// Enumerate current channels
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails ch;
|
||||
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
|
||||
addRow(ROW_CHANNEL, i);
|
||||
} else {
|
||||
break; // channels are contiguous
|
||||
if (_subScreen == SUB_CONTACTS) {
|
||||
// --- Contacts sub-screen: only contact-related rows ---
|
||||
addRow(ROW_CONTACT_MODE);
|
||||
if (getContactMode() == CONTACT_MODE_CUSTOM) {
|
||||
addRow(ROW_AUTOADD_CHAT);
|
||||
addRow(ROW_AUTOADD_REPEATER);
|
||||
addRow(ROW_AUTOADD_ROOM);
|
||||
addRow(ROW_AUTOADD_SENSOR);
|
||||
addRow(ROW_AUTOADD_OVERWRITE);
|
||||
}
|
||||
} else if (_subScreen == SUB_CHANNELS) {
|
||||
// --- Channels sub-screen: only channel-related rows ---
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails ch;
|
||||
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
|
||||
addRow(ROW_CHANNEL, i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
addRow(ROW_ADD_CHANNEL);
|
||||
} else {
|
||||
// --- Top-level settings list ---
|
||||
addRow(ROW_NAME);
|
||||
addRow(ROW_RADIO_PRESET);
|
||||
addRow(ROW_FREQ);
|
||||
addRow(ROW_BW);
|
||||
addRow(ROW_SF);
|
||||
addRow(ROW_CR);
|
||||
addRow(ROW_TX_POWER);
|
||||
addRow(ROW_UTC_OFFSET);
|
||||
addRow(ROW_MSG_NOTIFY);
|
||||
addRow(ROW_GPS_BAUD);
|
||||
addRow(ROW_PATH_HASH_SIZE);
|
||||
addRow(ROW_DARK_MODE);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
addRow(ROW_PORTRAIT_MODE);
|
||||
#endif
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
addRow(ROW_WIFI_SETUP);
|
||||
addRow(ROW_WIFI_TOGGLE);
|
||||
#endif
|
||||
#ifdef HAS_4G_MODEM
|
||||
addRow(ROW_MODEM_TOGGLE);
|
||||
#endif
|
||||
|
||||
// Folder rows for sub-screens
|
||||
addRow(ROW_CONTACTS_SUBMENU);
|
||||
addRow(ROW_CHANNELS_SUBMENU);
|
||||
|
||||
// Info section (stays at top level)
|
||||
addRow(ROW_INFO_HEADER);
|
||||
addRow(ROW_PUB_KEY);
|
||||
addRow(ROW_FIRMWARE);
|
||||
|
||||
#ifdef HAS_4G_MODEM
|
||||
addRow(ROW_IMEI);
|
||||
addRow(ROW_OPERATOR_INFO);
|
||||
addRow(ROW_APN);
|
||||
#endif
|
||||
}
|
||||
|
||||
addRow(ROW_ADD_CHANNEL);
|
||||
addRow(ROW_INFO_HEADER);
|
||||
addRow(ROW_PUB_KEY);
|
||||
addRow(ROW_FIRMWARE);
|
||||
|
||||
#ifdef HAS_4G_MODEM
|
||||
addRow(ROW_IMEI);
|
||||
addRow(ROW_OPERATOR_INFO);
|
||||
addRow(ROW_APN);
|
||||
#endif
|
||||
|
||||
// Clamp cursor
|
||||
if (_cursor >= _numRows) _cursor = _numRows - 1;
|
||||
if (_cursor < 0) _cursor = 0;
|
||||
@@ -321,7 +358,7 @@ private:
|
||||
#ifdef HAS_4G_MODEM
|
||||
&& t != ROW_IMEI && t != ROW_OPERATOR_INFO
|
||||
#endif
|
||||
;
|
||||
; // ROW_CONTACTS_SUBMENU and ROW_CHANNELS_SUBMENU ARE selectable
|
||||
}
|
||||
|
||||
void skipNonSelectable(int dir) {
|
||||
@@ -439,12 +476,15 @@ public:
|
||||
_numRows(0), _cursor(0), _scrollTop(0),
|
||||
_editMode(EDIT_NONE), _editPos(0), _editPickerIdx(0),
|
||||
_editFloat(0), _editInt(0), _confirmAction(0),
|
||||
_onboarding(false), _radioChanged(false) {
|
||||
_onboarding(false), _subScreen(SUB_NONE), _savedTopCursor(0),
|
||||
_radioChanged(false) {
|
||||
memset(_editBuf, 0, sizeof(_editBuf));
|
||||
}
|
||||
|
||||
void enter() {
|
||||
_editMode = EDIT_NONE;
|
||||
_subScreen = SUB_NONE;
|
||||
_savedTopCursor = 0;
|
||||
_cursor = 0;
|
||||
_scrollTop = 0;
|
||||
_radioChanged = false;
|
||||
@@ -628,6 +668,10 @@ public:
|
||||
display.setCursor(0, 0);
|
||||
if (_onboarding) {
|
||||
display.print("Welcome! Setup");
|
||||
} else if (_subScreen == SUB_CONTACTS) {
|
||||
display.print("Settings > Contacts");
|
||||
} else if (_subScreen == SUB_CHANNELS) {
|
||||
display.print("Settings > Channels");
|
||||
} else {
|
||||
display.print("Settings");
|
||||
}
|
||||
@@ -773,6 +817,19 @@ public:
|
||||
display.print(tmp);
|
||||
break;
|
||||
|
||||
case ROW_GPS_BAUD: {
|
||||
char baudStr[16];
|
||||
if (editing && _editMode == EDIT_PICKER) {
|
||||
snprintf(tmp, sizeof(tmp), "< GPS Baud: %s > *",
|
||||
gpsBaudLabel(GPS_BAUD_OPTIONS[_editPickerIdx], baudStr, sizeof(baudStr)));
|
||||
} else {
|
||||
snprintf(tmp, sizeof(tmp), "GPS Baud: %s *",
|
||||
gpsBaudLabel(_prefs->gps_baudrate, baudStr, sizeof(baudStr)));
|
||||
}
|
||||
display.print(tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
case ROW_DARK_MODE:
|
||||
snprintf(tmp, sizeof(tmp), "Dark Mode: %s",
|
||||
_prefs->dark_mode ? "ON" : "OFF");
|
||||
@@ -817,6 +874,17 @@ public:
|
||||
// break;
|
||||
#endif
|
||||
|
||||
// --- Submenu folder rows ---
|
||||
case ROW_CONTACTS_SUBMENU:
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
|
||||
display.print("Contacts >>");
|
||||
break;
|
||||
|
||||
case ROW_CHANNELS_SUBMENU:
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
|
||||
display.print("Channels >>");
|
||||
break;
|
||||
|
||||
// --- Contacts section ---
|
||||
case ROW_CONTACT_HEADER:
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
@@ -1093,10 +1161,17 @@ public:
|
||||
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
if (_editMode == EDIT_NONE) {
|
||||
display.print("Swipe:Scroll");
|
||||
const char* r = "Tap:Toggle Hold:Edit";
|
||||
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
|
||||
display.print(r);
|
||||
if (_subScreen != SUB_NONE) {
|
||||
display.print("Boot:Back");
|
||||
const char* r = "Tap:Toggle Hold:Edit";
|
||||
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
|
||||
display.print(r);
|
||||
} else {
|
||||
display.print("Swipe:Scroll");
|
||||
const char* r = "Tap:Toggle Hold:Edit";
|
||||
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
|
||||
display.print(r);
|
||||
}
|
||||
} else if (_editMode == EDIT_NUMBER) {
|
||||
display.print("Swipe:Adjust");
|
||||
const char* r = "Tap:OK Boot:Cancel";
|
||||
@@ -1155,7 +1230,11 @@ public:
|
||||
} else if (_editMode == EDIT_CONFIRM) {
|
||||
// Footer already covered by overlay
|
||||
} else {
|
||||
display.print("Q:Bck");
|
||||
if (_subScreen != SUB_NONE) {
|
||||
display.print("Q:Back");
|
||||
} else {
|
||||
display.print("Q:Bck");
|
||||
}
|
||||
const char* r = "W/S:Up/Dwn Entr:Chng";
|
||||
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
|
||||
display.print(r);
|
||||
@@ -1382,6 +1461,9 @@ public:
|
||||
if (type == ROW_CONTACT_MODE) {
|
||||
_editPickerIdx--;
|
||||
if (_editPickerIdx < 0) _editPickerIdx = CONTACT_MODE_COUNT - 1;
|
||||
} else if (type == ROW_GPS_BAUD) {
|
||||
_editPickerIdx--;
|
||||
if (_editPickerIdx < 0) _editPickerIdx = GPS_BAUD_OPTION_COUNT - 1;
|
||||
} else {
|
||||
// Radio preset
|
||||
_editPickerIdx--;
|
||||
@@ -1393,6 +1475,9 @@ public:
|
||||
if (type == ROW_CONTACT_MODE) {
|
||||
_editPickerIdx++;
|
||||
if (_editPickerIdx >= CONTACT_MODE_COUNT) _editPickerIdx = 0;
|
||||
} else if (type == ROW_GPS_BAUD) {
|
||||
_editPickerIdx++;
|
||||
if (_editPickerIdx >= GPS_BAUD_OPTION_COUNT) _editPickerIdx = 0;
|
||||
} else {
|
||||
// Radio preset
|
||||
_editPickerIdx++;
|
||||
@@ -1404,6 +1489,12 @@ public:
|
||||
if (type == ROW_CONTACT_MODE) {
|
||||
applyContactMode(_editPickerIdx);
|
||||
_editMode = EDIT_NONE;
|
||||
} else if (type == ROW_GPS_BAUD) {
|
||||
_prefs->gps_baudrate = GPS_BAUD_OPTIONS[_editPickerIdx];
|
||||
the_mesh.savePrefs();
|
||||
_editMode = EDIT_NONE;
|
||||
Serial.printf("Settings: GPS baud set to %lu (reboot to apply)\n",
|
||||
(unsigned long)_prefs->gps_baudrate);
|
||||
} else {
|
||||
// Apply radio preset
|
||||
if (_editPickerIdx >= 0 && _editPickerIdx < (int)NUM_RADIO_PRESETS) {
|
||||
@@ -1583,6 +1674,9 @@ public:
|
||||
case ROW_PATH_HASH_SIZE:
|
||||
startEditInt(_prefs->path_hash_mode + 1); // display as 1-3
|
||||
break;
|
||||
case ROW_GPS_BAUD:
|
||||
startEditPicker(findGpsBaudIndex(_prefs->gps_baudrate));
|
||||
break;
|
||||
case ROW_DARK_MODE:
|
||||
_prefs->dark_mode = _prefs->dark_mode ? 0 : 1;
|
||||
the_mesh.savePrefs();
|
||||
@@ -1704,6 +1798,24 @@ public:
|
||||
(_prefs->autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) ? "ON" : "OFF");
|
||||
break;
|
||||
|
||||
// --- Submenu folder rows ---
|
||||
case ROW_CONTACTS_SUBMENU:
|
||||
_savedTopCursor = _cursor;
|
||||
_subScreen = SUB_CONTACTS;
|
||||
_cursor = 0;
|
||||
_scrollTop = 0;
|
||||
rebuildRows();
|
||||
Serial.println("Settings: entered Contacts sub-screen");
|
||||
break;
|
||||
case ROW_CHANNELS_SUBMENU:
|
||||
_savedTopCursor = _cursor;
|
||||
_subScreen = SUB_CHANNELS;
|
||||
_cursor = 0;
|
||||
_scrollTop = 0;
|
||||
rebuildRows();
|
||||
Serial.println("Settings: entered Channels sub-screen");
|
||||
break;
|
||||
|
||||
case ROW_ADD_CHANNEL:
|
||||
startEditText("");
|
||||
break;
|
||||
@@ -1727,8 +1839,18 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Q: back  if radio changed, prompt to apply first
|
||||
// Q: back -- if in sub-screen, return to top level; else exit settings
|
||||
if (c == 'q' || c == 'Q') {
|
||||
if (_subScreen != SUB_NONE) {
|
||||
// Return to top-level settings list
|
||||
_subScreen = SUB_NONE;
|
||||
rebuildRows();
|
||||
_cursor = _savedTopCursor;
|
||||
if (_cursor >= _numRows) _cursor = _numRows - 1;
|
||||
skipNonSelectable(1);
|
||||
Serial.println("Settings: back to top level");
|
||||
return true;
|
||||
}
|
||||
if (_radioChanged) {
|
||||
_editMode = EDIT_CONFIRM;
|
||||
_confirmAction = 2;
|
||||
|
||||
@@ -138,6 +138,7 @@ class HomeScreen : public UIScreen {
|
||||
unsigned long _shutdown_at; // earliest time to proceed with shutdown (after e-ink refresh)
|
||||
bool _editing_utc;
|
||||
int8_t _saved_utc_offset; // for cancel/undo
|
||||
|
||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||
|
||||
|
||||
@@ -254,7 +255,7 @@ public:
|
||||
_shutdown_init(false), _shutdown_at(0), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { }
|
||||
|
||||
bool isEditingUTC() const { return _editing_utc; }
|
||||
void cancelEditUTC() {
|
||||
void cancelEditing() {
|
||||
if (_editing_utc) {
|
||||
_node_prefs->utc_offset_hours = _saved_utc_offset;
|
||||
_editing_utc = false;
|
||||
@@ -666,7 +667,9 @@ public:
|
||||
display.drawTextLeftAlign(0, y, "time(U)");
|
||||
display.drawTextRightAlign(display.width()-1, y, "no sync");
|
||||
}
|
||||
y = y + 12;
|
||||
}
|
||||
|
||||
// UTC offset editor overlay
|
||||
if (_editing_utc) {
|
||||
// Draw background box
|
||||
@@ -686,6 +689,7 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, by + bh - 10, "W/S:adj Enter:ok Q:cancel");
|
||||
display.setTextSize(1);
|
||||
}
|
||||
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
} else if (_page == HomePage::SENSORS) {
|
||||
@@ -842,7 +846,7 @@ public:
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return _editing_utc ? 700 : 5000; // match e-ink refresh cycle while editing UTC
|
||||
return _editing_utc ? 700 : 5000;
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
@@ -1175,16 +1179,18 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
map_screen = nullptr;
|
||||
#endif
|
||||
|
||||
// Apply saved dark mode preference before first render
|
||||
if (_node_prefs->dark_mode) {
|
||||
::display.setDarkMode(true);
|
||||
}
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
// Apply saved display preferences before first render
|
||||
if (_node_prefs->portrait_mode) {
|
||||
::display.setPortraitMode(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Apply saved dark mode preference (both T-Deck Pro and T5S3)
|
||||
if (_node_prefs->dark_mode) {
|
||||
::display.setDarkMode(true);
|
||||
}
|
||||
|
||||
setCurrScreen(splash);
|
||||
}
|
||||
|
||||
@@ -1546,6 +1552,7 @@ if (curr) curr->poll();
|
||||
display.setDarkMode(_node_prefs->dark_mode != 0);
|
||||
}
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
// Sync portrait mode with prefs (T5S3 only)
|
||||
if (_node_prefs && display.isPortraitMode() != (_node_prefs->portrait_mode != 0)) {
|
||||
display.setPortraitMode(_node_prefs->portrait_mode != 0);
|
||||
// Text reader layout depends on orientation — force recalculation
|
||||
@@ -1987,7 +1994,7 @@ void UITask::injectKey(char c) {
|
||||
|
||||
void UITask::gotoHomeScreen() {
|
||||
// Cancel any active editing state when navigating to home
|
||||
((HomeScreen *) home)->cancelEditUTC();
|
||||
((HomeScreen *) home)->cancelEditing();
|
||||
setCurrScreen(home);
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
|
||||
@@ -29,6 +29,7 @@ enum VKBPurpose {
|
||||
VKB_ADMIN_CLI, // Repeater admin CLI command
|
||||
VKB_NOTES, // Insert text into notes
|
||||
VKB_SETTINGS_NAME, // Edit node name
|
||||
VKB_SETTINGS_TEXT, // Generic settings text edit (channel name, freq, APN)
|
||||
VKB_WIFI_PASSWORD, // WiFi password entry (settings screen)
|
||||
#ifdef MECK_WEB_READER
|
||||
VKB_WEB_URL, // Web reader URL entry
|
||||
|
||||
Reference in New Issue
Block a user