Skip to content

Connection Status Screen

The Connection Status Screen is a full-screen LVGL UI component that provides real-time, interactive visibility into the active CNC transport link — whether that is WiFi (STA), Bluetooth Classic (SPP), Bluetooth BLE (GATT), Serial/UART, USB Serial, TCP Socket, or WebSocket. It is the diagnostic hub the user navigates to when a connection fails, stalls, or needs to be manually managed.

Unlike the compact ConnectionStatusComponent widget embedded in operational screens, this is a dedicated modal screen that exposes the full connection lifecycle: a color-coded header, protocol details, device info, and contextual action buttons that adapt to the precise connection state character reported by the transport layer.

Source files:

  • main/display/screens/connection_status_screen.h
  • main/display/screens/connection_status_screen.cpp

UIManager

Transport Clients - background tasks

Value System

UI Layer - LVGL / Core 1

owns

owns

subscribes

subscribes

subscribes (optional)

callbacks

(LVGL task)

registers

dispatches to

dispatches to

dispatches to

dispatches to

dispatches to

dispatches to

navigates to

(on auth failure)

connectionStatusScreen

(namespace)

GenericScreen

(base)

VirtualButtonsComponent

(3 buttons)

inputScreen

(PIN / password entry)

ESP3DValues

(observable store)

connection_status

(transport char)

server_status

(server char)

socket_server_status

(optional)

ESP3DWifiClient

(WiFi STA)

ESP3DBTSerialClient

(BT Classic SPP)

ESP3DBTBleClient

(BT BLE GATT)

ESP3DSerialClient

(UART)

ESP3DSocketClient

(TCP)

ESP3DWebsocketClient

(WebSocket)

UIManager

(screen registry)

The screen follows the same lifecycle patterns as firmware_status_screen — static module-level state, timer-based transitions, prepareForDestruction() guard, and UIManager registration — so developers already familiar with that screen can navigate this one immediately. See screens_architecture.md for the general screen system description.


The connection model uses a single char per logical layer, published through ESP3DValues. See connection_management.md for the full protocol definition.

CharMeaningTitle bar color token
CConnected (transport or server fully established)ESP3D_ACCENT_SELECT_COLOR (green)
RRead-only / Passive (connected but CNC control limited)ESP3D_ACCENT_ACTION_COLOR (orange)
TConnecting (in progress, spinner shown)ESP3D_ACCENT_ACTIVE_COLOR (blue)
AAuthentication failed (wrong PIN / password)ESP3D_ACCENT_ACTION_COLOR (orange)
.Radio/transport off (not attempting to connect)ESP3D_DISABLED_COLOR (grey)
?Disconnected / unknownESP3D_ACCENT_ALERT_COLOR (red)

Two independent status indices are tracked simultaneously:

  • connection_status → transport_char_: state of the transport layer (WiFi association, BT link, Serial readiness).
  • server_status → server_char_: state of the CNC server connection on top of that transport (TCP socket peer, WebSocket handshake). Only meaningful when the output client is a socket or WebSocket client.

The displayed status character is computed by computeDisplayChar(), which mirrors the same logic used by the embedded ConnectionStatusComponent::compute_display_state():

≠ 'C'

== 'C'

'?'

other

transport_char_?

display = transport_char_

server_char_?

display = 'T'

WiFi up, CNC connecting

display = server_char_

This ensures that when WiFi is connected but the TCP/WS peer has not yet responded, the display correctly shows “Connecting” (blue, spinner) rather than the misleading “Connected” of the transport layer alone.


lv_screen

(full display)

GenericScreen container

(rotatable)

Title bar

(colored: getStatusColor())

status_s icon

(left of title)

Status text label

(Connected / Connecting / ...)

Content area

(column flex)

Transport label

(Socket-TCP / Bluetooth / Serial)

WiFi: SSID label

(WiFi mode only, color-coded)

Device info label

(host:port / BT name+MAC / baud rate)

Socket server status

(ESP3D_SOCKET_SERVER_FEATURE only)

LVGL spinner

(shown when display == 'T')

VirtualButtonsComponent

(3 buttons)

The content area uses LVGL LV_FLEX_FLOW_COLUMN with center alignment. The spinner is always created in the column but hidden via LV_OBJ_FLAG_HIDDEN when not in connecting state, avoiding object creation/deletion overhead inside callbacks.


