Skip to content

FluidNC Module — Connection Status Component

FluidNC Module — Connection Status Component

Section titled “FluidNC Module — Connection Status Component”

The fluidnc_module_connection_status module defines a compact, clickable LVGL UI widget that continuously reflects the live connection state between the pendant and the FluidNC CNC controller. It renders an icon (or animated spinner) in a corner of any host screen and navigates to the connection_status_screen when the user taps it.

Source file: main/display/cnc/fluidnc/components/connection_status.h

Sibling components: Structurally identical variants exist for the other supported CNC firmwares:

  • Grbl — main/display/cnc/grbl/components/connection_status.h
  • GrblHAL — main/display/cnc/grblhal/components/connection_status.h

All three share the same public API and behaviour; only the firmware-specific status parsing differs in their .cpp implementations.


LVGL (Core 1 - UI Thread)

Core Platform

Common Screens

fluidnc_module (FluidNC-specific UI)

subscribes transport & server status

callback: static_on_status_update

spawns on click

open_status_screen_cb

creates / manages

return_screen

embed

embed

embed

ConnectionStatusComponent

(connection_status.h)

change_tool_screen.cpp

files_screen.cpp

probe_screen.cpp

connection_status_screen

(connection_status_screen.h/.cpp)

ESP3DValues

(esp3d_values.h)

ESP3DScreenType

(esp3d_screen_type.h)

lv_obj_t objects

(img, spinner, click_container,

press_circle)

lv_timer_t

(screen transition timer)

The component is an embedded sub-widget — host screens (change_tool, files, probe) instantiate it directly, passing their own lv_obj_t* as the parent. It is not a full screen; it lives on top of whatever screen currently has focus.


A plain-data configuration struct passed to the component at construction (and updatable at runtime via updateConfig()).

struct ConnectionStatusConfig {
lv_align_t align = LV_ALIGN_TOP_RIGHT; // LVGL alignment anchor
int32_t x_offset = 0; // Pixel offset from anchor (X)
int32_t y_offset = 0; // Pixel offset from anchor (Y)
ESP3DScreenType return_screen = ESP3DScreenType::main; // Where to go after connection_status_screen closes
void (*prepare_parent_screen_destruction)(void) = nullptr; // Optional teardown hook
};
FieldPurpose
alignLVGL alignment constant — defaults to top-right corner
x_offset / y_offsetFine-tune pixel position relative to the anchor
return_screenThe screen to restore when the user closes the connection status screen
prepare_parent_screen_destructionCallback invoked just before the host screen is destroyed during a navigation transition

The widget class. It owns the LVGL object tree for the indicator and manages the full lifecycle of subscriptions and timers.

MethodDescription
ConnectionStatusComponent(parent, host_screen, config, angle)Constructor — builds LVGL objects, subscribes to value callbacks, sets initial visual state
~ConnectionStatusComponent()Destructor — unsubscribes callbacks (if not already done), deletes LVGL objects
updateOrientation(angle)Repositions the indicator when the screen rotates (0°, 90°, 180°, 270°)
updatePosition(align, x, y)Moves the indicator to a new anchor without reconstructing it
updateConfig(config)Replaces the full config at runtime
prepareForDestruction()Unsubscribes from ESP3DValues and marks a flag; safe to call inside an LVGL event callback
isValid()Returns true if construction succeeded and the component is operational
get_x_position(get_orientation)Static helper — returns the correct X offset for the current or a default orientation
get_y_position(get_orientation)Static helper — Y offset
get_alignment(get_orientation)Static helper — lv_align_t for the current orientation
getHostScreen()Static — returns which screen type currently owns the component

parent lv_obj_t

(host screen root)

click_container_

(lv_obj_t - transparent, sized for touch target)

img_

(lv_img - status icon)

spinner_

(lv_spinner - shown during 'T' state)

press_circle_

(lv_obj - visual press feedback)

  • click_container_ — an invisible, touch-sized container registered with the LV_EVENT_CLICKED callback. Sizing it larger than the icon ensures a comfortable touch target on small displays.
  • img_ — renders the status icon image from the ui_resources partition.
  • spinner_ — an animated LVGL spinner, made visible only when the state is connecting (T).
  • press_circle_ — a translucent circle that flashes briefly when the user taps, providing tactile feedback.

The component tracks two independent status characters — one for the transport layer, one for the CNC server — and merges them into a single display state.

initial (both = '?')

transport = '.'

transport = 'T' or server = 'T'

server = 'A'

server = 'C'

transport changes to 'T'

server = 'C'

server = 'A'

transport or server resets to '?'

server != 'C'

server = 'T'

retrying

? - Disconnected\nIcon: disconnected\nSpinner: hidden

dot - Radio off\nIcon: radio-off\nSpinner: hidden

T - Connecting\nIcon: hidden\nSpinner: visible

A - Auth failed\nIcon: warning\nSpinner: hidden

C - Full control\nIcon: connected\nSpinner: hidden

CharSemanticVisual
?Disconnected / no signalDisconnected icon
.Radio off (WiFi/BT disabled)Radio-off icon
TTransport or server connectingAnimated spinner
AAuthentication failedWarning icon
CConnected — full CNC controlConnected icon

The private method compute_display_state() maps the combination of _transport_status_ and _server_status_ to a single uint8_t state code, which update_connection_status() then uses to select the correct LVGL visual.


LVGL UI Thread (Core 1)on_status_update() [instance]static_on_status_update()ESP3DValuesCommunication Transport(serial / BLE / socket / USB)LVGL UI Thread (Core 1)on_status_update() [instance]static_on_status_update()ESP3DValuesCommunication Transport(serial / BLE / socket / USB)set_value(transport_status_index, "T")invoke subscribed callbackdelegates to current instanceupdate _transport_status_\nor _server_status_compute_display_state()update_connection_status(state)\nshow/hide img_, spinner_, press_circle_

