mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-06 13:32:43 +02:00
standalone device phase 1 complete - utc offset from gps homepage and gps timesync without ble enabled
This commit is contained in:
@@ -228,6 +228,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||
file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||
file.read((uint8_t *)&_prefs.utc_offset_hours, sizeof(_prefs.utc_offset_hours)); // 88
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -263,6 +264,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||
file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||
file.write((uint8_t *)&_prefs.utc_offset_hours, sizeof(_prefs.utc_offset_hours)); // 88
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -598,4 +600,4 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
|
||||
}
|
||||
return false; // error
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -523,13 +523,6 @@ void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint3
|
||||
const char *text) {
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
queueMessage(from, TXT_TYPE_CLI_DATA, pkt, sender_timestamp, NULL, 0, text);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Route CLI responses to admin UI if active
|
||||
if (_ui && _admin_contact_idx >= 0) {
|
||||
_ui->onAdminCliResponse(from.name, text);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
@@ -657,55 +650,6 @@ bool MyMesh::uiSendDirectMessage(uint32_t contact_idx, const char* text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyMesh::uiLoginToRepeater(uint32_t contact_idx, const char* password) {
|
||||
ContactInfo contact;
|
||||
if (!getContactByIdx(contact_idx, contact)) return false;
|
||||
|
||||
ContactInfo* recipient = lookupContactByPubKey(contact.id.pub_key, PUB_KEY_SIZE);
|
||||
if (!recipient) return false;
|
||||
|
||||
uint32_t est_timeout;
|
||||
int result = sendLogin(*recipient, password, est_timeout);
|
||||
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
MESH_DEBUG_PRINTLN("UI: Admin login send failed to %s", recipient->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
clearPendingReqs();
|
||||
memcpy(&pending_login, recipient->id.pub_key, 4);
|
||||
_admin_contact_idx = contact_idx;
|
||||
|
||||
MESH_DEBUG_PRINTLN("UI: Admin login sent to %s (%s), timeout=%dms",
|
||||
recipient->name, result == MSG_SEND_SENT_FLOOD ? "flood" : "direct",
|
||||
est_timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyMesh::uiSendCliCommand(uint32_t contact_idx, const char* command) {
|
||||
ContactInfo contact;
|
||||
if (!getContactByIdx(contact_idx, contact)) return false;
|
||||
|
||||
ContactInfo* recipient = lookupContactByPubKey(contact.id.pub_key, PUB_KEY_SIZE);
|
||||
if (!recipient) return false;
|
||||
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
uint32_t est_timeout;
|
||||
int result = sendCommandData(*recipient, timestamp, 0, command, est_timeout);
|
||||
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
MESH_DEBUG_PRINTLN("UI: CLI command send failed to %s: %s", recipient->name, command);
|
||||
return false;
|
||||
}
|
||||
|
||||
_admin_contact_idx = contact_idx;
|
||||
|
||||
MESH_DEBUG_PRINTLN("UI: CLI command sent to %s (%s): %s, timeout=%dms",
|
||||
recipient->name, result == MSG_SEND_SENT_FLOOD ? "flood" : "direct",
|
||||
command, est_timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||
uint8_t len, uint8_t *reply) {
|
||||
if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
|
||||
@@ -764,11 +708,6 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
out_frame[i++] = 0; // legacy: is_admin = false
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6);
|
||||
i += 6; // pub_key_prefix
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Notify UI of successful legacy login
|
||||
if (_ui) _ui->onAdminLoginResult(true, 0, tag);
|
||||
#endif
|
||||
} else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response
|
||||
uint16_t keep_alive_secs = ((uint16_t)data[5]) * 16;
|
||||
if (keep_alive_secs > 0) {
|
||||
@@ -782,21 +721,11 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
i += 4; // NEW: include server timestamp
|
||||
out_frame[i++] = data[7]; // NEW (v7): ACL permissions
|
||||
out_frame[i++] = data[12]; // FIRMWARE_VER_LEVEL
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Notify UI of successful login
|
||||
if (_ui) _ui->onAdminLoginResult(true, data[6], tag);
|
||||
#endif
|
||||
} else {
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_FAIL;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6);
|
||||
i += 6; // pub_key_prefix
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Notify UI of login failure
|
||||
if (_ui) _ui->onAdminLoginResult(false, 0, 0);
|
||||
#endif
|
||||
}
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (len > 4 && // check for status response
|
||||
@@ -968,7 +897,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
memset(_sent_track, 0, sizeof(_sent_track));
|
||||
_sent_track_idx = 0;
|
||||
_admin_contact_idx = -1;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
@@ -1022,6 +950,7 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||
_prefs.utc_offset_hours = constrain(_prefs.utc_offset_hours, -12, 14); // Valid timezone range
|
||||
|
||||
#ifdef BLE_PIN_CODE // 123456 by default
|
||||
if (_prefs.ble_pin == 0) {
|
||||
@@ -1805,6 +1734,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
savePrefs();
|
||||
}
|
||||
#endif
|
||||
// UTC offset for local clock display (works regardless of GPS)
|
||||
if (strcmp(sp, "utc_offset") == 0) {
|
||||
int offset = atoi(np);
|
||||
_prefs.utc_offset_hours = constrain(offset, -12, 14);
|
||||
savePrefs();
|
||||
}
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "Meck v0.8.1"
|
||||
#define FIRMWARE_VERSION "Meck v0.8.2"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
|
||||
@@ -28,4 +28,5 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
||||
uint32_t gps_interval; // GPS read interval in seconds
|
||||
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
||||
int8_t utc_offset_hours; // UTC offset in hours (-12 to +14), default 0
|
||||
};
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <SD.h>
|
||||
#include "TextReaderScreen.h"
|
||||
#include "ContactsScreen.h"
|
||||
#include "RepeaterAdminScreen.h"
|
||||
extern SPIClass displaySpi; // From GxEPDDisplay.cpp, shared SPI bus
|
||||
|
||||
TCA8418Keyboard keyboard(I2C_ADDR_KEYBOARD, &Wire);
|
||||
@@ -380,6 +379,13 @@ void setup() {
|
||||
MESH_DEBUG_PRINTLN("setup() - GPS enabled by default");
|
||||
#endif
|
||||
|
||||
// T-Deck Pro: BLE starts disabled for standalone-first operation
|
||||
// User can toggle it on from the Bluetooth home page (Enter or long-press)
|
||||
#if defined(LilyGo_TDeck_Pro) && defined(BLE_PIN_CODE)
|
||||
serial_interface.disable();
|
||||
MESH_DEBUG_PRINTLN("setup() - BLE disabled at boot (standalone mode)");
|
||||
#endif
|
||||
|
||||
MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ===");
|
||||
}
|
||||
|
||||
@@ -631,56 +637,6 @@ void handleKeyboardInput() {
|
||||
return;
|
||||
}
|
||||
|
||||
// *** REPEATER ADMIN MODE ***
|
||||
if (ui_task.isOnRepeaterAdmin()) {
|
||||
RepeaterAdminScreen* admin = (RepeaterAdminScreen*)ui_task.getRepeaterAdminScreen();
|
||||
RepeaterAdminScreen::AdminState astate = admin->getState();
|
||||
|
||||
// In password entry, pass all printable chars and special keys through
|
||||
// Q only navigates back if password is empty (handleInput returns false)
|
||||
if (astate == RepeaterAdminScreen::STATE_PASSWORD_ENTRY) {
|
||||
if (key == 'q' || key == 'Q') {
|
||||
// Try passing to screen - if not handled (empty password), navigate back
|
||||
bool handled = admin->handleInput(key);
|
||||
if (!handled) {
|
||||
Serial.println("Nav: Back to contacts from admin login");
|
||||
ui_task.gotoContactsScreen();
|
||||
}
|
||||
ui_task.forceRefresh();
|
||||
} else {
|
||||
ui_task.injectKey(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// In menu state
|
||||
if (astate == RepeaterAdminScreen::STATE_MENU) {
|
||||
if (key == 'q' || key == 'Q') {
|
||||
Serial.println("Nav: Back to contacts from admin menu");
|
||||
ui_task.gotoContactsScreen();
|
||||
return;
|
||||
}
|
||||
// C key: allow entering compose mode from admin menu
|
||||
if (key == 'c' || key == 'C') {
|
||||
composeDM = false;
|
||||
composeDMContactIdx = -1;
|
||||
composeMode = true;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
drawComposeScreen();
|
||||
lastComposeRefresh = millis();
|
||||
return;
|
||||
}
|
||||
// All other keys pass to admin screen
|
||||
ui_task.injectKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// In waiting/response/error states, pass all keys through
|
||||
ui_task.injectKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal mode - not composing
|
||||
switch (key) {
|
||||
case 'c':
|
||||
@@ -741,7 +697,7 @@ void handleKeyboardInput() {
|
||||
case 'w':
|
||||
case 'W':
|
||||
// Navigate up/previous (scroll on channel screen)
|
||||
if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen() || ui_task.isOnRepeaterAdmin()) {
|
||||
if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen()) {
|
||||
ui_task.injectKey('w'); // Pass directly for channel/contacts switching
|
||||
} else {
|
||||
Serial.println("Nav: Previous");
|
||||
@@ -752,7 +708,7 @@ void handleKeyboardInput() {
|
||||
case 's':
|
||||
case 'S':
|
||||
// Navigate down/next (scroll on channel screen)
|
||||
if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen() || ui_task.isOnRepeaterAdmin()) {
|
||||
if (ui_task.isOnChannelScreen() || ui_task.isOnContactsScreen()) {
|
||||
ui_task.injectKey('s'); // Pass directly for channel/contacts switching
|
||||
} else {
|
||||
Serial.println("Nav: Next");
|
||||
@@ -784,7 +740,6 @@ void handleKeyboardInput() {
|
||||
|
||||
case '\r':
|
||||
// Select/Enter - if on contacts screen, enter DM compose for chat contacts
|
||||
// or repeater admin for repeater contacts
|
||||
if (ui_task.isOnContactsScreen()) {
|
||||
ContactsScreen* cs = (ContactsScreen*)ui_task.getContactsScreen();
|
||||
int idx = cs->getSelectedContactIdx();
|
||||
@@ -799,15 +754,9 @@ void handleKeyboardInput() {
|
||||
Serial.printf("Entering DM compose to %s (idx %d)\n", composeDMName, idx);
|
||||
drawComposeScreen();
|
||||
lastComposeRefresh = millis();
|
||||
} else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) {
|
||||
// Open repeater admin screen
|
||||
char rname[32];
|
||||
cs->getSelectedContactName(rname, sizeof(rname));
|
||||
Serial.printf("Opening repeater admin for %s (idx %d)\n", rname, idx);
|
||||
ui_task.gotoRepeaterAdmin(idx);
|
||||
} else if (idx >= 0) {
|
||||
// Non-chat, non-repeater contact (room, sensor, etc.) - future use
|
||||
Serial.printf("Selected contact type=%d idx=%d\n", ctype, idx);
|
||||
// Non-chat contact selected (repeater, room, etc.) - future use
|
||||
Serial.printf("Selected non-chat contact type=%d idx=%d\n", ctype, idx);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Nav: Enter/Select");
|
||||
@@ -818,9 +767,21 @@ void handleKeyboardInput() {
|
||||
case 'q':
|
||||
case 'Q':
|
||||
case '\b':
|
||||
// Go back to home screen (admin mode handled above)
|
||||
Serial.println("Nav: Back to home");
|
||||
ui_task.gotoHomeScreen();
|
||||
// If editing UTC offset on GPS page, pass through to cancel
|
||||
if (ui_task.isEditingHomeScreen()) {
|
||||
ui_task.injectKey('q');
|
||||
} else {
|
||||
// Go back to home screen
|
||||
Serial.println("Nav: Back to home");
|
||||
ui_task.gotoHomeScreen();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
case 'U':
|
||||
// UTC offset editing (on GPS home page)
|
||||
Serial.println("Nav: UTC offset");
|
||||
ui_task.injectKey('u');
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include "../MyMesh.h"
|
||||
#include "target.h"
|
||||
#include "RepeaterAdminScreen.h"
|
||||
#ifdef WIFI_SSID
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
@@ -103,6 +102,8 @@ class HomeScreen : public UIScreen {
|
||||
NodePrefs* _node_prefs;
|
||||
uint8_t _page;
|
||||
bool _shutdown_init;
|
||||
bool _editing_utc;
|
||||
int8_t _saved_utc_offset; // for cancel/undo
|
||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||
|
||||
|
||||
@@ -184,7 +185,15 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts)
|
||||
public:
|
||||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
||||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
||||
_shutdown_init(false), sensors_lpp(200) { }
|
||||
_shutdown_init(false), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { }
|
||||
|
||||
bool isEditingUTC() const { return _editing_utc; }
|
||||
void cancelEditUTC() {
|
||||
if (_editing_utc) {
|
||||
_node_prefs->utc_offset_hours = _saved_utc_offset;
|
||||
_editing_utc = false;
|
||||
}
|
||||
}
|
||||
|
||||
void poll() override {
|
||||
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
||||
@@ -205,6 +214,29 @@ public:
|
||||
// battery voltage
|
||||
renderBatteryIndicator(display, _task->getBattMilliVolts());
|
||||
|
||||
// centered clock (tinyfont) - only show when time is valid
|
||||
{
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
if (now > 1700000000) { // valid timestamp (after ~Nov 2023)
|
||||
// Apply UTC offset from prefs
|
||||
int32_t local = (int32_t)now + ((int32_t)_node_prefs->utc_offset_hours * 3600);
|
||||
int hrs = (local / 3600) % 24;
|
||||
if (hrs < 0) hrs += 24;
|
||||
int mins = (local / 60) % 60;
|
||||
if (mins < 0) mins += 60;
|
||||
|
||||
char timeBuf[6];
|
||||
sprintf(timeBuf, "%02d:%02d", hrs, mins);
|
||||
|
||||
display.setTextSize(0); // tinyfont
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t tw = display.getTextWidth(timeBuf);
|
||||
int clockX = (display.width() - tw) / 2;
|
||||
display.setCursor(clockX, -3); // align with battery text Y
|
||||
display.print(timeBuf);
|
||||
display.setTextSize(1); // restore
|
||||
}
|
||||
}
|
||||
// curr page indicator
|
||||
int y = 14;
|
||||
int x = display.width() / 2 - 5 * (HomePage::Count-1);
|
||||
@@ -332,6 +364,42 @@ public:
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
}
|
||||
// Show RTC time and UTC offset on GPS page
|
||||
{
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
if (now > 1700000000) {
|
||||
int32_t local = (int32_t)now + ((int32_t)_node_prefs->utc_offset_hours * 3600);
|
||||
int hrs = (local / 3600) % 24;
|
||||
if (hrs < 0) hrs += 24;
|
||||
int mins = (local / 60) % 60;
|
||||
if (mins < 0) mins += 60;
|
||||
display.drawTextLeftAlign(0, y, "time(U)");
|
||||
sprintf(buf, "%02d:%02d UTC%+d", hrs, mins, _node_prefs->utc_offset_hours);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
} else {
|
||||
display.drawTextLeftAlign(0, y, "time(U)");
|
||||
display.drawTextRightAlign(display.width()-1, y, "no sync");
|
||||
}
|
||||
}
|
||||
// UTC offset editor overlay
|
||||
if (_editing_utc) {
|
||||
// Draw background box
|
||||
int bx = 4, by = 20, bw = display.width() - 8, bh = 40;
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(bx, by, bw, bh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(bx, by, bw, bh);
|
||||
|
||||
// Show current offset value
|
||||
display.setTextSize(2);
|
||||
sprintf(buf, "UTC%+d", _node_prefs->utc_offset_hours);
|
||||
display.drawTextCentered(display.width() / 2, by + 4, buf);
|
||||
|
||||
// Show controls hint
|
||||
display.setTextSize(0);
|
||||
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) {
|
||||
@@ -415,10 +483,44 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
|
||||
}
|
||||
}
|
||||
return 5000; // next render after 5000 ms
|
||||
return _editing_utc ? 700 : 5000; // match e-ink refresh cycle while editing UTC
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
// UTC offset editing mode - intercept all keys
|
||||
if (_editing_utc) {
|
||||
if (c == 'w' || c == KEY_PREV) {
|
||||
// Increment offset
|
||||
if (_node_prefs->utc_offset_hours < 14) {
|
||||
_node_prefs->utc_offset_hours++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == 's' || c == KEY_NEXT) {
|
||||
// Decrement offset
|
||||
if (_node_prefs->utc_offset_hours > -12) {
|
||||
_node_prefs->utc_offset_hours--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
// Save and exit
|
||||
Serial.printf("UTC offset saving: %d\n", _node_prefs->utc_offset_hours);
|
||||
the_mesh.savePrefs();
|
||||
_editing_utc = false;
|
||||
_task->showAlert("UTC offset saved", 800);
|
||||
Serial.println("UTC offset save complete");
|
||||
return true;
|
||||
}
|
||||
if (c == 'q' || c == 'u') {
|
||||
// Cancel - restore original value
|
||||
_node_prefs->utc_offset_hours = _saved_utc_offset;
|
||||
_editing_utc = false;
|
||||
return true;
|
||||
}
|
||||
return true; // Consume all other keys while editing
|
||||
}
|
||||
|
||||
if (c == KEY_LEFT || c == KEY_PREV) {
|
||||
_page = (_page + HomePage::Count - 1) % HomePage::Count;
|
||||
return true;
|
||||
@@ -452,6 +554,11 @@ public:
|
||||
_task->toggleGPS();
|
||||
return true;
|
||||
}
|
||||
if (c == 'u' && _page == HomePage::GPS) {
|
||||
_editing_utc = true;
|
||||
_saved_utc_offset = _node_prefs->utc_offset_hours;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
||||
@@ -609,7 +716,6 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
channel_screen = new ChannelScreen(this, &rtc_clock);
|
||||
contacts_screen = new ContactsScreen(this, &rtc_clock);
|
||||
text_reader = new TextReaderScreen(this);
|
||||
repeater_admin = new RepeaterAdminScreen(this, &rtc_clock);
|
||||
setCurrScreen(splash);
|
||||
}
|
||||
|
||||
@@ -651,8 +757,8 @@ switch(t){
|
||||
|
||||
void UITask::msgRead(int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
if (msgcount == 0 && curr == msg_preview) {
|
||||
gotoHomeScreen(); // only leave msg_preview when queue is empty
|
||||
if (msgcount == 0) {
|
||||
gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,11 +1094,22 @@ void UITask::injectKey(char c) {
|
||||
}
|
||||
curr->handleInput(c);
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
// Debounce refresh when editing UTC offset - e-ink takes 644ms per refresh
|
||||
// so don't queue another render until the current one could have finished
|
||||
if (isEditingHomeScreen()) {
|
||||
unsigned long earliest = millis() + 700;
|
||||
if (_next_refresh < earliest) {
|
||||
_next_refresh = earliest;
|
||||
}
|
||||
} else {
|
||||
_next_refresh = 100; // trigger refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::gotoHomeScreen() {
|
||||
// Cancel any active editing state when navigating to home
|
||||
((HomeScreen *) home)->cancelEditUTC();
|
||||
setCurrScreen(home);
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
@@ -1001,6 +1118,10 @@ void UITask::gotoHomeScreen() {
|
||||
_next_refresh = 100;
|
||||
}
|
||||
|
||||
bool UITask::isEditingHomeScreen() const {
|
||||
return curr == home && ((HomeScreen *) home)->isEditingUTC();
|
||||
}
|
||||
|
||||
void UITask::gotoChannelScreen() {
|
||||
((ChannelScreen *) channel_screen)->resetScroll();
|
||||
setCurrScreen(channel_screen);
|
||||
@@ -1045,38 +1166,4 @@ void UITask::addSentChannelMessage(uint8_t channel_idx, const char* sender, cons
|
||||
|
||||
// Add to channel history with path_len=0 (local message)
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, 0, sender, formattedMsg);
|
||||
}
|
||||
|
||||
void UITask::gotoRepeaterAdmin(int contactIdx) {
|
||||
// Get contact name for the screen header
|
||||
ContactInfo contact;
|
||||
char name[32] = "Unknown";
|
||||
if (the_mesh.getContactByIdx(contactIdx, contact)) {
|
||||
strncpy(name, contact.name, sizeof(name) - 1);
|
||||
name[sizeof(name) - 1] = '\0';
|
||||
}
|
||||
|
||||
RepeaterAdminScreen* admin = (RepeaterAdminScreen*)repeater_admin;
|
||||
admin->openForContact(contactIdx, name);
|
||||
setCurrScreen(repeater_admin);
|
||||
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
|
||||
void UITask::onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) {
|
||||
if (isOnRepeaterAdmin()) {
|
||||
((RepeaterAdminScreen*)repeater_admin)->onLoginResult(success, permissions, server_time);
|
||||
_next_refresh = 100; // trigger re-render
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::onAdminCliResponse(const char* from_name, const char* text) {
|
||||
if (isOnRepeaterAdmin()) {
|
||||
((RepeaterAdminScreen*)repeater_admin)->onCliResponse(text);
|
||||
_next_refresh = 100; // trigger re-render
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,6 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* channel_screen; // Channel message history screen
|
||||
UIScreen* contacts_screen; // Contacts list screen
|
||||
UIScreen* text_reader; // *** NEW: Text reader screen ***
|
||||
UIScreen* repeater_admin; // Repeater admin screen
|
||||
UIScreen* curr;
|
||||
|
||||
void userLedHandler();
|
||||
@@ -80,7 +79,6 @@ public:
|
||||
void gotoChannelScreen(); // Navigate to channel message screen
|
||||
void gotoContactsScreen(); // Navigate to contacts list
|
||||
void gotoTextReader(); // *** NEW: Navigate to text reader ***
|
||||
void gotoRepeaterAdmin(int contactIdx); // Navigate to repeater admin
|
||||
void showAlert(const char* text, int duration_millis) override;
|
||||
void forceRefresh() override { _next_refresh = 100; }
|
||||
int getMsgCount() const { return _msgcount; }
|
||||
@@ -89,7 +87,7 @@ public:
|
||||
bool isOnChannelScreen() const { return curr == channel_screen; }
|
||||
bool isOnContactsScreen() const { return curr == contacts_screen; }
|
||||
bool isOnTextReader() const { return curr == text_reader; } // *** NEW ***
|
||||
bool isOnRepeaterAdmin() const { return curr == repeater_admin; }
|
||||
bool isEditingHomeScreen() const; // UTC offset editing on GPS page
|
||||
uint8_t getChannelScreenViewIdx() const;
|
||||
|
||||
void toggleBuzzer();
|
||||
@@ -101,17 +99,12 @@ public:
|
||||
|
||||
// Add a sent message to the channel screen history
|
||||
void addSentChannelMessage(uint8_t channel_idx, const char* sender, const char* text) override;
|
||||
|
||||
// Repeater admin callbacks
|
||||
void onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) override;
|
||||
void onAdminCliResponse(const char* from_name, const char* text) override;
|
||||
|
||||
// Get current screen for checking state
|
||||
UIScreen* getCurrentScreen() const { return curr; }
|
||||
UIScreen* getMsgPreviewScreen() const { return msg_preview; }
|
||||
UIScreen* getTextReaderScreen() const { return text_reader; } // *** NEW ***
|
||||
UIScreen* getContactsScreen() const { return contacts_screen; }
|
||||
UIScreen* getRepeaterAdminScreen() const { return repeater_admin; }
|
||||
|
||||
// from AbstractUITask
|
||||
void msgRead(int msgcount) override;
|
||||
|
||||
Reference in New Issue
Block a user