Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Command from a low-rate program

At the end of this page you will have moved the end effector to targets set from an ordinary thread, at whatever rate your program runs, with the crate’s realtime thread doing the 1 kHz work. This is the interface for planners, policies, teleoperation and scripts, and the one the Python bindings use.

Prerequisites: First program works against your robot or the simulator, the arm is at the ready pose (move_to_ready), and the space around the end effector is free.

extern crate franka;
use std::sync::Arc;
use franka::{RealtimeConfig, Robot, TargetControlOptions};

fn main() -> franka::FrankaResult<()> {
let robot = Arc::new(Robot::new("172.16.0.2", RealtimeConfig::Enforce)?);
let control = robot.start_cartesian_target_control(TargetControlOptions::default())?;
let start = control.target();            // the start position, base frame, metres
control.set_position([start[0] + 0.05, start[1], start[2]])?;   // any thread, any rate
std::thread::sleep(std::time::Duration::from_secs(1));
let state = control.state();             // the latest RobotState, copied out
assert!(control.is_running());
control.stop()?;                         // settle, finish the motion, join: the loop's result
let _ = state; Ok(()) }

start_cartesian_target_control returns once the loop’s first cycle has run, so target() and state() are valid at once. A 5 cm step along one axis under the default budget becomes an S-curve that peaks at about 0.12 m/s and lands after about 0.85 s (the budget is a norm and each axis gets 1/√3 of it).

The API

impl Robot {
    pub fn start_cartesian_target_control(self: &Arc<Self>, options: TargetControlOptions)
        -> FrankaResult<CartesianTargetControl>;
    pub fn start_joint_target_control(self: &Arc<Self>, options: JointTargetControlOptions)
        -> FrankaResult<JointTargetControl>;
}
impl CartesianTargetControl {
    pub fn set_position(&self, position_in_base: [f64; 3]) -> FrankaResult<()>;
    pub fn set_orientation(&self, orientation_xyzw: [f64; 4]) -> FrankaResult<()>;
    pub fn set_target(&self, position_in_base: [f64; 3], orientation_xyzw: [f64; 4])
        -> FrankaResult<()>;
    pub fn set_pose(&self, pose: &[f64; 16]) -> FrankaResult<()>;   // column-major, as O_T_EE
    pub fn target(&self) -> [f64; 3];
    pub fn target_orientation(&self) -> [f64; 4];
    pub fn target_pose(&self) -> [f64; 16];
    pub fn state(&self) -> RobotState;
    pub fn tune(&self, update: &TuningUpdate) -> FrankaResult<Vec<&'static FieldBound>>;
    pub fn tuning(&self) -> FrankaResult<LiveTuning>;
    pub fn is_running(&self) -> bool;
    pub fn stop(self) -> FrankaResult<()>;
}
// JointTargetControl is the same with set_joints([f64; 7]) and target() -> [f64; 7],
// and without tune / tuning (below).

The Cartesian target is a pose, absolute in the base frame. set_position moves its position and keeps its orientation (the start orientation until something sets it), set_orientation the other way round, set_target and set_pose set both. Orientations are unit quaternions in [x, y, z, w] order, the scalar part last, or the rotation block of a column-major pose in the convention of O_T_EE. A quaternion or rotation block within 1e-3 of unit or orthonormal is normalised on the way in; one further off is refused. set_joints takes the seven joint positions in radians. Only the latest target counts.

Every setter returns FrankaError::InvalidArgument for a non-finite or malformed value and FrankaError::InvalidOperation once the loop has ended for any reason; is_running() is false then, and stop() has the reason. target() is the latest target, state() the latest RobotState the loop received. While the loop runs it holds the robot’s control lock, so robot.read() and the other loops fail with InvalidOperation.

stop() lands the command on the last target, holds it for 250 cycles (the robot’s echo of it in the robot-controller backend; the loop’s own last setpoint in the impedance backend), sets motion_finished on one more, joins the thread and returns the loop’s result. In the impedance backend the finish also waits for the arm: motion_finished goes out only once every joint moves slower than REST_JOINT_VELOCITY (0.01 rad/s), or after the 5 s timeout, the law kept on the held goal meanwhile, so that an arm still closing its lag is not handed to the robot’s controller short of the target. The result is Ok for a regular end, FrankaError::Control if the robot aborted the motion or the deviation guard fired. A target published just before stop() is not lost. Dropping the handle without stop() requests the stop and detaches: the loop settles and finishes on its own, holding its Arc<Robot> until it has. robot.stop() from elsewhere preempts the loop, and the handle’s stop() then returns the preemption as FrankaError::Control.

