Skip to main content

franka/
lib.rs

1//! A pure-Rust client for the Franka Control Interface (FCI), the 1 kHz realtime protocol a
2//! Franka robot's control box speaks over Ethernet.
3//!
4//! # Supported robots
5//! * **Franka Research 3** — FCI v10, mirroring libfranka 0.21.2.
6//! * **Franka Emika Robot (FER)** — FCI v5, mirroring libfranka 0.9.2.
7//!
8//! The version is negotiated during the `Connect` handshake ([`VersionPolicy`]): [`Robot`]
9//! probes v10 first and falls back to v5 when the control box rejects it and reports version 5,
10//! so the same binary drives either arm. Every byte-level difference is funnelled through
11//! [`wire::robot::codec`]; nothing above it names a protocol version.
12//!
13//! # Four ways to control the robot
14//! 1. **Callbacks** — [`Robot::control_joint_positions`] and its siblings run libfranka's
15//!    `ControlLoop`: your closure is called once per millisecond and returns the next setpoint
16//!    (see the [`robot`] module).
17//! 2. **`ActiveControl`** — [`Robot::start_torque_control`] and its siblings hand back a
18//!    [`ActiveTorqueControl`] / [`ActiveMotionGenerator`] handle you drive yourself with
19//!    `read_once` / `write_once` (see [`robot::active_control`]).
20//! 3. **Target control** — [`Robot::start_cartesian_target_control`] and
21//!    [`Robot::start_joint_target_control`] run the loop on a thread of their own and hand
22//!    back a [`CartesianTargetControl`] / [`JointTargetControl`] whose `set_position` /
23//!    `set_joints` any low-rate commander can call at any time; the loop turns the steps into
24//!    a smooth, limit-respecting command (see [`robot::target_control`]).
25//! 4. **Read-only** — [`Robot::read_once`] and [`Robot::read`] stream [`RobotState`] without
26//!    commanding anything.
27//!
28//! # Model and gripper
29//! [`Model`] answers forward kinematics, both Jacobians, the mass matrix, Coriolis and gravity.
30//! It runs on a native URDF-based backend by default and can also load the robot's own
31//! `libfcimodels` shared object ([`Robot::load_model_from_robot`]). [`Gripper`] is the separate
32//! Franka Hand client, on its own TCP/UDP session.
33//!
34//! # Realtime requirements
35//! A control loop must complete within 1 ms. [`RealtimeConfig::Enforce`] (libfranka's default)
36//! raises the calling thread to the highest `SCHED_FIFO` priority and fails when it cannot; run
37//! on a `PREEMPT_RT` kernel with the process allowed to set realtime priorities.
38//! [`RealtimeConfig::Ignore`] skips both checks, which is what the simulator tests and the
39//! examples use.
40//!
41//! # Example
42//! ```no_run
43//! use franka::{ControllerMode, JointPositions, RealtimeConfig, Robot, DEFAULT_CUTOFF_FREQUENCY};
44//!
45//! # fn main() -> franka::FrankaResult<()> {
46//! let robot = Robot::new("192.168.0.1", RealtimeConfig::Enforce)?;
47//! let initial = robot.read_once()?.q_d;
48//! let mut time = 0.0;
49//! robot.control_joint_positions(
50//!     |_state, period| {
51//!         time += period.as_secs_f64();
52//!         let mut q = initial;
53//!         q[3] += std::f64::consts::PI / 8.0 * (1.0 - (std::f64::consts::PI / 2.5 * time).cos());
54//!         let mut output = JointPositions::new(q);
55//!         output.motion_finished = time >= 5.0;
56//!         output
57//!     },
58//!     ControllerMode::JointImpedance,
59//!     true,
60//!     DEFAULT_CUTOFF_FREQUENCY,
61//! )?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! # Credit
67//! This crate is an independent implementation whose API shape was informed by
68//! [marcbone's libfranka-rs](https://github.com/marcbone/libfranka-rs) (no code shared);
69//! [libfranka](https://github.com/frankarobotics/libfranka) itself is the reference every
70//! type, constant, error text and wire layout here is checked against; the doc comments name
71//! the C++ counterpart wherever there is one.
72//!
73//! # Module layout
74//! Following libfranka: [`wire`] (packed protocol structs), [`network`] (the TCP command
75//! channel with command-id demultiplexing and the UDP state/command channel), [`robot`] (the
76//! public [`Robot`] API, control loops and `ActiveControl`), [`rate_limiting`], [`model`],
77//! [`gripper`].
78
79#![warn(missing_docs)]
80#![deny(rustdoc::broken_intra_doc_links)]
81#![allow(non_snake_case)]
82
83pub mod control_types;
84pub mod duration;
85pub mod error;
86pub mod errors;
87pub mod gripper;
88pub mod joint_velocity_limits;
89pub mod lowpass_filter;
90pub(crate) mod math_utils;
91pub mod model;
92pub mod network;
93pub mod otg;
94pub mod rate_limiting;
95pub mod realtime;
96pub mod robot;
97pub mod robot_state;
98pub mod wire;
99
100// -- Re-exports of the public API, mirroring `#include <franka/...>` in libfranka ------------
101
102pub use control_types::{
103    motion_finished, CartesianPose, CartesianVelocities, ControllerMode, Finishable,
104    JointPositions, JointVelocities, MotionGenerator, MotionGeneratorKind, Torques,
105};
106pub use duration::Duration;
107pub use error::{ControlException, FrankaError, FrankaResult, MoveStatus, Record, RobotCommandLog};
108pub use errors::{Errors, ERROR_NAMES};
109pub use gripper::{Gripper, GripperState};
110pub use joint_velocity_limits::JointVelocityLimitsConfig;
111pub use lowpass_filter::{
112    cartesian_low_pass_filter, low_pass_filter, DEFAULT_CUTOFF_FREQUENCY, MAX_CUTOFF_FREQUENCY,
113};
114pub use model::{Frame, Model};
115pub use otg::{CartesianOtg, MultiOtg, Otg, OtgLimits};
116/// libfranka puts the `limitRate` overloads and the `kMax*` rate constants of the **FR3**
117/// (FCI v10) in the bare `franka::` namespace (`include/franka/rate_limiting.h`), so they are
118/// re-exported here; every item also keeps its `franka::rate_limiting::` path.
119///
120/// The FER (FCI v5) constants are deliberately **not** part of this list: they live at
121/// `franka::rate_limiting::fer::*` only, so that `franka::MAX_JOINT_ACCELERATION` and its
122/// neighbours unambiguously mean the FR3's and the two envelopes can never be mixed up at a
123/// glance. The control loop picks between them from the negotiated [`FciVersion`]; user code
124/// rarely needs either.
125pub use rate_limiting::{
126    compute_lower_limits_joint_velocity, compute_upper_limits_joint_velocity,
127    limit_rate_cartesian_pose, limit_rate_cartesian_velocity, limit_rate_joint_position,
128    limit_rate_joint_positions, limit_rate_joint_velocities, limit_rate_joint_velocity,
129    limit_rate_torques, DELTA_T, FACTOR_CARTESIAN_ROTATION_POSE_INTERFACE,
130    JOINT_VELOCITY_LIMITS_TOLERANCE, LIMIT_EPS, MAX_ELBOW_ACCELERATION, MAX_ELBOW_JERK,
131    MAX_ELBOW_VELOCITY, MAX_JOINT_ACCELERATION, MAX_JOINT_JERK, MAX_ROTATIONAL_ACCELERATION,
132    MAX_ROTATIONAL_JERK, MAX_ROTATIONAL_VELOCITY, MAX_TORQUE_RATE, MAX_TRANSLATIONAL_ACCELERATION,
133    MAX_TRANSLATIONAL_JERK, MAX_TRANSLATIONAL_VELOCITY, MIN_ELBOW_VELOCITY, NORM_EPS,
134    TOL_NUMBER_PACKETS_LOST,
135};
136pub use realtime::RealtimeConfig;
137pub use robot::{
138    impedance_torques, ActiveMotionGenerator, ActiveMotionInput, ActiveTorqueControl, Backend,
139    CartesianSent, CartesianTargetControl, IkOptions, ImpedanceGains, ImpedanceOptions, JointSent,
140    JointTargetControl, JointTargetControlOptions, Leash, Robot, RobotOptions, Settle,
141    TargetControlOptions, VersionPolicy, VirtualWallCuboid,
142};
143pub use robot_state::{RobotMode, RobotState};
144/// The FCI protocol version a connection speaks: v5 (FER) or v10 (FR3).
145pub use wire::robot::codec::FciVersion;
146/// The controller a motion is started with; unlike [`ControllerMode`] this includes the
147/// external controller, which is what `Robot::start_*_control` needs.
148pub use wire::robot::MoveControllerMode;
149
150/// Checks that the README's "Quick example" block and `examples/readme_joint_move.rs` cannot
151/// drift apart, and that the block stays within its 20-line budget.
152///
153/// This lives in the library (rather than in `tests/`) so that it runs as part of the
154/// Docker-free `cargo test -p franka-rs --lib` that CI's `check` job executes.
155#[cfg(test)]
156mod readme_sync;