FluidNC Module — Probe Screen
Section titled “FluidNC Module — Probe Screen”Introduction
Section titled “Introduction”The probe screen is a FluidNC-specific UI screen that provides an interactive CNC probing interface on the pendant. It orchestrates full G38.2 probing cycles — fast seek, retract, precise feed, and work-coordinate-zero application — driven by a deterministic state machine that reacts to live firmware status and [PRB:...] messages from FluidNC.
The screen is implemented in main/display/cnc/fluidnc/screens/probe_screen.cpp under the probeScreen namespace and exposes a small public API for inter-screen communication.
Parallel probe screens exist for grbl and grblHAL targets (same structure, same GCode commands). This document covers only the FluidNC variant. For the shared CNC screen infrastructure see fluidnc_module.md and UI_Framework_Screens.md.
Module Architecture
Section titled “Module Architecture”Component Relationships
Section titled “Component Relationships”Data Structures
Section titled “Data Structures”ProbeAxisValues
Section titled “ProbeAxisValues”Holds the four user-configurable probe parameters per axis. Since a14 they are stored in NVS (settings esp3d_<axis>_probe_params, edited from this screen only) as "offset;max_travel;retract;feed_rate". Linear axes hold one section per work unit (mm;…;in;…, selected by G20/G21 without conversion, see inch_mm_units_reference.md); rotary axes (degrees) a plain list. probe_axis_values_map (keyed by real axis index 0=X … 5=C) caches the values of the unit they were loaded for, with a per-axis loaded unit (-1 = not loaded, 0 = mm, 1 = inch); they are reloaded when the probe unit changes. An edit is written into the section of the active unit, the other section is kept. A malformed stored list falls back to the setting default.
| Field | Default X/Y (mm / in) | Default Z (mm / in) | Default A/B/C (°) | Description |
|---|---|---|---|---|
offset | 0 / 0 | 10 / 0.4 | 0 | Work-coordinate offset applied via G10 L20 after probing |
max_travel | 30 / 1.2 | −50 / −2 | 360 | Maximum probe travel; negative = probe toward machine origin |
retract | 5 / 0.2 | 3 / 0.12 | 10 | Retract distance after first contact (always positive) |
feed_rate | 300 / 12 | 200 / 8 | 100 | Seek and retract feed rate; the precise feed uses half this value |
Probe unit (a14): the unit of a linear axis is the work unit G20/G21, or the report unit ($13 / report_inches) when esp3d_jog_position_unit (“Commands in position unit”) is on, read at each screen creation; rotary axes are never in inches. Values are shown and sent with up to 4 decimals (trailing zeros removed): one decimal used to round them (a 15.05 mm plate became Z15.1, 0.59 in became Z0.6). The feed rate is an integer in mm/min and has 1 decimal in in/min.
ProbeResult (public)
Section titled “ProbeResult (public)”Used for inter-screen communication. Declared in probe_screen.h.
enum class ProbeResult : uint8_t { NONE = 0, // No probe performed / reset SUCCESS = 1, // Probe completed and offset applied FAILED = 2, // Error, alarm, or no contact CANCELLED = 3 // User pressed STOP};ProbeState (internal)
Section titled “ProbeState (internal)”Internal state machine enum controlling which GCode commands are sent and which firmware replies are expected.
| State | Description |
|---|---|
IDLE | Ready, awaiting user action |
PRE_CHECK | Machine already Idle: start at once (the value is read directly, since ESP3DValues only notifies on change); otherwise ? is sent and the screen waits for Idle. A stable non-Idle state that never becomes Idle on its own (Hold, Alarm, Door, error) refuses the start and reports it; transient states (Run, Jog, Home) wait |
SEEK | Fast G38.2 probe in progress, waiting for [PRB:...] |
RETRACT | Retract G1 in progress, waiting for Idle |
FEED | Slow precise G38.2 in progress, waiting for [PRB:...] |
APPLY_OFFSET | G10 L20 in progress, waiting for Idle |
SUCCESS | Sequence complete, offset applied |
FAILED | Any step produced an error, alarm, or no contact |
CANCELLED | User pressed STOP: feed hold sent and the next resume armed as a soft reset |
Probe Sequence State Machine
Section titled “Probe Sequence State Machine”Timeout safety: A one-shot LVGL timer is started at
startProbeSequence()and covers the whole sequence: SEEK =|max_travel|atfeed_rate, RETRACT =retractatfeed_rate, FEED =|max_travel|atfeed_rate / 2. Its duration isclamp(sum of step times (min) × 60 × 1.5 + 5, 10, 300) seconds(the limit was 120 s, too short with the half-speed pass on long travels). On expiry, feed-hold (!) is sent and the state transitions toFAILED.
STOP (a14): the feed hold is followed by
esp3dGcodeHandler.requestResetBeforeResume(): resuming would restart the cancelled probe move, so the next~becomes a host purge + soft reset (see cnc_fluidnc.md). The hold keeps the machine, and the CANCELLED / FAILED report, in place until the user acts. Pressing STOP again in CANCELLED sends nothing (no second!). Error values are matched case-insensitively (ERROR:<code>,ERROR:<msg>or a rawError:line).
GCode Commands
Section titled “GCode Commands”| Probe Step | GCode Sent | Notes |
|---|---|---|
PRE_CHECK | ? (realtime), only if not already Idle | Confirms machine is Idle before starting |
SEEK | G91 [unit] G38.2 {Axis}{max_travel} F{feed_rate} | Fast probe pass; max_travel may be negative (e.g., Z down) |
RETRACT | G91 [unit] G1 {Axis}{±retract} F{feed_rate} | Retract in opposite direction of max_travel sign, at the user feed rate (a fixed F200 meant 200 in/min in G20) |
FEED | G91 G38.2 {Axis}{max_travel} F{feed_rate/2} | Slow precise probe |
APPLY_OFFSET | G10 L20 P0 {Axis}{offset}, then the restore line | Set work-coordinate zero at probed position + offset; then restore the work unit and distance mode read from [GC:] at start |
| STOP / Timeout | ! (realtime Feed Hold) | Sent on user cancel or safety timeout |
Modal words (prepareProbeModalState(), a14): every probe line is G91 and carries a unit word (G20 / G21) when the probe unit is not the work unit (report unit with esp3d_jog_position_unit, or a rotary axis in G20). Both are modal, so the last line of a successful sequence restores the distance mode and the work unit read from [GC:]. Nothing is restored after a failure (alarm) or a STOP (hold, then reset on resume): the reset restores the controller’s default modes, and a queued line would be refused in alarm. With an unknown parser state (no [GC:] yet) nothing is restored.
The axis letter is resolved from ESP3DValuesIndex::axis_names to correctly honor grblHAL $376 UVW naming (e.g., U, V, W instead of A, B, C). FluidNC falls back to XYZABC when the field is absent.
UI Layout
Section titled “UI Layout”The screen uses a GenericScreen container with three zones:
┌──────────────────────────────────────────┐│ [Axis label / lock icon] WPos: 0.000 ││ MPos: 0.000 ││ [O: 10.0] [T: -50.0] ││ [R: 3.0] [F: 200 ] ││ ────────────────────────────────────── ││ Probe status text... [spinner/icon]││ X Y Z A B C P (pin indicators) │├──────────────────────────────────────────┤│ [OK] [Play/Stop] [Back] │└──────────────────────────────────────────┘- Axis button (
btnAxe): shows the active axis letter or a lock icon. Tapping cycles axes in touch mode; hardware-switch mode uses physical position. - Position display: two stacked labels — WPos (large font, work coordinates) and MPos (medium font, machine coordinates) for the selected axis.
- Parameter buttons (2×2 grid):
O:offset,T:max travel,R:retract,F:feed rate. All four are items inPanelComponentand are encoder-navigable. - Status bar: a scrolling text label plus either a spinner (active states) or a colored icon (
ok_bgreen = SUCCESS,close_bred = FAILED/CANCELLED). - Pin indicators: 7 small square labels (X Y Z A B C P) that appear/hide based on active pin states from
[PRB:...]and limit inputs; only pins up toaxis_countare shown (P=probe pin is always visible). - Button 1 (center): icon swaps dynamically between
play_bandstop_bto reflect the current probe state.
Value Subscription Data Flow
Section titled “Value Subscription Data Flow”Performance Optimizations
Section titled “Performance Optimizations”| Layer | Mechanism | Detail |
|---|---|---|
| Layer 2 — Pin states | String change detection | last_known_pin_states[] compared via strcmp; all 7 LVGL hide/show calls skipped when the string is unchanged (callbacks fire at 10 Hz) |
| Layer 3 — Position display | Per-group timestamp throttle | Separate last_wpos_display_update_ms / last_mpos_display_update_ms; minimum interval 250 ms per group so both WPos and MPos callbacks are throttled independently |
| Layer 3 — Position flush | position_flush_timer | Fires every 300 ms to apply any pending throttled update, ensuring no update is permanently lost |
| Deferred overlays | lv_timer_create at startup | FirmwareStatusComponent and ConnectionStatusComponent are created after ESP3D_DEFERED_SCREEN_CREATION_DELAY_MS to avoid blocking the initial screen paint |
Multi-Axis Support
Section titled “Multi-Axis Support”The screen supports 3 to 6 physical axes via axis_count (driven by ESP3DValuesIndex::axis_count):
Touch mode (!ESP3D_HARDWARE_SWITCH_FEATURE): tapping the axis label advances through all real axes in a loop (X→Y→Z→A→…→X). The lock position is not part of the touch cycle — locking is managed by the main screen.
Hardware switch mode (ESP3D_HARDWARE_SWITCH_FEATURE): the physical 4-position switch drives axis selection. The axis label button is only clickable when axis_count > 4 and the switch is in slot 3, to cycle the extended axis (A/B/C).
Probe parameter values (offset, max_travel, retract, feed_rate) are stored per real axis index. initializeProbeAxisValues() only inserts missing entries — existing user edits are never overwritten.
Screen Lifecycle and Cleanup
Section titled “Screen Lifecycle and Cleanup”Destruction follows the standard ESP3D_TRANSITION_* macro pattern shared across all screens. prepareForDestruction() is guarded against double execution by cleanup_executed_ and is_prepared_for_destruction_ flags. If the probe sequence is active when destruction is triggered, a realtime feed-hold (!) is sent before unsubscribing.
Public API
Section titled “Public API”Declared in main/display/cnc/fluidnc/screens/probe_screen.h.
| Function | Signature | Description |
|---|---|---|
create | void create() | Creates and loads the probe screen. Throws std::runtime_error on critical allocation failure (screen, PanelComponent). |
setReturnScreen | void setReturnScreen(ESP3DScreenType screen) | Overrides the default return target (main screen). Must be called before create() if a custom caller needs to receive the result. Resets to default after each transition. |
clearResult | void clearResult() | Resets last_result to ProbeResult::NONE. Should be called before navigating to the probe screen. |
getResult | ProbeResult getResult() | Returns the outcome of the last completed probe sequence. |
Typical Caller Flow (e.g., change_tool_screen)
Section titled “Typical Caller Flow (e.g., change_tool_screen)”Dependencies
Section titled “Dependencies”| Dependency | Role | Reference |
|---|---|---|
GenericScreen | Host screen container, rotation, VirtualButtonsComponent | UI_Framework_Screens.md |
PanelComponent | Encoder-navigable list of the 4 probe parameter items | UI_Framework_Screens.md |
FirmwareStatusComponent | Overlay showing FluidNC machine state | fluidnc_module.md |
ConnectionStatusComponent | Overlay showing transport connection state | fluidnc_module_connection_status.md |
inputScreen | Numeric InputEditor for editing probe parameters | UI_Framework_Screens.md |
ESP3DValues | Observable system — subscriptions drive all UI updates | Core_Platform_Infrastructure.md |
ESP3DGCodeHandlerService | Sends G38.2 / G1 / G10 and realtime ? / ! commands | CNC_Firmware_Integration.md |
UIManager | Lock state (user + system), theme tokens, orientation angle | UI_Framework_Screens.md |
ESP3DTranslationService | All user-visible strings (step labels, error messages) | Core_Platform_Infrastructure.md |
esp_timer | High-resolution monotonic clock for position throttle timestamps | ESP-IDF built-in |
Key Implementation Notes
Section titled “Key Implementation Notes”- No
std::stringin hot-path formatting: probe parameter display values are formatted into fixed-sizechar buffer[32]withsnprintf, avoiding heap allocation in high-frequency paths. std::mapfor probe values: acceptable because the map is accessed only during user interaction (axis selection, parameter edits, sequence start), never inside a high-frequency subscription callback.- No throw from LVGL timer callbacks: deferred overlay creation uses
new (std::nothrow)and logs errors rather than throwing; an uncaught exception inside an LVGL timer callback would invokestd::terminateand reset the board. - Axis letter resolution at call time:
getAxisLetter()readsESP3DValuesIndex::axis_nameson each call (not cached) to correctly reflect grblHAL$376UVW naming throughout the session without requiring a re-render on setting changes. - Focus persistence through InputEditor round-trip:
saved_focus_item_idstores the item being edited before navigating toinput_screen. On screen re-creation (return from InputEditor),panel_component->setFocusedItem(saved_focus_item_id)restores the visual highlight immediately. - Dual-callback state machine:
firmware_statusdrives phase transitions (detecting Idle between steps), whileprobe_statusconfirms or denies physical contact within SEEK and FEED phases. Both callbacks are required; neither alone is sufficient to advance the sequence correctly. - Positions on axis change (a14): ESP3DValues only notifies on change, so a machine at rest never refreshes them; the screen reads the current values of the new axis instead of keeping the previous axis or
0000.000. - Position button badges (a14): the unit of the reported positions (
mm/in,position_unit.h) top right, and the active WCS (the oneG10 L20 P0overwrites) bottom right, refreshed on each opening (on grbl / grblHAL the parser state is only queried at connection;$Gis re-sent unless not connected). - Opened from the tool change screen (a14): the probe is set to Z and the status line tells the user what to do instead of
Idle(nothing moves until Play). - Lock visual feedback: when
UIManager::getLockState()istrue, all interactive elements (parameter buttons, position labels, panel items) receiveLV_OPA_30andLV_STATE_DISABLED. TheVirtualButtonsComponentre-renders with lock-aware styles. Locking is triggered by either the hardware switch position 3 (≤ 3-axis mode) or a non-connected transport (server_status != "C").