Skip to main content
stub — This page will be curated before publishing. Content below reflects the current implementation.

What it gives you

The Piper twin plans against a planning scene you describe. A move that would hit the desk, the container, or another object is refused, not executed, and every refusal names a reason. Five commands, alongside the existing grip, release, home and stop. Poses are metres in the arm base frame (base_link) unless you name another frame, and describe the grasp TCP (the grasp centre, not the wrist). These are the arm-driver commands of the Python SDK. Their full argument tables and defaults are in Arm motion building blocks. ee_pose is set_pose with motion: "ptp" on the default backend.

When it is available

The commands, their arguments and their refusal codes are the same whichever backend answers them. The Piper has two:
  • moveit: MoveIt beside the arm. The Piper’s standard service graph runs it; that graph is used when the twin does not define its own services.
  • pinocchio: the in-process planner.
With moveit.enabled true, moveit is the default backend. Otherwise pinocchio is. A request can name the other one with backend. With MoveIt, a PTP move tries Pilz PTP first and falls back to OMPL. A LIN move is Pilz LIN. The in-process backend differs in two ways:
  • a PTP move that is blocked is refused, because it has no sampling planner to route around an obstacle;
  • an allow_touch object is left out of the collision model, where MoveIt still checks it against every arm link except the gripper.
If MoveIt is the backend but is not running, the commands are refused with backend_unavailable, and the detail starts with moveit:. If the in-process backend cannot plan at all, the refusal is also backend_unavailable, with a detail starting pinocchio:. The jog commands always plan in-process. Every backend the driver uses holds the same scene and payload. A scene_set or payload change is applied to all of them, or to none. If a wrist camera is fitted and declared in the driver configuration, the planner includes it, so its geometry counts toward collisions.

A pick, in order

grasp plans three phases for each candidate, in rank order: a PTP move to the pregrasp pose, a straight approach onto the grasp pose, and a straight lift that is checked but not executed. The first candidate whose three phases all plan is executed up to the grasp pose. The gripper is left for you to close, and the lift is yours to send. allow_touch is what makes a grasp plannable: with the target in the scene, no path to a pose at the target is collision-free, because the fingers have to reach it. Listing it there permits contact with that object and nothing else. On MoveIt, each candidate is checked as one plan-only Pilz sequence (PTP, LIN, LIN). The chosen candidate is executed as a PTP move and a LIN move, and the result reports the planner pilz_sequence. piper.set_pose(pose, ...) sends the set_pose command with the same arguments. piper.move(pose, ...) is a shortcut for it.

Scene revisions

scene_revision is a name you choose for one observation of the world. The motion commands quote it, and a request quoting a stale revision is refused. That is what stops a grasp computed from one camera frame being executed against a world that has since changed. If you leave scene_revision out of scene_set, the driver numbers the scene for you (scene-1, scene-2, …). A motion command that leaves it out is not checked against a revision. Re-observe and send a fresh scene_set before a final approach. The planner knows nothing you did not tell it.

Watching progress

Status is published on the twin’s state/motion stream on every transition, at 2 Hz while a motion runs, and every 5 seconds while idle. It carries:
  • the default backend (moveit or pinocchio) and whether it is available;
  • the active scene revision and the attached payload;
  • the motion state (idle, planning, executing, failed, cancelled);
  • the command, backend and motion_id of the current motion;
  • which planner produced the last trajectory;
  • the last result or refusal.
Commands carry no request id. The driver numbers every motion it starts, and the last_result or last_refusal that ends a motion names the same motion_id. A refusal of a command that never started has no motion_id. Every result has its own timestamp_ms. For a grasp, last_result.selected_candidate names the candidate that was executed. See Results for how to match a command to its result, or to get an accepted / rejected reply over MQTT v5.

Refusals

A refusal is a normal outcome, not an error. Each one carries a code: backend_unavailable, scene_revision_unknown, stale_robot_state, busy, invalid_scene, invalid_request, payload_already_attached, planning_failed, execution_failed, cancelled. With MoveIt, planning_failed carries MoveIt’s own reason in its detail, so a target inside an obstacle (GOAL_IN_COLLISION) reads differently from one the arm cannot reach (NO_IK_SOLUTION). The in-process planner names the collision or the failed IK sample instead. Each grasp candidate that is refused also raises its own grasp_candidate_refused alert (type planning_failed) on the twin. So you can see why every candidate before the chosen one failed.

Optional expected_joints

Send the arm posture you observed when you computed a target. If the arm has moved since, the command is refused stale_robot_state rather than acting on a pose derived from a stale view.