Skip to content

Settings List Screen

The Settings List Screen (settings_settings_list_screen) is a scrollable list-based UI screen that exposes all user-configurable pendant settings. It is the second layer of the settings hierarchy, reached by selecting the Settings Editor section from the parent Settings Screen.

The screen is built on top of ListMenuScreen and presents a flat, scrollable list of heterogeneous setting entries. Each entry is one of two behavioural kinds:

KindBehaviour
NavigationTaps/OK opens a secondary screen (input, scan, confirmation)
ToggleTaps/OK flips a boolean NVS value in-place; the checkmark updates immediately

All items, their labels, and their enable/visibility are resolved at runtime from the active transport, build flags, and NVS values, making the list fully adaptive to the current firmware variant and connection mode.


Settings section

Settings Editor section

Polling

Screen Timeout

File Extensions / SSID / Password

Server Address / Port / WS Path

Auth PIN / Passkey / Steps / Feedrates

Forget WiFi / Forget BT Host

Back button

Main Screen

(main_screen)

Settings Screen

(settings_screen)

CircularMenu

Settings List Screen

(settings_list_screen)

← current module →

Polling Screen

Screen Timeout Screen

Input Screen

(text / numeric / PIN / list)

Message Box Screen

(confirmation / info)

creates and owns

holds vector of

uses static array

registers screen

reads/writes NVS

reads axis_names

translates labels

styled via

settingsListScreen

+create()

-initializeSettingItems()

-displaySettingItem()

-onSettingItemClick()

-toggleSetting()

-prepareForDestruction()

-onScreenDestroy()

-cleanup_timer_cb()

-transition_timer_cb()

ListMenuScreen

+ListMenuScreen(parent, host_screen, title, items, ...)

+getListMenu() : ListMenuComponent

+prepareForDestruction()

+isValid() : bool

+getScreen() : lv_obj_t

+rotateContainer(angle)

SettingItem

+ESP3DLabel label

+SettingType type

+ESP3DSettingIndex setting_index

+int8_t axis_index

+on_click() : void

JogSettingAxisData

+char axis_letter

+ESP3DSettingIndex steps_index

+ESP3DSettingIndex feedrate_index

UIManager

+registerScreen()

+unregisterScreen()

+getComponent()

+getOrientationAngle()

+applyLabelStyle()

+tagListNodeStatusColor()

ESP3DSettings

+readByte()

+writeByte()

+readString()

+writeString()

+readUint32()

+writeUint32()

+getSettingPtr()

ESP3DValues

+get_value(axis_names)

ESP3DTranslationService

+translate(ESP3DLabel)

+translate(ESP3DLabel, char)


Describes a single row in the settings list. All rows are stored in a std::vector<SettingItem> built once at screen-creation time by initializeSettingItems().

struct SettingItem {
ESP3DLabel label; // Translated display text key
SettingType type; // Navigation | Toggle
ESP3DSettingIndex setting_index; // NVS key (toggles), or unknown_index for navigation
void (*on_click)(); // Navigation handler; nullptr for pure toggles
int8_t axis_index; // 0-5 for jog rows (X/Y/Z/A/B/C), -1 otherwise
};
enum class SettingType : uint8_t {
Navigation = 0, // Opens another screen
Toggle = 1 // Reads/writes a boolean NVS byte in place
};

A static compile-time array mapping axis indices (0–5) to their NVS setting keys. Used by openJogStepsEditor() and openJogFeedrateEditor().

// Static table — one entry per axis
static const JogSettingAxisData jog_axis_configs[] = {
{'X', esp3d_x_jog_steps, esp3d_x_jog_feedrates},
{'Y', esp3d_y_jog_steps, esp3d_y_jog_feedrates},
{'Z', esp3d_z_jog_steps, esp3d_z_jog_feedrates},
{'A', esp3d_a_jog_steps, esp3d_a_jog_feedrates},
{'B', esp3d_b_jog_steps, esp3d_b_jog_feedrates},
{'C', esp3d_c_jog_steps, esp3d_c_jog_feedrates},
};

Axis display letters are resolved at runtime via ESP3DValuesIndex::axis_names (to honour grblHAL’s $376 UVW renaming), falling back to the static letter when the observable is unset or empty for that position.


All items are built into a single flat std::vector by initializeSettingItems() and presented in the order described below.

1 — Navigation Settings (open a secondary screen)

Section titled “1 — Navigation Settings (open a secondary screen)”

Build / transport conditional

Always shown

Polling Interval

Screen Timeout

BRIGHTNESS_CONTROL

File Extensions

not grblHAL

BT PIN / BLE Passkey

