fix jogging stepping by holding one velocity instead of re-arming
`set_cartesian_velocity` passed `duration=velocity_command_ttl_s` on every message, so each one re-armed a timed segment and the controller re-planned the same motion. On a real Lite6 that is not smooth travel: the arm moves in steps. Reducing how often the velocity is sent does not help — re-arming four times a second still steps — so it now uses `duration=0`, the SDK's "always effective, will not stop automatically", and skips a velocity that has not changed. One command stands until a different one replaces it. That removes the controller-side expiry, which `_expire_jog` was relying on: it flipped `_jogging` and reported `ready` without commanding anything, because the controller's own timer had already stopped the arm. With the velocity held indefinitely that would leave an arm travelling while the node reported idle, so `_expire_jog` now queues the stop itself. It goes through the job queue rather than the driver, since it runs on the reporter thread and the control thread owns the driver. `VELOCITY_COMMAND_TTL_S` is unchanged in value and meaning to a caller, but it is now enforced here rather than by the controller. Both halves are one commit on purpose: `duration=0` without the stop in `_expire_jog` is an arm that never stops when its sender dies. Adds tests/test_driver.py — the driver had no direct coverage, because FakeArm replaces it everywhere else, which is why this was invisible to the suite.
This commit is contained in:
+17
-2
@@ -205,8 +205,7 @@ def test_a_zero_velocity_jog_does_not_make_a_running_motion_look_idle(monkeypatc
|
||||
|
||||
|
||||
def test_a_sender_that_goes_quiet_stops_being_called_jogging(monkeypatch):
|
||||
# The controller expires the command on its own; nothing tells us, so the
|
||||
# clock has to.
|
||||
# Nothing tells us a sender died, so the clock has to.
|
||||
control = build(monkeypatch, motion=MotionConfig(velocity_command_ttl_s=0.02))
|
||||
try:
|
||||
wait_for(lambda: READY in control.take_state_changes())
|
||||
@@ -218,6 +217,22 @@ def test_a_sender_that_goes_quiet_stops_being_called_jogging(monkeypatch):
|
||||
control.shutdown()
|
||||
|
||||
|
||||
def test_a_sender_that_goes_quiet_actually_stops_the_arm(monkeypatch):
|
||||
# The velocity is held by the controller indefinitely, so the expiry is the
|
||||
# deadman itself and not a note about one. Reporting `ready` without
|
||||
# commanding the stop would leave the arm travelling while the node said it
|
||||
# was idle, which is worse than not expiring it at all.
|
||||
control = build(monkeypatch, motion=MotionConfig(velocity_command_ttl_s=0.02))
|
||||
try:
|
||||
wait_for(lambda: READY in control.take_state_changes())
|
||||
control.start_jog([20.0, 0, 0, 0, 0, 0])
|
||||
wait_for(lambda: ("jog", [20.0, 0, 0, 0, 0, 0]) in control.arms[0].calls)
|
||||
|
||||
wait_for(lambda: ("jog", [0.0] * 6) in control.arms[0].calls)
|
||||
finally:
|
||||
control.shutdown()
|
||||
|
||||
|
||||
def test_a_jog_preempts_a_motion_that_is_running(monkeypatch):
|
||||
control = build(monkeypatch)
|
||||
control.arms[0].block = threading.Event()
|
||||
|
||||
Reference in New Issue
Block a user