Skip to content

grblhal_module_probe

The grblhal_module_probe module implements the tool-length / workpiece probing screen for the grblHAL firmware target. It provides a touch/encoder-driven UI that guides the operator through a two-pass probing cycle (fast seek → retract → slow precise feed → apply offset), monitors the machine state via grblHAL real-time status reports, and communicates the probe result back to the calling screen.

The module lives at:

main/display/cnc/grblhal/screens/probe_screen.cpp
namespace probeScreen

It is one of three parallel probe-screen implementations — the other two target grbl_module_probe and fluidnc_module_probe — each adapted to its firmware’s status-reporting protocol while sharing the same visual structure.


Core Platform

UI Framework

grblhal_module

createScreen(probe)

param edit

subscribe/get

sendGcode

lock state

translate labels

Screen Router

(esp3d_screen_type.cpp)

ConnectionStatusComponent

(grblhal variant)

FirmwareStatusComponent

(shared CNC)

probeScreen namespace

(probe_screen.cpp)

changeTool Screen

files Screen

GenericScreen

PanelComponent

(4 probe params)

VirtualButtonsComponent

(OK · Play/Stop · Back)

inputScreen (numeric)

ESP3DValues

(observable store)

esp3dGcodeHandler

(grblHAL)

UIManager

TranslationService

See grblhal_module for the broader screen set.
See cnc_grblhal for the GCode handler that dispatches commands.
See gcode_host for streaming and flow-control mechanics.


Per-axis probe parameter set. One instance is kept per axis index in a std::map<uint32_t, ProbeAxisValues> that persists across axis switches — user edits on one axis are not lost when the operator selects another axis.

FieldTypeDefault (Z)Description
offsetfloat10.0 mmWork-coordinate offset applied after contact via G10 L20
max_travelfloat−50.0 mmMaximum probe travel; negative = downward (Z), positive = upward
retractfloat3.0 mmRetract distance between seek and feed passes (always positive)
feed_ratefloat200.0 mm/minFast-seek feed rate; feed pass runs at feed_rate / 2

Default values per axis family:

Axisoffsetmax_travelretractfeed_rate
X, Y0 mm+30 mm5 mm300 mm/min
Z10 mm−50 mm3 mm200 mm/min
A, B, C0 mm+360 mm10 mm100 mm/min

Transient context passed to the inputScreen when the operator opens a numeric editor for one of the four probe parameters.

Terminal result returned to the caller after the screen exits:

  • NONE — no probe has run yet
  • SUCCESS — contact detected and offset applied
  • FAILED — probe did not trigger or a grblHAL error occurred
  • CANCELLED — operator pressed Stop

See Probe State Machine below.


encoder nav + activate

edit value

GenericScreen (ESP3DScreenType::probe)

Axis Chip (btnAxe)

tap-to-cycle axis

Position Area

WPos (large) / MPos (small)

4x Param Buttons (2x2 grid)

O: Offset · T: MaxTravel

R: Retract · F: FeedRate

Probe Status Bar

text label · spinner · ok/close icon

Pin Indicators linear (XYZ)

Pin Indicators rotary (ABC · P · O)

VirtualButtons

btn0: OK/EncoderMode

btn1: Play-Stop

btn2: Back

FirmwareStatusComponent

(deferred, top-left)

ConnectionStatusComponent

(deferred, top-right)

PanelComponent

(4 items, encoder focus ring)

inputScreen (numeric)

IDShort LabelFull LabelUnitConstraints
0O:Offsetmm / in-9999.9 … +9999.9, 3 decimals
1T:Max Travelmm / in-9999.9 … +9999.9, 3 decimals
2R:Retract Distancemm / in0 … +9999.9 (positive only), 3 decimals
3F:Feed Ratemm/min1 … +9999, 0 decimals (integer)

The focused item is highlighted with a thicker border_focus-colored border; all others show ESP3D_MENU_BORDER_COLOR at standard width. The PanelComponent drives encoder-based navigation between these four items.

IndexIdle iconActive (probing) iconFunction
0ok_bok_bToggle encoder mode (navigation vs editing)
1play_bstop_bStart probe / stop probe. Disabled while probe is disconnected (Pn:O).
2back_bback_bNavigate back (with transition cleanup)

Eight square indicator labels are maintained — one per machine pin character:

IndexPin charContainerShown when
0–2X, Y, Zpins_container_linearaxis_count covers it AND pin is active
3–5A / U, B / V, C / Wpins_container_rotaryaxis_count covers it AND pin is active
6Ppins_container_rotaryProbe contact triggered (Pn:P)
7Opins_container_rotaryProbe disconnected (Pn:O)