The three virtual buttons are positioned left / center / right and their icon and enabled state are recalculated on every subscription callback via updateButtonsForStatus().

Controls the transport layer: WiFi association, BT link, Serial open/close.

transport_char_ == '?'

transport_char_ == 'C'

transport_char_ == 'T'

transport_char_ == 'A'

press → connect()

press → disconnect()

press → open password or PIN input

button disabled - no abort

Disconnected

Connected

Connecting

AuthFailed

InputScreen

Icon mapping by transport type:

Transport? stateC stateT stateA state
WiFi STAwifi_s (enabled)no_wifi_swifi_s (disabled)unlock_b
BT Classicconnect_bt_bdisconnect_bt_bconnect_bt_b (disabled)unlock_b
BT BLEconnect_bt_bdisconnect_bt_bconnect_bt_b (disabled)unlock_b
Serial / UARTserial_mdisconnect_serial_mserial_m (disabled)—
UART Extext_module_mdisconnect_ext_module_mext_module_m (disabled)—

Present only in specific build configurations. Behavior depends on the active feature flags:

  • WiFi + Socket Client or WebSocket Client (ESP3D_SOCKET_CLIENT_FEATURE or ESP3D_WS_CLIENT_SERVICE_FEATURE): controls the TCP/WS connection to the CNC host. Hidden when the active output client is not an IP-based CNC client.
  • Serial / UART (no BT, no WiFi socket): shows a “refresh init commands” button, visible only when transport is connected.
  • BT builds: always hidden (BT host = server, single connection layer, no separate server control).

transport_char_ ≠ 'C'

transport_char_ == 'C'

connect_telnet_b shown but disabled

server_char_ == '?'

server_char_ == 'C' or 'R' or 'T'

server_char_ == 'A'

press → socketClient.connect()

press → socketClient.disconnect()

press → enter credentials

WiFi_Down

WiFi_Up

CNC_Disconnected

CNC_Connected

CNC_AuthFailed

InputScreen

a14 changes (IP CNC client builds):

  • No server saved: pressing the server button (or WiFi connecting during the WiFi setup flow, open_server_scan_when_connected) opens the server scan screen instead of trying to connect to nothing.
  • Client running vs stopped: server_status alone cannot tell “retrying” from “stopped by the user” (both report ? until the TCP link is up), so the screen also checks whether the IP CNC client task is alive. Running covers connected, read-only and retrying; a client stopped by the user is shown as not connected, not as connecting. After a press the button state is refreshed from the client state, since server_status may not change.
  • Fallback to the server scan: while the screen shows the client trying to reach its server, a one-shot timer (SERVER_AUTO_CONNECT_TIMEOUT_MS = 20 s: one TCP attempt can take 10 s + 5 s retry delay) is armed; it is disarmed as soon as the server is connected or the client stopped. On expiry the server scan opens so the user can confirm or pick another server (the client keeps retrying in the background).
  • WiFi setup flow: when WiFi connects after an SSID change (which blocked the CNC auto-reconnect), the screen opens the server scan if no server is saved, otherwise connects to the saved one with the same fallback.

Always present and always enabled. Triggers the standard cleanup_timer → transition_timer chain that safely destroys this screen and navigates to config.return_screen (typically ESP3DScreenType::main).

The screen is about the CNC link only:

  • The WiFi row is shown and driven only when WiFi carries the CNC link (socket / WebSocket client output), whatever the radio mode (in the wifi_ap_config fallback the user still needs to see the WiFi state to fix it). With a serial / UART Ext output the WiFi (remote / debug telnet) stays invisible.
  • The socket (telnet) server status label is shown only in WiFi STA with a WiFi CNC client output.
  • UART Ext uses the serial baud rate setting when UART_EXT_SAME_AS_SERIAL is set.
  • Opening the BT scan screen is wrapped in try / catch; the screen registers with registerScreen(type, screen, prepareForDestruction).

connectionStatusScreenLVGL Task (Core 1)ESP3DValuesTransport Task (Core 0)connectionStatusScreenLVGL Task (Core 1)ESP3DValuesTransport Task (Core 0)set_value(connection_status, "C")enqueue updatehandle() - called each LVGL tickonConnectionStatusUpdate(connection_status, "C")transport_char_ = 'C'updateDisplayForStatus()updateButtonsForStatus()lv_label_set_text(title_label_, "Connected")lv_obj_set_style_bg_color(title_section_, green)lv_obj_add_flag(spinner_, LV_OBJ_FLAG_HIDDEN)vb->update_button(0, &no_wifi_s, true)

