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:
@@ -265,7 +265,11 @@ class ExternalAxis(object):
|
||||
"""Synchronously run an external move. Blocks until the ESP
|
||||
reports done. Used by the legacy /api/aux/move and /api/aux/jog
|
||||
endpoints which may want to wait. Most planner-driven motion
|
||||
goes through enqueue_target_mm instead, which is non-blocking."""
|
||||
goes through enqueue_target_mm instead, which is non-blocking.
|
||||
|
||||
Updates state.<axis>p immediately on completion. For the
|
||||
planner-driven path that goes through enqueue_target_mm, the
|
||||
AVR's own ap reports drive state.<axis>p instead."""
|
||||
if not self.enabled:
|
||||
raise ExternalAxisError(
|
||||
'External axis %r not available (aux disabled or '
|
||||
@@ -286,29 +290,30 @@ class ExternalAxis(object):
|
||||
self._busy.clear()
|
||||
|
||||
def enqueue_target_mm(self, ext_mm):
|
||||
"""Non-blocking variant: post a target to the worker queue
|
||||
and update the host's notion of the axis position immediately
|
||||
so subsequent line splits compute correct deltas.
|
||||
"""Non-blocking variant: post a target to the worker queue.
|
||||
Used by Planner.__encode in the hot path.
|
||||
|
||||
We deliberately do NOT mirror the new target into state.<axis>p
|
||||
here. The AVR also receives the line block (we don't strip
|
||||
the external axis from the AVR target) and its trapezoid
|
||||
progressively updates ex.position[A] -> reports back as ap.
|
||||
If we set state.ap here ahead of the AVR, the DRO would jump
|
||||
to the target and then snap back to intermediate values as
|
||||
AVR reports stream in. Instead we let the AVR drive state.ap.
|
||||
We still mirror the new abs into our internal _pos_mm so
|
||||
the next line block computes the correct delta.
|
||||
|
||||
The Planner.__encode hook calls this so the AVR comm thread
|
||||
is never blocked by serial RPCs to the ESP. v1 accepts that
|
||||
XYZ on the AVR and A on the ESP run concurrently when they
|
||||
appear on the same gcode line; the planner's S-curve math is
|
||||
applied to both, so velocities and accelerations are bounded
|
||||
by whichever axis is most constrained."""
|
||||
is never blocked by serial RPCs to the ESP."""
|
||||
if not self.enabled:
|
||||
raise ExternalAxisError(
|
||||
'External axis %r not available' % self.axis_letter)
|
||||
steps, abs_mm = self._compute_move(ext_mm)
|
||||
# Update host position immediately so the next line block
|
||||
# sees the new absolute target as the starting point.
|
||||
# Internal mirror only - drives subsequent delta computation.
|
||||
# state.<axis>p is left to the AVR's status reports.
|
||||
self._pos_mm = abs_mm
|
||||
self.ctrl.state.set(self.axis_letter + 'p', self._pos_mm)
|
||||
if steps == 0:
|
||||
return
|
||||
# Enqueue. The worker fires the RPC; if it fails it logs
|
||||
# and we keep going - aborting motion is the user's job
|
||||
# via the planner stop / e-stop.
|
||||
self._work_q.put(('move', steps))
|
||||
|
||||
def _compute_move(self, ext_mm):
|
||||
|
||||
Reference in New Issue
Block a user