Settings Selection Screens
Section titled “Settings Selection Screens”Overview
Section titled “Overview”The Settings Selection Screens module is a collection of five single-purpose list-picker screens that allow the user to choose a value from a predefined set of options and save it to NVS (Non-Volatile Storage). Each screen is built on top of ListMenuScreen and follows a strictly uniform lifecycle pattern, making them consistent, memory-safe, and safe to destroy from within LVGL event callbacks.
The five screens are:
| Screen | Namespace | Setting modified |
|---|---|---|
| Baud Rate | baudrateScreen | esp3d_baud_rate |
| Polling Interval | pollingScreen | esp3d_polling_interval (via GCode handler) |
| Screen Timeout | screenTimeoutScreen | esp3d_screen_timeout |
| Output Client | outputSelectionScreen | esp3d_output_client / esp3d_radio_mode |
| Language | languagesScreen | active translation pack |
Architecture
Section titled “Architecture”Position in the UI Hierarchy
Section titled “Position in the UI Hierarchy”The settings selection screens are leaf nodes reached from the settings navigation flow:
main_screen └── settings_screen (circular menu) ├── baudrate_screen ← this module ├── output_selection_screen ← this module ├── languages_screen ← this module └── settings_list_screen ├── polling_screen ← this module └── screen_timeout_screen (*) ← this module (* requires ESP3D_BRIGHTNESS_CONTROL_FEATURE)None of the screens in this module open further sub-screens, except output_selection_screen, which may open a message_box_screen confirmation dialog before restarting.
Component Dependencies
Section titled “Component Dependencies”| Dependency | Used by |
|---|---|
ListMenuScreen | all five screens (base class) |
UIManager (esp3d_ui.h) | all five screens |
ESP3DSettings | baudrate, polling, screen_timeout, output_selection |
ESP3DTranslationService | all five screens (labels and language list) |
esp3d_resources (icons) | all five screens (back_b, ok_b, refresh_b) |
message_box_screen | output_selection (restart confirmation) |
ActivityMonitoring | screen_timeout (live timeout update) |
ESP3DGCodeHandlerService | polling (live interval update) |
ESP3DSerialClient / ESP3DUartExtClient | baudrate (live baud rate change) |
ESP3DNetwork | output_selection (radio mode switch) |
Common Screen Lifecycle
Section titled “Common Screen Lifecycle”All five screens implement the same state machine enforced through shared macros
(ESP3D_TRANSITION_START, ESP3D_TRANSITION_RESET, ESP3D_CLEANUP_TIMER_BODY,
ESP3D_PREPARE_DESTRUCTION_GUARD, ESP3D_TRANSITION_TIMER_BODY).
create() ├── reset transition state (ESP3D_TRANSITION_RESET) ├── build item list ├── new ListMenuScreen(...) [std::nothrow + try/catch] ├── ui_manager.registerScreen(type, screen, prepareForDestruction) ├── lv_obj_add_event_cb(..., LV_EVENT_DELETE, onScreenDestroy) └── move_to_index(initial_selection)
─── user presses Back ─────────────────────────────────────────── └── onBackButtonRelease() └── ESP3D_TRANSITION_START → cleanup_timer_cb scheduled
cleanup_timer_cb() ├── prepareForDestruction() │ └── screen->prepareForDestruction() [idempotent guard] └── schedule transition_timer_cb(delay)
transition_timer_cb() └── createScreen(next_screen_target) [dispatches to target]
─── LVGL deletes screen object ────────────────────────────────── └── onScreenDestroy() ├── prepareForDestruction() (if not already called) ├── ui_manager.unregisterScreen(...) └── delete instance_ptr; ptr = nullptrKey Lifecycle Functions
Section titled “Key Lifecycle Functions”| Function | Role |
|---|---|
create() | Builds item list, creates ListMenuScreen, registers with UIManager (with its prepareForDestruction, so a global overlay such as the critical alert can tear the screen down, see ui_core.md), attaches LV_EVENT_DELETE callback |
onScreenDestroy() | LVGL delete callback: triggers prepareForDestruction() if not yet done, then unregisters and frees the ListMenuScreen |
prepareForDestruction() | Idempotent (via ESP3D_PREPARE_DESTRUCTION_GUARD): calls screen->prepareForDestruction() to cancel pending LVGL timers and callbacks |
cleanup_timer_cb() | Fires after button release; calls prepareForDestruction(), then schedules transition_timer_cb |
transition_timer_cb() | Fires after ESP3D_TRANSITION_SCREEN_DELAY_MS; calls createScreen(next_screen_target) |
Common Button Layout
Section titled “Common Button Layout”| Position | Default assignment | languages_screen |
|---|---|---|
| Button 1 (left) | OK | Set / Select |
| Button 2 (middle) | (empty) | Refresh |
| Button 3 (right) | Back | Back |
Memory Safety
Section titled “Memory Safety”All new calls use std::nothrow inside a try/catch block. An uncaught std::bad_alloc inside an LVGL callback would call std::terminate and reset the ESP32. On failure the function logs the error and returns, leaving the previous screen intact.
Item list vectors also wrap their construction in try/catch: if allocation fails partway through, create() detects the empty or partial list and returns early without creating a broken screen.
List Item Display Pattern
Section titled “List Item Display Pattern”Every screen’s display*Item() callback follows the same layout:
lv_obj_clean(container)— clear before redraw.- If item is the current active value: create
LV_SYMBOL_OKlabel atLV_ALIGN_LEFT_MIDwithESP3D_INDICATOR_SUCCESS_COLOR. - Create text label at
ESP3D_LIST_START_TEXToffset withLV_LABEL_LONG_DOToverflow handling. - Disable scrollbars on both label and container.
Screen Reference
Section titled “Screen Reference”baudrate_screen
Section titled “baudrate_screen”File: main/display/screens/baudrate_screen.cpp
Purpose: Lets the user pick a serial baud rate from SupportedBaudList. The change is applied immediately to the UART driver without a restart.
Item Structure
Section titled “Item Structure”struct BaudrateItem { uint32_t baudrate; // e.g. 115200 std::string display; // e.g. "115200 bps"};Data Flow on Selection
Section titled “Data Flow on Selection”onBaudrateItemClick(item) ├── esp3dXsettings.writeUint32(esp3d_baud_rate, value) └── serialClient.change_baud_rate(value) [or uartExtClient if active] └── on success → refresh_visible_items() [update checkmark]Behaviour Notes
Section titled “Behaviour Notes”- Item list is built from the board-specific
SupportedBaudList[]/SupportedBaudListSizecompile-time constants. - Each entry is labelled
"<value> bps"using theESP3DLabel::bpstranslation. - When
ESP3D_UART_EXT_FEATUREis enabled, the live baud rate change is routed touartExtClient.change_baud_rate()if that is the active output client. - Navigation: Back →
ESP3DScreenType::settings
polling_screen
Section titled “polling_screen”File: main/display/screens/polling_screen.cpp
Purpose: Lets the user pick the CNC status-report polling interval from SupportedPollingList. The change is applied live via the GCode handler service.
Item Structure
Section titled “Item Structure”struct PollingItem { uint32_t interval; // milliseconds; 0 = disabled std::string display; // e.g. "200 ms", "1 sec", "None"};Data Flow on Selection
Section titled “Data Flow on Selection”onPollingItemClick(item) └── esp3dGcodeHandler.updateReportingInterval(interval_ms) ├── saves to NVS (esp3d_polling_interval) ├── updates live reporting timer └── on success → refresh_visible_items() [update checkmark]Behaviour Notes
Section titled “Behaviour Notes”- Display formatting:
0→"None",< 1000 ms→"X ms",≥ 1000 ms→"X sec". - grblHAL constraint: values
> 1000 msare skipped from the list because grblHAL’s$481auto-report interval is capped at 1000 ms. - The
updateReportingInterval()call handles both NVS persistence and live timer update in one step. - Navigation: Back →
ESP3DScreenType::settings_list
screen_timeout_screen
Section titled “screen_timeout_screen”File: main/display/screens/screen_timeout_screen.cpp
Compile guard: ESP3D_BRIGHTNESS_CONTROL_FEATURE
Purpose: Lets the user pick a screen auto-off timeout from SupportedTimeoutList. The change is saved to NVS and immediately applied to the activity monitoring subscriber.
Item Structure
Section titled “Item Structure”struct TimeoutItem { uint32_t timeout_seconds; // 0 = disabled std::string display; // e.g. "30 sec", "5 min", "1 hour", "None"};Data Flow on Selection
Section titled “Data Flow on Selection”onTimeoutItemClick(item) ├── esp3dXsettings.writeUint32(esp3d_screen_timeout, seconds) └── ActivityMonitoring::g_screenSubscriber.setTimeout(seconds * 1000) └── on success → refresh_visible_items() [update checkmark]Behaviour Notes
Section titled “Behaviour Notes”- Display formatting:
0→"None",< 60 s→"X sec",< 3600 s→"X min",≥ 3600 s→"X hour". - The timeout value is converted to milliseconds before being passed to
ActivitySubscriber::setTimeout(). - This screen is absent from the build when
ESP3D_BRIGHTNESS_CONTROL_FEATUREis not defined. Thesettings_list_screenalso conditionally omits its menu entry under the same guard. - Navigation: Back →
ESP3DScreenType::settings_list
output_selection_screen
Section titled “output_selection_screen”File: main/display/screens/output_selection_screen.cpp
Compile guard: (ESP3D_BT_SERIAL_FEATURE || ESP3D_BT_BLE_FEATURE) || ESP3D_WIFI_FEATURE || ESP3D_UART_EXT_FEATURE
Purpose: Lets the user select the CNC communication output client. Since a14 every change is saved to NVS and applied by a board restart, on every build.
Available Clients (by Feature Flag)
Section titled “Available Clients (by Feature Flag)”| Client type | Feature flag | Display label |
|---|---|---|
serial | always included | ESP3DLabel::serial |
bt_serial | ESP3D_BT_SERIAL_FEATURE | ESP3DLabel::bluetooth |
bt_ble | ESP3D_BT_BLE_FEATURE | ESP3DLabel::bluetooth_ble |
socket_client | ESP3D_SOCKET_CLIENT_FEATURE | ESP3DLabel::wifi |
websocket_client | ESP3D_WS_CLIENT_SERVICE_FEATURE | ESP3DLabel::wifi_websocket |
uart_ext | ESP3D_UART_EXT_FEATURE | ESP3DLabel::external_module |
Row Indicators
Section titled “Row Indicators”The running client (esp3dCommands.getOutputClient()) and the saved one (esp3d_output_client in NVS) can differ until the next boot, so each row shows one of three states:
| Indicator | Meaning |
|---|---|
LV_SYMBOL_OK (success color) | Running and saved: active and persistent |
-> (success color) | Running, but another client is saved for the next boot |
LV_SYMBOL_WARNING (warning color) | Saved for the next boot, not running yet |
Before a14 the saved client showed a check mark at once, which suggested the switch had already happened (field report 2026-10-14).
Decision Flow on Selection
Section titled “Decision Flow on Selection”onOutputItemClick(item) ├── [item == running client?] → beep, return ├── [item == client saved in NVS?] → beep, showRestartConfirmation(), return ├── writeByte(esp3d_output_client, new_client) ├── writeByte(esp3d_radio_mode, derived_radio_mode) ├── refresh the visible rows └── showRestartConfirmation() ├── [confirmed] → show_information("Board will restart...") │ → esp_restart() after 2 s └── [cancelled] → stay; the row shows the warning iconClicking a row that is already saved for the next boot offers the restart again, so the user can still trigger it after cancelling.
NVS Fields Written
Section titled “NVS Fields Written”| Field | Type | Notes |
|---|---|---|
esp3d_output_client | uint8_t | The selected ESP3DClientType |
esp3d_radio_mode | uint8_t | Derived from client type: off, wifi_sta, bluetooth_serial, bluetooth_ble |
Behaviour Notes
Section titled “Behaviour Notes”- Both
esp3d_output_clientandesp3d_radio_modeare written atomically so they remain consistent across reboots. - Every change needs a restart. The former live switch between serial and WiFi was removed:
setOutputClient()changed the routing at once whilesetModeAsync()took seconds to connect, and bytes in flight on the old channel were cut in the middle of a G-code line (field report 2026-10-14). BT and UART Ext always needed a restart (radio stack, boot-time client selection). The old live-apply branch is still in the source but unreachable (constexpr bool needs_restart = true). esp3d_radio_modeis derived from the client:bluetooth_serial/bluetooth_blefor BT,wifi_stafor socket and WebSocket clients,offotherwise. In debug builds that log over the network (ESP3D_LOG_BACKEND3 = telnet or 4 = WebSocket) the current radio mode is kept for serial / UART Ext, so the logs stay reachable.- The
prepareForDestructionfunction pointer is passed tomessage_box_screenso the current screen is properly torn down before the dialog appears. - Navigation: Back →
ESP3DScreenType::settings
languages_screen
Section titled “languages_screen”Files: main/display/screens/languages_screen.cpp, main/display/screens/languages_screen.h
Purpose: Lets the user select a UI language from the language packs stored in the ui_resources partition. The change is applied immediately — no restart required. The title bar itself updates to reflect the new locale.
LanguageInfo Structure (from languages_screen.h)
Section titled “LanguageInfo Structure (from languages_screen.h)”struct LanguageInfo { std::string code; // e.g. "fr-fr", "default" std::string display; // e.g. "Français", "English"
LanguageInfo(const std::string& c, const std::string& d) : code(c), display(d) {}};Data Flow
Section titled “Data Flow”create() └── build_language_list() ├── esp3dTranslationService.getLanguagesList() └── populate scanned_languages_ from values[] + labels[] └── ListMenuScreen(...) └── onScreenCreated() → select_current_language()
─── user taps item or Set button ────────────────────────────────onLanguageItemClick(item) └── ui_manager.setLanguage(code) ├── on success → update_title_text(translate(select_language)) └── refresh_visible_items() [update checkmark]
─── user presses Refresh button ─────────────────────────────────onRefreshButtonRelease() └── refresh_language_list() ├── build_language_list() [re-read partition slots] ├── updateItemList(...) └── select_current_language() [scroll to active entry]Behaviour Notes
Section titled “Behaviour Notes”- Language data comes from
esp3dTranslationService, which reads available language slots directly from theui_resourcespartition — no filesystem scan is needed and the operation is instant. select_current_language()scrolls the list to the entry whosecodematchesesp3dTranslationService.getLanguageCode(), falling back to"default"if none is set.- After a language change,
update_title_text()is called with the re-translatedselect_languagelabel so the screen title immediately reflects the new language. - The Refresh button re-reads the partition in case a language pack was added via SD card update without rebooting.
- Navigation: Back →
ESP3DScreenType::settings
range_editor_screen
Section titled “range_editor_screen”Files: main/display/screens/range_editor_screen.cpp, main/display/screens/range_editor_screen.h (new in a14)
Purpose: Generic editor for a bounded integer value (for example 0–100). An arc shows the value in its range, with the title above and, in the center, an optional description of the current value and the value itself. It is not a ListMenuScreen.
Controls
Section titled “Controls”| Input | Action |
|---|---|
| Encoder rotation | Value − / + one step per detent |
| Buttons 0 / 2 (left / right) | - / +, or Set / Back |
| Button 1 (middle) | Switches buttons 0 / 2 between the two modes (opens in - / + mode) |
| Touch | The arc can be dragged |
RangeEditorScreenData
Section titled “RangeEditorScreenData”| Field | Role |
|---|---|
title, unit | Shown above the arc / appended to the value (for example %). The title is copied into rangeEditorScreen::shared_title, because translate() returns a shared buffer |
min_value, max_value, step, value | Range, change per detent or press, initial value (clamped) |
describe(value, user_data) | Optional text for the current value |
on_change(value, user_data) | Optional live preview on each change (for example brightness) |
on_set(value, user_data) | Called on Set with the final value: save it here |
on_back(initial_value, user_data) | Optional, called on Back (for example revert a preview) |
return_screen | Screen opened after Set or Back |
prepare_current_screen_destruction | Caller cleanup, run in a 0 ms timer before the editor is created |
Lifecycle
Section titled “Lifecycle”Same pattern as inputScreen / messageBoxScreen (screen_transitions_flow.md, Pattern 3): the caller calls rangeEditorScreen::create(data) directly from its click handler (no ESP3D_TRANSITION_START). The caller cleanup runs in a 0 ms timer, then the editor is created in a deferred timer. Set and Back return to return_screen with the standard ESP3D_TRANSITION_START flow. The arc uses applyRangeArcStyle() (ui_core.md).
Current caller: settings_list_screen → onWheelSensitivityClick() (ESP3D_HARDWARE_ENCODER_FEATURE), which edits esp3d_jog_wheel_sensitivity (0 = classic jog, 1–100 = proportional, see handwheel_jog.md).
Caller Reference
Section titled “Caller Reference”The table below summarises where each screen is opened from and which function triggers the transition.
| Screen | Opened from | Trigger function |
|---|---|---|
baudrate_screen | settings_screen | Baud rate menu entry callback |
output_selection_screen | settings_screen | Output client menu entry callback |
languages_screen | settings_screen | Language menu entry callback |
polling_screen | settings_list_screen | onPollingClick() |
screen_timeout_screen | settings_list_screen | onScreenTimeoutClick() |
range_editor_screen | settings_list_screen | onWheelSensitivityClick() (direct create(), see above) |
All callers of the list screens use the ESP3D_TRANSITION_START macro, which saves the current scroll position (last_selected_index), prepares the calling screen for destruction, and schedules the target screen creation via transition_timer_cb.
Related Documentation
Section titled “Related Documentation”- UI_Framework_&_Screens —
ListMenuScreen,UIManager, screen lifecycle macros,GenericScreenbase class - Core_Platform_&_Infrastructure —
ESP3DSettings(NVS read/write),ESP3DTranslationService - Communication_Transports —
ESP3DSerialClient(change_baud_rate),ESP3DSocketClient,ESP3DBTSerialClient - Network_&_Web_Services —
ESP3DNetwork(setModeAsync) - CNC_Firmware_Integration —
ESP3DGCodeHandlerService(updateReportingInterval) - screens_architecture.md — Full screen architecture reference
- screen_transitions_flow.md — Transition timing and macro details
- ui_style_guide.md — Style tokens and
applyLabelStyle/applyListNodeStyleusage - theme_palette.md —
ESP3D_INDICATOR_SUCCESS_COLORand related color tokens - development.md —
ui_resourcespartition: language pack binary format and SD-card update workflow