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

Write a 1 kHz callback

At the end of this page you will have run a motion from a closure the crate calls once per robot state, ended it with motion_finished, and read the control log a reflex leaves behind. Prerequisites: First program works, the arm is at the ready pose, and you have read The realtime rules: the closure runs on the realtime thread.

extern crate franka;
use franka::{ControllerMode, JointVelocities, RealtimeConfig, Robot, DEFAULT_CUTOFF_FREQUENCY};

fn main() -> franka::FrankaResult<()> {
let robot = Robot::new("172.16.0.2", RealtimeConfig::Enforce)?;
let mut time = 0.0;
robot.control_joint_velocities(
    |_state, period| {
        time += period.as_secs_f64();
        let omega = 0.5 * (std::f64::consts::PI * time / 2.0).sin();
        let mut out = JointVelocities::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, omega]);
        out.motion_finished = time >= 4.0;
        out
    },
    ControllerMode::JointImpedance,
    /* limit_rate  = */ false,
    DEFAULT_CUTOFF_FREQUENCY,
)?;
Ok(()) }

The closure is FnMut(&RobotState, Duration) -> T, T the command type of the method. The Duration is the robot time since the previous call (zero on the first); integrate against it rather than assuming 1 ms.

The methods

methodcallback returnscommands
control_joint_positionsJointPositionsq
control_joint_velocitiesJointVelocitiesdq
control_cartesian_poseCartesianPoseO_T_EE (+ optional elbow)
control_cartesian_velocitiesCartesianVelocitiesO_dP_EE (+ optional elbow)
control_torquesTorquestau_J
control_torques_and_joint_positionsTorques + JointPositionsboth
control_torques_and_joint_velocitiesTorques + JointVelocitiesboth
control_torques_and_cartesian_poseTorques + CartesianPoseboth
control_torques_and_cartesian_velocitiesTorques + CartesianVelocitiesboth

The four motion-only methods take a ControllerMode, JointImpedance or CartesianImpedance, which selects the robot’s internal controller; the control_torques* methods do not, there the controller is yours. Seed a position or pose motion from state.q_d / state.O_T_EE_c, not from the measured q / O_T_EE (realtime rule 5).

Ending a motion

A motion ends when the callback returns a command with motion_finished set. Set the field, or wrap the value:

#![allow(unused)]
fn main() {
extern crate franka;
use franka::JointVelocities;
fn f(out: JointVelocities, done: bool) -> JointVelocities {
if done {
    franka::motion_finished(out)   // sets the flag and returns the value
} else {
    out
}
}
}

The flag belongs to the Finishable trait all five command types implement. The last command is still sent, and the crate then closes the motion with the robot. Never return early or panic out of the callback to stop a motion: set the flag, or call stop() from another thread. A panic that unwinds out of the callback cancels the motion on the robot, as a thrown exception does in libfranka, but that is an abort, not a finished motion.

Rate limiting and the low-pass filter

Every method takes limit_rate: bool and cutoff_frequency: f64 explicitly, because Rust has no default arguments and libfranka’s limit_rate default differs by version: false in libfranka 0.21 for the FR3 (the robot does its own limiting; the client-side limiter can distort a motion), true in libfranka 0.9.2 for the FER. cutoff_frequency defaults to DEFAULT_CUTOFF_FREQUENCY in both; MAX_CUTOFF_FREQUENCY disables the first-order low-pass filter. Which constants the limiter uses follows from the negotiated version, not from you; they and the limit_rate_* functions are in Rate limiting and filtering.

ControlException and the control log

A motion that ends abnormally returns FrankaError::Control(ControlException), the port of franka::ControlException:

#![allow(unused)]
fn main() {
extern crate franka;
use franka::{FrankaError, Robot, RealtimeConfig};
fn f(robot: &Robot) {
let result: franka::FrankaResult<()> = Ok(());
match result {
    Err(FrankaError::Control(e)) => {
        println!("{}", e.message);                 // libfranka's exact text
        println!("{:?}", e.move_status);           // terminal Move status, if any
        println!("{:?}", e.last_motion_errors);    // the reflex reason
        for record in &e.log {                     // newest last
            println!("{:?} -> {:?}", record.state.q, record.command);
        }
    }
    _ => {}
}
}
}

message is libfranka’s full string, with the error names and the success-rate line when a reflex aborted the motion. log is Vec<Record>, Record { state, command: Option<RobotCommandLog> }, newest last: what was commanded in the 50 ms before the robot stopped. The default size is 50 cycles (franka::DEFAULT_LOG_SIZE); Robot::new_with_log_size or RobotOptions::new(..).with_log_size(n) changes it. The flight recorder replays the log (Record and replay a run); with the serde feature it saves as JSON. After a reflex, robot.automatic_error_recovery() clears the errors for the next motion.

Stopping from another thread

Robot is Send + Sync and every method takes &self: share it as an Arc<Robot> and call stop() from a second thread. The loop then fails with FrankaError::Control carrying "libfranka: Move command preempted!".

extern crate franka;
use std::sync::Arc;
use franka::{ControllerMode, JointVelocities, RealtimeConfig, Robot, DEFAULT_CUTOFF_FREQUENCY};

fn main() -> franka::FrankaResult<()> {
let robot = Arc::new(Robot::new("172.16.0.2", RealtimeConfig::Enforce)?);

let stopper = Arc::clone(&robot);
std::thread::spawn(move || {
    std::thread::sleep(std::time::Duration::from_secs(2));
    let _ = stopper.stop();
});

let result = robot.control_joint_velocities(
    |_state, _period| JointVelocities::new([0.0; 7]),
    ControllerMode::JointImpedance,
    false,
    DEFAULT_CUTOFF_FREQUENCY,
);
// `result` is Err(FrankaError::Control(..)) with "Move command preempted!"
let _ = result; Ok(()) }

Only one control or read operation may run at a time; a second one returns FrankaError::InvalidOperation with libfranka’s own message. The generate_* examples are ports of libfranka’s on this interface; see Run the examples.