Skip to content

UI Framework & Screens

The UI_Framework_&_Screens module (main/display/) is the complete display and interaction layer of the Pibot CNC Pendant firmware. It owns every pixel on screen: the LVGL task lifecycle, theme system, resource loading from flash, screen navigation, reusable widget components, and all CNC firmware-specific UI screens. It runs exclusively on Core 1 and is the sole consumer of LVGL APIs anywhere in the codebase.

The module is structured in four tiers:

TierSub-moduleResponsibility
Foundationui_coreLVGL task runner, UIManager, theme tokens/styles, flash resource loader
Componentsui_componentsReusable widgets: circular menu, list menu, panel, virtual buttons, custom keyboard
Screenscommon_screens, activity_monitoringTransport-agnostic screens (splash, input, message box, scan, settings pickers)
CNC firmware UIcnc_shared, fluidnc_module, grbl_module, grblhal_moduleMachine-specific screens: main, jog, status, GC state, files, probe, tool change

Two other UI builds exist next to the CNC pendant UI, selected by CMake:

BuildFolderStatus
Small UI tier (UI_TIER=SMALL → ESP3D_UI_SMALL)main/display/small_ui/ (ESP3DSmallUi)Basic, self-contained read-only dashboard for small displays (1.47” 172×320). Uses nothing from the pendant UI: own LVGL task, built-in LVGL fonts and symbols only (no ui_resources). Pages cycled by the board page button: (1) remote link icon (WiFi STA / AP / USB / BT), network state, IP, job or update progress arc; (2) SD card state and SSID. cmake/sanity_check.cmake only accepts SMALL (or unset) and requires TFT_UI_SERVICE=ON
3D printer UI (TARGET_FW_MARLIN / REPETIER / SMOOTHIEWARE)main/display/3dprinter/Groundwork: main screen, settings screen header and macro manager; the other folders hold dummy.c placeholders

CNC Firmware Integration

BSP / Hardware Drivers

Core Platform & Infrastructure

UI_Framework_&_Screens (main/display/)

reads/writes

notifies orientation

restarts on lang change

spi_flash_mmap

pinned Core 1 task

backlight_set

subscribe server_status

subscribe observables

update observables

sendGcode

Firmware-specific modules

fluidnc_module

ConnectionStatus

ChangeTool / Files / Probe

grbl_module

ConnectionStatus

ChangeTool / Files / Probe

Screen Router

grblhal_module

ConnectionStatus

ChangeTool / Files / Probe

Screen Router

ui_core

esp3d_x_ui / UIManager

Theme system / Resources

ui_components

CircularMenu / ListMenu

Panel / VirtualButtons

CustomKeyboard

common_screens

Splash / Input / MessageBox

NetworkScan / SettingsPickers

ConnectionStatus / Update

activity_monitoring

Screen timeout / Backlight fade

cnc_shared

Main / Jog / Status / GcState

Settings / Macros

FirmwareStatus / CriticalAlert

ESP3DValues

(observable system)

ESP3DSettings (NVS)

ESP3DTranslationService

esp3d_log

board_init.c

lvgl_flush_cb / increase_lvgl_tick

disp_backlight

ui_resources partition

(flash, XIP)

esp3dGcodeHandler

(per firmware target)

LVGLUIManagertft_ui_task (Core 1)ESP3DXUimain.cpp (app_main)LVGLUIManagertft_ui_task (Core 1)ESP3DXUimain.cpp (app_main)loop[Every LVGL tick]esp3dXui.begin()xTaskCreatePinnedToCore(Core 1)ui_manager.initialize()esp3d_resources_init() → spi_flash_mmapesp3d_sound.reloadDefinitions() (buzzer builds)loadThemeNamesesp3dXValues.initialize()esp3dTranslationService.begin()criticalAlert::begin() (CNC targets)loadStateFromSettings / applyTheme / initializeStylescreate_application() → splash_screen::create()_lock_acquireesp3dXValues.handle()lv_timer_handler()_lock_releasenotifyFirstFrameRendered()waitFirstFrameRendered(timeout_ms)