P and O are mutually exclusive in grblHAL reports and share the same flex slot, so no extra horizontal space is consumed.


The screen adapts to grblHAL’s configured axis count (received via ESP3DValuesIndex::axis_count):

5 or more axes

switch pos 0-2 - X / Y / Z

switch pos 3 - extended slot

(tap axis chip to cycle A-B-C)

4 axes

switch pos 0-3 - X / Y / Z / A

3 axes or fewer

switch pos 0-2 - X / Y / Z

switch pos 3 - LOCK

The lock position (switch slot 3 on machines with 3 axes or fewer) sets UIManager::userSetLockState(true), graying out all interactive elements and disabling the Play button.

getAxisLetter(uint32_t axis_index) reads the axis_names observable (ESP3DValuesIndex::axis_names). When grblHAL reports "XYZUVW", axes 3/4/5 are named U/V/W instead of A/B/C. All GCode commands (G38.2, G1, G10 L20) and pin indicator labels automatically honor this naming.


Play pressed (probe connected)

Play pressed (probe disconnected Pn:O)

firmware_status IDLE - machine ready

firmware_status ALARM or ERROR

PRB contact 1 - contact detected

PRB contact 0 - no contact or ALARM/ERROR

firmware_status IDLE - retract complete

firmware_status ALARM or ERROR

PRB contact 1 - contact detected

PRB contact 0 - no contact or ALARM/ERROR

firmware_status IDLE - G10 complete

firmware_status ALARM or ERROR

Stop pressed

Stop pressed

Stop pressed

Stop pressed

timeout

timeout

timeout

timeout

timeout

Play pressed again

Play pressed again

Play pressed again

Back pressed

Back pressed

Back pressed

Back pressed

IDLE

PRE_CHECK

FAILED

SEEK

RETRACT

FEED

APPLY_OFFSET

SUCCESS

CANCELLED

Each terminal state (SUCCESS, FAILED, CANCELLED) sets ProbeResult for inter-screen communication and updates the status-bar icon (ok_b green / close_b red).


ESP3DValuesgrblHAL Firmwareesp3dGcodeHandlerprobeScreenESP3DValuesgrblHAL Firmwareesp3dGcodeHandlerprobeScreenPlay pressed - startProbeSequence()ok_b icon shown, ProbeResult SUCCESS stored"?" realtime status, high priorityfirmware_status = IDLEon_firmware_status_update - PRE_CHECK to SEEK"G91 G38.2 Z-50.0 F200" - SEEK fast passfirmware_status = RUNPRB 0.000,0.000,-12.345:1on_probe_status_update - contact true - SEEK to RETRACT"G91 G1 Z3 F200" - RETRACT (F = user feed rate)firmware_status = IDLEon_firmware_status_update - RETRACT to FEED"G91 G38.2 Z-50.0 F100" - FEED rate divided by 2PRB 0.000,0.000,-12.347:1on_probe_status_update - contact true - FEED to APPLY_OFFSET"G10 L20 P0 Z10.0" - APPLY_OFFSETfirmware_status = IDLEon_firmware_status_update - SUCCESS
StepGCode templateNotes
PRE_CHECK? (realtime)Sent at high priority; verifies machine is Idle before probing
SEEKG91 G38.2 {axis}{max_travel} F{feed_rate}Fast probe pass; incremental mode
RETRACTG91 G1 {axis}{±retract} F{feed_rate}Direction is opposite to max_travel sign; user feed rate since a14 (was a fixed F200)
FEEDG91 G38.2 {axis}{max_travel} F{feed_rate/2}Slow precise pass at half the seek speed
APPLY_OFFSETG10 L20 P0 {axis}{offset}Sets work-coordinate offset at probe contact point
STOP! (feed hold, realtime)High priority; sent on user cancel, axis change, or timeout

fires

user stop / axis change / screen destroy

startProbeSequence()

Calculate timeout:

(seek + retract + feed times) x 60 x 1.5 + 5 s

clamped to 10 s min, 300 s max

One-shot lv_timer

- probeTimeoutCallback()

Send Feed Hold (!)

setProbeUIState(FAILED, timeout)

stopProbeSequence()

Timer deleted

Additionally, the screen refuses to start a probe sequence when probe_disconnected_ is true (set by Pn:O in the pin-states string). The Play button is also proactively disabled in this state.


probeScreen subscriptions

ESP3DValues Observable Store

read directly

