Skip to content

firmware_status Module

The firmware_status module is a CNC-specific UI module that surfaces the live state of the connected CNC firmware to the operator. It is composed of two complementary pieces:

PieceFilesRole
FirmwareStatusComponentmain/display/cnc/components/firmware_status.hCompact, always-visible icon embedded inside other screens. Clicking it opens the full-screen view.
FirmwareStatusScreenmain/display/cnc/screens/firmware_status_screen.h/.cppFull-screen view with a dynamic header, scrollable message history, and contextual control buttons.

Both pieces are pure consumers. All CNC state comes through the esp3dXValues observable bus (see values) and the underlying data is provided by the target-specific GCode handler service (see CNC Firmware Integration).


Host Screens

firmware_status Module

Data Providers (Other Modules)

firmware_status

message_history

local_message_history

subscribe(firmware_status)

subscribe(connection_status)

subscribe(firmware_status

message_history

local_message_history)

embedded in

embedded in

embedded in

click → open

getMessageHistory()

get_history()

esp3dGcodeHandler

(CNC Firmware Parser)

esp3d_system_message_history

(Pendant System Log)

esp3dXValues

(Observable Bus)

FirmwareStatusComponent

(Embedded Icon Widget)

FirmwareStatusScreen

(Full-Screen View)

Jog Screen

Status Screen

Other CNC Screens


FirmwareStatusComponent is a reusable LVGL widget that host screens embed to give operators a persistent, at-a-glance view of the CNC firmware state. It is a clickable icon: tapping it transitions to FirmwareStatusScreen.

// firmware_status.h
struct FirmwareStatusConfig {
lv_align_t align = LV_ALIGN_TOP_LEFT;
int32_t x_offset = 0;
int32_t y_offset = 0;
ESP3DScreenType return_screen = ESP3DScreenType::main;
void (*prepare_parent_screen_destruction)(void) = nullptr;
};
FieldDescription
alignLVGL alignment anchor within the parent container
x_offset / y_offsetPosition offsets from the anchor point
return_screenScreen to return to when the operator closes FirmwareStatusScreen
prepare_parent_screen_destructionOptional callback invoked before the host screen is destroyed during the navigation transition
┌─────────────────────────┐
│ [Status Icon] │ ← lv_img (icon changes with firmware state)
│ [Press Circle] │ ← feedback circle on tap
└─────────────────────────┘

The icon imagery maps to ESP3DFirmwareState values (defined in main/display/cnc/esp3d_firmware_states.h):

StateIcon variableMeaning
Unknownstatus_sNot connected / state unknown
idleidle_sMachine idle, ready
runrun_sExecuting a job
alarmalarm_sAlarm condition
checkcheck_sCheck mode (dry run)
doordoor_sDoor interlock open
holdhold_sFeed hold active
homehome_sHoming cycle
sleepsleep_sSleep mode
jogjog_sJogging
tooltools_sTool change
errorerror_sError state
IndexHandlerEffect
ESP3DValuesIndex::firmware_statusstatic_on_status_updateRedraws the icon to match the new firmware state
ESP3DValuesIndex::server_statusstatic_on_connection_updateShows or hides the component based on connection availability

Input overflow (a14): while gcodeHostService.inputOverflowLatched() is true, the icon shows error whatever the CNC state, until the user presses Restart in FirmwareStatusScreen (which calls gcodeHostService.resetAll()). The last state received is kept in current_state_.

Theme refresh (a14): updateThemeColor() re-applies the press circle border color (ESP3D_ACCENT_SELECT_COLOR) and redraws the icon from current_state_. The settings screen calls it on a live theme preview and when a theme is applied.

Construction (a14): the constructor no longer throws (callers use new (std::nothrow) and check isValid(); an exception would reach std::terminate). On a failure it returns early after rolling back what it already did (value subscriptions, component registration), and the destructor then skips prepareForDestruction().

FirmwareStatusScreenopen_status_screen_cb (LVGL timer)FirmwareStatusComponentOperatorFirmwareStatusScreenopen_status_screen_cb (LVGL timer)FirmwareStatusComponentOperatorTap iconon_click_event()lv_timer_create (one-shot)firmwareStatusScreen::create(config)Full-screen view displayed

The timer indirection keeps the LVGL event callback short and non-blocking (LVGL constraint: no heavy work inside event callbacks; no screen destruction from within an event). Once the timer is created, the component calls ui_manager.beginScreenTransition(): the global transition signal tells code outside the screen flow (the critical alert) that a transition is pending (see ui_core.md).

static int32_t get_x_position(bool get_orientation = true);
static int32_t get_y_position(bool get_orientation = true);
static lv_align_t get_alignment(bool get_orientation = true);

These helpers let host screens place the component consistently across all display orientations without duplicating layout arithmetic.


