diff --git a/README.md b/README.md index 1e1bee2..cda4657 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# OneFinity CNC Controller Firmware (community fork) +# OneFinity CNC Controller Firmware (A-axis fork) -This is the OneFinity / Buildbotics bbctrl firmware with a redesigned -UI (V09), Font Awesome 6, faster cold boot, and a streamlined macOS -dev / deploy workflow. +This is the community-fork firmware (V09 UI, FA6, cold-boot work, +macOS dev tooling) with a virtual A axis driven by an auxcnc ESP32 +over USB serial. See [docs/AUX_A_AXIS.md](docs/AUX_A_AXIS.md) for the +design and config. ## Layout @@ -16,7 +17,7 @@ src/svelte-components/ Newer Svelte UI for dialogs and settings src/pug/ Pug templates compiled into build/http/index.html src/resources/ Static assets and config templates scripts/ Install / update / RPi build helpers -docs/ Architecture, dev setup +docs/ Architecture, dev setup, A-axis docs ``` ## Build & flash (quick path, macOS or Linux) @@ -101,6 +102,7 @@ bbctrl restarts, then the new UI). ```bash curl -s http://onefinity.local/ | grep -c "OneFinity" curl -s http://onefinity.local/api/diag/timing | head +curl -s http://onefinity.local/api/aux/status # if A axis is enabled ``` ## Build & flash (full path, Debian/Linux) @@ -108,3 +110,15 @@ curl -s http://onefinity.local/api/diag/timing | head For AVR + GPlan rebuilds, see [docs/development.md](docs/development.md). That path uses qemu + chroot to cross-compile gplan for ARM and needs the `gcc-avr` / `avr-libc` toolchain. + +## A axis (auxcnc) + +This fork adds a virtual A axis. See +[docs/AUX_A_AXIS.md](docs/AUX_A_AXIS.md) for: + +- G-code surface (`G28 A0`, `G1 A25`, etc.) +- The G-code preprocessor and hook architecture +- aux.json keys +- REST API (`/api/aux/*`) +- UI surface (jog row in Control, settings panel in Settings) +- Edge cases (ESP reboot mid-job, limit closed at home start, …) diff --git a/docs/AUX_A_AXIS.md b/docs/AUX_A_AXIS.md new file mode 100644 index 0000000..43ca1a7 --- /dev/null +++ b/docs/AUX_A_AXIS.md @@ -0,0 +1,183 @@ +# 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:)` 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 + +```gcode +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 `/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 ` 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 diff --git a/scripts/deploy/local.sh b/scripts/deploy/local.sh index 3045ab5..da52296 100755 --- a/scripts/deploy/local.sh +++ b/scripts/deploy/local.sh @@ -9,6 +9,9 @@ # * The full V09 chrome (header tabs, settings rail, jog grid, DRO # skeleton, status strip). # * A "DISCONNECTED" overlay because there's no controller backend. +# * The A axis row in jog/DRO is hidden (correct: it appears only when +# the controller reports `aux_enabled = true`). To exercise the A +# axis end-to-end, deploy to the Pi (`./deploy.sh hardware`). set -euo pipefail