Options

TargetControlOptions (Cartesian)defaultJointTargetControlOptionsdefault
limits: OtgLimits, a norm budget0.3 m/s, 0.5 m/s², 20 m/s³limits: Option<[OtgLimits; 7]>None: 20 % of the negotiated version’s joint limits
rotation_limits: OtgLimits, a norm budget0.5 rad/s, 1.0 rad/s², 20 rad/s³
backend: BackendImpedance(ImpedanceOptions::cartesian())backendImpedance(ImpedanceOptions::joint())
controller_mode (robot-controller backend only)CartesianImpedancecontroller_modeJointImpedance
max_deviation0.30 mmax_deviation1.0 rad
max_angular_deviation0.5 rad
settle: Settle (landing tolerance, hold cycles)1 mm, 250 cyclessettle1 mrad, 250 cycles
limit_ratetruelimit_ratetrue
realtime_priority: Option<i32>None (highest)samesame
cpu: Option<usize>None (not pinned)samesame
observernoneobservernone

Every field is public and has a with_* builder; validate() checks the options without starting anything. A Cartesian budget is a norm and each axis gets 1/√3 of it. With the robot’s controller tracking, an FER’s joint-space continuity check refuses 2.5 m/s² near the ready pose and its collision threshold trips above about 1 m/s², so the default 0.5 m/s² sits below both. The continuity check applies to a pose stream, so to the robot-controller backend only; the collision thresholds apply to both. The joint default is slow on purpose; raise it with JointTargetControlOptions::scaled_limits(version, fraction) or explicit limits.

The observer is FnMut(&RobotState, &CartesianSent) (&JointSent for joints), called every cycle on the realtime thread with the state and what was sent: the pose or q after the backstop, the target, the generator’s velocity and acceleration (angular too for a pose), and by how much the backstop altered the command. In the impedance backend pose / q is the loop’s setpoint, the backstop alterations are 0, and the record also carries the joint goal q_goal, its velocity dq_goal, the fraction of the generator’s step the goal carried when a joint limit cut it (cap_scale, exactly 1 when none did) and the joints held on a bound (pinned), the clamped torques tau and the velocity and position envelopes’ shares of them (tau_envelope, tau_position), what the leash took off the desired state this cycle (leash_alteration, m for a pose and rad for joints, plus leash_angular_alteration, rad, for a pose; 0 while the arm follows) and, for a pose, the inverse kinematics residual ik_error (the norm of the position error, m, and the orientation error times rotation_weight), its active-set passes ik_passes and the stall at a joint limit (stall_pressure, stalled). It must not allocate or block; copying into a preallocated ring is what it is for, and how franka_rerun::Recorder::push and the commander example’s CSV log hook in.

realtime_priority sets the loop thread’s SCHED_FIFO priority; None is the highest, as Robot::new uses for its caller. A program with other realtime threads gives the loop a lower one. A failure to raise it is fatal under RealtimeConfig::Enforce and ignored under Ignore, so the simulator runs the loop on an ordinary kernel.

Pinning the loop thread. with_cpu(Some(n)) pins the loop thread to core n with sched_setaffinity right after the priority is raised (franka::realtime::pin_current_thread_to_cpu is the call); None, the default, leaves the placement to the scheduler. On a host that keeps a core free for the loop (isolcpus= on the kernel command line) the loop never migrates and never shares its core with the rest of the program; two arms in one process can each be given a core. A failure to pin is treated like a failure to raise the priority: fatal under Enforce, ignored under Ignore.

Backends

The generator produces a setpoint stream; backend decides who tracks it.

Backend::Impedance(ImpedanceOptions), the default, runs the loop through control_torques and sends, every cycle, the torques of the hybrid joint impedance law that DROID’s controller (polymetis HybridJointImpedanceControl) runs:

Kp  = Jᵀ Kx J + diag(Kq)
Kd  = Jᵀ Kxd J + diag(Kqd)
tau = Kp (q_goal − q) + Kd (g dq_goal − dq) + coriolis(q, dq)
      clamped to ±torque_limits, then low-pass filtered at cutoff_frequency