FirmwareStatusScreen is the full-screen detail view. It provides:

  • A status header whose background color reflects the urgency of the firmware state.
  • A scrollable message list that can display either remote CNC firmware messages (MSG:ERR, MSG:INFO) or the pendant’s own local system messages.
  • Contextual action buttons whose icons and behaviour adapt to the current firmware state.
  • Encoder scrolling for the message list (hardware encoder or virtual button scroll mode).
// firmware_status_screen.h
struct FirmwareStatusScreenConfig {
ESP3DScreenType return_screen = ESP3DScreenType::main;
};

return_screen is the screen recreated when the operator presses Back. There is no prepare_parent_screen_destruction callback because the parent screen is always recreated via createScreen(), not restored from a saved state.

┌──────────────────────────────────────────────┐
│ [Icon] [STATUS TEXT] title_section │ ← color-coded by state
├──────────────────────────────────────────────┤
│ [RemoteName / LocalName ⟳] │ ← source_toggle_bar (clickable)
├──────────────────────────────────────────────┤
│ │
│ MSG: Latest message (newest first) │ ← message_list
│ MSG: Older message │ (scrollable, flex-column)
│ MSG: Oldest visible │
│ ... │
│ │
├──────────────────────────────────────────────┤
│ [Action] [Secondary] [Back] │ ← VirtualButtonsComponent
└──────────────────────────────────────────────┘
Firmware StateHeader Color TokenPrimary ButtonGCode SentSecondary ButtonGCode Sent
ALARMESP3D_ACCENT_ALERT_COLORalarm_off_b$X (unlock)soft_reset_b0x18 (soft reset)
HOLDESP3D_ACCENT_ACTION_COLORplay_b~ (resume)status_query_b? (status query)
RUNESP3D_ACCENT_SELECT_COLORhold_feed_b! (feed hold)status_query_b? (status query)
JOGESP3D_ACCENT_SELECT_COLORstop_jog_b0x85 (jog cancel)status_query_b? (status query)
IDLEESP3D_ACCENT_ACTIVE_COLORsoft_reset_b0x18 (soft reset)status_query_b? (status query)
ERRORESP3D_ACCENT_ALERT_COLORsoft_reset_b0x18 (soft reset)status_query_b? (status query)
OthersESP3D_ACCENT_SELECT_COLORsoft_reset_b0x18 (soft reset)status_query_b? (status query)

The Back button (button 2) always navigates to return_screen. None of the contextual buttons close the screen — the operator can issue multiple commands without leaving the view.

Soft reset (a14): before sending 0x18, the soft reset action calls gcodeHostService.resetAll(). The controller discards its buffer on reset, so the line in flight would never be acknowledged and the host would wait forever; resetAll() drops the host queues and also clears the input overflow latch. A status query ? follows the reset.

Input overflow (a14): while gcodeHostService.inputOverflowLatched() is true, the header shows ERROR (and the ERROR buttons, with Restart) over the CNC status, until Restart clears the latch. See CNC_Firmware_Integration.md, “Lost Lines, Input Overflow and Resets”.

The secondary button uses the status_query_b icon (refresh arrows with a ?), so it reads as “query the CNC status” rather than a local refresh.

IndexSourceHandlerEffect
ESP3DValuesIndex::firmware_statusesp3dGcodeHandleronFirmwareStatusUpdateUpdates header color, icon, title text, and button set
ESP3DValuesIndex::message_historyesp3dGcodeHandleronMessageHistoryUpdateRebuilds message list when Remote source is active
ESP3DValuesIndex::local_message_historyesp3d_system_message_historyonMessageHistoryUpdateRebuilds message list when Local source is active

Files: main/display/cnc/components/critical_alert.h/.cpp (new in a14). criticalAlert::begin() is called once from UIManager::initialize() (LVGL task).

The critical alert opens an error message box on top of whatever screen is active when a condition raised outside the UI needs the user. Today the only one is the input overflow purge.

messageBoxScreenUIManagercriticalAlert (LVGL task)esp3dXValuesgcode host taskmessageBoxScreenUIManagercriticalAlert (LVGL task)esp3dXValuesgcode host taskloop[until safe]set critical_alert "overflow:<details>"onCriticalAlert()build title + content, start retry timer (100 ms)isScreenTransitionPending()? getActiveScreen()? getActiveScreenDestructor()?create(Error, prepare = active screen destructor, OK = onRecover)OK pressedresetAll(), then 0x18 soft reset + ? status query
  • The gcode host task publishes ESP3DValuesIndex::critical_alert as "<kind>[:<details>]"; an empty value clears it (and cancels a pending open). Value callbacks run in the LVGL task.
  • overflow uses the translated input_overflow_title / input_overflow_msg labels with the details in parentheses; any other kind shows the generic error title with the raw value.
  • Opening follows Pattern 3 of screen_transitions_flow.md, with the destructor the active screen registered in UIManager::registerScreen(). The timer retries every CRITICAL_ALERT_RETRY_MS (100 ms) while it is not safe: a deferred screen transition is pending, there is no active screen, a firmware update is running (never interrupted), or a message box is already shown (single instance, it cannot replace itself).
  • OK (onRecover) calls gcodeHostService.resetAll(), sends a soft reset (high priority) and a status query, and the message box returns to the main screen.

