grbl_module_change_tool
Section titled “grbl_module_change_tool”Introduction
Section titled “Introduction”The grbl_module_change_tool module implements the Tool Change screen for the grbl CNC firmware target. It provides a complete user workflow for managing tool changes on a grbl-controlled CNC machine, supporting two distinct operation modes: a physical CHANGE (M6T) that pauses the firmware to let the operator physically swap the cutting tool, and a logical SET (M61Q) that updates the active tool number in firmware state without moving the machine.
The screen is LVGL-driven, runs on Core 1, and follows the same safe timer-based transition and lifecycle patterns used across the rest of the pendant UI. It subscribes to the ESP3DValues observable system for firmware state, parser state, and live machine positions; all updates are validated for change before touching LVGL objects.
Source file: main/display/cnc/grbl/screens/change_tool_screen.cpp
Screen type enum: ESP3DScreenType::change_tool
Architecture
Section titled “Architecture”Module Position in the System
Section titled “Module Position in the System”The screen is registered in the grbl screen router (grbl_module_screen_router) alongside all other grbl-target screens. When the router receives ESP3DScreenType::change_tool, it delegates to changeToolScreen::create().
Component Structure
Section titled “Component Structure”| Component | Role |
|---|---|
ToolChangeState | Internal state machine — drives UI label, button labels, and allowed actions |
ToolChangeMode | Selects the GCode to send — CHANGE (M6 T<n>) or SET (M61 Q<n>) |
PanelComponent | Interactive header showing Mode and Target tool fields; encoder navigates/edits |
| Position display | Two-row live display of Work-Position (WPos) and Machine-Position (MPos) per axis |
| Status bar | Single-line text area reflecting the current state message |
FirmwareStatusComponent | Optional overlay — shows firmware alarm/busy state; shared across CNC screens |
ConnectionStatusComponent | Optional overlay — shows transport link health; tappable to open connection status screen |
State Machine
Section titled “State Machine”The tool change lifecycle is modelled as an explicit state machine. The current state drives which actions the centre button exposes (Start / Resume / Cancel) and whether the panel and back button are accessible.
States Reference
Section titled “States Reference”| State | Meaning | Centre Button Label |
|---|---|---|
IDLE | Ready to start | Start |
MOVING_TO_CHANGE | M6 command sent; firmware moving to change position | Cancel |
WAITING_USER | Firmware halted (HOLD); operator swaps tool manually | Resume |
PROBING | Firmware probing tool length after resume | Cancel |
SUCCESS | Tool change confirmed; current_tool updated | Start (next change) |
FAILED | Error, alarm, timeout, or same-tool detected | Start (retry) |
CANCELLED | User cancelled; firmware being reset | (disabled) |
Tool Change Modes
Section titled “Tool Change Modes”The active mode is toggled by pressing OK (Button 0) while the Mode panel item is focused.
Button Layout
Section titled “Button Layout”The screen uses three hardware/virtual buttons mapped to GenericScreen:
| Button | Physical ID | Normal label | Context-sensitive behaviour |
|---|---|---|---|
| OK | Button 0 | OK icon | Toggle Mode field OR toggle encoder editing on Target field |
| Change / Action | Button 1 | Change icon | Start / Resume / Cancel depending on current_state |
| Back | Button 2 | Back icon | Navigate to return screen (blocked during active operations and probe decision) |
Button 1 Behaviour by State
Section titled “Button 1 Behaviour by State”current_state | Button 1 action | GCode / command sent |
|---|---|---|
IDLE + CHANGE mode | Start tool change | M6 T<target> |
IDLE + SET mode | Set current tool | M61 Q<target> |
WAITING_USER | Resume (cycle start) | GRBL_RT_CYCLE_START (realtime, high priority) |
MOVING_TO_CHANGE or PROBING | Cancel | feed-hold + reset sequence |
Data Flow
Section titled “Data Flow”Value Subscriptions
Section titled “Value Subscriptions”Position Update Throttling
Section titled “Position Update Throttling”Position values arrive at high frequency from the firmware. A dedicated LVGL timer (position_flush_timer) coalesces updates and writes to the display at most every POSITION_DISPLAY_MIN_INTERVAL_MS + 50 ms. Pending flags (wpos_update_pending, mpos_update_pending) track dirty state between ticks.
on_firmware_status_update Transition Logic
Section titled “on_firmware_status_update Transition Logic”This is the primary driver of the state machine. It handles four categories of event:
MOVING_TO_CHANGE→ HOLD detected → transition toWAITING_USER(operator must swap tool)WAITING_USER→ RUN detected → transition toPROBING(firmware is now probing)PROBING→ IDLE detected → transition toSUCCESS;current_toolset totarget_tool- ALARM detected (any non-editable state) → revert tool in firmware; transition to
FAILED - HOLD→IDLE or error→IDLE recovery → transition back to
IDLEorFAILED(same-tool guard)
Screen Lifecycle
Section titled “Screen Lifecycle”Destruction Guard
Section titled “Destruction Guard”The module uses the project-standard ESP3D_TRANSITION_* macros to guard against double-free and premature destruction:
is_prepared_for_destruction— set before any navigation starts; suppresses subscription callbacks.cleanup_timer+transition_timer— two-phase LVGL timer sequence:cleanup_timer_cbfinalises teardown, thentransition_timer_cbcallscreateScreen()for the next screen.- Back navigation is blocked while
current_stateisMOVING_TO_CHANGE,WAITING_USER, orPROBING, and whileawaiting_probe_decisionis true.
Probe Screen Integration
Section titled “Probe Screen Integration”When a SET (M61Q) operation succeeds and an optional probe step is desired, the screen presents an in-screen prompt offering two choices:
- Probe — navigates to
grbl_module_probewith the change tool screen set as the return target. - Ignore — dismisses the prompt and stays on the SUCCESS state.
Panel Component — Tool Selector
Section titled “Panel Component — Tool Selector”The header area is an interactive PanelComponent with two items:
| Panel Item | ID | Content | Encoder interaction |
|---|---|---|---|
| Mode | ITEM_ID_MODE | CHANGE or SET text | Not encoder-editable; toggled with OK button |
| Target | ITEM_ID_TARGET | T<n> target tool number | Encoder increments/decrements value in editing mode |
The encoder transitions between navigation mode (focus moves between items) and editing mode (encoder changes the focused item’s value) via panel_component->enableEncoderFor(). The target tool value is persisted in saved_target_tool across screen re-creation (e.g., returning from the probe screen).
Timeout Handling
Section titled “Timeout Handling”A watchdog timeout_timer is started when MOVING_TO_CHANGE begins. If firmware does not reach HOLD within the configured timeout:
The pending_timeout_revert flag is a deferred-failure mechanism: if the firmware is not yet IDLE when the timeout fires, the FAILED state is applied on the next IDLE transition instead, avoiding a race between the timer and the firmware status callback.
Parser State — Current Tool Extraction
Section titled “Parser State — Current Tool Extraction”on_parser_state_update parses the grbl $G/$I style parser state string to extract the active tool number (T<n>), keeping current_tool in sync with what the firmware reports. Updates are applied only when current_state is an editable state (IDLE, SUCCESS, FAILED, CANCELLED) to avoid overwriting an in-progress operation.
Dependencies
Section titled “Dependencies”| Dependency | Module reference | Used for |
|---|---|---|
GenericScreen | common_screens | Screen container + 3-button virtual button bar |
PanelComponent | ui_components | Interactive mode/target selector with encoder support |
FirmwareStatusComponent | cnc_shared | Optional firmware alarm overlay |
ConnectionStatusComponent | grbl_module_connection_status | Optional transport health overlay |
probeScreen | grbl_module_probe | Optional post-SET probe workflow |
ESP3DValues / esp3dXValues | values | Observable system for firmware state, positions, connection |
ESP3DGCodeHostService | gcode_host | Sending M6, M61, real-time cycle-start, feed-hold commands |
ESP3DTranslationService | translations | All user-visible status messages |
UIManager / ui_manager | ui_core | Screen registration, orientation angle |
createScreen() router | grbl_module_screen_router | Screen transitions to/from change_tool |
Relationship to Parallel Implementations
Section titled “Relationship to Parallel Implementations”The grbl change tool screen is one of three parallel implementations sharing the same UX design:
All three implement identical state machines and UX flows. The grbl variant uses grbl-specific GCode commands (M6 T<n>, M61 Q<n>, GRBL_RT_CYCLE_START) and parses grbl’s status response format. See fluidnc_module_change_tool for the FluidNC equivalent.
LVGL Constraints
Section titled “LVGL Constraints”- All UI operations (object creation, event callbacks, timer callbacks) run on Core 1 in the LVGL task context — never from communication or GCode host tasks.
- The
position_flush_timercoalesces high-frequency position updates so the LVGL task is not overloaded by directlv_label_set_textcalls on every firmware status poll. - Screen destruction is deferred via
cleanup_timer/transition_timerto avoid deleting LVGL objects inside event callbacks. - All heap allocations for overlay components use
std::nothrow; allocation failure downgrades the overlay to absent rather than crashing the firmware.
Memory Notes
Section titled “Memory Notes”screen_instance(GenericScreen*),panel_component,firmware_status_component, andconnection_status_componentare heap-allocated withnew (std::nothrow).last_firmware_stateis astd::stringused only in non-critical update paths; not on hot paths.- Static state variables (
current_tool,target_tool,saved_target_tool,returning_from_probe, etc.) are module-scoped statics. They persist across screen re-creation within the same firmware session so that navigating away to the probe screen and returning does not lose the user’s tool selection or operation context.
a14 Changes
Section titled “a14 Changes”- Cancel while moving. Cancel during
MOVING_TO_CHANGE/PROBINGsends a feed hold andrequestResetBeforeResume(): the next resume becomes a soft reset, so the cancelled sequence can never restart (same as probe STOP). - No timeout while the user works. No timeout runs while the screen waits for the user to swap the tool (
WAITING_USER); a freshTOOL_CHANGE_TIMEOUT_MS(60 s) starts when the sequence goes on. A pending timeout ends on IDLE only: any other change (for example HOLD → RUN after a late Resume) is not the end of the sequence; an ALARM ends it through the alarm handling. - Error display. Errors get a warning icon under the message (error color); the message has a fixed width and scrolls instead of going edge to edge. Error values are matched case-insensitively (
ERROR:<code>/Error:…). - Probe from the tool change. “User stopped the probe” and “backed out without probing” are reported like “Ignore”: M61Q already succeeded, nothing was cancelled. The probe screen opened from here measures Z and shows a hint instead of
Idle. - Sound mute guard.
SoundMuteGuard(RAII) restores the saved mute state on every exit path ofcreate()(normal return, early return, exception), so the buzzer can never stay muted until reboot. - Small
mm/inunit after the MPos / WPos titles (unit of the reported positions).