BT_SERIAL or BT_BLE

Forget BT Host

BT_SERIAL or BT_BLE

WiFi SSID

WIFI_FEATURE

WiFi Password

WIFI_FEATURE

Forget WiFi

WIFI_FEATURE

Server Address

IP_CNC_CLIENT

Server Port

IP_CNC_CLIENT

WS Path

WS_CLIENT_SERVICE

Wheel sensitivity

HARDWARE_ENCODER

polling_screen

screen_timeout_screen

input_screen - text

message_box_screen - confirmation

range_editor_screen - 0..100

Wheel sensitivity (a14, ESP3D_HARDWARE_ENCODER_FEATURE): onWheelSensitivityClick() opens the range editor on esp3d_jog_wheel_sensitivity (0–100, step 1), returning to settings_list. The description under the arc is Off (0), Low (1–33), Medium (34–66) or High (67–100); on_set writes the byte to NVS. The jog screen reads it at entry: 0 = classic handwheel jog, 1–100 = feed follows the wheel speed and the next step is used above a speed threshold (see handwheel_jog.md).

2 — Toggle Settings (in-place boolean flip)

Section titled “2 — Toggle Settings (in-place boolean flip)”
LabelNVS KeyCondition
Macrosesp3d_macros_enabledAlways shown
Probeesp3d_probe_enabledAlways shown
Tool Changeesp3d_change_tool_enabledHidden on GRBL
Bypass Safety Focusesp3d_bypass_safety_focusAlways shown
Show All Axesesp3d_jog_show_all_axesAlways shown, above the jog axis rows
Commands in position unitesp3d_jog_position_unitToggle, always shown, right after Show All Axes
MPG Modeesp3d_mpg_enabledgrblHAL only; hidden over Telnet/WebSocket transport
Swap RX/TXesp3d_swap_rx_tx / esp3d_uart_ext_swap_rxtxSerial or UART ext transport only

When a toggle is flipped, toggleSetting() immediately:

  1. Reads the current NVS byte.
  2. Inverts it and writes it back.
  3. Applies item-specific side effects (see table below).
  4. Calls refresh_visible_items() to redraw visible rows (updates ✓/✗ symbol without a full screen reload).

Side effects per toggle:

ToggleSide Effect
Macros enabledmacroManager::loadFromFile()
Macros disabledmacroManager::clearAllMacros() + markClean()
MPG modeShows “Restart required” information dialog
Swap RX/TXCalls serialClient.swap_rx_tx() or uartExtClient.swap_rx_tx() hot
Show All AxesRebuilds the list (initializeSettingItems() + updateItemList()) in a 0 ms one-shot timer — rows are never deleted inside their own event callback — keeping the view: same first visible row, same selected row (move_to_index() on the former last visible row, then on the selection)

3 — Jog Settings (per-axis navigation to input)

Section titled “3 — Jog Settings (per-axis navigation to input)”

Six axes × two parameters = up to 12 jog entries. Show All Axes off (default): only the rows of the detected axes are kept (axis_count, “3” before connection, clamped to 3..6); on: the 6 axes. Each opens an input_screen with a specialised keyboard type:

EntryKeyboard TypeNVS Key
<Axis> StepsNumericListFloatesp3d_<axis>_jog_steps
<Axis> FeedratesNumericListIntesp3d_<axis>_jog_feedrates

Unit sections (a14): for the linear axes (X, Y, Z) the lists hold one section per unit (mm;…;in;…). The editor is opened with unit_sections = true and a mm/in toggle key on the keyboard; it opens on the section of the current jog unit (isMachineInInches(): the report unit when esp3d_jog_position_unit is on, otherwise the G20/G21 work unit). The unit labels are mm / in for steps and mm/min / in/min for feedrates. The title and labels are copied into local buffers, because translate() returns a shared buffer. Rotary axes (A, B, C) are in degrees, with no sections. See jog_unit_lists.md.

Y (idx 1) through C (idx 5)

(same two-entry pattern for each axis)

X axis (idx 0)

X Steps

input_screen - NumericListFloat

X Feedrates

input_screen - NumericListInt


LVGLUIManagerListMenuScreensettingsListScreen::create()settings_screenLVGLUIManagerListMenuScreensettingsListScreen::create()settings_screenScreen is active - user scrolls and interactscreate()initializeSettingItems() - builds vector of SettingItemnew ListMenuScreen(type, title, items, display_cb, click_cb, buttons)lv_obj_create, lv_list_create, etc.registerScreen(settings_list, screen_obj, prepareForDestruction)lv_obj_add_event_cb(screen, onScreenDestroy, LV_EVENT_DELETE)rotateContainer(orientation_angle)move_to_index(last_selected_index)Back button pressed - onBackButtonRelease()ESP3D_TRANSITION_START - sets next_screen_target = settingsstart cleanup_timercleanup_timer_cb fires - calls prepareForDestruction()transition_timer_cb fires - calls createScreen(settings)LV_EVENT_DELETE fires - onScreenDestroy()unregisterScreen(settings_list)delete settings_list_screen_obj_instance

