Skip to main content

franka/robot/
active_control.rs

1//! Externally driven control: `read_once` / `write_once` instead of a callback loop.
2//!
3//! Port of `franka::ActiveControl`, `franka::ActiveTorqueControl` and
4//! `franka::ActiveMotionGenerator` (libfranka 0.21.2 `src/active_control.cpp`,
5//! `src/active_torque_control.cpp`, `src/active_motion_generator.cpp`).
6//!
7//! Neither rate limiting nor low-pass filtering is applied here — exactly like libfranka, the
8//! caller is responsible for sending smooth setpoints.
9//!
10//! Both types hold the robot's control lock for their whole lifetime, so no other control or
11//! read operation can run in parallel, and both cancel an unfinished motion when dropped.
12
13use std::sync::MutexGuard;
14
15use crate::control_types::{
16    CartesianPose, CartesianVelocities, Finishable, JointPositions, JointVelocities,
17    MotionGenerator, Torques,
18};
19use crate::duration::Duration;
20#[allow(unused_imports)] // `FrankaError` is referenced by the `# Errors` doc sections below.
21use crate::error::{FrankaError, FrankaResult};
22use crate::robot::robot_impl::{control_error, time_since, RobotImpl};
23use crate::robot_state::RobotState;
24use crate::wire::f64s_to_wire;
25use crate::wire::robot::{ControllerCommand, MotionGeneratorCommand, MoveControllerMode};
26
27/// Motion command types that can drive an [`ActiveMotionGenerator`].
28///
29/// Implemented for exactly the four `franka::MotionGenerator` command types; the method is the
30/// Rust equivalent of the `Robot::Impl::createMotionCommand` overload set, which validates the
31/// user's values before they are put on the wire.
32pub trait ActiveMotionInput: MotionGenerator {
33    /// Validates the command and converts it into the wire motion command.
34    fn create_motion_command(&self) -> FrankaResult<MotionGeneratorCommand>;
35}
36
37impl ActiveMotionInput for JointPositions {
38    fn create_motion_command(&self) -> FrankaResult<MotionGeneratorCommand> {
39        self.validate()?;
40        Ok(MotionGeneratorCommand {
41            q_c: f64s_to_wire(&self.q),
42            ..MotionGeneratorCommand::default()
43        })
44    }
45}
46
47impl ActiveMotionInput for JointVelocities {
48    fn create_motion_command(&self) -> FrankaResult<MotionGeneratorCommand> {
49        self.validate()?;
50        Ok(MotionGeneratorCommand {
51            dq_c: f64s_to_wire(&self.dq),
52            ..MotionGeneratorCommand::default()
53        })
54    }
55}
56
57impl ActiveMotionInput for CartesianPose {
58    fn create_motion_command(&self) -> FrankaResult<MotionGeneratorCommand> {
59        self.validate()?;
60        Ok(MotionGeneratorCommand {
61            O_T_EE_c: f64s_to_wire(&self.O_T_EE),
62            valid_elbow: u8::from(self.has_elbow),
63            elbow_c: f64s_to_wire(if self.has_elbow {
64                &self.elbow
65            } else {
66                &[0.0; 2]
67            }),
68            ..MotionGeneratorCommand::default()
69        })
70    }
71}
72
73impl ActiveMotionInput for CartesianVelocities {
74    fn create_motion_command(&self) -> FrankaResult<MotionGeneratorCommand> {
75        self.validate()?;
76        Ok(MotionGeneratorCommand {
77            O_dP_EE_c: f64s_to_wire(&self.O_dP_EE),
78            valid_elbow: u8::from(self.has_elbow),
79            elbow_c: f64s_to_wire(if self.has_elbow {
80                &self.elbow
81            } else {
82                &[0.0; 2]
83            }),
84            ..MotionGeneratorCommand::default()
85        })
86    }
87}
88
89/// Validates a torque command and converts it (`Robot::Impl::createControllerCommand`).
90pub(crate) fn create_controller_command(
91    control_input: &Torques,
92) -> FrankaResult<ControllerCommand> {
93    control_input.validate()?;
94    Ok(ControllerCommand {
95        tau_J_d: f64s_to_wire(&control_input.tau_J),
96        torque_command_finished: 0,
97    })
98}
99
100/// State shared by both active-control types (the C++ `ActiveControl` base class).
101struct ActiveControl<'a> {
102    robot: &'a RobotImpl,
103    motion_id: u32,
104    lock: Option<MutexGuard<'a, ()>>,
105    control_finished: bool,
106    last_read_access: Option<Duration>,
107}
108
109impl<'a> ActiveControl<'a> {
110    fn new(robot: &'a RobotImpl, motion_id: u32, lock: MutexGuard<'a, ()>) -> ActiveControl<'a> {
111        ActiveControl {
112            robot,
113            motion_id,
114            lock: Some(lock),
115            control_finished: false,
116            last_read_access: None,
117        }
118    }
119
120    /// `ActiveControl::readOnce`.
121    fn read_once(&mut self) -> FrankaResult<(RobotState, Duration)> {
122        let robot_state = self.robot.read_once()?;
123        self.robot
124            .throw_on_motion_error(&robot_state, self.motion_id)?;
125
126        let time_since_last_read = time_since(self.last_read_access, robot_state.time);
127        self.last_read_access = Some(robot_state.time);
128
129        Ok((robot_state, time_since_last_read))
130    }
131
132    /// Marks the control as finished and releases the robot's control lock.
133    ///
134    /// Only called after a *successful* `finish_motion`: `active_torque_control.cpp:61-64` and
135    /// `active_motion_generator.cpp:112-123` set `control_finished` after `finishMotion`
136    /// returns, so a failing finish leaves the flag false and the destructor sends the
137    /// `StopMove`. See [`ActiveControl::release_lock`] for the failing path.
138    fn finish(&mut self) {
139        self.control_finished = true;
140        self.lock = None;
141    }
142
143    /// Releases the robot's control lock without marking the control as finished, so that
144    /// [`Drop`] still cancels the motion.
145    fn release_lock(&mut self) {
146        self.lock = None;
147    }
148
149    /// Applies the outcome of a `finish_motion`: on success the control is over, on failure the
150    /// motion is still considered running and the drop guard cancels it.
151    fn apply_finish(&mut self, result: FrankaResult<()>) -> FrankaResult<()> {
152        match result {
153            Ok(()) => {
154                self.finish();
155                Ok(())
156            }
157            Err(error) => {
158                self.release_lock();
159                Err(error)
160            }
161        }
162    }
163
164    fn check_not_finished(&self) -> FrankaResult<()> {
165        if self.control_finished {
166            return Err(control_error(
167                "writeOnce must not be called after the motion has finished.",
168            ));
169        }
170        Ok(())
171    }
172}
173
174impl Drop for ActiveControl<'_> {
175    /// `ActiveControl::~ActiveControl`: an unfinished motion is cancelled.
176    fn drop(&mut self) {
177        if !self.control_finished {
178            let _ = self.robot.cancel_motion(self.motion_id);
179        }
180    }
181}
182
183/// An external torque controller driven by the caller
184/// (`Robot::start_torque_control`).
185///
186/// # FCI v5
187/// An FER has no torque-only motion generator mode, so the motion is started with a joint
188/// velocity generator and every [`ActiveTorqueControl::write_once`] sends an all-zero
189/// `dq_c` alongside the torques, ending with `motion_generation_finished` instead of
190/// `torque_command_finished`. That is exactly what libfranka 0.9.2's
191/// `Robot::control(control_callback, ...)` does (`src/robot.cpp:41-57`); the API is
192/// unchanged.
193///
194/// # Example
195/// ```no_run
196/// # use franka::{Robot, RealtimeConfig, Torques};
197/// # fn main() -> franka::FrankaResult<()> {
198/// let robot = Robot::new("192.168.0.1", RealtimeConfig::Ignore)?;
199/// let mut control = robot.start_torque_control()?;
200/// for _ in 0..1000 {
201///     let (_state, _period) = control.read_once()?;
202///     control.write_once(&Torques::new([0.0; 7]))?;
203/// }
204/// control.write_once(&franka::motion_finished(Torques::new([0.0; 7])))?;
205/// # Ok(())
206/// # }
207/// ```
208///
209/// # Threading
210/// Unlike [`crate::Robot`], which is `Send + Sync`, this type is **`!Send`**: it holds the
211/// [`std::sync::MutexGuard`] for the robot's control lock (`franka::assertOwningLock`) for as
212/// long as the motion runs, and a `MutexGuard` may not cross threads. Read and write the motion
213/// from the thread that started it; a [`crate::Robot::stop`] from another thread still works,
214/// because it goes through the `Arc<Robot>` and not through this handle.
215pub struct ActiveTorqueControl<'a> {
216    inner: ActiveControl<'a>,
217}
218
219impl<'a> ActiveTorqueControl<'a> {
220    pub(crate) fn new(
221        robot: &'a RobotImpl,
222        motion_id: u32,
223        lock: MutexGuard<'a, ()>,
224    ) -> ActiveTorqueControl<'a> {
225        ActiveTorqueControl {
226            inner: ActiveControl::new(robot, motion_id, lock),
227        }
228    }
229
230    /// Waits for the next robot state and returns it together with the time elapsed since the
231    /// previous `read_once` (zero on the first call).
232    ///
233    /// # Errors
234    /// [`FrankaError::Control`] if the motion was aborted, [`FrankaError::Network`] if the
235    /// connection was lost.
236    pub fn read_once(&mut self) -> FrankaResult<(RobotState, Duration)> {
237        self.inner.read_once()
238    }
239
240    /// Sends the given torques.
241    ///
242    /// Setting `motion_finished` on `control_input` ends the control process; the control lock
243    /// is released and any further call fails.
244    ///
245    /// # Errors
246    /// [`FrankaError::Control`] if the motion has already finished or was aborted,
247    /// [`FrankaError::InvalidArgument`] if a torque is NaN or infinite,
248    /// [`FrankaError::Network`] if the connection was lost.
249    pub fn write_once(&mut self, control_input: &Torques) -> FrankaResult<()> {
250        self.inner.check_not_finished()?;
251
252        let control_command = create_controller_command(control_input)?;
253        if control_input.is_finished() {
254            let result = self
255                .inner
256                .robot
257                .finish_torque_only(self.inner.motion_id, &control_command);
258            return self.inner.apply_finish(result);
259        }
260
261        self.inner
262            .robot
263            .network()
264            .tcp
265            .throw_if_connection_closed()?;
266        self.inner.robot.write_once_torque_only(&control_command)
267    }
268}
269
270/// An externally driven motion generator (`Robot::start_*_control`).
271///
272/// `T` is one of [`JointPositions`], [`JointVelocities`], [`CartesianPose`] or
273/// [`CartesianVelocities`].
274///
275/// # Threading
276/// Unlike [`crate::Robot`], which is `Send + Sync`, this type is **`!Send`**: it holds the
277/// [`std::sync::MutexGuard`] for the robot's control lock (`franka::assertOwningLock`) for as
278/// long as the motion runs, and a `MutexGuard` may not cross threads. Read and write the motion
279/// from the thread that started it; a [`crate::Robot::stop`] from another thread still works,
280/// because it goes through the `Arc<Robot>` and not through this handle.
281pub struct ActiveMotionGenerator<'a, T: ActiveMotionInput> {
282    inner: ActiveControl<'a>,
283    controller_type: MoveControllerMode,
284    marker: std::marker::PhantomData<T>,
285}
286
287impl<'a, T: ActiveMotionInput> ActiveMotionGenerator<'a, T> {
288    pub(crate) fn new(
289        robot: &'a RobotImpl,
290        motion_id: u32,
291        lock: MutexGuard<'a, ()>,
292        controller_type: MoveControllerMode,
293    ) -> ActiveMotionGenerator<'a, T> {
294        ActiveMotionGenerator {
295            inner: ActiveControl::new(robot, motion_id, lock),
296            controller_type,
297            marker: std::marker::PhantomData,
298        }
299    }
300
301    /// Waits for the next robot state and returns it together with the time elapsed since the
302    /// previous `read_once` (zero on the first call).
303    pub fn read_once(&mut self) -> FrankaResult<(RobotState, Duration)> {
304        self.inner.read_once()
305    }
306
307    /// Sends the next motion setpoint, and the torques when the motion runs with an external
308    /// controller.
309    ///
310    /// `control_input` must be `Some` if and only if the motion was started with
311    /// `MoveControllerMode::ExternalController`. Setting `motion_finished` on either input ends
312    /// the control process.
313    ///
314    /// # Errors
315    /// [`FrankaError::Control`] if the motion has already finished, if torques are given
316    /// without an external controller (or missing with one), or if the motion was aborted;
317    /// [`FrankaError::InvalidArgument`] for non-finite values, an invalid transformation matrix
318    /// or an invalid elbow configuration.
319    pub fn write_once(
320        &mut self,
321        motion_input: &T,
322        control_input: Option<&Torques>,
323    ) -> FrankaResult<()> {
324        self.inner.check_not_finished()?;
325
326        let external = self.controller_type == MoveControllerMode::ExternalController;
327        if control_input.is_some() && !external {
328            return Err(control_error(
329                "Torques can only be commanded in kExternalController mode.",
330            ));
331        }
332        if control_input.is_none() && external {
333            return Err(control_error(
334                "Torque command missing, please use writeOnce(const MotionGeneratorType& \
335                 motion_generator_input, const Torques& control_input) for external controllers.",
336            ));
337        }
338
339        let torque_finished = control_input.map(|t| t.is_finished()).unwrap_or(false);
340        if motion_input.is_finished() || torque_finished {
341            let motion_command = motion_input.create_motion_command()?;
342            let result = match control_input {
343                None => self.inner.robot.finish_motion(
344                    self.inner.motion_id,
345                    Some(&motion_command),
346                    None,
347                ),
348                Some(control_input) => {
349                    let control_command = create_controller_command(control_input)?;
350                    self.inner.robot.finish_motion(
351                        self.inner.motion_id,
352                        Some(&motion_command),
353                        Some(&control_command),
354                    )
355                }
356            };
357            return self.inner.apply_finish(result);
358        }
359
360        let motion_command = motion_input.create_motion_command()?;
361        match control_input {
362            None => {
363                self.inner
364                    .robot
365                    .network()
366                    .tcp
367                    .throw_if_connection_closed()?;
368                self.inner.robot.write_once_motion(&motion_command)
369            }
370            Some(control_input) => {
371                let control_command = create_controller_command(control_input)?;
372                self.inner
373                    .robot
374                    .network()
375                    .tcp
376                    .throw_if_connection_closed()?;
377                self.inner
378                    .robot
379                    .write_once_motion_and_control(&motion_command, &control_command)
380            }
381        }
382    }
383}