Add W axis integration via auxcnc ESP32 over /dev/ttyUSB0

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.
This commit is contained in:
Claude
2026-04-30 16:51:24 +02:00
parent 54a15f9d12
commit c7cf9483b3
10 changed files with 1092 additions and 16 deletions

View File

@@ -60,6 +60,8 @@ class Ctrl(object):
if not args.demo: self.jog = bbctrl.Jog(self)
self.pwr = bbctrl.Pwr(self)
self.hooks = bbctrl.Hooks(self)
self.aux = bbctrl.AuxAxis(self)
self._register_aux_hooks()
self.mach.connect()
@@ -110,8 +112,46 @@ class Ctrl(object):
self.preplanner.start()
def _register_aux_hooks(self):
"""Wire up the auxcnc HOOK: events to AuxAxis methods."""
log = self.log.get('AuxAxis')
def _hook_move(ctx):
data = (ctx.get('data') or '').strip()
if not data:
raise Exception('aux hook missing target')
self.aux.move_abs_mm(float(data))
def _hook_move_rel(ctx):
data = (ctx.get('data') or '').strip()
if not data:
raise Exception('aux_rel hook missing delta')
self.aux.move_rel_mm(float(data))
def _hook_home(ctx):
self.aux.home()
def _hook_setzero(ctx):
data = (ctx.get('data') or '').strip()
mm = float(data) if data else 0.0
self.aux.set_position_mm(mm)
self.hooks.register_internal('aux', _hook_move,
block_unpause=True, auto_resume=True)
self.hooks.register_internal('aux_rel', _hook_move_rel,
block_unpause=True, auto_resume=True)
self.hooks.register_internal('aux_home', _hook_home,
block_unpause=True, auto_resume=True,
timeout=180)
self.hooks.register_internal('aux_setzero', _hook_setzero,
block_unpause=True, auto_resume=True)
log.info('Aux hooks registered')
def close(self):
self.log.get('Ctrl').info('Closing %s' % self.id)
self.ioloop.close()
self.avr.close()
self.mach.planner.close()
try: self.aux.close()
except Exception: pass