Thread safety: All lv_obj_* calls happen on the LVGL task (Core 1) because ESP3DValues::handle() is called from the LVGL tick timer and subscription callbacks are invoked synchronously from handle(). Transport tasks on Core 0 only call set_value(), which enqueues the update into a thread-safe ring buffer without touching LVGL objects.


connectionStatusScreen::create(config)

Reset transition state

ESP3D_TRANSITION_RESET macro

Read initial transport_char_ and server_char_

from ESP3DValues::get_value()

Configure 3 virtual_button_conf_t entries

new GenericScreen - nothrow

Build LVGL widget tree

title bar, content box, labels, spinner

updateButtonsForStatus()

subscribe(connection_status)

subscribe(server_status)

subscribe(socket_server_status) - optional

UIManager::registerScreen()

Attach LV_EVENT_DELETE → onScreenDestroy

Screen active - receiving live updates

User presses Back

on_back_release()

cleanup_timer_cb

ESP3D_CLEANUP_TIMER_BODY

prepareForDestruction()

unsubscribe all

screen_instance_->prepareForDestruction()

null all UI pointers

transition_timer_cb fires after

ESP3D_TRANSITION_SCREEN_DELAY_MS

createScreen(return_screen)

LV_EVENT_DELETE → onScreenDestroy

UIManager::unregisterScreen()

delete screen_instance_

The prepareForDestruction() guard (is_prepared_for_destruction_ + cleanup_executed_) prevents double-unsubscription if both a timer callback and onScreenDestroy fire in quick succession — a real risk in the LVGL event model during rapid transitions. This pattern is described in detail in screen_transitions_flow.md.


WiFi Authentication Failure (transport_char_ == 'A')

Section titled “WiFi Authentication Failure (transport_char_ == 'A')”

The user pressed Btn 0 while transport status is 'A'. The screen opens inputScreen in alphanumeric (password) mode and returns to wifi_scan on completion.

ESP3DNetworkinputScreen (password)connectionStatusScreenUserESP3DNetworkinputScreen (password)connectionStatusScreenUserpress Btn 0 (unlock_b icon)on_transport_release()read stored sta_password from NVSinputScreen::create(data)\nkeyboard=Alphanumeric, return=wifi_scanenters new password → OKwriteString(sta_password, new_pwd)setModeAsync(wifi_sta)connection_status → 'T' then 'C' or 'A'

Bluetooth PIN / BLE Passkey Failure (transport_char_ == 'A')

Section titled “Bluetooth PIN / BLE Passkey Failure (transport_char_ == 'A')”

The openBTPinInput() helper selects the correct PIN length (4 digits for BT Classic HC-06, 6 digits for BLE passkey) and NVS setting index based on the active output client. Passing allow_empty=true permits an empty PIN, which means no authentication (SEC_NONE).

BTSerialClient or BTBleClientinputScreen (PIN)connectionStatusScreenUserBTSerialClient or BTBleClientinputScreen (PIN)connectionStatusScreenUserpress Btn 0 (unlock_b icon)openBTPinInput()show_pin_input(return_screen, title, stored_pin, ...)\nallow_empty=true for no-auth modeenters PIN or passkey → OKwriteString(pin_setting, new_pin)clearBondedDevices()connect()connection_status → 'T' then 'C' or 'A'

See Input_system.md for details on the PIN and alphanumeric keyboard modes.


The compact ConnectionStatusComponent (defined separately for each CNC firmware variant in main/display/cnc/fluidnc/components/, main/display/cnc/grbl/components/, and main/display/cnc/grblhal/components/) is a lightweight embeddable widget shown inside operational screens (status screen, jog screen, probe screen).

AspectConnectionStatusComponentconnectionStatusScreen
TypeInline widget (icon + spinner)Full-screen modal
PurposePassive status indicatorInteractive diagnostic and action hub
ButtonsNone — tap opens this screen3 adaptive action buttons
NavigationTap → opens connection_status screenBack button → returns to return_screen
Status trackedtransport_char_ + server_char_transport_char_ + server_char_
Display logiccompute_display_state()computeDisplayChar() (identical logic)
Auth retryNot availableFull PIN / password re-entry flow

