Files
onefinity-firmware/docs/AUX_A_AXIS.md
Henrik Muehe 789ca4871b docs: A axis architecture (renamed from W) + README section
- Move docs/AUX_W_AXIS.md to docs/AUX_A_AXIS.md and rebadge W -> A
  throughout, with a header note pointing at ExternalAxis as the
  current implementation.
- README: A-axis fork heading, link to AUX_A_AXIS.md, /api/aux/status
  in verify-flash, small comment in scripts/deploy/local.sh.
2026-05-03 15:10:26 +02:00

8.8 KiB

A axis (auxcnc) integration

Note: This document describes the original out-of-band W-axis architecture (gcode preprocessor rewriting W tokens into HOOK messages dispatched between blocks). The current implementation integrates the auxcnc-driven stepper as a virtual A axis through gplan via a synthetic motor (bbctrl/ExternalAxis.py), so A is blended with XYZ in the same S-curve plan and the gcode surface below applies as plain A words.

The HOOK pipeline still exists for ATC pneumatics (M100..M103), see bbctrl/AuxPreprocessor.py.

This adds a virtual A 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: A moves run between G-code blocks, not blended with XYZ.

Pipeline:

  1. User uploads a G-code file containing A words.
  2. FileHandler runs AuxPreprocessor on the upload, rewriting W tokens in place into (MSG,HOOK:aux:<mm>) etc. The original line minus the A word continues to drive XYZ.
  3. The planner sees only XYZ + message comments. When it reaches a message line, the message goes through state.add_message which Hooks._on_state_change watches for the HOOK: prefix.
  4. Hooks._fire('custom', ...) finds the registered internal handler for the event name (aux, aux_rel, aux_home, aux_setzero).
  5. The handler runs in a hook thread, gating Mach.unpause until done. While the handler is busy the machine is in HOLDING - no XYZ motion can resume until A finishes.
  6. The handler talks to the ESP over /dev/ttyUSB0 via AuxAxis, blocking on a deterministic reply token ([step] done, [home] done, etc).

MDI commands containing A words are rewritten the same way at the Mach.mdi() boundary so manual jog and macros work too.

G-code surface

G21 G90
G28 A0          ; home A axis
G1 A25 F300     ; move A to 25 mm absolute
G1 X100 W12.5   ; mixed: A moves first, then XYZ (configurable)
G91
G1 A-2.5        ; relative A move
G90
G92 A0          ; set current A as zero (G92-style)

Rules:

  • G28 / G28.2 with W only -> homing hook; the bare G28 is NOT emitted to gplan (that would mean home-all).
  • G28.2 X0 Y0 W0 -> emit hook, then keep G28.2 X0 Y0 for 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_first constructor 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).

UI

Control view

  • A jog row appears under the XYZ jog grid when aux_enabled is true, with three buttons: A-, A+, and a wide Home W. There is intentionally no separate "set zero" or "W origin" button - homing lands the axis at home_position_mm (0 by default), so home and zero are the same point.
  • The DRO table shows a A axis row with position, status (OFFLINE / UNHOMED / HOMED), and a single Home button in the actions column (the cog and map-marker columns are placeholders for layout).

Settings view

A "W Axis (auxcnc)" section exposes every aux.json field except enabled (which stays read-only - flipping the A axis on/off requires editingaux.json on the controller, so a fresh install can't surprise the user with hardware that isn't there). Saving PUTs the merged config to /api/aux/config/save, which writes aux.json and pushes HOMECFG to the ESP. A status line shows whether the axis is disabled / offline / connected-unhomed / homed at <pos> mm.

State surface

These are pushed via state.set and visible in the websocket stream:

  • aux_enabled - bool, axis is configured + enabled
  • aux_present - bool, ESP responding on serial
  • aux_homed - bool, has been homed since last ESP reset
  • aux_pos - float, current W in mm (4 decimals)

Edge cases

  • ESP reboots mid-job: [boot] auxcnc v=N banner -> aux_homed cleared, message added: "A axis controller restarted - re-home before use". Subsequent A 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 A move completes. For an immediate stop hit estop; the Hooks listener will call aux.abort() which sends ABORT\n to the ESP and the step-pulse loop exits.
  • Connection loss: if /dev/ttyUSB0 can't be opened at startup, aux_present=False and 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 A 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 layer
  • src/py/bbctrl/AuxPreprocessor.py (new): G-code rewriter
  • src/py/bbctrl/Hooks.py: register_internal(), fix the messages listener so (MSG,HOOK:...) actually fires
  • src/py/bbctrl/Ctrl.py: instantiate AuxAxis, register hooks
  • src/py/bbctrl/Mach.py: rewrite MDI commands containing W
  • src/py/bbctrl/FileHandler.py: rewrite uploads in place
  • src/py/bbctrl/Web.py: REST endpoints
  • src/py/bbctrl/__init__.py: export AuxAxis
  • src/pug/templates/control-view.pug: W jog row + DRO row
  • src/js/control-view.js: aux_home / aux_jog / aux_jog_incr handlers
  • src/js/axis-vars.js: _compute_aux_axis for W state
  • src/svelte-components/src/components/WAxisSettings.svelte: settings panel
  • src/svelte-components/src/components/SettingsView.svelte: hosts WAxisSettings
  • auxcnc/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