From 2ca97fa8ce33ecbb460890cdb8e138ad79c4afa0 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Wed, 7 Oct 2026 06:33:58 +1100 Subject: [PATCH] v1.15: Settings > Experimental Features (delete all contacts, Alt+B-only backlight); TEMP Max charger readout Version - MyMesh.h: FIRMWARE_VERSION default "Meck v1.14" -> "Meck v1.15"; FIRMWARE_BUILD_DATE updated. - variants/lilygo_tdeck_pro/platformio.ini, variants/lilygo_tdeck_max/platformio.ini: FIRMWARE_VERSION strings v1.14 -> v1.15. Settings > Experimental Features - SettingsScreen.h: new "Experimental Features >>" folder row as the last top-level Settings row, opening "Settings > Experimental". - Delete all contacts (all T-Deck builds), ported from Meck-P4 v0.7.4. Flow: "Delete all contacts?" box (Enter:Yes Q:No) -> second "Are you sure? This cannot be undone." popup (its Enter is ignored for the first second) -> "Purging" -> "Contacts deleted" or "Purge failed" -> restart 3 s later. Removes every contact, with favourites and custom paths, and the DM history; channel messages are kept. Q cancels at either confirmation; keys are ignored once the purge starts. - MyMesh.h: purgeAllContacts() closes any chunked contact save still in progress, empties the contact table and saves the empty set. - ChannelScreen.h: purgeDMs() marks the DM entries in /meshcore/messages.bin empty in place and re-saves, keeping channel messages, and clears the DM unread count. - main.cpp: meckPurgeAllContacts() runs both and checks the contacts file is empty afterwards. All touch is ignored while the purge box is open. - Change Backlight to Alt+B (Max only): ON stops the heart touch key toggling the frontlight, and Alt+B then lights it at the Backlight Brightness setting. OFF leaves both as before. - NodePrefs.h, DataStore.cpp: new pref backlight_alt_b_only at byte 1522 (prefs file now 1523 bytes); older files load it as 0 (heart key on). Timezones comment "these final fields" -> "these fields". TEMPORARY (Max only; remove after the charging investigation) - TDeckProMaxBoard.h, TDeckProMaxBoard.cpp: chargerDebugPrint(), a read-only SY6970 status, fault and register dump plus a BQ27220 gauge line on serial. - UITask.cpp: calls it every 10 s from UITask::loop(). --- examples/companion_radio/DataStore.cpp | 5 + examples/companion_radio/MyMesh.cpp | 1 + examples/companion_radio/MyMesh.h | 13 +- examples/companion_radio/NodePrefs.h | 8 +- examples/companion_radio/main.cpp | 35 ++++ .../companion_radio/ui-new/ChannelScreen.h | 22 ++ .../companion_radio/ui-new/SettingsScreen.h | 195 ++++++++++++++++++ examples/companion_radio/ui-new/UITask.cpp | 11 + .../lilygo_tdeck_max/TDeckProMaxBoard.cpp | 51 +++++ variants/lilygo_tdeck_max/TDeckProMaxBoard.h | 4 + variants/lilygo_tdeck_max/platformio.ini | 16 +- variants/lilygo_tdeck_pro/platformio.ini | 20 +- 12 files changed, 360 insertions(+), 21 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 8199473a..e5da0725 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -291,6 +291,9 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no if (file.read((uint8_t *)&_prefs.clock_slot_b, sizeof(_prefs.clock_slot_b)) != sizeof(_prefs.clock_slot_b)) { _prefs.clock_slot_b = 0; // default: Zone 2 at UTC+0 } + if (file.read((uint8_t *)&_prefs.backlight_alt_b_only, sizeof(_prefs.backlight_alt_b_only)) != sizeof(_prefs.backlight_alt_b_only)) { + _prefs.backlight_alt_b_only = 0; // default: heart key on + } // Clamp to valid ranges if (_prefs.dark_mode > 1) _prefs.dark_mode = 0; @@ -308,6 +311,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no if (_prefs.kb_backlight_pct < 5 || _prefs.kb_backlight_pct > 100) _prefs.kb_backlight_pct = 50; if (_prefs.clock_slot_a < -12 || _prefs.clock_slot_a > 14) _prefs.clock_slot_a = 0; if (_prefs.clock_slot_b < -12 || _prefs.clock_slot_b > 14) _prefs.clock_slot_b = 0; + if (_prefs.backlight_alt_b_only > 1) _prefs.backlight_alt_b_only = 0; // Force NUL termination on each canned message slot in case of garbage for (int i = 0; i < (int)(sizeof(_prefs.canned_msgs) / sizeof(_prefs.canned_msgs[0])); i++) { _prefs.canned_msgs[i][sizeof(_prefs.canned_msgs[0]) - 1] = '\0'; @@ -378,6 +382,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)_prefs.canned_msgs, sizeof(_prefs.canned_msgs)); // 177 file.write((uint8_t *)&_prefs.clock_slot_a, sizeof(_prefs.clock_slot_a)); // 1520 file.write((uint8_t *)&_prefs.clock_slot_b, sizeof(_prefs.clock_slot_b)); // 1521 + file.write((uint8_t *)&_prefs.backlight_alt_b_only, sizeof(_prefs.backlight_alt_b_only)); // 1522 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 1985ce58..f6f7987d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2950,6 +2950,7 @@ void MyMesh::handleCmdFrame(size_t len) { // as upstream replies to a command it does not have. char *text = (char *)&cmd_frame[1]; text[len - 1] = 0; // ensure null + Serial.printf("APP CLI: %s\n", text); // TEMPORARY test print: each command the app sends while (*text == ' ') text++; // skip leading spaces char reply_buf[166]; char *reply = reply_buf; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 9e717dca..5abe02d3 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "15 Sept 2026" +#define FIRMWARE_BUILD_DATE "7 Oct 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "Meck v1.14" +#define FIRMWARE_VERSION "Meck v1.15" #endif #if defined(STM32_PLATFORM) @@ -311,6 +311,15 @@ public: void saveContacts() { _store->saveContacts(this); } + // Settings > Experimental Features > Delete all contacts (Meck-P4 port). + // Empties the in-RAM contact table (BaseChatMesh::resetContacts is + // protected) and saves the empty set. A chunked background save still in + // progress is closed first, so it cannot later put an older list back. + void purgeAllContacts() { + if (_store->isSaveInProgress()) _store->finishSaveContacts(); + resetContacts(); + _store->saveContacts(this); + } void saveMainIdentity() { _store->saveMainIdentity(self_id); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 8aaa0a44..e1f684f2 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -92,11 +92,17 @@ struct NodePrefs { // persisted to file // --- Timezones home page (world clock) --- // UTC offsets (-12..+14) of the page's two extra zones; its Home row is - // utc_offset_hours. Older prefs files short-read these final fields and + // utc_offset_hours. Older prefs files short-read these fields and // keep the default 0 (UTC+0). int8_t clock_slot_a; // Zone 1 UTC offset, default 0 int8_t clock_slot_b; // Zone 2 UTC offset, default 0 + // --- Experimental Features --- + // 1 = "Change Backlight to Alt+B" is on: the Max's heart touch key no + // longer toggles the frontlight, leaving Alt+B. Older prefs files + // short-read this final field and keep the default 0 (heart key on). + uint8_t backlight_alt_b_only; // default 0 + // --- Font helpers (inline, no overhead) --- // Returns the DisplayDriver text-size index for "small/body" text. // T-Deck Pro: 0 = built-in 6×8 (or 7pt with custom fonts), 1 = 9pt. diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index ebaf26cf..9ad33908 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -896,6 +896,26 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store /* END GLOBAL OBJECTS */ +#if defined(LilyGo_TDeck_Pro) +// --------------------------------------------------------------------------- +// Settings > Experimental Features > Delete all contacts (ported from +// Meck-P4 v0.7.4). Empties the contact table and saves the empty set, and +// removes the direct-message history from the SD message store (channel +// messages are kept). SettingsScreen restarts the device afterwards. +// --------------------------------------------------------------------------- +void meckPurgeAllContacts(int* contactsRemoved, bool* contactsOk, int* dmsRemoved) { + *contactsRemoved = the_mesh.getNumContacts(); + the_mesh.purgeAllContacts(); + File f = SPIFFS.open("/contacts3", "r"); + *contactsOk = f && f.size() == 0; + if (f) f.close(); + ChannelScreen* chScr = (ChannelScreen*)ui_task.getChannelScreen(); + *dmsRemoved = chScr ? chScr->purgeDMs() : -1; + Serial.printf("Purge: %d contacts removed, contacts file %s, DMs removed %d\n", + *contactsRemoved, *contactsOk ? "empty" : "NOT EMPTY", *dmsRemoved); +} +#endif + #if defined(BLE_PIN_CODE) && defined(MECK_WIFI_COMPANION) // --------------------------------------------------------------------------- // Combined BLE + WiFi companion (meck_max_ble_wifi test build) @@ -1302,6 +1322,9 @@ static void openMapScreen() { if (!pressed) return; switch (key_id) { case 0: // heart -- toggle frontlight at the user-set brightness + // Settings > Experimental Features "Change Backlight to Alt+B" turns + // the heart key off, leaving Alt+B as the backlight toggle. + if (the_mesh.getNodePrefs()->backlight_alt_b_only) break; if (board.isBacklightOn()) { board.backlightOff(); } else { @@ -3518,6 +3541,12 @@ void loop() { { // Guard: skip touch when locked bool touchBlocked = ui_task.isLocked(); + // Settings > Experimental Features "Delete all contacts" box open: keys + // only, so a stray touch can't confirm it or leave Settings mid-purge + if (ui_task.isOnSettingsScreen()) { + SettingsScreen* purgeSS = (SettingsScreen*)ui_task.getSettingsScreen(); + if (purgeSS && purgeSS->isPurgeBoxOpen()) touchBlocked = true; + } #ifdef HAS_4G_MODEM // SMS dialer has its own dedicated touch handler — don't consume touch data here if (smsMode) { @@ -3855,6 +3884,12 @@ void handleKeyboardInput() { // Alt+B toggles the e-ink frontlight (MAX only -- working backlight on IO41) if (key == KB_KEY_BACKLIGHT) { if (board.isBacklightOn()) board.backlightOff(); + else if (the_mesh.getNodePrefs()->backlight_alt_b_only) { + // Experimental "Change Backlight to Alt+B" on: use the Backlight + // Brightness setting, as the heart key does + uint8_t blPct = the_mesh.getNodePrefs()->backlight_brightness_pct; + board.backlightSetBrightness((uint8_t)((blPct * 255 + 50) / 100)); + } else board.backlightOn(); return; } diff --git a/examples/companion_radio/ui-new/ChannelScreen.h b/examples/companion_radio/ui-new/ChannelScreen.h index 33f941a4..03a2b01a 100644 --- a/examples/companion_radio/ui-new/ChannelScreen.h +++ b/examples/companion_radio/ui-new/ChannelScreen.h @@ -603,6 +603,28 @@ public: } + // Settings > Experimental Features > Delete all contacts: drop every direct + // message and save, keeping channel messages. The file keeps the circular- + // buffer layout, so DM slots are marked empty in place. Returns how many + // were removed, or -1 if the SD card is not ready (nothing could be saved). + int purgeDMs() { +#if defined(HAS_SDCARD) && defined(ESP32) + if (!_sdReady) return -1; + int removed = 0; + for (int i = 0; i < CHANNEL_MSG_HISTORY_SIZE; i++) { + if (_messages[i].valid && _messages[i].channel_idx == 0xFF) { + _messages[i].valid = false; + removed++; + } + } + _unread[MAX_GROUP_CHANNELS] = 0; // DM unread slot + saveToSD(); + return removed; +#else + return -1; +#endif + } + // Load message buffer from SD card. Returns true if messages were loaded. bool loadFromSD() { #if defined(HAS_SDCARD) && defined(ESP32) diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index d3311e88..d0b40cd0 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -195,6 +195,11 @@ enum SettingsRowType : uint8_t { ROW_OPERATOR_INFO, // Carrier/operator display (read-only) ROW_APN, // APN setting (editable) #endif + ROW_EXPERIMENTAL_SUBMENU, // Folder row: enters Experimental Features sub-screen (last row) +#if defined(LilyGo_TDeck_Pro_Max) + ROW_ALT_B_BACKLIGHT, // Toggle: heart touch key off, Alt+B only toggles the backlight (MAX only) +#endif + ROW_PURGE_CONTACTS, // "Delete all contacts": confirm, purge, restart }; // --------------------------------------------------------------------------- @@ -216,6 +221,7 @@ enum EditMode : uint8_t { EDIT_OTA, // OTA firmware update flow (multi-phase overlay) EDIT_FILEMGR, // SD file manager flow (WiFi file browser) #endif + EDIT_PURGE, // Delete all contacts: confirm / purging / result overlay }; // --------------------------------------------------------------------------- @@ -233,6 +239,15 @@ enum SubScreen : uint8_t { SUB_EXPORT_IMPORT, // Export/Import menu SUB_EXPORT_FLAGS, // Export checkboxes + trigger #endif + SUB_EXPERIMENTAL, // Experimental Features sub-screen +}; + +// Delete all contacts (Experimental Features) overlay phases +enum PurgePhase : uint8_t { + PURGE_CONFIRM, // "Delete all contacts?" Enter:Yes Q:No + PURGE_CONFIRM2, // second confirmation popup ("Are you sure?"); Enter accepted after 1 s + PURGE_RUNNING, // "Purging" shown; the work runs from poll() + PURGE_DONE, // result shown; restart follows }; #ifdef MECK_OTA_UPDATE @@ -317,6 +332,13 @@ private: // Sub-screen navigation SubScreen _subScreen; int _savedTopCursor; // cursor position to restore when leaving sub-screen + + // Experimental Features > Delete all contacts (EDIT_PURGE overlay) + uint8_t _purgePhase; // PurgePhase + unsigned long _purgeAt; // CONFIRM2: when Enter is accepted; RUNNING: when the purge runs; DONE: when the restart happens + int _purgeContacts; // contacts removed (count shown on the confirm box) + int _purgeDMs; // direct messages removed, -1 = SD card not ready + bool _purgeContactsOk; // the contacts file is empty afterwards #ifdef HAS_SDCARD int _savedExportCursor; // cursor in SUB_EXPORT_IMPORT when entering SUB_EXPORT_FLAGS uint8_t _exportFlags; // bitmask of MECK_EXPORT_* flags for export checkboxes @@ -483,6 +505,12 @@ private: addRow(ROW_EXPORT_AUTOADD); addRow(ROW_EXPORT_NOW); #endif + } else if (_subScreen == SUB_EXPERIMENTAL) { + // --- Experimental Features sub-screen --- +#if defined(LilyGo_TDeck_Pro_Max) + addRow(ROW_ALT_B_BACKLIGHT); +#endif + addRow(ROW_PURGE_CONTACTS); } else { // --- Top-level settings list --- #if defined(LilyGo_TDeck_Pro_Max) @@ -549,6 +577,9 @@ private: addRow(ROW_OPERATOR_INFO); addRow(ROW_APN); #endif + + // Experimental Features (last row) + addRow(ROW_EXPERIMENTAL_SUBMENU); } // Clamp cursor @@ -711,6 +742,11 @@ public: _sharePickerScroll = 0; _shareRequested = false; _shareContactIdx = -1; + _purgePhase = PURGE_CONFIRM; + _purgeAt = 0; + _purgeContacts = 0; + _purgeDMs = 0; + _purgeContactsOk = false; #ifdef MECK_OTA_UPDATE _otaServer = nullptr; _otaPhase = OTA_PHASE_CONFIRM; @@ -768,6 +804,7 @@ public: bool isOnboarding() const { return _onboarding; } bool isEditing() const { return _editMode != EDIT_NONE; } + bool isPurgeBoxOpen() const { return _editMode == EDIT_PURGE; } // main.cpp ignores touch while open bool hasRadioChanges() const { return _radioChanged; } bool isOnChannelsSubScreen() const { return _subScreen == SUB_CHANNELS; } bool isOnDeletableChannel() const { @@ -1736,6 +1773,8 @@ public: } else if (_subScreen == SUB_OTA_TOOLS) { display.print("Settings > OTA Tools"); #endif + } else if (_subScreen == SUB_EXPERIMENTAL) { + display.print("Settings > Experimental"); } else { display.print("Settings"); } @@ -2225,6 +2264,23 @@ public: display.print(tmp); break; + case ROW_EXPERIMENTAL_SUBMENU: + display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN); + display.print("Experimental Features >>"); + break; + +#if defined(LilyGo_TDeck_Pro_Max) + case ROW_ALT_B_BACKLIGHT: + snprintf(tmp, sizeof(tmp), "Change Backlight to Alt+B: %s", + _prefs->backlight_alt_b_only ? "ON" : "OFF"); + display.print(tmp); + break; +#endif + + case ROW_PURGE_CONTACTS: + display.print("Delete all contacts"); + break; + #ifdef HAS_4G_MODEM case ROW_IMEI: { const char* imei = modemManager.getIMEI(); @@ -2320,6 +2376,85 @@ public: display.setTextSize(1); } + // === Delete all contacts overlay (Experimental Features) === + if (_editMode == EDIT_PURGE) { + int bx = 2, by = 14, bw = display.width() - 4; + int bh = display.height() - 28; + display.setColor(DisplayDriver::DARK); + display.fillRect(bx, by, bw, bh); + display.setColor(DisplayDriver::LIGHT); + display.drawRect(bx, by, bw, bh); + + display.setTextSize(_prefs->smallTextSize()); + int lh = _prefs->smallLineH(); + int cx = display.width() / 2; + int y = by + 4; + if (_purgePhase == PURGE_CONFIRM || _purgePhase == PURGE_CONFIRM2) { + display.setColor(DisplayDriver::YELLOW); + display.drawTextCentered(cx, y, "Delete all contacts?"); + y += lh + 2; + display.setColor(DisplayDriver::LIGHT); + snprintf(tmp, sizeof(tmp), "All %d contact%s, with", _purgeContacts, _purgeContacts == 1 ? "" : "s"); + display.drawTextCentered(cx, y, tmp); y += lh; + display.drawTextCentered(cx, y, "favourites and custom"); y += lh; + display.drawTextCentered(cx, y, "paths, and the DM history"); y += lh; + display.drawTextCentered(cx, y, "will be deleted. Channel"); y += lh; + display.drawTextCentered(cx, y, "messages are kept."); y += lh + 2; + display.setColor(DisplayDriver::RED); + display.drawTextCentered(cx, y, "The device will RESTART."); + display.setColor(DisplayDriver::LIGHT); + display.drawTextCentered(cx, by + bh - lh - 2, "Enter:Yes Q:No"); + if (_purgePhase == PURGE_CONFIRM2) { + // Second confirmation popup, drawn over the first box + int tx = 10, tw = display.width() - 20; + int th = lh * 3 + 12; + int ty = by + (bh - th) / 2; + display.setColor(DisplayDriver::DARK); + display.fillRect(tx, ty, tw, th); + display.setColor(DisplayDriver::LIGHT); + display.drawRect(tx, ty, tw, th); + display.setColor(DisplayDriver::YELLOW); + display.drawTextCentered(cx, ty + 3, "Are you sure?"); + display.setColor(DisplayDriver::LIGHT); + display.drawTextCentered(cx, ty + 3 + lh + 2, "This cannot be undone."); + display.drawTextCentered(cx, ty + th - lh - 2, "Enter:Yes Q:No"); + } + } else if (_purgePhase == PURGE_RUNNING) { + display.setColor(DisplayDriver::YELLOW); + display.drawTextCentered(cx, y, "Purging"); + y += lh + 2; + display.setColor(DisplayDriver::LIGHT); + display.drawTextCentered(cx, y, "Deleting all contacts and"); y += lh; + display.drawTextCentered(cx, y, "the DM history."); y += lh; + display.drawTextCentered(cx, y, "Please wait..."); y += lh + 4; + display.setColor(DisplayDriver::RED); + display.drawTextCentered(cx, y, "Do not switch off. The"); y += lh; + display.drawTextCentered(cx, y, "device restarts when done."); + } else { + bool ok = _purgeContactsOk && _purgeDMs >= 0; + display.setColor(DisplayDriver::YELLOW); + display.drawTextCentered(cx, y, ok ? "Contacts deleted" : "Purge failed"); + y += lh + 2; + display.setColor(DisplayDriver::LIGHT); + if (ok) { + snprintf(tmp, sizeof(tmp), "Done. %d contact%s and the", _purgeContacts, _purgeContacts == 1 ? "" : "s"); + display.drawTextCentered(cx, y, tmp); y += lh; + display.drawTextCentered(cx, y, "DM history have been"); y += lh; + display.drawTextCentered(cx, y, "deleted from this device."); y += lh + 4; + } else { + snprintf(tmp, sizeof(tmp), "Contacts file: %s", _purgeContactsOk ? "ok" : "failed"); + display.drawTextCentered(cx, y, tmp); y += lh; + snprintf(tmp, sizeof(tmp), "DM history: %s", _purgeDMs >= 0 ? "ok" : "SD not ready"); + display.drawTextCentered(cx, y, tmp); y += lh; + display.drawTextCentered(cx, y, "The restart reloads what"); y += lh; + display.drawTextCentered(cx, y, "storage still holds."); y += lh + 4; + } + display.setColor(DisplayDriver::RED); + display.drawTextCentered(cx, y, "RESTARTING NOW..."); + } + display.setTextSize(1); + } + // === Notification sound picker overlay === #if defined(MECK_AUDIO_VARIANT) || defined(HAS_4G_MODEM) if (_editMode == EDIT_NOTIF_SOUND) { @@ -2853,6 +2988,29 @@ public: return true; // consume all keys in confirm mode } + // --- Delete all contacts (Experimental Features) --- + if (_editMode == EDIT_PURGE) { + if (_purgePhase == PURGE_CONFIRM || _purgePhase == PURGE_CONFIRM2) { + if (c == KEY_CANCEL || c == 'q') { + _editMode = EDIT_NONE; + return true; + } + if (c == '\r' || c == 13) { + if (_purgePhase == PURGE_CONFIRM) { + // First Yes: show the second confirmation. Its Enter is ignored for + // 1 s, so a double press or key bounce can't pass both boxes. + _purgePhase = PURGE_CONFIRM2; + _purgeAt = millis() + 1000; + } else if ((long)(millis() - _purgeAt) >= 0) { + _purgePhase = PURGE_RUNNING; + _purgeAt = millis() + 1500; // let the "Purging" box reach the e-ink first + } + return true; + } + } + return true; // other keys ignored; no dismissing once the purge has started + } + // --- Notification sound picker --- #if defined(MECK_AUDIO_VARIANT) || defined(HAS_4G_MODEM) if (_editMode == EDIT_NOTIF_SOUND) { @@ -3713,6 +3871,27 @@ public: startEditCanned(_rows[_cursor].param); break; + case ROW_EXPERIMENTAL_SUBMENU: + _savedTopCursor = _cursor; + _subScreen = SUB_EXPERIMENTAL; + _cursor = 0; + _scrollTop = 0; + rebuildRows(); + Serial.println("Settings: entered Experimental Features sub-screen"); + break; +#if defined(LilyGo_TDeck_Pro_Max) + case ROW_ALT_B_BACKLIGHT: + _prefs->backlight_alt_b_only = _prefs->backlight_alt_b_only ? 0 : 1; + the_mesh.savePrefs(); + Serial.printf("Settings: Change Backlight to Alt+B = %s\n", + _prefs->backlight_alt_b_only ? "ON" : "OFF"); + break; +#endif + case ROW_PURGE_CONTACTS: + _purgeContacts = the_mesh.getNumContacts(); + _purgePhase = PURGE_CONFIRM; + _editMode = EDIT_PURGE; + break; #ifdef MECK_OTA_UPDATE case ROW_OTA_TOOLS_SUBMENU: #ifndef MECK_40MHZ_TEST @@ -3965,6 +4144,22 @@ public: } // Override handleInput for UIScreen compatibility (used by injectKey) + // Delete all contacts: run the purge once the "Purging" box has been drawn, + // then restart a few seconds after the result is on screen. The restart + // rebuilds everything keyed by contact index from the empty store. + void poll() override { + if (_editMode != EDIT_PURGE) return; + if (_purgePhase == PURGE_RUNNING && (long)(millis() - _purgeAt) >= 0) { + extern void meckPurgeAllContacts(int* contactsRemoved, bool* contactsOk, int* dmsRemoved); + meckPurgeAllContacts(&_purgeContacts, &_purgeContactsOk, &_purgeDMs); + _purgePhase = PURGE_DONE; + _purgeAt = millis() + 3000; // result stays on screen before the restart + } else if (_purgePhase == PURGE_DONE && (long)(millis() - _purgeAt) >= 0) { + Serial.println("Settings: purge done, restarting"); + ESP.restart(); + } + } + bool handleInput(char c) override { return handleKeyInput(c); } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 4dc30824..d5218224 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2426,6 +2426,17 @@ if (curr) curr->poll(); next_batt_chck = millis() + 8000; } #endif + +#if defined(LilyGo_TDeck_Pro_Max) + // TEMPORARY: charger + fuel gauge readout on serial every 10 s (read-only) + { + static unsigned long nextChargerDump = 0; + if ((long)(millis() - nextChargerDump) >= 0) { + nextChargerDump = millis() + 10000; + board.chargerDebugPrint(); + } + } +#endif } char UITask::checkDisplayOn(char c) { diff --git a/variants/lilygo_tdeck_max/TDeckProMaxBoard.cpp b/variants/lilygo_tdeck_max/TDeckProMaxBoard.cpp index 4d011d2e..823aea85 100644 --- a/variants/lilygo_tdeck_max/TDeckProMaxBoard.cpp +++ b/variants/lilygo_tdeck_max/TDeckProMaxBoard.cpp @@ -865,4 +865,55 @@ int16_t TDeckProMaxBoard::getBattTemperature() { #else return 0; #endif +} + +// ---- TEMPORARY: charger + fuel gauge readout (read-only) ---- +// Register decoding follows XPowersLib's PowersSY6970.hpp (lewisxhe). Reading +// REG0C returns the fault bits and clears the latched ones. +static int sy6970_readReg(uint8_t reg) { + Wire.beginTransmission(I2C_ADDR_SY6970); + Wire.write(reg); + if (Wire.endTransmission(false) != 0) return -1; + if (Wire.requestFrom((uint8_t)I2C_ADDR_SY6970, (uint8_t)1) != 1) return -1; + return Wire.read(); +} + +void TDeckProMaxBoard::chargerDebugPrint() { + static const char* const kInput[8] = { "none", "USB-SDP", "USB-CDP", "USB-DCP", + "HVDCP", "adapter", "non-std-adapter", "OTG" }; + static const char* const kState[4] = { "not-charging", "pre-charge", "fast-charge", "charge-done" }; + int r[0x15]; + for (uint8_t i = 0; i <= 0x14; i++) r[i] = sy6970_readReg(i); + + if (r[0x0B] < 0) { + Serial.println("CHG: SY6970 not responding at 0x6A"); + } else { + int vregCode = (r[0x06] & 0xFC) >> 2; + int vreg = (vregCode > 0x30) ? 4608 : vregCode * 16 + 3840; + int ntc = r[0x0C] & 0x07; + const char* ntcName = (ntc == 0) ? "normal" : (ntc == 2) ? "warm" : (ntc == 3) ? "cool" + : (ntc == 5) ? "cold" : (ntc == 6) ? "hot" : "other"; + Serial.printf("CHG: input=%s pg=%d state=%s hiz=%d chg_en=%d vreg=%dmV ichg=%dmA iinlim=%dmA term_en=%d wdt=%d timer_en=%d\n", + kInput[(r[0x0B] >> 5) & 0x07], (r[0x0B] >> 2) & 1, kState[(r[0x0B] >> 3) & 0x03], + (r[0x00] >> 7) & 1, (r[0x03] >> 4) & 1, + vreg, (r[0x04] & 0x7F) * 64, (r[0x00] & 0x3F) * 50 + 100, + (r[0x07] >> 7) & 1, (r[0x07] >> 4) & 0x03, (r[0x07] >> 3) & 1); + Serial.printf("CHG: fault=0x%02X watchdog=%d boost=%d charge(bits5:4)=%d battery=%d ntc=%s(%d)\n", + r[0x0C] & 0xFF, (r[0x0C] >> 7) & 1, (r[0x0C] >> 6) & 1, (r[0x0C] >> 4) & 0x03, + (r[0x0C] >> 3) & 1, ntcName, ntc); + } + Serial.print("CHG regs:"); + for (uint8_t i = 0; i <= 0x14; i++) { + if (r[i] < 0) Serial.printf(" %02X=--", i); + else Serial.printf(" %02X=%02X", i, r[i]); + } + Serial.println(); + + #if HAS_BQ27220 + int16_t t = getBattTemperature(); + Serial.printf("GAUGE: V=%umV I=%dmA avgI=%dmA remain=%umAh full=%umAh design=%umAh soc=%u%% T=%d.%dC\n", + getBattMilliVolts(), (int16_t)bq27220_read16(BQ27220_REG_CURRENT), getAvgCurrent(), + getRemainingCapacity(), bq27220_read16(BQ27220_REG_FULL_CAP), getDesignCapacity(), + bq27220_read16(BQ27220_REG_SOC), t / 10, abs(t % 10)); + #endif } \ No newline at end of file diff --git a/variants/lilygo_tdeck_max/TDeckProMaxBoard.h b/variants/lilygo_tdeck_max/TDeckProMaxBoard.h index 9660aac7..9ed34cd4 100644 --- a/variants/lilygo_tdeck_max/TDeckProMaxBoard.h +++ b/variants/lilygo_tdeck_max/TDeckProMaxBoard.h @@ -134,6 +134,10 @@ public: uint16_t getDesignCapacity(); int16_t getBattTemperature(); bool configureFuelGauge(uint16_t designCapacity_mAh = BQ27220_DESIGN_CAPACITY_MAH); + // TEMPORARY: read-only charger (SY6970) and fuel gauge readout on serial, + // called every 10 s from UITask::loop() while the charging stop is + // investigated. Writes nothing to either chip. + void chargerDebugPrint(); // ------------------------------------------------------------------------- // Sleep / power-off (moved verbatim from TDeckBoard when MAX was decoupled). diff --git a/variants/lilygo_tdeck_max/platformio.ini b/variants/lilygo_tdeck_max/platformio.ini index ee2edf22..cbccee40 100644 --- a/variants/lilygo_tdeck_max/platformio.ini +++ b/variants/lilygo_tdeck_max/platformio.ini @@ -1,5 +1,5 @@ ; ============================================================================= -; T-Deck Pro MAX V0.1 — Meck Build Environments +; T-Deck Pro MAX V0.1-v0.3 — Meck Build Environments ; ; Hardware: ESP32-S3 + XL9555 I/O expander + combined 4G (A7682E) + Audio (ES8311) ; @@ -179,7 +179,7 @@ build_flags = ; -D MECK_BLE_SMALL_MTU_SPLIT=1 ; -D MECK_WATCH_EXT=1 ; -D MECK_BLE_SLOW_INTERVAL=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -213,7 +213,7 @@ build_flags = -D MECK_BLE_SMALL_MTU_SPLIT=1 -D MECK_WATCH_EXT=1 -D MECK_BLE_SLOW_INTERVAL=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -250,7 +250,7 @@ build_flags = -D MECK_OTA_UPDATE=1 -D EINK_FASTLUT_EXPERIMENT=1 -D FAST_LUT_FRAMES=9 - -D FIRMWARE_VERSION='"Meck v1.14.MAX.FASTLUT"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX.FASTLUT"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -282,7 +282,7 @@ build_flags = -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 -D HAS_4G_MODEM=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -316,7 +316,7 @@ build_flags = -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 -D HAS_4G_MODEM=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX.BLE-WiFi"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX.BLE-WiFi"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -342,7 +342,7 @@ build_flags = -D MECK_OTA_UPDATE=1 -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX.SA"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX.SA"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + @@ -398,7 +398,7 @@ build_flags = -D CPU_FREQ_BOOST=40 -D CPU_FREQ_LOW_POWER=40 ;-D MESH_DEBUG=1 - -D FIRMWARE_VERSION='"Meck v1.14.MAX.SA40"' + -D FIRMWARE_VERSION='"Meck v1.15.MAX.SA40"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + + diff --git a/variants/lilygo_tdeck_pro/platformio.ini b/variants/lilygo_tdeck_pro/platformio.ini index 7486012f..564d4fcb 100644 --- a/variants/lilygo_tdeck_pro/platformio.ini +++ b/variants/lilygo_tdeck_pro/platformio.ini @@ -164,7 +164,7 @@ build_flags = -D MECK_OTA_UPDATE=1 -D EINK_FASTLUT_EXPERIMENT=1 -D FAST_LUT_FRAMES=9 - -D FIRMWARE_VERSION='"Meck v1.14.FASTLUT"' + -D FIRMWARE_VERSION='"Meck v1.15.FASTLUT"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -211,7 +211,7 @@ build_flags = -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 -D MECK_PRO_HYN_TOUCH=1 - -D FIRMWARE_VERSION='"Meck v1.14.HYN"' + -D FIRMWARE_VERSION='"Meck v1.15.HYN"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -249,7 +249,7 @@ build_flags = -D MECK_AUDIO_VARIANT -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.14.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.15.WiFi"' ;-D WIFI_DEBUG_LOGGING=1 ;-D MESH_PACKET_LOGGING=1 ;-D MESH_DEBUG=1 @@ -288,7 +288,7 @@ build_flags = -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 -D MECK_PRO_HYN_TOUCH=1 - -D FIRMWARE_VERSION='"Meck v1.14.WiFi.HYN"' + -D FIRMWARE_VERSION='"Meck v1.15.WiFi.HYN"' ;-D WIFI_DEBUG_LOGGING=1 ;-D MESH_PACKET_LOGGING=1 ;-D MESH_DEBUG=1 @@ -357,7 +357,7 @@ build_flags = -D MECK_AUDIO_VARIANT -D MECK_OTA_UPDATE=1 -D MECK_PRO_HYN_TOUCH=1 - -D FIRMWARE_VERSION='"Meck v1.14.A.SA.HYN"' + -D FIRMWARE_VERSION='"Meck v1.15.A.SA.HYN"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -409,7 +409,7 @@ build_flags = -D CPU_FREQ_BOOST=40 -D CPU_FREQ_LOW_POWER=40 -D MESH_DEBUG=1 - -D FIRMWARE_VERSION='"Meck v1.14.A.SA40"' + -D FIRMWARE_VERSION='"Meck v1.15.A.SA40"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -444,7 +444,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.14.4G"' + -D FIRMWARE_VERSION='"Meck v1.15.4G"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -480,7 +480,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.14.4G.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.15.4G.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -514,7 +514,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.14.4G.SA"' + -D FIRMWARE_VERSION='"Meck v1.15.4G.SA"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -557,7 +557,7 @@ build_flags = -D CPU_FREQ_BOOST=40 -D CPU_FREQ_LOW_POWER=40 -D MESH_DEBUG=1 - -D FIRMWARE_VERSION='"Meck v1.14.4G.SA40"' + -D FIRMWARE_VERSION='"Meck v1.15.4G.SA40"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + -