From e9bf6d9e17ad7bb4b78d5d22046cc676b64a0ddd Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Sat, 22 Aug 2026 23:34:50 +0200 Subject: [PATCH] console: make `ui` actually leave console mode Typing `ui` or `exit` rebooted straight back into the console. touchPrefsSetConsoleMode() does not write the pref, it QUEUES an A/B snapshot: SdNvsPrefs deliberately keeps filesystem I/O off the calling thread, and touchPrefsFlush() is what forces the queue out. The console called ESP.restart() directly, so the queued write was discarded and the next boot still read "console". The Settings toggle never showed this because it goes through rebootDevice(), whose last act is that flush. The console now exits through the same path, which also means it leaves on the same terms as everything else: chat history, the Discovered ring and the sync replay ring are persisted first. rebootDevice()'s save-failure branch called lv_refr_now() unconditionally, which is not survivable with no LVGL, so that one call is now conditional; showAlert was already console-aware. Co-Authored-By: Claude Opus 5 --- src/ui-touch/ConsoleUI.cpp | 9 +++++++-- src/ui-touch/UITask.cpp | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/ui-touch/ConsoleUI.cpp b/src/ui-touch/ConsoleUI.cpp index 0ef7360..99e89ab 100644 --- a/src/ui-touch/ConsoleUI.cpp +++ b/src/ui-touch/ConsoleUI.cpp @@ -50,6 +50,9 @@ extern int luaHostDiscoverAt(int idx, char* pk_hex, size_t pk_cap, char* name, // Board touch driver, read directly. lvglTouchRead() is only an adapter that // feeds LVGL, so there is nothing to unpick here: the driver polls either way. extern bool heltecV4CapTouchGetLive(uint16_t* x, uint16_t* y); +#if defined(ESP32) +extern void consoleHostRebootToUi(); // clears the pref, flushes it, reboots +#endif #endif namespace { @@ -526,11 +529,13 @@ void submit() { // The way back to the graphical UI. One of three, per CONSOLE_MODE.md: this, // a key held at boot, and clearing the pref over serial or the flasher. if (!strcasecmp(cmd, "ui") || !strcasecmp(cmd, "exit")) { - touchPrefsSetConsoleMode(false); consoleWriteLine("switching to the graphical UI, rebooting..."); render(); delay(600); // let the line land on the panel before the reset - ESP.restart(); + // Clears the pref AND flushes it. Doing the write here and calling + // ESP.restart() left the queued snapshot unwritten, so this came straight + // back to the console. + consoleHostRebootToUi(); return; } #endif diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index 0b056fb..8aca006 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -51966,8 +51966,12 @@ void UITask::rebootDevice() { char why[64]; chatSaveFailText(why, sizeof why); char msg[128]; snprintf(msg, sizeof msg, "%s\n%s", TR("Chat history save FAILED"), why); - showAlert(msg, 1800); + showAlert(msg, 1800); // console-aware: prints instead when there is no LVGL +#if CAP_CONSOLE + if (!s_console_mode) lv_refr_now(NULL); +#else lv_refr_now(NULL); +#endif delay(1500); // let the warning actually paint before the reset } discoveredFlushNow(); // persist the Discovered ring before we go down @@ -51977,6 +51981,27 @@ void UITask::rebootDevice() { if (_board) _board->reboot(); } +#if CAP_CONSOLE && defined(ESP32) +// The console's way back to the graphical UI (the `ui` / `exit` command). +// +// It must go through rebootDevice(), not ESP.restart(), and the reason is not +// obvious: touchPrefsSetConsoleMode() only QUEUES an A/B snapshot. SdNvsPrefs +// deliberately keeps filesystem I/O off the calling thread, and +// touchPrefsFlush() -- which rebootDevice() calls last -- is what forces the +// queued write out. Restarting directly discarded it, so the pref still said +// "console" on the next boot and `ui` looked like it did nothing. +// +// Going through rebootDevice() also means the console exits on the same terms +// as everything else: chat history, the Discovered ring and the sync replay +// ring are all persisted first. +void consoleHostRebootToUi() { + touchPrefsSetConsoleMode(false); + if (g_lv.task) { g_lv.task->rebootDevice(); return; } + touchPrefsFlush(); // no UITask (should not happen): at least do not lose the pref + ESP.restart(); +} +#endif + void UITask::msgRead(int msgcount) { _msgcount = msgcount; } // Shared core for newMsg / newMsgFromPubWithMeta. Bundles the channel-vs-DM