The screen uses a two-flag guard to prevent double-cleanup regardless of which path (user navigation vs LVGL-initiated deletion) triggers destruction:

  • is_prepared_for_destruction_ — set once by prepareForDestruction(); subsequent calls are no-ops guarded by ESP3D_PREPARE_DESTRUCTION_GUARD.
  • cleanup_executed_ — additional idempotency guard inside the macro.

onScreenDestroy() (the LV_EVENT_DELETE callback) calls prepareForDestruction() itself if the normal transition path has not already done so, ensuring that LVGL-initiated destruction (e.g., from a parent screen switch) is always clean.

The selected index is persisted in the file-scope last_selected_index so the list re-enters at the same row when the user returns from a sub-screen.


SettingType::Toggle

Macros enabled

Macros disabled

MPG mode on grblHAL

Swap RX/TX

None

User taps row or presses OK

onSettingItemClick

toggleSetting(index, label)

readByte from NVS

new_value = !current

writeByte to NVS

Special side

effect needed?

macroManager::loadFromFile

macroManager::clearAllMacros + markClean

show_information - Restart required

serialClient / uartExtClient .swap_rx_tx()

refresh_visible_items

SettingType::Navigation

input_screen

message_box

list screen

OK callback fires

return_screen = settings_list

Yes callback fires

User taps row or presses OK

onSettingItemClick

on_click() - e.g. onWifiSSIDClick()

save last_selected_index

Destination type?

inputScreen::create(data)

or show_text_input / show_pin_input

messageBoxScreen::show_confirmation(...)

ESP3D_TRANSITION_START

polling or screen_timeout screen

Save value to NVS

settingsListScreen::create()

restores saved index

Perform forget / clear action

readByte / readString / readUint32

writeByte / writeString / writeUint32

Backed by

settings_list_screen

ESP3DSettings

esp3dXsettings

ESP-IDF NVS

Flash storage


The complete item list is gated by preprocessor flags evaluated at compile time and transport checks evaluated at screen creation time.

Setting ItemGuard
Screen TimeoutESP3D_BRIGHTNESS_CONTROL_FEATURE
File Extensions!TARGET_IS_GRBLHAL
BT PIN / BLE PasskeyESP3D_BT_SERIAL_FEATURE or ESP3D_BT_BLE_FEATURE + active transport match
Forget BT HostESP3D_BT_SERIAL_FEATURE or ESP3D_BT_BLE_FEATURE + active transport match
WiFi SSID / Password / ForgetESP3D_WIFI_FEATURE
Server Address / PortESP3D_IP_CNC_CLIENT_FEATURE
WebSocket PathESP3D_WS_CLIENT_SERVICE_FEATURE
Tool Change toggle!TARGET_IS_GRBL
MPG Mode toggleTARGET_IS_GRBLHAL + serial or uart_ext transport (runtime check)
Swap RX/TX toggleserial or ESP3D_UART_EXT_FEATURE transport (runtime check)

Entry point called by settings_screen when the user selects the Settings Editor section. Resets all transition guards, calls initializeSettingItems(), allocates the ListMenuScreen with std::nothrow, registers the screen with UIManager, attaches the LV_EVENT_DELETE cleanup callback, applies rotation, and restores the previously saved selection index. Protected against double-instantiation via an explicit existence check that throws std::runtime_error.

Clears and rebuilds the file-scope setting_items_ vector in dependency order. All conditional items are evaluated here against compile-time flags and the runtime active transport. The entire body is wrapped in try/catch(std::exception) to catch std::bad_alloc on a fragmented heap — a partial list is preferable to a board reset.

displaySettingItem(container, item, screen_instance, user_data)

Section titled “displaySettingItem(container, item, screen_instance, user_data)”

LVGL display callback invoked by ListMenuComponent for each visible row during rendering or after refresh_visible_items().

  • Toggle rows: renders a ✓ (ESP3D_INDICATOR_SUCCESS_COLOR) or ✗ (ESP3D_INDICATOR_ERROR_COLOR) symbol read live from NVS, followed by the translated label. The colour is tagged with ui_manager.tagListNodeStatusColor() so applyListNodeStyle() correctly restores it when the row loses selection highlight.
  • Navigation rows: renders the translated label; for jog entries the %c axis-letter placeholder is filled by getAxisLetter(axis_index).

