grblHAL Module — Screen Router
Section titled “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.
Table of Contents
Section titled “Table of Contents”- Module Overview
- Architecture Position
- Component Reference
- Screen Type Catalogue
- Dispatch Flow
- Feature-Gated Screens
- Relationship to the grbl Screen Router
- Data Flow Diagram
- Dependencies
- Design Rules & Constraints
1. Module Overview
Section titled “1. Module Overview”| Property | Value |
|---|---|
| Source file | main/display/cnc/grblhal/screens/esp3d_screen_type.cpp |
| Header | main/display/cnc/grblhal/screens/esp3d_screen_type.h |
| Public API | void createScreen(ESP3DScreenType screen_type) |
| CNC target | grblHAL |
| LVGL thread | Core 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.
2. Architecture Position
Section titled “2. Architecture Position”The router sits at the boundary between the UI Framework (UIManager, screen lifecycle) and the grblHAL-specific screen implementations.
See also
- grblhal_module_connection_status.md —
connection_statusscreen component wired here- grblhal_module_change_tool.md —
change_toolscreen- grblhal_module_files.md —
filesscreen- grblhal_module_probe.md —
probescreen- grbl_module_screen_router.md — grbl counterpart (structurally identical)
3. Component Reference
Section titled “3. Component Reference”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};ComponentType enum (esp3d_screen_type.h)
Section titled “ComponentType enum (esp3d_screen_type.h)”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.
| Aspect | Detail |
|---|---|
| Signature | void createScreen(ESP3DScreenType screen_type) |
| Thread | Must be called on the LVGL task thread (Core 1) |
| Side-effects | Calls one screen namespace’s create(), which allocates LVGL objects |
| Error path | Unknown enum value → esp3d_log_e() + fallback to mainScreen::create() |
| Logging | On 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.
4. Screen Type Catalogue
Section titled “4. Screen Type Catalogue”The table below lists every case dispatched by the grblHAL router, the namespace it calls, and the source module that owns the screen implementation.
ESP3DScreenType | Namespace called | Owning module / doc |
|---|---|---|
splash | splashScreen | common_screens |
main | mainScreen | cnc_shared / navigation_screens |
settings | settingsScreen | cnc_shared / settings |
settings_list | settingsListScreen | cnc_shared / settings |
information | informationScreen | cnc_shared / navigation_screens |
languages | languagesScreen | common_screens |
baudrate | baudrateScreen | common_screens |
polling | pollingScreen | common_screens |
status | statusScreen | cnc_shared / status_screen |
jog | jogScreen | cnc_shared / jog_screen |
macros | macrosScreen | cnc_shared / macro_system |
change_tool | changeToolScreen | grblhal_module_change_tool.md |
probe | probeScreen | grblhal_module_probe.md |
files | filesScreen | grblhal_module_files.md |
connection_status | connectionStatusScreen | grblhal_module_connection_status.md |
output_selection (optional) | outputSelectionScreen | common_screens |
scan_bt (optional) | scanBTScreen | common_screens |
wifi_scan (optional) | wifiScanScreen | common_screens |
server_scan (optional) | serverScanScreen | common_screens |
screen_timeout (optional) | screenTimeoutScreen | common_screens |
update (optional) | updateScreen | common_screens |
Not routed here:
none,message_box,input,firmware_status,emptyThese screen types are managed directly by their owner screens (modal overlays, inline components) and are never requested viacreateScreen().
5. Dispatch Flow
Section titled “5. Dispatch Flow”6. Feature-Gated Screens
Section titled “6. Feature-Gated Screens”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.
The output_selection screen is included when any of the following flags are set:
ESP3D_BT_SERIAL_FEATUREESP3D_BT_BLE_FEATUREESP3D_WIFI_FEATUREESP3D_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.
7. Relationship to the grbl Screen Router
Section titled “7. Relationship to the grbl Screen Router”The grblHAL and grbl routers are structurally identical. They share:
- The same
ESP3DScreenTypeenum andComponentTypeenum (defined inesp3d_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:
| Screen | grbl router calls | grblHAL router calls |
|---|---|---|
change_tool | grbl/screens/change_tool_screen.cpp | grblhal/screens/change_tool_screen.cpp |
probe | grbl/screens/probe_screen.cpp | grblhal/screens/probe_screen.cpp |
files | grbl/screens/files_screen.cpp | grblhal/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.
8. Data Flow Diagram
Section titled “8. Data Flow Diagram”This diagram shows the lifecycle of a screen transition request from the moment a UI event fires through to LVGL object creation.
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. Uselv_timer_create()for deferred transitions. Seedocs/architecture/screen_transitions_flow.mdfor the transition timer pattern.
9. Dependencies
Section titled “9. Dependencies”Direct includes
Section titled “Direct includes”| Header | Purpose |
|---|---|
esp3d_screen_type.h | ESP3DScreenType enum, SCREEN_STR macro, createScreen declaration |
esp3d_log.h | esp3d_log() / esp3d_log_e() logging macros |
screens/connection_status_screen.h | ConnectionStatusScreenConfig struct (the only direct header include besides the module’s own header) |
Forward-declared namespaces
Section titled “Forward-declared namespaces”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.
Runtime dependencies (via UIManager)
Section titled “Runtime dependencies (via UIManager)”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.
10. Design Rules & Constraints
Section titled “10. Design Rules & Constraints”Adding a new screen to the grblHAL router
Section titled “Adding a new screen to the grblHAL router”- Define the enum value in
ESP3DScreenTypeinesp3d_screen_type.h. Mirror the change in thescreen_type_names[]debug array (same file, inside#if ESP3D_LOG). - Add a forward declaration in
esp3d_screen_type.cpp:namespace myNewScreen {void create();} - Add a
caseincreateScreen():case ESP3DScreenType::my_new_screen:myNewScreen::create();break; - Wrap with a feature guard if the screen is optional:
#if YOUR_FEATURE_FLAGcase ESP3DScreenType::my_new_screen:myNewScreen::create();break;#endif
- Implement
myNewScreen::create()following the transition-timer and LVGL lifecycle patterns documented indocs/architecture/screen_transitions_flow.md.
Invariants
Section titled “Invariants”| Rule | Rationale |
|---|---|
createScreen() is call-and-return only | The router holds no state; all screen state lives in the screen’s own translation unit |
Every case must have break | Fall-through would invoke two create() functions sequentially, producing duplicate LVGL objects |
Unknown types fall back to main with an error log | Prevents blank display on unexpected enum values |
connection_status always returns to settings | grblHAL-variant policy enforced at the router level; grbl router has the same hard-code |
All create() calls happen on Core 1 | LVGL 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 |
LVGL constraints summary
Section titled “LVGL constraints summary”- 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()(notlv_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.mdScreen architecture overview:docs/architecture/screens_architecture.mdLogging guide:docs/guides/esp3d_log_guide.md