AuxAxis: wire DROPTOOL/GRABTOOL/RELEASE/CLAMP as gcode hooks

Adds atc_droptool/atc_grabtool/atc_release/atc_clamp wrappers in
AuxAxis (each just an RPC waiting on the matching terminal reply
line from the firmware), and registers them as internal hook
handlers in Ctrl. Macros and gcode programs can now invoke the
tool changer with:

  (MSG,HOOK:droptool:)
  (MSG,HOOK:grabtool:)
  (MSG,HOOK:release:)
  (MSG,HOOK🗜️)

block_unpause + auto_resume mirrors the W-axis hooks: the program
pauses while the ESP runs the pneumatic sequence and resumes when
done. Soft timeouts match the worst-case ESP sequence durations.
This commit is contained in:
2026-05-01 16:35:45 +02:00
parent 1a6f926181
commit 06f0e6517e
2 changed files with 69 additions and 0 deletions

View File

@@ -230,6 +230,55 @@ class AuxAxis(object):
except Exception as e: except Exception as e:
self.log.warning('ABORT send failed: %s' % e) self.log.warning('ABORT send failed: %s' % e)
# ---------------------------------------------------------- ATC commands
#
# The auxcnc firmware drives an AMB 1050 FME-W DI tool changer via
# three pneumatic valves on relays 1-3. The ESP runs the timed
# sequences itself; the host just kicks them off and waits for the
# terminal reply.
def atc_droptool(self, timeout=30.0):
"""Eject the current tool. Opens the collet (V1), oscillates the
ejector (V2), then re-clamps with a bleed cycle. Blocks until
the ESP reports done. Raises on failure."""
self._require_present()
line = self._rpc('DROPTOOL', topic='droptool', timeout=timeout)
if line.startswith('done'):
return
reason = line.split('reason=', 1)[1] if 'reason=' in line else line
raise AuxAxisError('DROPTOOL failed: %s' % reason)
def atc_grabtool(self, timeout=30.0):
"""Pick up a tool that's already been seated by the operator.
Opens V1 (releases the collet), waits for the operator to insert
the holder, then re-clamps with a bleed cycle. Blocks."""
self._require_present()
line = self._rpc('GRABTOOL', topic='grabtool', timeout=timeout)
if line.startswith('done'):
return
reason = line.split('reason=', 1)[1] if 'reason=' in line else line
raise AuxAxisError('GRABTOOL failed: %s' % reason)
def atc_release(self, timeout=5.0):
"""Manually open the collet (release-only, no clamp). Use
atc_clamp() afterwards once the new holder is in place."""
self._require_present()
line = self._rpc('RELEASE', topic='release', timeout=timeout)
if line.startswith('done'):
return
reason = line.split('reason=', 1)[1] if 'reason=' in line else line
raise AuxAxisError('RELEASE failed: %s' % reason)
def atc_clamp(self, timeout=10.0):
"""Manually clamp the collet (run a full bleed cycle). Pairs
with atc_release() for two-step manual tool changes."""
self._require_present()
line = self._rpc('CLAMP', topic='clamp', timeout=timeout)
if line.startswith('done'):
return
reason = line.split('reason=', 1)[1] if 'reason=' in line else line
raise AuxAxisError('CLAMP failed: %s' % reason)
def close(self): def close(self):
self._stop.set() self._stop.set()
try: try:

View File

@@ -156,6 +156,11 @@ class Ctrl(object):
mm = float(data) if data else 0.0 mm = float(data) if data else 0.0
self.aux.set_position_mm(mm) self.aux.set_position_mm(mm)
def _hook_droptool(ctx): self.aux.atc_droptool()
def _hook_grabtool(ctx): self.aux.atc_grabtool()
def _hook_release(ctx): self.aux.atc_release()
def _hook_clamp(ctx): self.aux.atc_clamp()
self.hooks.register_internal('aux', _hook_move, self.hooks.register_internal('aux', _hook_move,
block_unpause=True, auto_resume=True) block_unpause=True, auto_resume=True)
self.hooks.register_internal('aux_rel', _hook_move_rel, self.hooks.register_internal('aux_rel', _hook_move_rel,
@@ -165,6 +170,21 @@ class Ctrl(object):
timeout=180) timeout=180)
self.hooks.register_internal('aux_setzero', _hook_setzero, self.hooks.register_internal('aux_setzero', _hook_setzero,
block_unpause=True, auto_resume=True) block_unpause=True, auto_resume=True)
# ATC pneumatics. block_unpause + auto_resume so a program
# using M6 - implemented as (MSG,HOOK:droptool:) etc - pauses
# at the right point and resumes once the sequence is done.
self.hooks.register_internal('droptool', _hook_droptool,
block_unpause=True, auto_resume=True,
timeout=60)
self.hooks.register_internal('grabtool', _hook_grabtool,
block_unpause=True, auto_resume=True,
timeout=60)
self.hooks.register_internal('release', _hook_release,
block_unpause=True, auto_resume=True,
timeout=10)
self.hooks.register_internal('clamp', _hook_clamp,
block_unpause=True, auto_resume=True,
timeout=15)
log.info('Aux hooks registered') log.info('Aux hooks registered')