Skip to content

grblHAL Module — Screen Router

The grblHAL screen router is the single entry-point for creating every UI screen in the grblHAL firmware variant. It implements the Factory pattern: callers supply an ESP3DScreenType enum value, and the router delegates to the correct screen namespace’s create() function. All conditional-compilation guards that control optional-feature screens (Bluetooth, Wi-Fi, server scan, brightness, OTA update) live here — no other code needs to know which screen types are actually compiled in.


  1. Module Overview
  2. Architecture Position
  3. Component Reference
  4. Screen Type Catalogue
  5. Dispatch Flow
  6. Feature-Gated Screens
  7. Relationship to the grbl Screen Router
  8. Data Flow Diagram
  9. Dependencies
  10. Design Rules & Constraints

PropertyValue
Source filemain/display/cnc/grblhal/screens/esp3d_screen_type.cpp
Headermain/display/cnc/grblhal/screens/esp3d_screen_type.h
Public APIvoid createScreen(ESP3DScreenType screen_type)
CNC targetgrblHAL
LVGL threadCore 1 — must only be called from the LVGL task

The router’s entire runtime behaviour is one switch statement over the ESP3DScreenType enum. Each case calls the corresponding namespace’s create() function and returns. No screen state is held inside the router itself.


The router sits at the boundary between the UI Framework (UIManager, screen lifecycle) and the grblHAL-specific screen implementations.

createScreen()

UIManager

esp3d_ui.h

grblHAL Screen Router

esp3d_screen_type.cpp

Common Screens

screens/

CNC Shared Screens

cnc/screens/

grblHAL-Specific Screens

cnc/grblhal/screens/

splash / main / settings

languages / baudrate

polling / update …

status / gc_state / jog / macros

firmware_status / settings_list

information

change_tool

probe

files

See also


ESP3DScreenType enum (esp3d_screen_type.h)

Section titled “ESP3DScreenType enum (esp3d_screen_type.h)”

A uint8_t-backed enum class that names every possible screen in the system. Both the grblHAL and grbl routers share this same enum; each router compiles only the case branches whose feature flags are active.

enum class ESP3DScreenType : uint8_t {
none = 0,
splash, main, settings, information, languages,
#if (ESP3D_BT_SERIAL_FEATURE || ESP3D_BT_BLE_FEATURE)
scan_bt,
#endif
baudrate, change_tool, probe, macros, files, jog, status, gc_state,
output_selection, message_box, polling, screen_timeout,
input, range_editor, settings_list, firmware_status, connection_status,
#if ESP3D_WIFI_FEATURE
wifi_scan,
#endif
#if ESP3D_IP_CNC_CLIENT_FEATURE
server_scan,
#endif
#if ESP3D_UPDATE_FEATURE
update,
#endif
empty
};

Enumerates reusable sub-components registered in UIManager. Used throughout the screen system but not dispatched by the router itself.

enum class ComponentType : uint8_t {
none = 0,
generic_screen, circular_menu_screen, virtual_buttons,
circular_menu, list_menu, list_menu_screen,
connection_status, firmware_status, message_box, panel
};

createScreen(ESP3DScreenType screen_type) (esp3d_screen_type.cpp)

Section titled “createScreen(ESP3DScreenType screen_type) (esp3d_screen_type.cpp)”

The sole public function of this module.

AspectDetail
Signaturevoid createScreen(ESP3DScreenType screen_type)
ThreadMust be called on the LVGL task thread (Core 1)
Side-effectsCalls one screen namespace’s create(), which allocates LVGL objects
Error pathUnknown enum value → esp3d_log_e() + fallback to mainScreen::create()
LoggingOn entry: esp3d_log("Creating screen of type: %s", SCREEN_STR(screen_type))

connection_status special case — this is the only case that passes a configuration struct to create():

case ESP3DScreenType::connection_status: {
connectionStatusScreen::ConnectionStatusScreenConfig cfg;
cfg.return_screen = ESP3DScreenType::settings;
connectionStatusScreen::create(cfg);
break;
}

The return target is hard-coded to settings for this firmware variant.

SCREEN_STR(index) macro (esp3d_screen_type.h)