J is the zero Jacobian at the end-effector frame for the measured q, so the Cartesian gains act at the frame O_T_EE targets are in; q_goal is the generator’s output on the joint interface and, on the Cartesian interface, the joint configuration a differential inverse kinematics finds for the generator’s pose each cycle; dq_goal is that goal’s velocity. Gravity is the robot’s, as in every torque loop. g is 0 by default, DROID’s form: the damping acts on the velocity, and a goal moving at v is tracked Kd v / Kp behind. velocity_feedforward = true sets g to velocity_feedforward_gain (default 1), damping the velocity error instead. It is off because real arms vibrated with it on: dq_goal is a finite difference (of the IK solution on the Cartesian interface), and Kd carries its noise into the torque. velocity_feedforward_cutoff, a first-order low-pass on dq_goal, is the knob to try against that; no value has been validated on hardware. What differs from DROID’s law by default is that the generator is leashed to the arm (below).

ImpedanceOptionsCartesian interface (::cartesian())joint interface (::joint())
gains.cartesian_stiffness Kx (N/m, Nm/rad)750, 750, 750, 15, 15, 150
gains.cartesian_damping Kxd (Ns/m, Nms/rad)50, 50, 90, 2, 2, 20
gains.joint_stiffness Kq (Nm/rad)40, 30, 50, 25, 35, 25, 10600, 600, 600, 600, 250, 150, 50
gains.joint_damping Kqd (Nms/rad)4, 6, 5, 5, 3, 2, 150, 50, 50, 50, 30, 25, 15
torque_limits (Nm)86, 86, 86, 86, 11.5, 11.5, 11.5same
cutoff_frequency (Hz)100100
velocity_feedforwardfalsefalse
velocity_feedforward_gain g, in [0, 1]11
velocity_feedforward_cutoff (Hz)1000 (MAX_CUTOFF_FREQUENCY: off)same
leash: Leash0.025 m, 0.15 rad0.1 rad per joint (the torque clamp, not the leash, bounds the torque: 600 × 0.1 = 60 Nm on joints 1 to 4, under their 86 Nm clamp; on joints 5 and 6 the 11.5 Nm clamp binds first)
project_joint_gainsfalsefalse
posture (IK nullspace reference)None: the start configurationnot used
ik: IkOptionsλ 0.05, nullspace gain 1.0 /s, 3 iterations, tolerance 1e-6 (weighted), rotation weight 0.1 m/radnot used
joint_velocity_fraction (of max_joint_velocity(version))0.70.7
velocity_barrier_fraction0.850.85
joint_position_margin (rad)0.050.05