When the user taps the ConnectionStatusComponent icon, a timer fires createScreen(ESP3DScreenType::connection_status) with the parent screen set as return_screen, bringing up this full-screen view.

tap → timer

Back button

ConnectionStatusComponent

(embedded widget)

connectionStatusScreen

(this module)

parent CNC screen

main / status / jog


The screen compiles conditionally based on active transport features. Only enabled features contribute code:

Preprocessor symbolEffect on this screen
ESP3D_WIFI_FEATUREEnables WiFi transport button, network label (WiFi: SSID), esp_wifi_sta_get_ap_info()
ESP3D_BT_SERIAL_FEATUREEnables BT Classic connect / disconnect / PIN flow
ESP3D_BT_BLE_FEATUREEnables BLE connect / disconnect / passkey flow
ESP3D_SOCKET_CLIENT_FEATUREEnables center button for TCP socket CNC connection
ESP3D_WS_CLIENT_SERVICE_FEATUREEnables center button for WebSocket CNC connection
ESP3D_UART_EXT_FEATUREEnables external UART module connect / disconnect
ESP3D_SOCKET_SERVER_FEATUREAdds socket_label_ showing local socket server state (WiFi STA only)

Mutual exclusion: ESP3D_SOCKET_CLIENT_FEATURE and ESP3D_BT_SERIAL_FEATURE / ESP3D_BT_BLE_FEATURE are mutually exclusive at the build level, enforced by cmake/sanity_check.cmake. The center button logic reflects this: BT builds always hide Btn 1; WiFi + Socket builds show it only when the active output client is an IP client. See feature_resource_matrix.md for the full compatibility matrix.


No LVGL Object Access from Background Tasks

Section titled “No LVGL Object Access from Background Tasks”

All lv_obj_* calls (label text, color, visibility, spinner) happen exclusively inside subscription callbacks and button release callbacks, which both execute on the LVGL task (Core 1). Transport tasks on Core 0 only call ESP3DValues::set_value(), which enqueues the update into a thread-safe ring buffer without touching LVGL objects. This is the mandatory pattern for all UI updates in this project — see the LVGL constraints section of CLAUDE.md.

Any code path that creates a new screen (Back press, PIN input entry, WiFi scan redirect) must call prepareForDestruction() first, or supply it as the prepare_current_screen_destruction callback. This unsubscribes all value callbacks before the LVGL object tree starts being torn down, preventing callbacks from firing on a partially-destroyed widget tree during the transition delay.

GenericScreen is allocated with new (std::nothrow) to prevent uncaught exceptions in the LVGL context. An uncaught exception in an LVGL callback would reset the board. A failed allocation is logged with heap diagnostics and the function returns early, leaving the existing screen intact.

The status_s icon in the title bar uses LVGL 9.2’s I4 (4-bit indexed) image format. Applying image_recolor with LV_OPA_COVER on indexed images silently drops pixel alpha, making the icon invisible. The palette is pre-baked to near-white pixels that render correctly on all title-bar background colors without requiring runtime recolor.

The spinner is created once at screen creation time and inserted into the content flex column. It is shown or hidden with LV_OBJ_FLAG_HIDDEN based on computeDisplayChar() == 'T'. This avoids the LVGL object creation and deletion cost inside a subscription callback that fires on every status change.

In WiFi + socket/WebSocket builds, the network label and device info label use three-level color coding updated on every status callback:

  • Grey (ESP3D_DISABLED_COLOR): WiFi is not associated — CNC host is unreachable.
  • Orange (ESP3D_INDICATOR_WARNING_COLOR): WiFi is up but CNC is not connected.
  • Normal text (ESP3D_SCREEN_BACKGROUND_TEXT_COLOR): both layers are connected.

  • screens_architecture.md — Screen system overview, GenericScreen base class, UIManager screen registry
  • screen_transitions_flow.md — Timer-based transition patterns: cleanup_timer, transition_timer, prepareForDestruction() guard
  • connection_management.md — Full connection status protocol (U, T, C, ?, A), shared initialization sequence across transports
  • Input_system.md — inputScreen PIN / password entry modal used for authentication retry flows
  • ui_components.md — VirtualButtonsComponent, GenericScreen, and other shared UI building blocks
  • values.md — ESP3DValues observable store, subscription API, thread-safety model
  • feature_resource_matrix.md — WiFi / BT / socket-client mutual exclusion rules, resource budgets