esp3d_resources_get_blob()

partition absent or stale

initializeStyles()

applyButtonDefaultStyle()

applyListNodeStyle()

applyCircularMenuStyle()

…

ui_resources partition

blobs: theme 0..3

104 bytes tokens + name

esp3d_theme_palettes.h

compiled-in defaults

ThemeTokens

26 semantic RGBA tokens

(bg, text, accent, glow,

indicator groups)

ThemeStyles

~40 lv_style_t objects

apply* API

LVGL Widgets

(all screens & components)

connection established or timeout

menu section → Jog

menu section → Status

menu section → Settings

menu section → Console

sub-settings

about

view 3, parser state

back

handwheel sensitivity

set / back

M6 requested (firmware event)

user navigates

optional tool probe

result returned

file menu

job launched

confirm / delete

return

transport info

connect to WiFi

connect to TCP server

connect to BT host

baud selection

language picker

timeout picker

polling interval picker

transport picker

edit step/feedrate

edit overrides

Splash

Main

Jog

Status

Settings

FirmwareStatus

SettingsList

Information

GcState

RangeEditor

ChangeTool

Probe

Files

MessageBox

ConnectionStatus

WiFiScan

ServerScan

BtScan

Baudrate

Language

ScreenTimeout

Polling

OutputSelection

Input

The critical alert (main/display/cnc/components/critical_alert.cpp) is not a navigation target: it subscribes to the critical_alert value and opens a message box on top of whatever screen is active when a condition raised outside the UI needs the user (input overflow, Recover action).

Two shared headers carry the unit logic used by several screens:

  • main/display/esp3d_unit_sections.h — jog step / feedrate lists hold one section per work unit (mm;0.01;0.1;1;10;in;0.001;0.01;0.1;1); the section is selected by G20 / G21, no conversion is ever done.
  • main/display/cnc/screens/position_unit.h — the mm / in label next to the positions follows the report unit ($13 / report_inches), not G20 / G21; also the active WCS badge of the position button.

Detail: jog_unit_lists.md, inch_mm_units_reference.md.

«abstract»

RotaryBaseComponent

+handleEncoderEvent(lv_event_t*)

+setDebounceThreshold(int64_t)

#processEncoderStep(dir, step, total)

CircularMenuComponent

+showMenu()

+simulate_click(section_id)

+updateOrientation(orientation)

+updateThemeColor()

+enableEncoderFor(index)

+prepareForDestruction()

ListMenuComponent

+move_up() / move_down()

+updateItemList(items, count)

+setEncoderInterceptCallback(cb)

+prepareForDestruction()

PanelComponent

+addItem(obj, values) : int32_t

+setFocusedItem(id)

+enableEncoderFor(mode)

+activateItem(id)

+prepareForDestruction()

VirtualButtonsComponent

+update_button(idx, icon, enabled)

+simulate_click(idx)

+updateOrientation(orientation)

+attachSwitchEmulation(obj, n)

+prepareForDestruction()

«namespace»

customKeyboard

+create(KeyboardConfig) : lv_obj_t

+set_type(obj, KeyboardType)

+finalize_numeric_input(obj, allowLeadingZeros)