The Cartesian column, ImpedanceGains::CARTESIAN, is DROID’s preset with the translational damping raised from 37 to 50, 50, 90 Ns/m: a damping ratio of about 0.8 from the arm’s apparent masses at the ready pose (0.94 kg along x and y, 3.9 kg along z, computed from the model), where 37 leaves z at 0.34 and ringing. ImpedanceGains::DROID is the preset as DROID ran it, and with velocity_feedforward off it is the polymetis law for replaying policies trained on it. The joint column is the fer_joint_impedance example’s gains. What each knob does:

  • cartesian_stiffness: how hard the arm pulls back toward the target, per metre and per radian of end-effector error; the arm is a spring of this stiffness to anything that pushes it. cartesian_damping: the resistance to end-effector velocity error, per m/s and rad/s.
  • joint_stiffness, joint_damping: the same in joint space, on every joint including the one direction the Cartesian term cannot see (the elbow’s swing); on the Cartesian interface they regularise the nullspace and, unprojected, also stiffen the end effector (below); on the joint interface they are the whole law.
  • torque_limits: the per-joint clamp on the command, before the filter.
  • cutoff_frequency: the low-pass filter on the torques; MAX_CUTOFF_FREQUENCY turns it off.
  • velocity_feedforward: Kd (g dq_goal − dq) when on, −Kd dq when off, which is the default because real arms vibrated with it on.
  • velocity_feedforward_gain: g, the weight of the goal velocity, when the feedforward is on. At the default 1 the damping acts on the velocity error, so a goal at steady speed needs no lag; lower values trade that lag, (Kqd / Kq) v at 0, against how much of the goal’s own ripple the law forwards.
  • velocity_feedforward_cutoff: a first-order low-pass on dq_goal before it is fed forward. The default 1000 Hz is MAX_CUTOFF_FREQUENCY, the filter off; the floor validate() accepts is MIN_FEEDFORWARD_CUTOFF, 1 Hz. No value has been validated on hardware.
  • leash: how far the desired state may run ahead of the measured one. Every cycle the generator is anchored on the measured pose (the model’s, for the measured q) pulled toward the previous desired by at most the leash, the torque-mode form of the third generator rule: while the arm follows, that is exactly the previous desired and nothing changes; held back by a hand, an obstacle or an unreachable target, the desired stays within the leash of the arm, so the spring force on whoever holds it is limited by the leashed position error. On release the generator resumes from where the arm is, under its budget. The leash keeps acting during the stop’s hold. On the joint interface each joint’s goal stays within leash.joint of the measured joint; there the torque clamp bounds the torque (the JOINT preset’s 600 Nm/rad × 0.1 rad is 60 Nm on joints 1 to 4, under their 86 Nm clamp; on joints 5 and 6 the 11.5 Nm clamp binds before the leash does).
  • project_joint_gains: confines the joint gains to the Jacobian’s nullspace (N Kq N, N = I − J⁺ J), so that the stiffness you set is the stiffness felt at the end effector, to within the damping of the projector and away from singularities. Unprojected, the joint springs are felt through J on top of Kx: at the ready pose the default 750 N/m is felt as about 990 to 1180 N/m in translation (computed on the FER model), and two to three times Kx in rotation. With the projection on, zero Cartesian gains would leave the end effector free.
  • posture: the joint configuration the inverse kinematics drifts toward in the nullspace, at most 0.5 rad/s; None is the configuration the loop started in. A posture inside joint_position_margin of a joint limit is refused with InvalidArgument, as is a joint target there.
  • joint_velocity_fraction: no joint of the goal moves faster than this fraction of the arm’s velocity limit, which narrows toward the joint position limits on the FR3 and is flat on the FER (target_control::max_joint_velocity away from them: the FER’s MAX_JOINT_VELOCITY, the published 2.175 and 2.61 rad/s less libfranka’s tolerance, so 2.13 to 2.15 rad/s on joints 1 to 4 and 2.564 / 2.549 / 2.549 on 5 to 7, or the FR3’s 2.62, 5.26, 4.18, 5.26). A faster step is scaled down as a whole, so the goal keeps its direction and an unreachable or singular pose is approached at a bounded rate; the generator is then re-anchored on what went out, so it never plans from a velocity the arm was not sent. In (0, 1].
  • velocity_barrier_fraction: a joint measured faster than this fraction of its limit meets VELOCITY_BARRIER_GAIN (20 Nm per rad/s) of damping on the excess, at most its torque limit, added before the clamp. It catches what the command cap cannot, a spring-back or an arm overshooting its goal. Below it the law’s torque along a joint’s motion fades out linearly, from min(cap, onset − FADE_BAND × limit) (FADE_BAND 0.15) of measured speed to none at the onset, so a joint catching up on its lag cannot cancel the barrier. At least joint_velocity_fraction, at most 1.
  • joint_position_margin: how far the goal keeps from each joint position limit. Toward a limit the goal brakes to rest on the margin; a joint measured inside it keeps less and less of the law’s torque toward the limit and, 0.02 rad further in, meets a spring pushing it out. On the Cartesian interface the other joints make up what a joint held at its margin cannot, position before orientation (ik.rotation_weight; while the goal is stalled position comes first, to within micrometres): with several joints held, the tool stays put and the turn gets as far as the wall allows, and under a speed limit the orientation lags more. In [0.035, 0.5]. See The joint position limit guard.

Every gain must be finite and non-negative, the leash finite and positive (validate()). The law itself is public as franka::impedance_torques. The options are set through builders:

use franka::{Backend, ImpedanceGains, ImpedanceOptions, TargetControlOptions};

let gains = ImpedanceGains {
    cartesian_stiffness: [400.0, 400.0, 400.0, 15.0, 15.0, 15.0],
    ..ImpedanceGains::CARTESIAN
};
let options = TargetControlOptions::default()
    .with_backend(Backend::Impedance(ImpedanceOptions::cartesian().with_gains(gains)));
// DROID's law as it ran, for replaying policies trained on it: the feedforward the preset
// needs off is off by default.
let parity = ImpedanceOptions::cartesian().with_gains(ImpedanceGains::DROID);

Collision thresholds. Target control does not set collision thresholds; set_collision_behavior configures the robot’s reflexes. Choose thresholds for the task and payload. The leash limits position error, not total contact force: stiffness and velocity-dependent damping both contribute to the commanded torque. On the joint interface, the JOINT preset reaches a 20 Nm threshold at 0.033 rad of error. nonrealtime_commander takes --thresholds N for its collision thresholds.

