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 with the opt-in `model-library` feature
32//! ([`Robot::load_model_from_robot`]). [`Gripper`] is the separate Franka Hand client, on its
33//! own TCP/UDP session.
34//!
35//! # Realtime requirements
36//! A control loop must complete within 1 ms. [`RealtimeConfig::Enforce`] (libfranka's default)
37//! raises the calling thread to the highest `SCHED_FIFO` priority and fails when it cannot; run
38//! on a `PREEMPT_RT` kernel with the process allowed to set realtime priorities.
39//! [`RealtimeConfig::Ignore`] skips both checks, which is what the simulator tests and the
40//! examples use.
41//!
42//! # Example
43//! ```no_run
44//! use franka::{ControllerMode, JointPositions, RealtimeConfig, Robot, DEFAULT_CUTOFF_FREQUENCY};
45//!
46//! # fn main() -> franka::FrankaResult<()> {
47//! let robot = Robot::new("172.16.0.2", RealtimeConfig::Enforce)?;
48//! let initial = robot.read_once()?.q_d;
49//! let mut time = 0.0;
50//! robot.control_joint_positions(
51//! |_state, period| {
52//! time += period.as_secs_f64();
53//! let mut q = initial;
54//! q[3] += std::f64::consts::PI / 8.0 * (1.0 - (std::f64::consts::PI / 2.5 * time).cos());
55//! let mut output = JointPositions::new(q);
56//! output.motion_finished = time >= 5.0;
57//! output
58//! },
59//! ControllerMode::JointImpedance,
60//! true,
61//! DEFAULT_CUTOFF_FREQUENCY,
62//! )?;
63//! # Ok(())
64//! # }
65//! ```
66//!
67//! # Credit
68//! This crate is an independent implementation whose API shape was informed by
69//! [marcbone's libfranka-rs](https://github.com/marcbone/libfranka-rs) (no code shared);
70//! [libfranka](https://github.com/frankarobotics/libfranka) itself is the reference every
71//! type, constant, error text and wire layout here is checked against; the doc comments name
72//! the C++ counterpart wherever there is one.
73//!
74//! # Module layout
75//! Following libfranka: [`wire`] (packed protocol structs), [`network`] (the TCP command
76//! channel with command-id demultiplexing and the UDP state/command channel), [`robot`] (the
77//! public [`Robot`] API, control loops and `ActiveControl`), [`rate_limiting`], [`model`],
78//! [`gripper`].
79
80#![warn(missing_docs)]
81#![deny(rustdoc::broken_intra_doc_links)]
82#![allow(non_snake_case)]
83
84pub mod control_types;
85pub mod duration;
86pub mod error;
87pub mod errors;
88pub mod gripper;
89pub mod joint_velocity_limits;
90pub mod lowpass_filter;
91pub(crate) mod math_utils;
92pub mod model;
93pub mod network;
94pub mod otg;
95pub mod rate_limiting;
96pub mod realtime;
97pub mod robot;
98pub mod robot_state;
99pub mod wire;
100
101// -- Re-exports of the public API, mirroring `#include <franka/...>` in libfranka ------------
102
103pub use control_types::{
104 motion_finished, CartesianPose, CartesianVelocities, ControllerMode, Finishable,
105 JointPositions, JointVelocities, MotionGenerator, MotionGeneratorKind, Torques,
106};
107pub use duration::Duration;
108pub use error::{ControlException, FrankaError, FrankaResult, MoveStatus, Record, RobotCommandLog};
109pub use errors::{Errors, ERROR_NAMES};
110pub use gripper::{Gripper, GripperState};
111pub use joint_velocity_limits::JointVelocityLimitsConfig;
112pub use lowpass_filter::{
113 cartesian_low_pass_filter, low_pass_filter, DEFAULT_CUTOFF_FREQUENCY, MAX_CUTOFF_FREQUENCY,
114};
115pub use model::{Frame, Model};
116pub use otg::{CartesianOtg, MultiOtg, Otg, OtgLimits};
117/// libfranka puts the `limitRate` overloads and the `kMax*` rate constants of the **FR3**
118/// (FCI v10) in the bare `franka::` namespace (`include/franka/rate_limiting.h`), so they are
119/// re-exported here; every item also keeps its `franka::rate_limiting::` path.
120///
121/// The FER (FCI v5) constants are deliberately **not** part of this list: they live at
122/// `franka::rate_limiting::fer::*` only, so that `franka::MAX_JOINT_ACCELERATION` and its
123/// neighbours unambiguously mean the FR3's and the two envelopes can never be mixed up at a
124/// glance. The control loop picks between them from the negotiated [`FciVersion`]; user code
125/// rarely needs either.
126pub use rate_limiting::{
127 compute_lower_limits_joint_velocity, compute_upper_limits_joint_velocity,
128 limit_rate_cartesian_pose, limit_rate_cartesian_velocity, limit_rate_joint_position,
129 limit_rate_joint_positions, limit_rate_joint_velocities, limit_rate_joint_velocity,
130 limit_rate_torques, DELTA_T, FACTOR_CARTESIAN_ROTATION_POSE_INTERFACE,
131 JOINT_VELOCITY_LIMITS_TOLERANCE, LIMIT_EPS, MAX_ELBOW_ACCELERATION, MAX_ELBOW_JERK,
132 MAX_ELBOW_VELOCITY, MAX_JOINT_ACCELERATION, MAX_JOINT_JERK, MAX_ROTATIONAL_ACCELERATION,
133 MAX_ROTATIONAL_JERK, MAX_ROTATIONAL_VELOCITY, MAX_TORQUE_RATE, MAX_TRANSLATIONAL_ACCELERATION,
134 MAX_TRANSLATIONAL_JERK, MAX_TRANSLATIONAL_VELOCITY, MIN_ELBOW_VELOCITY, NORM_EPS,
135 TOL_NUMBER_PACKETS_LOST,
136};
137pub use realtime::RealtimeConfig;
138pub use robot::{
139 impedance_torques, ActiveMotionGenerator, ActiveMotionInput, ActiveTorqueControl, Backend,
140 CartesianSent, CartesianTargetControl, IkOptions, ImpedanceGains, ImpedanceOptions, JointSent,
141 JointTargetControl, JointTargetControlOptions, Leash, Robot, RobotOptions, Settle,
142 TargetControlOptions, VersionPolicy, VirtualWallCuboid,
143};
144pub use robot_state::{RobotMode, RobotState};
145/// The FCI protocol version a connection speaks: v5 (FER) or v10 (FR3).
146pub use wire::robot::codec::FciVersion;
147/// The controller a motion is started with; unlike [`ControllerMode`] this includes the
148/// external controller, which is what `Robot::start_*_control` needs.
149pub use wire::robot::MoveControllerMode;
150
151/// Checks that the README's "Quick example" block and `examples/readme_joint_move.rs` cannot
152/// drift apart, and that the block stays within its 20-line budget.
153///
154/// This lives in the library (rather than in `tests/`) so that it runs as part of the
155/// Docker-free `cargo test -p franka-rs --lib` that CI's `check` job executes.
156#[cfg(test)]
157mod readme_sync;