Section titled “SCREEN_STR(index) macro (esp3d_screen_type.h)”

Compile-time helper that maps an ESP3DScreenType value to a human-readable string for logging. Only defined when ESP3D_LOG is enabled.


The table below lists every case dispatched by the grblHAL router, the namespace it calls, and the source module that owns the screen implementation.

ESP3DScreenTypeNamespace calledOwning module / doc
splashsplashScreencommon_screens
mainmainScreencnc_shared / navigation_screens
settingssettingsScreencnc_shared / settings
settings_listsettingsListScreencnc_shared / settings
informationinformationScreencnc_shared / navigation_screens
languageslanguagesScreencommon_screens
baudratebaudrateScreencommon_screens
pollingpollingScreencommon_screens
statusstatusScreencnc_shared / status_screen
jogjogScreencnc_shared / jog_screen
macrosmacrosScreencnc_shared / macro_system
change_toolchangeToolScreengrblhal_module_change_tool.md
probeprobeScreengrblhal_module_probe.md
filesfilesScreengrblhal_module_files.md
connection_statusconnectionStatusScreengrblhal_module_connection_status.md
output_selection (optional)outputSelectionScreencommon_screens
scan_bt (optional)scanBTScreencommon_screens
wifi_scan (optional)wifiScanScreencommon_screens
server_scan (optional)serverScanScreencommon_screens
screen_timeout (optional)screenTimeoutScreencommon_screens
update (optional)updateScreencommon_screens

Not routed here: none, message_box, input, firmware_status, empty These screen types are managed directly by their owner screens (modal overlays, inline components) and are never requested via createScreen().


ESP3DScreenType

main

settings

settings_list

information

languages

baudrate

polling

status

jog

macros

change_tool

probe

files

splash

connection_status

optional screens

(BT/WiFi/IP/OTA/Brightness)

default

Caller

(UIManager / screen transition)

createScreen(screen_type)

esp3d_log(SCREEN_STR)

switch

screen_type

mainScreen::create()

settingsScreen::create()

settingsListScreen::create()

informationScreen::create()

languagesScreen::create()

baudrateScreen::create()

pollingScreen::create()

statusScreen::create()

jogScreen::create()

macrosScreen::create()

changeToolScreen::create()

probeScreen::create()

filesScreen::create()

splashScreen::create()

connectionStatusScreen::create(cfg)

cfg.return_screen = settings

optional screen::create()

esp3d_log_e + mainScreen::create()


Several screens are conditionally compiled based on build-time feature flags. The router uses the same flags to include or exclude the corresponding case branches.

ESP3D_UPDATE_FEATURE

update

ESP3D_BRIGHTNESS_CONTROL_FEATURE

screen_timeout

ESP3D_IP_CNC_CLIENT_FEATURE

server_scan

ESP3D_WIFI_FEATURE

wifi_scan

BT or WiFi or UART_EXT

output_selection

ESP3D_BT_SERIAL_FEATURE

or ESP3D_BT_BLE_FEATURE

scan_bt

Always compiled

main / settings / status

jog / probe / files

macros / change_tool

splash / information / languages

baudrate / polling / settings_list

connection_status

The output_selection screen is included when any of the following flags are set:

  • ESP3D_BT_SERIAL_FEATURE
  • ESP3D_BT_BLE_FEATURE
  • ESP3D_WIFI_FEATURE
  • ESP3D_UART_EXT_FEATURE

Memory note: On ESP32 with Bluetooth active, available heap can drop to ~10 KB. Feature flags that disable unused services reduce both binary size and runtime heap footprint. See docs/guides/esp32_memory_constraints.md.


The grblHAL and grbl routers are structurally identical. They share:

  • The same ESP3DScreenType enum and ComponentType enum (defined in esp3d_screen_type.h)
  • The same dispatched screen type list
  • The same feature compilation guards

They differ only in which screen implementation namespaces they forward-declare and call:

Screengrbl router callsgrblHAL router calls
change_toolgrbl/screens/change_tool_screen.cppgrblhal/screens/change_tool_screen.cpp
probegrbl/screens/probe_screen.cppgrblhal/screens/probe_screen.cpp
filesgrbl/screens/files_screen.cppgrblhal/screens/files_screen.cpp