onSettingItemClick(item, screen_instance, user_data)

Section titled “onSettingItemClick(item, screen_instance, user_data)”

Unified click handler called by both the OK button release path and a direct list-tap event:

  1. Plays the active-item beep.
  2. For Toggle items: calls toggleSetting().
  3. For Navigation items: calls on_click().

Reads the byte at index from NVS, inverts it, writes it back, applies any item-specific side effects, then calls refresh_visible_items() on the active ListMenuComponent to redraw only the visible rows without recreating the screen.

Resolves the display letter for a jog axis row. Reads ESP3DValuesIndex::axis_names from the observable system; falls back to jog_axis_configs[axis_idx].axis_letter when the observable is empty or shorter than axis_idx. This makes grblHAL UVW naming transparent across the entire settings UI.

openJogStepsEditor(axis_idx) / openJogFeedrateEditor(axis_idx)

Section titled “openJogStepsEditor(axis_idx) / openJogFeedrateEditor(axis_idx)”

Generic helpers used by the six per-axis click callbacks (onXStepsClick, …, onCFeedrateClick). They save last_selected_index, read the current NVS value into a stack buffer, then build and pass an InputScreenData struct to inputScreen::create(), encoding the axis index as user_data (via reinterpret_cast<intptr_t>) for the save callback.

Standard two-stage deferred transition used throughout the CNC UI layer. cleanup_timer_cb calls prepareForDestruction() to disable input and stop LVGL updates on the outgoing screen; transition_timer_cb then calls createScreen(next_screen_target). See Screen Transitions Architecture for the full pattern.

LV_EVENT_DELETE handler attached to the LVGL screen object. Calls prepareForDestruction() as a safety net if the normal transition path has not already done so, then calls ui_manager.unregisterScreen() and deletes the ListMenuScreen C++ object.


When the active transport is BT Serial or BT BLE, onNetworkAuthClick() reads the active transport from esp3dCommands.getOutputClient() and dispatches to the appropriate handler:

BT Serial

BT BLE

Serial / other

On OK

On OK

onNetworkAuthClick

Active transport?

handleBTSerialPIN()

show_pin_input - 4 digits

allow_empty = true

handleBTBLEPasskey()

show_pin_input - 6 digits

default 123456

No auth available - log warning only

writeString esp3d_btserial_pin

btSerialClient.clearBondedDevices()

writeString esp3d_btble_passkey

btBleClient.clearBondedDevices()

onForgetBTHostClick() shows a message_box_screen confirmation. On Yes the handler:

  1. Calls disconnect() on the active BT client.
  2. Calls clearTargetDevice() and clearBondedDevices().
  3. Writes an empty string to the PIN/Passkey NVS key.

This screen runs entirely on Core 1 (the LVGL task). All callbacks (displaySettingItem, onSettingItemClick, toggleSetting, onScreenDestroy, timer callbacks) are invoked from the LVGL task context and must not block.

Key constraints respected by this module:

  • NVS reads/writes in callbacks are synchronous but brief — no heap-heavy operations occur in the per-frame display path.
  • std::vector allocation in initializeSettingItems() happens once at screen creation, not per render frame.
  • refresh_visible_items() redraws only the visible LVGL objects, avoiding a full list rebuild and minimising GPU load.
  • Transition timers defer screen switching out of the LVGL event callback stack using lv_timer_create, avoiding re-entrant deletion.

For the general LVGL threading model and safety rules see CLAUDE.md — LVGL Constraints.


ModuleRelationship
settings_settings_screenParent screen; navigates here via the “Settings Editor” circular menu section
ui_components — ListMenuScreenBase class providing the scrollable list UI and virtual button bar
modal_dialog_screens — input_screenUsed for all text / numeric / PIN / list input dialogs launched from this screen
modal_dialog_screens — message_box_screenUsed for Forget WiFi and Forget BT Host confirmation dialogs
settings_selection_screens — polling_screenSub-screen for polling interval selection
settings_selection_screens — screen_timeout_screenSub-screen for screen backlight timeout selection
macro_systemmacroManager::loadFromFile() and clearAllMacros() called on Macros toggle
esp3d_core — ESP3DSettingsNVS read/write backend for all persisted settings
values — ESP3DValuesaxis_names observable read by getAxisLetter() for dynamic axis letter resolution
translationsesp3dTranslationService.translate() for all displayed labels and formatted jog titles
screens_architectureGeneral screen lifecycle, safe transition patterns, and two-stage timer model