Sub-modulePathKey filesDocumentation
ui_core — LVGL task, UIManager, theme, resourcesmain/display/esp3d_x_ui.h/cpp, esp3d_ui.h/cpp, esp3d_resources.cpp, esp3d_snapshot.h, esp3d_translations_id_map.hdocs/architecture/screens_architecture.md, docs/ui_resources/development.md, docs/ui_resources/theme_palette.md, docs/ui_resources/ui_style_guide.md
ui_components — reusable widgetsmain/display/components/circular_menu_component.h, list_menu_component.h, panel_component.h, virtual_buttons_component.h, custom_keyboard_component.h/cppdocs/architecture/Input_system.md, docs/architecture/screen_transitions_flow.md
common_screens — transport-agnostic screensmain/display/screens/generic_screen.h, circular_menu_screen.h, list_menu_screen.h, input_screen.*, message_box_screen.*, splash_screen.*, wifi_scan_screen.*, connection_status_screen.*, range_editor_screen.*docs/architecture/screens_architecture.md, docs/architecture/screen_transitions_flow.md
activity_monitoring — screen timeout/backlightmain/display/activity_monitoring.cppactivity_monitoring.cpp/.hcomponents/esp3d_activity_manager/
cnc_shared — shared CNC screensmain/display/cnc/screens/main_screen.cpp, jog_screen.cpp, status_screen.cpp, settings_screen.cpp, macros_screen.cpp, macro_manager.h/cpp, firmware_status_screen.cpp, information_screen.cpp, gc_state_screen.cpp, position_unit.h; components/critical_alert.cppdocs/ux_flows/
fluidnc_module — FluidNC-specific UImain/display/cnc/fluidnc/connection_status.h, change_tool_screen.cpp, files_screen.cpp, probe_screen.cppdocs/ux_flows/
grbl_module — grbl-specific UImain/display/cnc/grbl/esp3d_screen_type.cpp, connection_status.h, change_tool_screen.cpp, files_screen.cpp, probe_screen.cppdocs/ux_flows/
grblhal_module — grblHAL-specific UImain/display/cnc/grblhal/esp3d_screen_type.cpp, connection_status.h, change_tool_screen.cpp, files_screen.cpp, probe_screen.cppdocs/ux_flows/
TopicDocument
Screen architecture, GenericScreen base, registration patternsdocs/architecture/screens_architecture.md
Safe timer-based screen transition idiomdocs/architecture/screen_transitions_flow.md
Input system (encoder, buttons, switch, potentiometer)docs/architecture/Input_system.md
Display driver architecture (SPI / RGB / I80, orientation math)docs/architecture/display_drivers.md
ui_resources partition binary format, generate_resources.py, SD updatedocs/ui_resources/development.md
26 semantic color tokens, per-theme RGBA values, design conventionsdocs/ui_resources/theme_palette.md
ThemeStyles architecture, apply* catalog, per-screen style inventorydocs/ui_resources/ui_style_guide.md
Adding/modifying icons and fonts, SD-card update workflowdocs/guides/ui_resources_guide.md
FluidNC / grblHAL screen-by-screen UX flowsdocs/ux_flows/
UI sounds (semantic ESP3DSoundEvent API, ESP3D_SELECTION_BEEP …)docs/architecture/sound_system.md
Encoder jog, wheel sensitivity settingdocs/architecture/handwheel_jog.md
Small UI tier study (1.47” dashboard)docs/ongoing_reflection/small_display_dashboard_and_ui_tiers_study.md
Memory constraints (heap, fragmentation, WiFi vs BT)docs/guides/esp32_memory_constraints.md

RuleReason
All lv_obj_* / lv_style_* calls must be inside _lock_acquire / _lock_release or within LVGL event/timer callbacksLVGL is strictly single-threaded on Core 1
Never destroy LVGL objects inside an event callbackUse lv_timer_create() cleanup timers — see screen_transitions_flow.md
initializeStyles() / resetStyles() only from the LVGL task or before it startslv_style_* APIs are not thread-safe
Never call lv_obj_remove_style_all() on widgets styled via apply*Style()Styles are shared objects; removing all strips them from the shared pool
After esp3d_resources_write_blob(), the mmap view is staleThe update service must reboot immediately after a successful blob write
All calloc/malloc in display code must be checked; log total free + largest block on failureHeap can drop to ~10 KB in Bluetooth mode; silent allocation failure crashes the system