1mod exception;
17mod motion;
18
19pub(crate) use exception::create_control_exception;
20
21use std::sync::Mutex;
22
23use crate::duration::Duration;
24use crate::error::{
25 ControlException, FrankaError, FrankaResult, MoveStatus, Record, RobotCommandLog,
26};
27use crate::errors::Errors;
28use crate::joint_velocity_limits::JointVelocityLimitsConfig;
29use crate::model::Model;
30use crate::network::{connect_handshake, Network};
31use crate::realtime::{
32 has_realtime_kernel, set_current_thread_to_highest_scheduler_priority, RealtimeConfig,
33 NO_REALTIME_KERNEL_MESSAGE,
34};
35use crate::robot::commands::{
36 handle_move_response, handle_stop_move_response, handle_terminal_move_response, CommandName,
37};
38use crate::robot::logger::RobotStateLogger;
39use crate::robot::VersionPolicy;
40use crate::robot_state::{RobotMode, RobotState};
41use crate::wire::robot::codec::{self, CommandKind, FciVersion, RobotCommandData, StateModes};
42use crate::wire::robot::{
43 ControllerCommand, ControllerMode as StateControllerMode, Deviation, MotionGeneratorCommand,
44 MotionGeneratorMode as StateMotionGeneratorMode, MoveControllerMode, MoveMotionGeneratorMode,
45 RobotMode as WireRobotMode,
46};
47use crate::wire::{
48 f64s_to_f64, incorrect_object_size, message_payload, HeaderLayout, ROBOT_COMMAND_PORT,
49};
50
51pub const DEFAULT_DEVIATION: (f64, f64, f64) = (10.0, 3.12, std::f64::consts::TAU);
54
55pub const NUM_JOINTS: usize = 7;
57
58#[derive(Debug)]
64struct State {
65 message_id: u64,
67 robot_mode: WireRobotMode,
69 motion_generator_mode: StateMotionGeneratorMode,
71 controller_mode: StateControllerMode,
73 current_move_motion_generator_mode: StateMotionGeneratorMode,
75 current_move_controller_mode: StateControllerMode,
77}
78
79impl State {
80 fn motion_generator_running(&self) -> bool {
82 self.motion_generator_mode != StateMotionGeneratorMode::Idle
83 && self.motion_generator_mode != StateMotionGeneratorMode::None
84 }
85
86 fn controller_running(&self) -> bool {
88 self.controller_mode == StateControllerMode::ExternalController
89 }
90
91 fn move_active(&self) -> bool {
93 self.motion_generator_mode == self.current_move_motion_generator_mode
94 && self.controller_mode == self.current_move_controller_mode
95 }
96}
97
98#[derive(Debug)]
100pub struct RobotImpl {
101 network: Network,
102 logger: Mutex<RobotStateLogger>,
103 realtime_config: RealtimeConfig,
104 version: FciVersion,
107 ri_version: u16,
108 state: Mutex<State>,
109 joint_velocity_limits: JointVelocityLimitsConfig,
113 robot_model_urdf: String,
115}
116
117impl RobotImpl {
118 pub fn new(
121 franka_address: &str,
122 realtime_config: RealtimeConfig,
123 log_size: usize,
124 ) -> FrankaResult<RobotImpl> {
125 RobotImpl::new_with_policy(
126 franka_address,
127 realtime_config,
128 log_size,
129 VersionPolicy::default(),
130 )
131 }
132
133 pub fn new_with_policy(
149 franka_address: &str,
150 realtime_config: RealtimeConfig,
151 log_size: usize,
152 policy: VersionPolicy,
153 ) -> FrankaResult<RobotImpl> {
154 let throw_on_error = realtime_config == RealtimeConfig::Enforce;
155 if let Err(message) = set_current_thread_to_highest_scheduler_priority() {
156 if throw_on_error {
157 log_error(&message);
158 return Err(FrankaError::Realtime(message));
159 }
160 }
161 if throw_on_error && !has_realtime_kernel() {
162 log_error(NO_REALTIME_KERNEL_MESSAGE);
163 return Err(FrankaError::Realtime(
164 NO_REALTIME_KERNEL_MESSAGE.to_string(),
165 ));
166 }
167
168 let (network, ri_version, version) = match policy {
169 VersionPolicy::Exact(version) => {
170 let (network, ri_version) = connect_as(franka_address, version)?;
171 (network, ri_version, version)
172 }
173 VersionPolicy::Auto => match connect_as(franka_address, FciVersion::V10) {
174 Ok((network, ri_version)) => (network, ri_version, FciVersion::V10),
175 Err(FrankaError::IncompatibleVersion {
178 server_version: 5, ..
179 }) => {
180 let (network, ri_version) = connect_as(franka_address, FciVersion::V5)?;
181 (network, ri_version, FciVersion::V5)
182 }
183 Err(other) => return Err(other),
184 },
185 };
186
187 let robot = RobotImpl {
188 network,
189 logger: Mutex::new(RobotStateLogger::new(log_size)),
190 realtime_config,
191 version,
192 ri_version,
193 state: Mutex::new(State {
194 message_id: 0,
195 robot_mode: WireRobotMode::Other,
196 motion_generator_mode: StateMotionGeneratorMode::Idle,
197 controller_mode: StateControllerMode::Other,
198 current_move_motion_generator_mode: StateMotionGeneratorMode::Idle,
199 current_move_controller_mode: StateControllerMode::Other,
200 }),
201 joint_velocity_limits: JointVelocityLimitsConfig::default(),
202 robot_model_urdf: String::new(),
203 };
204
205 let mut buffer = [0u8; codec::ROBOT_STATE_MAX_LEN];
207 let size = codec::state_size(version);
208 let received = robot.network.blocking_receive_bytes(&mut buffer)?;
209 if received != size {
210 return Err(incorrect_object_size());
211 }
212 robot.update_state(&codec::parse_state_modes(version, &buffer[..size])?);
213
214 if version == FciVersion::V5 {
215 return Ok(robot);
216 }
217
218 let urdf = robot.get_robot_model()?;
219 let joint_velocity_limits = JointVelocityLimitsConfig::from_urdf(&urdf)?;
220
221 Ok(RobotImpl {
222 robot_model_urdf: urdf,
223 joint_velocity_limits,
224 ..robot
225 })
226 }
227
228 pub fn version(&self) -> FciVersion {
230 self.version
231 }
232
233 pub fn server_version(&self) -> u16 {
235 self.ri_version
236 }
237
238 pub fn realtime_config(&self) -> RealtimeConfig {
241 self.realtime_config
242 }
243
244 pub fn robot_model_urdf(&self) -> &str {
246 &self.robot_model_urdf
247 }
248
249 pub(crate) fn network(&self) -> &Network {
252 &self.network
253 }
254
255 pub fn upper_joint_velocity_limits(&self, q: &[f64; NUM_JOINTS]) -> [f64; NUM_JOINTS] {
260 match self.version {
261 FciVersion::V5 => crate::rate_limiting::fer::MAX_JOINT_VELOCITY,
262 FciVersion::V10 => self.joint_velocity_limits.upper_limits(q),
263 }
264 }
265
266 pub fn lower_joint_velocity_limits(&self, q: &[f64; NUM_JOINTS]) -> [f64; NUM_JOINTS] {
270 match self.version {
271 FciVersion::V5 => crate::rate_limiting::fer::MIN_JOINT_VELOCITY,
272 FciVersion::V10 => self.joint_velocity_limits.lower_limits(q),
273 }
274 }
275
276 pub fn load_model_v5(&self) -> FrankaResult<Model> {
281 crate::model::load_from_robot(&self.network, self.version)
282 }
283
284 pub(crate) fn command_id(&self, name: CommandName) -> FrankaResult<u32> {
290 codec::command_id(self.version, name.kind()).ok_or_else(|| {
291 FrankaError::InvalidOperation(format!(
292 "libfranka: {} is not available on FCI version {}.",
293 name.as_str(),
294 self.version.number()
295 ))
296 })
297 }
298
299 pub(crate) fn robot_mode(&self) -> RobotMode {
301 let mode = self.lock().robot_mode;
302 RobotMode::from_u8(mode.to_u8()).unwrap_or(RobotMode::Other)
303 }
304
305 fn lock(&self) -> std::sync::MutexGuard<'_, State> {
306 self.state.lock().unwrap_or_else(|e| e.into_inner())
307 }
308
309 fn logger(&self) -> std::sync::MutexGuard<'_, RobotStateLogger> {
310 self.logger.lock().unwrap_or_else(|e| e.into_inner())
311 }
312
313 fn update_state(&self, modes: &StateModes) {
315 let mut guard = self.lock();
316 guard.robot_mode = WireRobotMode::from_u8(modes.robot_mode).unwrap_or(WireRobotMode::Other);
317 guard.motion_generator_mode =
318 StateMotionGeneratorMode::from_u8(modes.motion_generator_mode)
319 .unwrap_or(StateMotionGeneratorMode::Idle);
320 guard.controller_mode = StateControllerMode::from_u8(modes.controller_mode)
321 .unwrap_or(StateControllerMode::Other);
322 guard.message_id = modes.message_id;
323 }
324
325 fn receive_robot_state(&self) -> FrankaResult<RobotState> {
336 let size = codec::state_size(self.version);
337 let last_message_id = self.lock().message_id;
338 let mut buffer = [0u8; codec::ROBOT_STATE_MAX_LEN];
339 let mut latest = [0u8; codec::ROBOT_STATE_MAX_LEN];
340 let mut latest_message_id = last_message_id;
341
342 while let Some(received) = self.network.try_receive_bytes(&mut buffer)? {
343 if received != size {
344 return Err(incorrect_object_size());
345 }
346 let modes = codec::parse_state_modes(self.version, &buffer[..size])?;
347 if modes.message_id > latest_message_id {
348 latest_message_id = modes.message_id;
349 latest[..size].copy_from_slice(&buffer[..size]);
350 }
351 }
352
353 while latest_message_id == last_message_id {
354 let received = self.network.blocking_receive_bytes(&mut buffer)?;
355 if received != size {
356 return Err(incorrect_object_size());
357 }
358 let modes = codec::parse_state_modes(self.version, &buffer[..size])?;
359 if modes.message_id > latest_message_id {
360 latest_message_id = modes.message_id;
361 latest[..size].copy_from_slice(&buffer[..size]);
362 }
363 }
364
365 self.update_state(&codec::parse_state_modes(self.version, &latest[..size])?);
366 codec::parse_robot_state(self.version, &latest[..size])
367 }
368}
369
370fn connect_as(franka_address: &str, version: FciVersion) -> FrankaResult<(Network, u16)> {
387 const _: () = assert!(matches!(
390 codec::command_id(FciVersion::V5, CommandKind::Connect),
391 Some(0)
392 ));
393 const _: () = assert!(matches!(
394 codec::command_id(FciVersion::V10, CommandKind::Connect),
395 Some(0)
396 ));
397
398 let library_version = codec::connect_version(version);
399 let network = Network::connect(franka_address, ROBOT_COMMAND_PORT, HeaderLayout::Robot)?;
400 let ri_version = connect_handshake(&network, library_version)?;
401 if ri_version != library_version {
402 return Err(FrankaError::IncompatibleVersion {
403 server_version: ri_version,
404 library_version,
405 });
406 }
407 Ok((network, ri_version))
408}
409
410fn command_log(command: &RobotCommandData) -> RobotCommandLog {
412 RobotCommandLog {
413 q_c: command.q_c,
414 dq_c: command.dq_c,
415 O_T_EE_c: command.O_T_EE_c,
416 O_dP_EE_c: command.O_dP_EE_c,
417 elbow_c: command.elbow_c,
418 tau_J_d: command.tau_J_d,
419 }
420}
421
422pub(crate) fn control_error(message: &str) -> FrankaError {
424 FrankaError::Control(ControlException::new(message))
425}
426
427pub(crate) fn status_byte(message: &[u8]) -> FrankaResult<u8> {
429 let payload = message_payload(HeaderLayout::Robot, message);
430 payload
431 .first()
432 .copied()
433 .ok_or_else(|| FrankaError::Protocol("libfranka: Incorrect TCP message size.".to_string()))
434}
435
436pub(crate) fn log_error(message: &str) {
438 eprintln!("{message}");
439}
440
441pub(crate) fn log_warn(message: &str) {
443 eprintln!("{message}");
444}
445
446pub(crate) fn time_since(previous: Option<Duration>, now: Duration) -> Duration {
448 match previous {
449 Some(previous) => now - previous,
450 None => Duration::default(),
451 }
452}
453
454#[cfg(test)]
455mod tests;