Hand-guiding and the deviation guard. The guard measures the arm against the start pose (max_deviation 0.30 m, max_angular_deviation 0.5 rad). Compliance lets a person move the arm, and moving it past either bound ends the loop: the target freezes where the command is, the loop finishes at rest, and the robot’s own controller holds the arm where it was left, without a reflex. That is the right default against a runaway commander; a session where the arm is meant to be moved by hand raises both bounds.

Backend::RobotController sends the setpoint stream as poses (control_cartesian_pose) or joint positions (control_joint_positions) and the robot’s own controller, selected by controller_mode, tracks it: what target control did before the impedance backend existed.

let options = TargetControlOptions::default().with_backend(Backend::RobotController);

What changes between the two:

  • Compliance. The impedance backend is a spring: push the arm and it gives way by about force / stiffness (10 N against the default 750 N/m is about 1 cm, the unprojected joint term making the arm somewhat stiffer than Kx) and returns when released; the robot’s controller holds the pose stiffly. Hold the arm and the desired pose stops within the leash of it, limiting the position error that drives the spring. When released, the generator resumes from where the arm is.
  • No joint-side continuity refusals. The robot checks the inverse kinematics of a pose stream against its joint limits and refuses a stream whose joints would accelerate too fast (see Online trajectory generation). Torques are not checked that way, so the budget can be raised further in the impedance backend, within the collision thresholds.
  • The deviation guard applies to both. The measured pose is compared with the start in either backend; a compliant arm pushed past max_deviation ends the loop the same way.
  • Collision thresholds apply to both, and are yours to set. The robot’s contact and collision reflexes watch the external torques and forces whatever commands them; a compliant arm meeting an obstacle still trips them at the thresholds set_collision_behavior sets, and a spring reaches them by deflection (the paragraph above).
  • An unreachable or singular target lags in the impedance backend, because no joint of the goal moves faster than joint_velocity_fraction of its limit or closer to a limit than joint_position_margin, and the leash holds the desired pose near the arm; the robot’s controller refuses a pose stream it cannot follow.
  • The finish waits for the arm. stop() in the impedance backend sets motion_finished only once every joint is slower than REST_JOINT_VELOCITY, or after the 5 s timeout.

Tune the law while it runs

A Cartesian session on the impedance backend takes changes to its gains, its inverse kinematics and its two budgets while the arm moves:

use franka::robot::target_control::TuningUpdate;

let mut update = TuningUpdate::default();
update.cartesian_stiffness = Some(400.0);
update.budget = Some([0.2, 0.4, 20.0]);
let clamped = control.tune(&update)?;   // any thread, any rate; the bounds that had to clamp
let targets = control.tuning()?;        // the targets in force

TuningUpdate is #[non_exhaustive], so start from Default and assign what the update carries; a field it does not carry keeps its value and is not re-checked. The nine fields are joint_stiffness[7], joint_damping[7], cartesian_stiffness, ik_damping, ik_nullspace_gain, velocity_feedforward_gain, velocity_feedforward_cutoff, budget[3] and rotation_budget[3]: 25 scalars, together a LiveTuning. One Cartesian stiffness stands for all twelve Cartesian gains, the six stiffnesses and six dampings ImpedanceGains::scaled_cartesian derives from it, which is what holds the preset’s damping ratio. Only this kind of session has live tuning. tune and tuning return InvalidOperation with NO_TUNING_MESSAGE on the robot-controller backend, and with DERIVED_GAINS_MESSAGE on a Cartesian session whose gains one stiffness cannot rebuild (ImpedanceGains::DROID is such a preset). tune alone also returns ENDED_MESSAGE once the loop has ended; tuning has no such check and still reports the last published targets. JointTargetControl has neither method: a joint session runs the options it was started with for its whole life.

The bounds. LiveTuning::BOUNDS is one FieldBound per scalar: name, unit, min, max, group, the transition policy and the presentation hints (log_slider, slider_max, off_at, norm). It is a second and tighter gate than ImpedanceOptions::validate, which asks only whether a session can be built at all and has to stay permissive for every caller of the crate; BOUNDS asks how far a running session may be moved by hand. The table is the clamp itself, so a caller that publishes it — the node serves its schema from it — cannot drift from what is enforced.