FirmwareStatusScreen (LVGL / Core 1)

esp3dXValues Bus

System Log (Other Module)

GCode Handler (Other Module)

CNC Transport (Other Module)

set_value(firmware_status)

set_value(message_history)

set_value(local_message_history)

getMessageHistory()

get_history()

Serial / BT / USB RX

esp3d_rendering_client

processCommand()

parse status + MSG: lines

addMessageToHistory()

_message_history deque

[pthread_mutex protected]

esp3d_system_message_history

deque [mutex protected]

firmware_status

message_history

local_message_history

onFirmwareStatusUpdate()

→ updateStatusDisplay()

→ updateButtonsForStatus()

onMessageHistoryUpdate()

→ refreshMessageList()

getMessageHistory() snapshot

or get_history() snapshot

refreshMessageList() only rebuilds the LVGL child list when the view is already scrolled to the top (newest messages). If the operator has scrolled down to review older messages, incoming update notifications are silently skipped until the operator scrolls back to the top. This prevents a rapid stream of incoming firmware messages from fighting the operator’s scroll position.


The source toggle bar lets the operator switch the message list between two sources at runtime without leaving the screen:

Remote (default) → esp3dGcodeHandler.getMessageHistory()
Shows CNC firmware MSG:ERR and MSG:INFO lines
Error lines: ESP3D_INDICATOR_ERROR_COLOR
Info lines: ESP3D_INDICATOR_INFO_COLOR
Local → esp3d_system_message_get_history()
Shows pendant system messages (e.g. resource/theme warnings)
Error: ESP3D_INDICATOR_ERROR_COLOR
Warning: ESP3D_INDICATOR_WARNING_COLOR
Info: ESP3D_INDICATOR_INFO_COLOR

onMessageHistoryUpdate() compares the incoming notification index against the currently visible source and only redraws when they match, avoiding unnecessary LVGL work for the hidden source.


Hardware Encoder (ESP3D_HARDWARE_ENCODER_FEATURE defined)

Section titled “Hardware Encoder (ESP3D_HARDWARE_ENCODER_FEATURE defined)”

The screen registers onEncoderEvent() on LV_EVENT_KEY. On each encoder step:

  1. Checks scroll limits (lv_obj_get_scroll_top / lv_obj_get_scroll_bottom).
  2. Plays a directional beep (ESP3D_MOVEMENT_FORWARD_BEEP / ESP3D_MOVEMENT_BACKWARD_BEEP).
  3. Scrolls by ENCODER_SCROLL_STEP (15 px) via lv_obj_scroll_by(..., LV_ANIM_ON).

screen created

operator taps message list

operator taps button 0 (exit scroll mode)

button 1 → scroll up\nbutton 2 → scroll down

button 0 = contextual action\nbutton 1 = secondary action\nbutton 2 = back

Normal

ScrollMode

In scroll mode, buttons 1 and 2 emit synthetic encoder steps (emitEncoderStep(±1)) which are processed by onEncoderEvent(), keeping the scroll logic in one place and eliminating code duplication.


UIManageresp3dXValuesFirmwareStatusScreenCaller (FirmwareStatusComponent\nor other screen)UIManageresp3dXValuesFirmwareStatusScreenCaller (FirmwareStatusComponent\nor other screen)Screen is live - receives value updatesLVGL deletes screen objectfirmwareStatusScreen::create(config)Create GenericScreen (buttons, container)Build UI (title section, toggle bar, message list)subscribe(firmware_status)subscribe(message_history)subscribe(local_message_history)registerScreen(firmware_status, prepareForDestruction)lv_obj_add_event_cb(LV_EVENT_DELETE → onScreenDestroy)lv_obj_add_event_cb(LV_EVENT_KEY → onEncoderEvent)Operator presses Backcleanup_timer_cb → prepareForDestruction()unsubscribe(firmware_status)unsubscribe(message_history)unsubscribe(local_message_history)GenericScreen::prepareForDestruction()transition_timer_cb → createScreen(return_screen)unregisterScreen(firmware_status)delete screen_instance_

