ExternalAxis: option (b) homing - user A=0 at home, deterministic on re-home
Three changes that together implement option (b) home semantics: 1. Mach.home for the external axis: replace G28.3 with explicit AVR position sync (Cmd.set_axis) + planner abs sync (position_change) + G92 a0 (set user-coord origin to current physical position, computing offset = home_position_mm). G28.3 was wrong: it preserves the current user-coord position and adjusts the offset to bridge to the new abs. After a move away from home and a re-home, the offset accumulates (134 -> 268 -> ...). G92 a0 with a freshly-synced abs always produces offset = home_position_mm regardless of prior state. 2. Planner.__encode: stop stripping the external axis target from the AVR line. The AVR has no motor mapped to A so it steps no motor, but exec_move_to_target updates ex.position[A] which gets reported back as ap. Leaving A in the AVR target keeps state.ap consistent with gplan's idea of A; stripping it left ex.position[A] stale and clobbered ExternalAxis's state.ap on the next status report. Side benefit: removes the special-case empty-string return for pure external moves; every line block follows the same path now. 3. ExternalAxis.enqueue_target_mm: stop writing to state.<axis>p from the planner hot path. The AVR's status reports drive it instead, which avoids DRO jitter (jump to target then snap back to intermediate values as the trapezoid runs). _pos_mm internal mirror is still updated for delta computation. Re-verified with the integration smoke test in tmp/20260503_option_b/: home/move-down/move-up/re-home/home-from-bottom all produce the expected DRO position values (0 at home, -134 at bottom).
This commit is contained in:
@@ -272,17 +272,11 @@ class Planner():
|
||||
if type == 'line':
|
||||
ext = self._external_axis_for_line(block)
|
||||
if ext is not None:
|
||||
# Side-effects: run the ESP move synchronously,
|
||||
# split the line into ESP (already done) + AVR (rest).
|
||||
avr_block = self._dispatch_external_line(block, ext)
|
||||
if avr_block is None:
|
||||
# Pure external move - no AVR work to issue but
|
||||
# we still need to ack the block id so the planner
|
||||
# advances. CommandQueue.enqueue with no callback
|
||||
# at block id is what _encode does, so return an
|
||||
# empty cmd to short-circuit there.
|
||||
return ''
|
||||
block = avr_block
|
||||
# Side effect: enqueue the ESP move on the external-
|
||||
# axis worker. The AVR still receives the full target
|
||||
# (including A) so ex.position[A] tracks gplan; no
|
||||
# motor steps for A because no motor maps to it.
|
||||
self._dispatch_external_line(block, ext)
|
||||
self._enqueue_line_time(block)
|
||||
return Cmd.line(block['target'], block['exit-vel'],
|
||||
block['max-accel'], block['max-jerk'],
|
||||
@@ -394,34 +388,39 @@ class Planner():
|
||||
|
||||
def _dispatch_external_line(self, block, ext):
|
||||
"""Side-effect: enqueue the ESP move on the external-axis
|
||||
worker thread (non-blocking). Return a new block dict with
|
||||
the external axis stripped from `target`, or None if the
|
||||
line had no other axes.
|
||||
worker thread (non-blocking). Returns the block (possibly
|
||||
unchanged) for the AVR.
|
||||
|
||||
We do NOT strip the external axis target from the AVR line.
|
||||
The AVR's exec_move_to_target updates ex.position[axis] for
|
||||
every axis in the target dict regardless of motor mapping,
|
||||
and reports it back via the `p` indexed var. Leaving A in
|
||||
the target keeps state.ap in sync with gplan's idea of A
|
||||
(otherwise the AVR's stale ex.position[A] would clobber
|
||||
ExternalAxis's state.ap=N update on the next status report).
|
||||
|
||||
The AVR doesn't step any motor for the external axis (no
|
||||
motor maps to it) - so leaving A in the target is
|
||||
physically a no-op for the steppers, while keeping the
|
||||
host-side state coherent.
|
||||
|
||||
For mixed XYZ + external moves the AVR runs XYZ at the
|
||||
gplan-computed rate while the ESP runs the external delta in
|
||||
parallel. Pure external moves return None so __encode emits
|
||||
only the id-sync to keep planner ids advancing."""
|
||||
target = dict(block['target'])
|
||||
new_target, ext_mm = ext.split_target(target)
|
||||
|
||||
gplan-computed rate while the ESP runs the external delta
|
||||
in parallel. Final positions agree; intermediate ap reports
|
||||
from the AVR may briefly disagree with state.ap until the
|
||||
block completes."""
|
||||
target = block.get('target') or {}
|
||||
# Read the external target (case-insensitive) without modifying
|
||||
# the dict so the AVR still sees A.
|
||||
ext_mm = target.get(ext.axis_letter)
|
||||
if ext_mm is None:
|
||||
ext_mm = target.get(ext.axis_letter.upper())
|
||||
try:
|
||||
ext.enqueue_target_mm(ext_mm)
|
||||
except Exception as e:
|
||||
# Non-blocking enqueue should rarely fail; if it does we
|
||||
# still want the planner to stop so the user notices.
|
||||
self.log.error('External axis enqueue failed: %s' % e)
|
||||
raise
|
||||
|
||||
if not new_target:
|
||||
# Pure external move; nothing left for the AVR. Track the
|
||||
# trajectory time so the planner's plan_time stays correct.
|
||||
self._enqueue_line_time(block)
|
||||
return None
|
||||
# Build a clean copy with only the AVR axes left.
|
||||
avr_block = dict(block)
|
||||
avr_block['target'] = new_target
|
||||
return avr_block
|
||||
return block
|
||||
|
||||
def reset(self, *args, **kwargs):
|
||||
stop = kwargs.get('stop', True)
|
||||
|
||||
Reference in New Issue
Block a user