Rather than rebuild gplan + the AVR firmware to add a true 7th axis,
we treat W as a synchronous out-of-band axis that moves between G-code
blocks. The pipeline:
upload -> AuxPreprocessor rewrites W tokens into (MSG,HOOK:aux:N)
comments -> planner sees only XYZ + messages -> Hooks fires the
registered internal handler -> AuxAxis sends STEPS/HOME over serial
to the ESP and blocks the planner until done.
New files:
src/py/bbctrl/AuxAxis.py serial worker + RPC layer
src/py/bbctrl/AuxPreprocessor.py G-code rewriter
docs/AUX_W_AXIS.md design + ops notes
Changed:
Hooks.py register_internal(); fix the (MSG,HOOK:...) listener
to read the 'messages' state list (was broken before)
Ctrl.py instantiate AuxAxis, register aux/aux_rel/aux_home/
aux_setzero hooks
FileHandler.py rewrite uploads in place when they use W
Mach.py rewrite W tokens in MDI input the same way
Web.py REST endpoints under /api/aux/*
The ESP firmware in ../auxcnc was extended in lockstep: HOME, HOMECFG
(NVS-persisted), WPOS, HOMED?, LIMIT?, abortable STEPS with
limit-aware abort, trapezoidal ramps, deterministic [topic] reply
tokens, [boot] banner.
Real-time decisions (limit switch, step pulses) live on the ESP. The
host owns mm units, soft limits, and aux_homed bookkeeping. ESP
reboot mid-job clears aux_homed and surfaces a message; per design
manual jogs are still allowed without homing.
6.9 KiB
W axis (auxcnc) integration
This adds a virtual W axis to the bbctrl controller, driven by the
auxcnc ESP32 over USB serial (/dev/ttyUSB0). The ESP owns step-pulse
generation, real-time limit-switch monitoring, and the homing dance.
The Pi owns units (mm), soft limits, sequencing inside G-code jobs, and
a small REST API for jogging / homing from the UI.
How it works
The bbctrl planner (gplan) only understands xyzabc, so adding a true
7th axis would require rebuilding gplan + the AVR firmware. We avoid
that by treating W as a synchronous out-of-band axis: W moves run
between G-code blocks, not blended with XYZ.
Pipeline:
- User uploads a G-code file containing
Wwords. FileHandlerrunsAuxPreprocessoron the upload, rewriting W tokens in place into(MSG,HOOK:aux:<mm>)etc. The original line minus the W word continues to drive XYZ.- The planner sees only XYZ + message comments. When it reaches a
message line, the message goes through
state.add_messagewhichHooks._on_state_changewatches for theHOOK:prefix. Hooks._fire('custom', ...)finds the registered internal handler for the event name (aux,aux_rel,aux_home,aux_setzero).- The handler runs in a hook thread, gating
Mach.unpauseuntil done. While the handler is busy the machine is in HOLDING - no XYZ motion can resume until W finishes. - The handler talks to the ESP over
/dev/ttyUSB0viaAuxAxis, blocking on a deterministic reply token ([step] done,[home] done, etc).
MDI commands containing W words are rewritten the same way at the
Mach.mdi() boundary so manual jog and macros work too.
G-code surface
G21 G90
G28 W0 ; home W axis
G1 W25 F300 ; move W to 25 mm absolute
G1 X100 W12.5 ; mixed: W moves first, then XYZ (configurable)
G91
G1 W-2.5 ; relative W move
G90
G92 W0 ; set current W as zero (G92-style)
Rules:
G28/G28.2with W only -> homing hook; the bareG28is NOT emitted to gplan (that would mean home-all).G28.2 X0 Y0 W0-> emit hook, then keepG28.2 X0 Y0for XY homing.- A line with both W and XYZ axis words is split into two sequential
blocks. Default order: W first, then XYZ. Toggle via the
w_firstconstructor arg. - Lines inside parens or after
;are passed through verbatim.
Configuration
Per-controller config lives at <ctrl_path>/aux.json (created on first
save via the API). Keys:
| Key | Default | Notes |
|---|---|---|
enabled |
false |
Master switch |
port |
/dev/ttyUSB0 |
Serial device |
baud |
115200 |
|
steps_per_mm |
80.0 |
Logical steps per mm |
dir_sign |
1 |
+1 or -1: maps logical+ to motor+ |
min_w, max_w |
0, 100 |
Soft limits in mm |
home_dir |
'-' |
Direction toward limit switch |
home_position_mm |
0.0 |
mm value assigned at home |
home_fast_sps |
4000 |
Fast seek rate |
home_slow_sps |
400 |
Slow re-seek rate |
home_backoff_steps |
200 |
Backoff after touching limit |
home_maxtravel_steps |
200000 |
Hard cap on phase 1 seek |
step_max_sps |
4000 |
Cruise rate for STEPS |
step_accel_sps2 |
16000 |
Trapezoidal ramp accel |
step_start_sps |
200 |
Ramp floor |
limit_low |
true |
Switch active low (closed = LOW) |
Most of these are pushed to the ESP via HOMECFG on connect and
persisted there in NVS.
REST API
| Verb | Path | Body | Effect |
|---|---|---|---|
| GET | /api/aux/config |
- | Current config |
| PUT | /api/aux/config/save |
{key: val, ...} |
Save and re-push |
| GET | /api/aux/status |
- | {enabled, present, homed, pos_mm} |
| PUT | /api/aux/home |
- | Run home cycle (blocks) |
| PUT | /api/aux/abort |
- | Cancel running motion |
| PUT | /api/aux/jog |
{mm: 1.5} or {steps: 200} |
Relative move |
| PUT | /api/aux/move |
{mm: 12.5} |
Absolute move (mm) |
| PUT | /api/aux/set-zero |
{mm: 0} |
Set current pos to mm |
Steps-mode jog ignores soft limits (use it to inch the axis to the limit switch when the axis isn't homed yet).
State surface (UI)
These are pushed via state.set and visible in the websocket stream:
aux_enabled- bool, axis is configured + enabledaux_present- bool, ESP responding on serialaux_homed- bool, has been homed since last ESP resetaux_pos- float, current W in mm (4 decimals)
Edge cases
- ESP reboots mid-job:
[boot] auxcnc v=Nbanner ->aux_homedcleared, message added: "W axis controller restarted - re-home before use". Subsequent W moves still run; if you want a hard fail instead, that's a one-line change in_require_present. - Limit switch closed at boot of HOME:
[home] failed reason=already_at_limit-> hook raises -> Mach surfaces error. - Pause mid-W-move: the hook is blocking, so feed-hold takes
effect after the W move completes. For an immediate stop hit
estop; the Hooks listener will call
aux.abort()which sendsABORT\nto the ESP and the step-pulse loop exits. - Connection loss: if
/dev/ttyUSB0can't be opened at startup,aux_present=Falseand any G-code with W will fail-fast at the hook handler with "Aux axis not connected". - No home enforcement: per design, manual jogs and W moves are allowed even without a successful home. Soft limits still apply unless you use the raw step jog endpoint.
Files added/changed
src/py/bbctrl/AuxAxis.py(new): serial worker + RPC layersrc/py/bbctrl/AuxPreprocessor.py(new): G-code rewritersrc/py/bbctrl/Hooks.py: register_internal(), fix the messages listener so(MSG,HOOK:...)actually firessrc/py/bbctrl/Ctrl.py: instantiate AuxAxis, register hookssrc/py/bbctrl/Mach.py: rewrite MDI commands containing Wsrc/py/bbctrl/FileHandler.py: rewrite uploads in placesrc/py/bbctrl/Web.py: REST endpointssrc/py/bbctrl/__init__.py: export AuxAxisauxcnc/src/main.cpp: new commands HOME, HOMECFG, WPOS, HOMED?, LIMIT?, ABORT-able STEPS with limit-aware abort, trapezoidal ramps, NVS-persisted config,[boot]banner, deterministic reply tokens