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

Use the model

At the end of this page you have a Model for the connected arm and can evaluate poses, Jacobians, the mass matrix, Coriolis and gravity torques from a RobotState or from a joint configuration of your own, with no download and no C++ library.

Prerequisites: a connected Robot (First program).

extern crate franka;
use franka::{Frame, RealtimeConfig, Robot};

fn main() -> franka::FrankaResult<()> {
let robot = Robot::new("172.16.0.2", RealtimeConfig::Enforce)?;
let model = robot.load_model()?;
let state = robot.read_once()?;

let pose: [f64; 16] = model.pose(Frame::EndEffector, &state);       // column major
let jacobian: [f64; 42] = model.zero_jacobian(Frame::EndEffector, &state);
let body_j: [f64; 42] = model.body_jacobian(Frame::EndEffector, &state);
let mass: [f64; 49] = model.mass(&state);
let coriolis: [f64; 7] = model.coriolis(&state);
let gravity: [f64; 7] = model.gravity(&state);
Ok(()) }

The six calls

callreturnslayout
pose(frame, &state)[f64; 16]4x4 pose of frame in the base frame, column-major
zero_jacobian(frame, &state)[f64; 42]6x7, column-major; rows vx vy vz wx wy wz, base-frame aligned
body_jacobian(frame, &state)[f64; 42]6x7, column-major, expressed in frame
mass(&state)[f64; 49]7x7 mass matrix, column-major, symmetric, kg m²
coriolis(&state)[f64; 7]C(q, dq) dq, Nm
gravity(&state)[f64; 7]gravity torques, Nm

Frame has the ten libfranka values: Joint1Joint7, Flange (the URDF’s link8), EndEffector (flange post-multiplied by F_T_EE) and Stiffness (end effector post-multiplied by EE_T_K). Frame::ALL lists them in that order.

Supplying your own inputs: the *_q variants

Every call has a variant that takes the joint configuration and load parameters directly instead of reading them out of a RobotState: pose_q, zero_jacobian_q, body_jacobian_q, mass_q, coriolis_q, gravity_q. They are what you use offline, and gravity_q is the only way to supply your own gravity vector:

#![allow(unused)]
fn main() {
extern crate franka;
use franka::Model;
fn f(model: &Model, q: &[f64; 7]) {
let g = model.gravity_q(q, 0.73, &[0.0, 0.0, 0.1034], &[0.0, 0.0, -9.81]);
let _ = g; }
}

gravity(&state) uses state.O_ddP_O as Earth’s gravity, as libfranka does; coriolis(&state) uses the fixed [0, 0, -9.81] (franka::model::DEFAULT_GRAVITY_EARTH), also as libfranka does. franka-sim publishes O_ddP_O as [0, 0, 0], so on the simulator gravity(&state) is identically zero: use gravity_q with an explicit vector there (see Simulator gaps).

A Model can also be built without a robot: Model::from_urdf(&urdf) for an FR3’s URDF and Model::native_fer() for the Franka Emika Robot.

In a torque controller

Model is Send + Sync, so a control thread and a planner can share one. The robot adds gravity and friction to the torques you send, so a model-based controller typically adds the Coriolis term and uses a Jacobian for a Cartesian law:

#![allow(unused)]
fn main() {
extern crate franka;
use franka::{Model, RobotState, Torques};
fn f(model: &Model, state: &RobotState, tau_task: [f64; 7]) -> Torques {
let coriolis = model.coriolis(state);
let mut tau = tau_task;
for i in 0..7 {
    tau[i] += coriolis[i];
}
Torques::new(tau)
}
}

The full Cartesian impedance law (J^T (-K e - D J dq) + coriolis) is on Drive the loop yourself; the joint impedance variant for an FER is examples/fer_joint_impedance.rs.

Cost: evaluating all five dynamic and kinematic calls a model-based controller makes takes about 3 µs offline and 11–15 µs inside a 1 kHz loop on a laptop-class CPU. The in-loop figure is higher because a duty-cycled loop starts each cycle on a core that has just idled; see Benchmarks for the numbers and that caveat.

Where the parameters come from

On an FR3 (FCI v10), load_model() fetches the arm’s URDF from the robot with GetRobotModel and evaluates it natively; robot.robot_model() returns the URDF text. On a Franka Emika Robot (FCI v5) there is no such command, and load_model() returns the crate’s built-in FER model (Model::native_fer(), the parameters in franka::model::FER_URDF, identified from a real FER’s own model library). On neither robot does load_model() download or dlopen anything, and on an FER it cannot fail. How the FER parameters were fitted, and how closely both models agree with libfranka’s, is on Model parameters and conformance.

load_model_from_robot(): the robot’s own library

libfranka 0.9.2 gets an FER’s model by downloading the robot’s closed-source libfcimodels.so over the command channel and dlopening it. That path is still available, opt-in:

#![allow(unused)]
fn main() {
extern crate franka;
use franka::Robot;
fn f(robot: &Robot) -> franka::FrankaResult<()> {
let model = robot.load_model_from_robot()?;   // LoadModelLibrary + dlopen, as libfranka does
let _ = model; Ok(()) }
}

It needs the default model-library cargo feature (which links libloading), an x86-64 Linux host, and it executes code the robot served. On an FR3 it is load_model() exactly. Its caveats are listed on the reference page.

From Python

robot.model() returns the same model with the same six calls; see From Python.