CNC Firmware Integration
Section titled “CNC Firmware Integration”Purpose
Section titled “Purpose”The CNC_Firmware_Integration module is the bidirectional bridge between the pendant firmware and the connected CNC controller. It is responsible for:
- Scheduling, pacing, and acknowledging every outbound G-code or ESP command through a priority queue and FreeRTOS streaming engine (
ESP3DGCodeHostService). - Parsing firmware protocol responses (status reports,
ok/erroracknowledgements, file listings, parser state) and publishing decoded values into the observableESP3DValuesstore so the UI reacts in real time. - Enforcing flow-control gates before each transmitted line to avoid overrunning the controller’s RX buffer and planner queue.
- Managing firmware-family-specific session lifecycle (startup sequences, MPG token negotiation, status polling).
The module is compiled once per firmware target. CMake selects exactly one firmware handler and one flow-control implementation at build time — there is no runtime polymorphism overhead.
The same core (ESP3DGCodeHostService, ESP3DXStream) also serves the 3D printer domain: a Marlin handler and a 3D printer flow layer exist as groundwork (main/target/3dprinter/). Repetier and Smoothieware are placeholders only (description.txt, no code).
Architecture Overview
Section titled “Architecture Overview”Module Position in the Firmware Stack
Section titled “Module Position in the Firmware Stack”Build-Time Layer Selection
Section titled “Build-Time Layer Selection”Dual Queue System
Section titled “Dual Queue System”The host service maintains two independent stream queues processed every 10 ms tick:
Scripts always preempt file streams at ready_to_read_cursor or paused states. A file stream paused while scripts run auto-resumes once the script queue drains.
Stream State Machine
Section titled “Stream State Machine”Each queued stream advances through the following states (up to 4 steps per 10 ms tick):
The keepalive ? is sent on the high-priority path (no flow credit), so the 10 s timeout only fires on a dead link, not during a feed hold or a long dwell. The same 2 s ping protects a closed flow gate: if the pings stay unanswered for 10 s, the link is declared lost. An error: is a negative ack of exactly one line: one credit is released, the current stream and the main file stream go to error (command_rejected); there is no resend. Detail: gcode_host_streaming_flow.md, “ACK, timeout, and errors”.
CNC Flow Control Gates
Section titled “CNC Flow Control Gates”gcodeHostFlowCanSendLine() evaluates four conditions before any normal G-code line may be sent:
| Gate | Threshold |
|---|---|
| ok credit | getStreamMaxInFlight() — 4 for FluidNC / grblHAL / GRBL |
| Planner buffer | ≤ 5 free blocks → blocked (kPlannerBlocksThreshold) |
| RX window (hard) | getStreamRxBufferSize() — grblHAL: 1024 B, FluidNC: 256 B, GRBL: 128 B by default, updated from [OPT:] |
| RX bytes snapshot | ` |
The 3D printer flow (Marlin groundwork) keeps only the ok credit (1 line in flight) and the same character-counting RX window (120 B): classic Marlin has no |Bf:| planner report.
Firmware Handler Comparison
Section titled “Firmware Handler Comparison”| Feature | cnc_grbl | cnc_fluidnc | cnc_grblhal |
|---|---|---|---|
| Status reporting | Poll ? at interval | Auto-report ($Report/Interval) | Poll ? / auto-report ($481) |
| SD card files | Pendant SD only (streamed) | Firmware SD ($SD/Run=) | Firmware SD (firmware run cmd) |
| MPG token machine | No | No | Yes (4 states, 0x8B) |
| Homing queries | $22 only | Per-axis ($/axes/x/homing/…) | $22 + $44–$49 |
| Error format | error:N (codes 1–38) | error:N (codes 1–152) | error:N (codes 1–88, 253) |
| Alarm format | ALARM:N (codes 1–9) | [MSG:ERR:…] rich | ALARM:N (codes 1–21) |
| RX buffer | 128 B (from [OPT:]) | 256 B | 1024 B |
| Startup phases | 4-phase | 6-phase | 10-phase |
| Macro execution | Stream from pendant SD | $SD/Run=<path> | Firmware run command |
Priority and Urgent Bypass
Section titled “Priority and Urgent Bypass”High-priority messages bypass flow control entirely:
Lost Lines, Input Overflow and Resets
Section titled “Lost Lines, Input Overflow and Resets”A line lost on the way to or from the controller breaks the ok-credit accounting (an ok never comes, or comes for a line the host does not know). The host handles every loss explicitly instead of waiting for a timeout. The detection is allocation-free and works from any task (main/core/esp3d_client.cpp); the host task consumes the flags in handle().
| Event | Detection | Host reaction |
|---|---|---|
| Memory saturation (case A) — an input line refused because the heap is below the admission reserve (6144 B free / 2048 B largest block; CNC link vital lines down to 3072 / 1024 B) | esp3d_rx_overflow_raise() | Full purge of every queue, critical alert, input closed: every input except the CNC link and single safety realtime commands (! ~ ? 0x18 0x85) is refused until the user presses Restart or Recover. The firmware status icon and screen show ERROR while the input is closed |
| Local buffer overflow (case B) — a queue of one origin full while memory is fine | esp3d_rx_buffer_lost(origin) | Only that origin is purged, non-blocking notice; an SD job from another origin continues |
| Line for the controller not sent (write error, TX queue full on the CNC output client) | esp3d_tx_line_lost(client) | Flow reset, streams stopped, notice “Lines to CNC not sent: stream stopped” |
| Isolated input line dropped under low memory (no burst) | esp3d_rx_isolated_drop_take() | Line dropped, no purge; warning “Low memory: command dropped” (at most once per 10 s) |
| Controller session reset — welcome banner after a soft reset or a controller reboot | the firmware handler calls gcodeHostService.requestControllerSessionReset() | consumeControllerSessionReset() drops the host state; a running job is aborted with a message |
| User Restart / Recover (firmware status screen, critical alert) | gcodeHostService.resetAll() | Waits up to 500 ms for the host tick to be idle, drops every queue, clears the overflow flags, reopens the input, clears the critical alert. If refused (timeout), the soft reset sent next still triggers the session reset path |
Purges called from other tasks (resetAll(), purgeByOrigin(), purgeByRequestId()) run under the same exclusion as the host tick (_emergency_stopping + wait for idle). Detail: gcode_host_streaming_flow.md (“Purges from another task”) and esp32_memory_constraints.md §3 “Low-memory admission guard”.
Source File Map
Section titled “Source File Map”main/modules/gcode_host/├── esp3d_gcode_host_service.h/.cpp ← ESP3DGCodeHostService, dual queues, state machine├── esp3d_gcode_host_types.h ← Enums: stream type, stream state, host state, errors├── esp3d_x_stream.h/.cpp ← ESP3DXStream, transport orchestrator
main/target/cnc/modules/gcode_host/└── esp3d_gcode_host_flow.cpp ← CNC flow gates (ok credit + planner + RX window)
main/target/none/modules/gcode_host/└── esp3d_gcode_host_flow.cpp ← Permissive stubs (TARGET_FW_NONE)
main/target/cnc/fluidnc/└── esp3d_gcode_handler_service.h/.cpp ← FluidNC protocol handler
main/target/cnc/grbl/└── esp3d_gcode_handler_service.h/.cpp ← GRBL 1.1 protocol handler
main/target/cnc/grblhal/└── esp3d_gcode_handler_service.h/.cpp ← grblHAL protocol handler
main/target/3dprinter/modules/gcode_host/└── esp3d_gcode_host_flow.cpp ← 3D printer flow (ok credit + RX window)
main/target/3dprinter/marlin/└── esp3d_gcode_handler_service.h/.cpp ← Marlin protocol handler (groundwork)
main/target/none/└── esp3d_gcode_handler_service.h/.cpp ← Null stub handler (inline methods; the .cpp only defines the global instance)
main/core/esp3d_client.cpp ← Low-memory admission guard, lost-line / overflow flagsCore Component Documentation
Section titled “Core Component Documentation”| Document | Topic |
|---|---|
docs/architecture/gcode_host_architecture.md | Core + domain flow split, CMake wiring, testing scope |
docs/architecture/gcode_host_streaming_flow.md | State machine detail, pause/resume/abort semantics, flow gates |
docs/architecture/esp3d_message_priority_system.md | Normal vs high-priority message routing |
docs/architecture/connection_management.md | Transport lifecycle, connection status |
docs/features/feature_resource_matrix.md | Feature compatibility, WiFi/CNC usage model |
docs/guides/esp32_memory_constraints.md | Heap, fragmentation, allocation rules for stream nodes |
docs/guides/esp3d_log_guide.md | esp3d_log macros, flow instrumentation |
docs/ongoing_reflection/input_overflow_postmortem.md | Input overflow cases A / B, S7 (UART ring), S21 (TX loss), test status |