Context
Arm.move_p() (src/litearm/arm.py:1968) sends a Cartesian target and lets the firmware solve it:
self._write_cmd(P.CMD_MOVE_P, payload)
a.expect(P.RSP_ACK, 1.0, "move_p", echo_cmd=P.CMD_MOVE_P)
end = time.monotonic() + self.move_timeout
while time.monotonic() < end:
st = self.get_state(refresh=True, timeout=0.1).value
if st is not None and st.faulted:
raise MotorFaultError(f"move_p: FAULT {st.fault_detail}")
tcp = self.get_tcp().value
if st is not None and tcp is not None and self._pose_near(tcp, pose, pos_tol, rpy_tol):
return st
time.sleep(0.02)
raise MotionTimeoutError(f"move_p 未到目标位姿, 超时 {self.move_timeout}s")
The only ways out of the loop are "arrived" and "faulted". Whether the firmware could solve the target at all is not one of them.
Expected
A target the firmware cannot solve should be reported as such. The firmware decides this immediately and asynchronously, and the SDK's own comments elsewhere in the module are explicit that an immediate rejection and a motion that never completes are different events, with different remedies.
Actual
The 0x02 command is accepted, then the firmware fails to solve it and sends nothing further. The loop polls get_tcp() until move_timeout expires and raises:
MotionTimeoutError: move_p 未到目标位姿, 超时 15.0s
The message names a motion that did not reach its target. The situation is a target that was never solvable, and the difference matters: "not arrived" suggests torque, obstruction, a lost frame or a slipping arm, while the actual remedy is to choose a different target.
This is worst at the home pose. After homing, the arm is fully extended vertically and the pose is a kinematic singularity, so any target along the arm axis is unsolvable while lateral targets are fine. A user following the obvious instinct — move a little further along the direction the arm already points — gets a 15-second wait and an error pointing at the wrong subsystem.
Reproduce
Observed on hardware, firmware Litearm1.8.0-7J, via the C++ port. With the arm homed (TCP z ≈ 0.783 m, pointing up):
ik() with z + 0.02 m (further along the arm axis): no solution;
ik() with z - 0.02 m and z - 0.05 m: no solution;
ik() with x ± 0.05 m and y ± 0.05 m: solutions, and reachable.
move_p to the first of those targets waits out the full move_timeout and raises MotionTimeoutError, which is where the misattribution was first seen on real hardware.
ik() reports the truth immediately because it is a pure computation; the failure only becomes ambiguous on the asynchronous move_p path.
Suggested direction
The firmware already knows the answer at the moment it refuses to plan, so the cleanest fix is a reply the SDK can key on — an ERR for the 0x02, or a status bit meaning "no solution". If the firmware cannot be changed, the SDK can call ik(pose) before sending and raise a distinct error when it returns no solution; that costs one round trip and removes the ambiguity for the singular and unreachable cases, though it cannot see obstacle or limit rejections.
Context
Arm.move_p()(src/litearm/arm.py:1968) sends a Cartesian target and lets the firmware solve it:The only ways out of the loop are "arrived" and "faulted". Whether the firmware could solve the target at all is not one of them.
Expected
A target the firmware cannot solve should be reported as such. The firmware decides this immediately and asynchronously, and the SDK's own comments elsewhere in the module are explicit that an immediate rejection and a motion that never completes are different events, with different remedies.
Actual
The
0x02command is accepted, then the firmware fails to solve it and sends nothing further. The loop pollsget_tcp()untilmove_timeoutexpires and raises:The message names a motion that did not reach its target. The situation is a target that was never solvable, and the difference matters: "not arrived" suggests torque, obstruction, a lost frame or a slipping arm, while the actual remedy is to choose a different target.
This is worst at the home pose. After homing, the arm is fully extended vertically and the pose is a kinematic singularity, so any target along the arm axis is unsolvable while lateral targets are fine. A user following the obvious instinct — move a little further along the direction the arm already points — gets a 15-second wait and an error pointing at the wrong subsystem.
Reproduce
Observed on hardware, firmware
Litearm1.8.0-7J, via the C++ port. With the arm homed (TCP z ≈ 0.783 m, pointing up):ik()withz + 0.02 m(further along the arm axis): no solution;ik()withz - 0.02 mandz - 0.05 m: no solution;ik()withx ± 0.05 mandy ± 0.05 m: solutions, and reachable.move_pto the first of those targets waits out the fullmove_timeoutand raisesMotionTimeoutError, which is where the misattribution was first seen on real hardware.ik()reports the truth immediately because it is a pure computation; the failure only becomes ambiguous on the asynchronousmove_ppath.Suggested direction
The firmware already knows the answer at the moment it refuses to plan, so the cleanest fix is a reply the SDK can key on — an
ERRfor the0x02, or a status bit meaning "no solution". If the firmware cannot be changed, the SDK can callik(pose)before sending and raise a distinct error when it returns no solution; that costs one round trip and removes the ambiguity for the singular and unreachable cases, though it cannot see obstacle or limit rejections.