Why static callbacks? ESP3DValues::subscribe() accepts a plain function pointer (callbackFunctionPtr_t). C++ non-static member function pointers are not compatible with plain function pointers. The static wrapper static_on_status_update holds a reference to the current instance via host_screen_static_, bridging the two worlds while keeping instance state private.


connection_status_screen::create()open_status_screen_cb() [static]lv_timer (one-shot)on_click_event() [static]click_container_ (LVGL)connection_status_screen::create()open_status_screen_cb() [static]lv_timer (one-shot)on_click_event() [static]click_container_ (LVGL)deferred to next LVGL tick\nsafe: avoids destroying objects inside callbackUsertapLV_EVENT_CLICKEDstore config in pending_config_\ncall prepare_parent_screen_destruction (if set)lv_timer_create(open_status_screen_cb, 0ms, 1-shot)fires on next tickconnection_status_screen::create(config)Connection Status Screen shownUser

LVGL safety rule: Screen objects must never be destroyed inside an event callback — doing so risks use-after-free within the same LVGL dispatch cycle. The one-shot timer defers the screen switch to the next tick, which is the established safe pattern throughout this codebase. See screen_transitions_flow.md for the full rationale.


The pendant may be mounted in any of four orientations. The component supports this via:

  • updateOrientation(angle) — called by the host screen when the encoder or settings change the display angle.
  • Static helpers (get_x_position, get_y_position, get_alignment) — allow host screens to query the correct placement before the component is constructed, so the initial ConnectionStatusConfig is already orientation-correct.

Orientation Setting

(0° / 90° / 180° / 270°)

get_alignment()

get_x_position()

get_y_position()

ConnectionStatusConfig

.align / .x_offset / .y_offset

ConnectionStatusComponent

constructor or updatePosition()


No

Yes

Host Screen Creates Component

Constructor

create LVGL objects

subscribe ESP3DValues

isValid?

Component inert

is_valid_ = false

Running

Receives status callbacks

Handles tap events

prepareForDestruction

unsubscribe callbacks

set flag

Destructor

delete LVGL objects

Destroyed

prepareForDestruction() is intentionally separated from the destructor so host screens can call it safely during LVGL event callbacks before the C++ destructor runs. This two-phase teardown avoids stale callback invocations after the containing screen has begun destruction.


Three members are static on the class:

Static MemberRole
host_screen_static_Tracks which ESP3DScreenType currently owns the component; used by the static callback to route updates to the correct instance
screen_timer_Holds the pending one-shot timer handle; prevents double-creation if the user taps rapidly
pending_config_Stores the config captured at click time so open_status_screen_cb can read it after the instance may have been torn down

Only one ConnectionStatusComponent is active per screen at a time, which is why these can safely be static.


DependencyWhat it provides
esp3d_values.h (ESP3DValues module)Observable value bus — subscribe() / unsubscribe(), delivers transport and server status updates
screens/esp3d_screen_type.hESP3DScreenType enum identifying all navigable screens
connection_status_screen.h/.cpp (Common Screens)The full-screen overlay opened on tap; accepts ConnectionStatusScreenConfig carrying return_screen
LVGL (lvgl.h)Widget toolkit — lv_obj_t, lv_img, lv_spinner, lv_timer, event system

// Inside a FluidNC host screen's create() function:
ConnectionStatusConfig cs_config;
cs_config.align = ConnectionStatusComponent::get_alignment();
cs_config.x_offset = ConnectionStatusComponent::get_x_position();
cs_config.y_offset = ConnectionStatusComponent::get_y_position();
cs_config.return_screen = ESP3DScreenType::main;
cs_config.prepare_parent_screen_destruction = myScreen::prepareForDestruction;
auto* cs_component = new ConnectionStatusComponent(
screen_obj, // parent lv_obj_t*
ESP3DScreenType::main, // host screen identity
cs_config,
current_orientation_angle // int32_t degrees
);
if (!cs_component->isValid()) {
delete cs_component;
// handle error
}

During screen teardown:

// Safe to call from inside an LVGL event callback:
cs_component->prepareForDestruction();
// Destructor runs when the object goes out of scope or is deleted.

  • No exceptions from the constructor. Callers use new (std::nothrow) and check isValid(); an exception would reach std::terminate. On a failure the constructor returns early and rolls back what it already did (component registration in ui_manager, value subscriptions), and is_valid_ is only set once construction fully succeeded. The destructor then skips prepareForDestruction() for a partially constructed component.
  • updateThemeColor() re-applies every theme-dependent color: press circle border (ESP3D_ACCENT_SELECT_COLOR), spinner arc (ESP3D_ACCENT_ACTIVE_COLOR) and the state recolor, recomputed from the cached transport / server characters (compute_display_state()). The settings screen calls it on a live theme preview and when a theme is applied.
  • The radio layers write connection_status / server_status only when the radio is the CNC link (ESP3DNetwork::radioIsCncLink(), see network.md), so with a serial CNC link and WiFi for remote access the icon follows the CNC transport only.

DocumentTopic
connection_status_screen.mdThe full-screen overlay this component opens on tap
screen_transitions_flow.mdSafe screen transition patterns (timer-based destroy)
screens_architecture.mdOverall screen / component architecture
values.mdESP3DValues observable system — how status subscriptions work
connection_management.mdTransport lifecycle and the ? / T / C / A / . status model
fluidnc_module_change_tool.mdSibling FluidNC screen that embeds this component
fluidnc_module_files.mdSibling FluidNC screen that embeds this component
fluidnc_module_probe.mdSibling FluidNC screen that embeds this component