firmware_status

probe_status

last_error_status

axis_count

pin_states

server_status

position_w x y z a b c

position_m x y z a b c

axis_names

on_firmware_status_update

- state machine transitions

on_probe_status_update

- contact detection from PRB message

on_error_status_update

- force FAILED on firmware error

on_axis_count_update

- re-init params, redraw

on_pins_state_update

- pin indicators and probe_disconnected flag

on_connection_status_update

- system lock toggle

on_positions_update

- throttled WPos and MPos display

getAxisLetter()

Position updates arrive at 10 Hz (grblHAL status poll). The screen applies a 250 ms minimum interval per group (WPos and MPos independently), with a flush timer that fires 300 ms later to apply any pending update that was throttled. WPos and MPos each carry their own last_*_display_update_ms timestamp so that one group passing the gate cannot starve the other — both groups are dispatched from a single grblHAL status report in the same LVGL tick.

on_pins_state_update compares the incoming string against the last known value using strcmp. If identical, all LVGL hide/show operations are skipped. This avoids 8+ object updates per second when the machine is idle.


LVGL TimersLVGL CoreprobeScreen::create()Screen RouterLVGL TimersLVGL CoreprobeScreen::create()Screen RouterScreen is livecreate()Reset transition stateSubscribe to ESP3DValues (firmware, probe, error, axis, pins, positions, server)new GenericScreen(ESP3DScreenType::probe)new PanelComponent (4 probe-param items)Create axis chip, position labels, param buttons 2x2 gridCreate probe status bar (label + spinner + icon)Create pin indicator containers (linear XYZ + rotary ABC P O)Register encoder, switch, and destroy event callbacksDeferred timer - new FirmwareStatusComponentDeferred timer - new ConnectionStatusComponentposition_flush_timer (300 ms repeating)updateAxisDisplay() - initial stateBack button or transitionprepareForDestruction()Unsubscribe all ESP3DValuesstopProbeSequence() if probe is activeDelete probe_timeout_timerDelete position_flush_timerprepareForDestruction() on all componentsRemove encoder and switch event callbacksLV_EVENT_DELETE fires - onScreenDestroy()delete components, unregister screen, delete GenericScreen

All cleanup follows the prepare-then-destroy pattern used across the UI Framework: prepareForDestruction() unsubscribes and detaches event handlers first; the LVGL LV_EVENT_DELETE callback then frees memory. This prevents dangling callbacks from firing after object deletion.


namespace probeScreen {
// Called before navigating to the probe screen to configure its return target
void setReturnScreen(ESP3DScreenType screen);
// Entry point — called by the screen router
void create();
// Called after returning from the probe screen to read the outcome
ProbeResult getResult();
// Reset result to NONE (call before launching a new probe sequence)
void clearResult();
}

Typical caller pattern (e.g., from changeTool screen):

probeScreen::clearResult();
probeScreen::setReturnScreen(ESP3DScreenType::change_tool);
// transition to probe screen via UIManager ...
// ... after returning:
if (probeScreen::getResult() == ProbeResult::SUCCESS) {
// offset has been applied; resume tool change workflow
}

ConditionEffect
server_status != "C" (not connected)systemSetLockState(true) — all elements grayed, Play disabled
Switch in position 3 on machine with 3 axes or feweruserSetLockState(true) — same visual lockout
probe_disconnected_ (Pn:O received)Play button proactively disabled; probe start blocked with error feedback
Any of the above conditions clearedElements re-enabled, panel_component->updateFocusVisuals() called

When locked, the four probe-parameter buttons receive LV_STATE_DISABLED and their label opacity drops to 30%. Position labels are similarly dimmed. The Back button (isLockable = false) always remains active.


Child Components

LVGL Objects

probeScreen static state

getCurrentProbeValues()

setProbeUIState()

setProbeUIState()

setProbeUIState()

update_button(1, play_b or stop_b)

update_button(1, enabled?)

getAxisName()

updatePositionDisplay()

updatePositionDisplay()

ON_FOCUS - border highlight

ON_ACTIVE - open inputScreen

probe_axis_values_map

std::map per axis index

current_probe_state

ProbeState enum

axis_index

extended_axis_index

probe_disconnected_

bool - Pn:O gate

btnAxe - axis chip

position_big_label - WPos

position_small_label - MPos

probe_param_btns 0 to 3

probe_status_label

processing_spinner

probe_status_icon

pins_labels 0 to 7

PanelComponent

encoder focus, 4 items

VirtualButtonsComponent

3 buttons