An update is all or nothing and then clamped. A value that is not finite, or a zero where zero means something other than “softer” (cartesian_stiffness, ik_damping), is refused with InvalidArgument naming the field and nothing is written. Anything out of range is clamped instead, and the bounds that had to clamp come back so that a caller can show the value it will really get; read that value out of tuning(), not out of the bound. Raising a joint spring also raises any joint damping below LiveTuning::joint_damping_floor of it (0.25 √K), even one the update does not carry: the pair, not either half, is what has to stay damped. FieldBound::danger is advice for whoever publishes the table, not a gate this crate applies — TuningDanger::Advise on the fields that reach the torque directly, and TuningDanger::ConfirmAbove(v) on the ones that widen what the arm may do above v, both budgets and velocity_feedforward_gain above zero.

Reaching the loop. tune validates, clamps and publishes the whole set on the calling thread, allocating as it goes, into a single-writer seqlock beside the target slot. The loop takes a complete snapshot or keeps the previous one, never blocking and never seeing half an update; the slot holds the latest value rather than a queue, so two updates between cycles collapse into one. Live parameter protocol draws that slot and defines the node’s JSON layer on top of it.

Crossing to it. What is published are targets, and the loop moves its own copy toward them under each field’s TuningPolicy. A parameter that multiplies a generally-nonzero state would be a step in the torque, so the gains, the IK fields and the feedforward gain slew over SLEW_TAU, 0.3 s. The feedforward cutoff and both jerks step. A budget’s velocity and acceleration are raised on the cycle they arrive and lowered as a ramp at the next order’s limit (TuningPolicy::StepUpGateDown), because a narrower budget clamps the generator’s stored state rather than re-planning it; the fourth OTG rule is what Otg::set_limits owes a running generator. TuningPolicy::remaining reads how much of a crossing is still to come off the clock, so nothing has to be published back out of the realtime thread. The command never steps, but lowering an acceleration or jerk while the arm moves lengthens the stop toward v² / 2a: lower the velocity first, apply it, then the rest.

What the loop does every cycle

  1. Anchor. The first cycle takes the start, first target and first setpoint from the robot’s echo of its commanded pose or joints (O_T_EE_c, q_d) in the robot-controller backend, and from the measured state in the impedance backend, where no echo of the command exists: the measured q, and for a pose the model’s pose of that configuration rather than O_T_EE, so the inverse kinematics starts with a zero residual; q_goal starts at q. Every later cycle of the impedance backend anchors on the measured state pulled toward the previous desired by at most the leash.
  2. Read the slot. The latest target comes through a single-writer seqlock the loop polls without blocking; a torn read keeps the previous target for one cycle.
  3. Generate. One synchronised jerk-limited generator over all axes, per-axis limits, one nominal millisecond per command, re-anchored before every re-plan on the echo (robot-controller backend) or on the leashed anchor (impedance backend).
  4. Track. Impedance backend: the inverse kinematics step for a pose inside the joint limits’ box, or the joint goal’s step scaled into it; the law with its torque along a fast joint’s motion and toward a near limit faded, plus the velocity barrier and the position spring, the clamp, Torques. A goal that falls short restarts the generator from what went out. Robot-controller backend: the rate limiter under the same budget, then the loop’s own libfranka limiter; neither is meant to bind, and the observer sees when one does.
  5. Guard. If the measured pose strays past max_deviation or max_angular_deviation from the start, the target freezes and the loop ends with FrankaError::Control.
  6. Land, hold, finish. After stop(): run until every axis is within settle.tolerance of the target and at rest, hold the last command for settle.cycles, then motion_finished; if the generator has not landed within five seconds, hold from where the command is. The impedance backend finishes only once the arm itself is at rest (REST_JOINT_VELOCITY), or after five more seconds.

Why each step is what it is, and what goes wrong without it, is in Online trajectory generation; the impedance law, its inverse kinematics and its provenance are in The impedance backend.

Examples and Python

examples/nonrealtime_commander.rs drives this loop from a scripted commander that steps the target by ±5 cm with irregular holds, a 2 s stall and a burst of 20 targets in 100 ms (--stdin reads x y z lines instead; --rotate adds a ±15° yaw sweep through set_orientation; --log PATH writes one CSV row per cycle from the observer). Its --raw mode sends the same steps to a bare control_cartesian_pose so the robot refuses the first one, for contrast. crates/franka-rerun/examples/commander_live.rs is the same commander streamed into a Rerun viewer as it runs. Python’s move_to, move_by and follow are this loop; see From Python.

Target control works on the FR3 (FCI v10) and the FER (FCI v5) with either backend; the impedance backend is not yet validated on an FR3. See Backends.