Key lifecycle rules (consistent with the project-wide screen transition pattern):

  • No direct screen destruction inside event callbacks. All transitions go through one-shot LVGL timers (cleanup_timer_cb → prepareForDestruction → transition_timer_cb).
  • prepareForDestruction() is idempotent (guarded by is_prepared_for_destruction_). LV_EVENT_DELETE safely calls it a second time if the timer path already ran.
  • After unsubscribing, all static UI pointers (title_label_, message_list_, etc.) are set to nullptr so stale subscription callbacks cannot access freed LVGL objects.

firmware_status

(this module)

LVGL

(UI rendering)

esp3dXValues

(observable bus)

esp3dGcodeHandler

(CNC firmware integration)

esp3d_system_message_history

(pendant system log)

GenericScreen

(base screen)

VirtualButtonsComponent

(button bar)

UIManager

(screen registry and styles)

ESP3DFirmwareState

(state enum)

ESP3DScreenType

(screen router)

DependencyModule DocRole
LVGL—All UI object creation, layout, events, and rendering
esp3dXValuesvaluesObservable bus; the module subscribes to three indices
esp3dGcodeHandlerCNC Firmware IntegrationRemote message history provider and GCode command sink
esp3d_system_message_historyesp3d_coreLocal (pendant) message history provider
GenericScreenUI Framework & ScreensBase screen providing container, rotation support, and button bar
VirtualButtonsComponentUI Framework & ScreensThree-button bar with runtime icon and color updates
UIManagerUI Framework & ScreensStyle application, screen registry, orientation angle
ESP3DFirmwareState— (esp3d_firmware_states.h)Canonical state enum shared with FirmwareStatusComponent’s icon mapper
ESP3DScreenType—Typed screen identifiers for the router

ConcernMitigation
_message_history deque written from CNC RX task (Core 0)pthread_mutex_t in the provider; callers always snapshot via getMessageHistory() — the live deque is never iterated directly
esp3d_system_message_history deque written from logging hooksMutex in the provider; callers use esp3d_system_message_get_history() which returns a snapshot copy
Value subscription callbacks invoked on Core 1 (LVGL task)All LVGL object updates remain inside subscription callbacks — no cross-core LVGL calls
Screen deletion inside LVGL event callbacksProhibited; all destruction paths use one-shot LVGL timers to defer work out of the event context
Rapid firmware message stream vs. operator scroll positionSticky-scroll check in refreshMessageList() — rebuilds child list only when the view is pinned to the top

FunctionDescription
FirmwareStatusComponent(parent, host_screen, config, angle)Creates the icon widget and subscribes to value updates (no exceptions; check isValid())
updateThemeColor()Re-applies theme-dependent colors (press circle, icon)
~FirmwareStatusComponent()Unsubscribes from values and destroys LVGL objects
updateOrientation(angle)Repositions the icon for a new display rotation
updatePosition(align, x, y)Moves the icon within its parent
updateConfig(config)Updates the return screen and destruction callback
prepareForDestruction()Unsubscribes values before the host screen is destroyed
get_x_position(orientation)Static: returns orientation-aware X offset
get_y_position(orientation)Static: returns orientation-aware Y offset
get_alignment(orientation)Static: returns orientation-aware LVGL alignment

FirmwareStatusScreen (namespace firmwareStatusScreen)

Section titled “FirmwareStatusScreen (namespace firmwareStatusScreen)”
FunctionDescription
create(config)Builds and registers the full screen
onFirmwareStatusUpdate(idx, value, action)Subscription callback: updates header color, icon, and button set
onMessageHistoryUpdate(idx, value, action)Subscription callback: conditionally rebuilds message list for the active source
onSourceTogglePressed(e)Toggles the message list source between Remote (firmware) and Local (pendant)
onEncoderEvent(e)Handles hardware encoder steps to scroll the message list
on_action_press/release(idx, ...)Button 0: sends the context-appropriate GCode command
on_secondary_press/release(idx, ...)Button 1: sends secondary command (soft reset for ALARM, status query otherwise)
on_back_press/release(idx, ...)Button 2: initiates timer-based transition back to return_screen
cleanup_timer_cb(timer)Runs prepareForDestruction() then arms the transition timer
transition_timer_cb(timer)Calls createScreen(next_screen_target_) to recreate the parent screen
onScreenDestroy(e)LV_EVENT_DELETE handler: unregisters from UIManager and deletes the instance

  • UI Framework & Screens — GenericScreen base class, screen lifecycle patterns, transition macros
  • CNC Firmware Integration — GCode handler service, message history provider, realtime command constants
  • values — ESP3DValues observable bus, subscription model, value indices (critical_alert)
  • ui_core — screen registry destructors and the global transition signal used by the critical alert
  • esp3d_core — System message history (ESP3DSystemMessage, ESP3DSystemMessageType)
  • theme_palette — Semantic color tokens referenced in state-to-color mapping
  • development — Icon and font management (icon variables such as idle_s, alarm_s)