FirmwareStatusComponent

ConnectionStatusComponent


  1. Persistent per-axis parameter map — probe_axis_values_map is never cleared on axis switch. Defaults are only inserted for new entries (guarded by map::find), preserving any user modifications across axis changes within a single screen session.

  2. Two-pass probe (seek then feed) — The fast seek at full feed rate quickly finds approximate contact; the slow feed pass at feed_rate / 2 from the retracted position achieves precise repeatability. This is standard probing practice for grblHAL / Grbl controllers.

  3. grblHAL-specific Pn:O safety gate — The O pin character signals that the probe input is open (disconnected). The screen detects this in on_pins_state_update and both disables the Play button and refuses startProbeSequence(), preventing a false-contact or runaway condition unique to grblHAL’s extended pin reporting.

  4. No dynamic memory allocation during probing — GCode command strings are built in stack-allocated char cmd[128] buffers. The probe_axis_values_map is the only heap allocation and is populated at creation time, not during the probe sequence itself.

  5. Deferred overlay component creation — FirmwareStatusComponent and ConnectionStatusComponent are created via a single-shot LVGL timer (ESP3D_DEFERED_SCREEN_CREATION_DELAY_MS) after the main screen layout is complete, preventing LVGL task overload during the initial render.

  6. Per-group position throttle timestamps — WPos and MPos each have independent last_*_display_update_ms timestamps. A shared timestamp would cause one group’s gate-pass to starve the other, since both groups are dispatched from a single grblHAL status report in the same LVGL tick.

  7. Axis change aborts active probe — If the hardware switch or touch axis selector changes while a probe sequence is running, stopProbeSequence() is called immediately (Feed Hold !) and the state transitions to FAILED with an “axis changed” message. This prevents probing on the wrong axis.


a14 Changes (same as the FluidNC probe screen)

Section titled “a14 Changes (same as the FluidNC probe screen)”

See fluidnc_module_probe.md for the details; summary:

  • Parameters stored in NVS (esp3d_<axis>_probe_params, "offset;max_travel;retract;feed_rate", edited from this screen only). Linear axes hold one section per work unit (mm;…;in;…); rotary axes a plain list. probe_axis_values_map is now a cache of the values of the unit they were loaded for, reloaded when the probe unit changes; edits are written to the section of the active unit. Defaults: X/Y mm;0;30;5;300;in;0;1.2;0.2;12, Z mm;10;-50;3;200;in;0.4;-2;0.12;8, A/B/C 0;360;10;100.
  • Probe unit: G20/G21, or the report unit ($13) when esp3d_jog_position_unit is on; rotary axes never in inches. Every line is G91 with a unit word when the probe unit is not the work unit; after a successful probe the work unit and distance mode read from [GC:] are restored. Values have up to 4 decimals; the feed rate has 1 decimal in in/min.
  • Retract at the user feed rate (F[feed_rate]) instead of a fixed F200, which meant 200 in/min in G20.
  • Timeout covers the whole sequence (seek + retract + half-speed feed), × 1.5 + 5 s, clamped to 10–300 s (was 120 s).
  • Start refused when the machine is in a stable state that never becomes Idle on its own (Hold after a STOP, Alarm, Door, grblHAL Tool, error); an already Idle machine starts at once (value read directly, ESP3DValues only notifies on change).
  • STOP sends a feed hold and requestResetBeforeResume(): the next ~ becomes a host purge + soft reset, so the cancelled move never resumes (cnc_grbl.md, cnc_fluidnc.md).
  • Position button badges: unit of the reported positions and active WCS (refreshed with $G at each opening, since grbl / grblHAL only report the parser state at connection).

DocumentRelationship
grblhal_moduleParent module — screen set, screen router
grblhal_module_change_toolPrimary caller of the probe screen for tool-change workflow
grblhal_module_filesSibling screen in the grblHAL screen set
grblhal_module_connection_statusConnectionStatusComponent used as overlay
grbl_module_probeEquivalent probe screen for grbl firmware
fluidnc_module_probeEquivalent probe screen for FluidNC firmware
cnc_sharedShared FirmwareStatusComponent, jog screen, status screen
cnc_grblhalesp3dGcodeHandler — dispatches all probe GCode commands
gcode_hostStreaming, flow control, ESP3DCommandType priority levels
ui_componentsPanelComponent, VirtualButtonsComponent
common_screensinputScreen used for probe parameter numeric editing
ui_coreGenericScreen, UIManager, ThemeStyles
valuesESP3DValues observable store — all subscribed indices