All other screens (common + CNC shared) resolve to the same physical .cpp translation units for both routers.

See grbl_module_screen_router.md for the grbl variant.


This diagram shows the lifecycle of a screen transition request from the moment a UI event fires through to LVGL object creation.

LVGL(Core 1)Target Screennamespace::create()createScreen()grblHAL routerUIManagerActive Screen(e.g. mainScreen)LVGL(Core 1)Target Screennamespace::create()createScreen()grblHAL routerUIManagerActive Screen(e.g. mainScreen)Safe timer pattern - neverdestroy objects in event cbrequest transition (ESP3DScreenType)schedule timer-based transitioncreateScreen(screen_type)log + switch(screen_type)create()lv_obj_create() + layoutregisterScreen(type, lv_obj)object handle(returns)(returns)

LVGL threading constraint: createScreen() must be called from the LVGL task (Core 1). Screen objects must never be created or destroyed from ISRs, FreeRTOS tasks on Core 0, or inside LVGL event callbacks. Use lv_timer_create() for deferred transitions. See docs/architecture/screen_transitions_flow.md for the transition timer pattern.


HeaderPurpose
esp3d_screen_type.hESP3DScreenType enum, SCREEN_STR macro, createScreen declaration
esp3d_log.hesp3d_log() / esp3d_log_e() logging macros
screens/connection_status_screen.hConnectionStatusScreenConfig struct (the only direct header include besides the module’s own header)

The router uses C++ namespace forward declarations — no additional header inclusions — to keep coupling minimal. Each namespace is resolved at link time to its screen’s .cpp translation unit.

fwd decl

fwd decl

fwd decl

fwd decl

fwd decl

fwd decl

fwd decl

fwd decl

fwd decl

grblHAL Screen Router

mainScreen

settingsScreen

settingsListScreen

statusScreen

jogScreen

changeToolScreen

★ grblHAL-specific

probeScreen

★ grblHAL-specific

filesScreen

★ grblHAL-specific

macrosScreen

languagesScreen / baudrateScreen

pollingScreen / splashScreen

informationScreen / …

The router itself does not interact with UIManager directly. The create() functions it invokes call UIManager::registerScreen() internally. See ui_core module for UIManager documentation.


  1. Define the enum value in ESP3DScreenType in esp3d_screen_type.h. Mirror the change in the screen_type_names[] debug array (same file, inside #if ESP3D_LOG).
  2. Add a forward declaration in esp3d_screen_type.cpp:
    namespace myNewScreen {
    void create();
    }
  3. Add a case in createScreen():
    case ESP3DScreenType::my_new_screen:
    myNewScreen::create();
    break;
  4. Wrap with a feature guard if the screen is optional:
    #if YOUR_FEATURE_FLAG
    case ESP3DScreenType::my_new_screen:
    myNewScreen::create();
    break;
    #endif
  5. Implement myNewScreen::create() following the transition-timer and LVGL lifecycle patterns documented in docs/architecture/screen_transitions_flow.md.
RuleRationale
createScreen() is call-and-return onlyThe router holds no state; all screen state lives in the screen’s own translation unit
Every case must have breakFall-through would invoke two create() functions sequentially, producing duplicate LVGL objects
Unknown types fall back to main with an error logPrevents blank display on unexpected enum values
connection_status always returns to settingsgrblHAL-variant policy enforced at the router level; grbl router has the same hard-code
All create() calls happen on Core 1LVGL is single-threaded; Core 0 access causes data races and assertion failures
All conditionals use braces {}Logging macros (esp3d_log) expand to nothing in production; a bare if (cond) esp3d_log(...) becomes if (cond) ; — broken control flow
  • Never call createScreen() directly from an LVGL event callback.
  • Always schedule navigation with lv_timer_create() + lv_timer_set_repeat_count(t, 1).
  • Use lv_obj_del_async() (not lv_obj_del()) when deleting a screen from an event context.
  • Apply change detection before lv_obj_* calls to avoid unnecessary redraws.

Full transition rules: docs/architecture/screen_transitions_flow.md Screen architecture overview: docs/architecture/screens_architecture.md Logging guide: docs/guides/esp3d_log_guide.md