1use crate::error::{FrankaError, FrankaResult};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ControllerMode {
20 JointImpedance,
22 CartesianImpedance,
24}
25
26pub trait Finishable {
32 fn is_finished(&self) -> bool;
34 fn set_finished(&mut self, finished: bool);
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum MotionGeneratorKind {
45 JointPosition,
47 JointVelocity,
49 CartesianPosition,
51 CartesianVelocity,
53}
54
55pub trait MotionGenerator: Finishable + Copy {
60 const KIND: MotionGeneratorKind;
62}
63
64pub fn motion_finished<T: Finishable>(mut command: T) -> T {
68 command.set_finished(true);
69 command
70}
71
72pub fn is_valid_elbow(elbow: &[f64; 2]) -> bool {
76 elbow[1] == -1.0 || elbow[1] == 1.0
77}
78
79pub fn is_homogeneous_transformation(transform: &[f64; 16]) -> bool {
84 const ORTHONORMAL_THRESHOLD: f64 = 1e-5;
85
86 if transform[3] != 0.0 || transform[7] != 0.0 || transform[11] != 0.0 || transform[15] != 1.0 {
87 return false;
88 }
89 for j in 0..3 {
90 let norm = (transform[j * 4].powi(2)
92 + transform[j * 4 + 1].powi(2)
93 + transform[j * 4 + 2].powi(2))
94 .sqrt();
95 if (norm - 1.0).abs() > ORTHONORMAL_THRESHOLD {
96 return false;
97 }
98 }
99 for i in 0..3 {
100 let norm =
102 (transform[i].powi(2) + transform[4 + i].powi(2) + transform[8 + i].powi(2)).sqrt();
103 if (norm - 1.0).abs() > ORTHONORMAL_THRESHOLD {
104 return false;
105 }
106 }
107 true
108}
109
110pub fn check_finite(values: &[f64]) -> FrankaResult<()> {
114 if values.iter().all(|v| v.is_finite()) {
115 Ok(())
116 } else {
117 Err(FrankaError::InvalidArgument(
118 "Commanding value is infinite or NaN.".to_string(),
119 ))
120 }
121}
122
123pub fn check_matrix(transform: &[f64; 16]) -> FrankaResult<()> {
127 check_finite(transform)?;
128 if !is_homogeneous_transformation(transform) {
129 return Err(FrankaError::InvalidArgument(
130 "libfranka: Attempt to set invalid transformation in motion generator. Has to be \
131 column major!"
132 .to_string(),
133 ));
134 }
135 Ok(())
136}
137
138pub fn check_elbow(elbow: &[f64; 2]) -> FrankaResult<()> {
142 check_finite(elbow)?;
143 if !is_valid_elbow(elbow) {
144 return Err(FrankaError::InvalidArgument(
145 "Invalid elbow configuration given! Only +1 or -1 are allowed for the sign of the 4th \
146 joint."
147 .to_string(),
148 ));
149 }
150 Ok(())
151}
152
153macro_rules! impl_finishable {
154 ($t:ty) => {
155 impl Finishable for $t {
156 fn is_finished(&self) -> bool {
157 self.motion_finished
158 }
159 fn set_finished(&mut self, finished: bool) {
160 self.motion_finished = finished;
161 }
162 }
163 };
164}
165
166#[derive(Debug, Clone, Copy, PartialEq)]
170pub struct Torques {
171 pub tau_J: [f64; 7],
173 pub motion_finished: bool,
175}
176
177impl Torques {
178 pub fn new(tau_J: [f64; 7]) -> Self {
180 Torques {
181 tau_J,
182 motion_finished: false,
183 }
184 }
185
186 pub fn validate(&self) -> FrankaResult<()> {
189 check_finite(&self.tau_J)
190 }
191}
192
193impl_finishable!(Torques);
194
195#[derive(Debug, Clone, Copy, PartialEq)]
199pub struct JointPositions {
200 pub q: [f64; 7],
202 pub motion_finished: bool,
204}
205
206impl JointPositions {
207 pub fn new(q: [f64; 7]) -> Self {
209 JointPositions {
210 q,
211 motion_finished: false,
212 }
213 }
214
215 pub fn validate(&self) -> FrankaResult<()> {
218 check_finite(&self.q)
219 }
220}
221
222impl_finishable!(JointPositions);
223
224impl MotionGenerator for JointPositions {
225 const KIND: MotionGeneratorKind = MotionGeneratorKind::JointPosition;
226}
227
228#[derive(Debug, Clone, Copy, PartialEq)]
232pub struct JointVelocities {
233 pub dq: [f64; 7],
235 pub motion_finished: bool,
237}
238
239impl JointVelocities {
240 pub fn new(dq: [f64; 7]) -> Self {
242 JointVelocities {
243 dq,
244 motion_finished: false,
245 }
246 }
247
248 pub fn validate(&self) -> FrankaResult<()> {
251 check_finite(&self.dq)
252 }
253}
254
255impl_finishable!(JointVelocities);
256
257impl MotionGenerator for JointVelocities {
258 const KIND: MotionGeneratorKind = MotionGeneratorKind::JointVelocity;
259}
260
261#[derive(Debug, Clone, Copy, PartialEq)]
265pub struct CartesianPose {
266 pub O_T_EE: [f64; 16],
269 pub elbow: [f64; 2],
272 pub has_elbow: bool,
277 pub motion_finished: bool,
279}
280
281impl CartesianPose {
282 pub fn new(O_T_EE: [f64; 16]) -> Self {
286 CartesianPose {
287 O_T_EE,
288 elbow: [0.0; 2],
289 has_elbow: false,
290 motion_finished: false,
291 }
292 }
293
294 pub fn with_elbow(O_T_EE: [f64; 16], elbow: [f64; 2]) -> Self {
296 CartesianPose {
297 O_T_EE,
298 elbow,
299 has_elbow: elbow != [0.0, 0.0],
300 motion_finished: false,
301 }
302 }
303
304 pub fn validate(&self) -> FrankaResult<()> {
307 check_matrix(&self.O_T_EE)?;
308 if self.has_elbow {
309 check_elbow(&self.elbow)?;
310 }
311 Ok(())
312 }
313}
314
315impl_finishable!(CartesianPose);
316
317impl MotionGenerator for CartesianPose {
318 const KIND: MotionGeneratorKind = MotionGeneratorKind::CartesianPosition;
319}
320
321#[derive(Debug, Clone, Copy, PartialEq)]
325pub struct CartesianVelocities {
326 pub O_dP_EE: [f64; 6],
329 pub elbow: [f64; 2],
331 pub has_elbow: bool,
333 pub motion_finished: bool,
335}
336
337impl CartesianVelocities {
338 pub fn new(O_dP_EE: [f64; 6]) -> Self {
340 CartesianVelocities {
341 O_dP_EE,
342 elbow: [0.0; 2],
343 has_elbow: false,
344 motion_finished: false,
345 }
346 }
347
348 pub fn with_elbow(O_dP_EE: [f64; 6], elbow: [f64; 2]) -> Self {
350 CartesianVelocities {
351 O_dP_EE,
352 elbow,
353 has_elbow: elbow != [0.0, 0.0],
354 motion_finished: false,
355 }
356 }
357
358 pub fn validate(&self) -> FrankaResult<()> {
361 check_finite(&self.O_dP_EE)?;
362 if self.has_elbow {
363 check_elbow(&self.elbow)?;
364 }
365 Ok(())
366 }
367}
368
369impl_finishable!(CartesianVelocities);
370
371impl MotionGenerator for CartesianVelocities {
372 const KIND: MotionGeneratorKind = MotionGeneratorKind::CartesianVelocity;
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 const IDENTITY: [f64; 16] = [
380 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
381 ];
382
383 fn message(result: FrankaResult<()>) -> String {
384 match result {
385 Err(FrankaError::InvalidArgument(msg)) => msg,
386 other => panic!("expected InvalidArgument, got {other:?}"),
387 }
388 }
389
390 #[test]
392 fn torques_can_construct_from_array() {
393 let array = [0., 1., 2., 3., 4., 5., 6.];
394 let t = Torques::new(array);
395 assert_eq!(array, t.tau_J);
396 assert!(!t.motion_finished);
397 assert!(t.validate().is_ok());
398 }
399
400 #[test]
402 fn joint_positions_can_construct_from_array() {
403 let array = [0., 1., 2., 3., 4., 5., 6.];
404 let jp = JointPositions::new(array);
405 assert_eq!(array, jp.q);
406 assert!(jp.validate().is_ok());
407 }
408
409 #[test]
411 fn joint_velocities_can_construct_from_array() {
412 let array = [0., 1., 2., 3., 4., 5., 6.];
413 let jv = JointVelocities::new(array);
414 assert_eq!(array, jv.dq);
415 assert!(jv.validate().is_ok());
416 }
417
418 #[test]
420 fn cartesian_pose_can_construct_from_array() {
421 let p = CartesianPose::new(IDENTITY);
422 assert_eq!(IDENTITY, p.O_T_EE);
423 assert!(!p.has_elbow);
424 assert!(p.validate().is_ok());
425 }
426
427 #[test]
429 fn cartesian_pose_can_construct_from_array_with_elbow() {
430 let elbow = [0., -1.];
431 let p = CartesianPose::with_elbow(IDENTITY, elbow);
432 assert_eq!(IDENTITY, p.O_T_EE);
433 assert_eq!(elbow, p.elbow);
434 assert!(p.has_elbow);
435 assert!(p.validate().is_ok());
436 }
437
438 #[test]
440 fn cartesian_velocities_can_construct_from_array() {
441 let array = [0., 1., 2., 3., 4., 5.];
442 let cv = CartesianVelocities::new(array);
443 assert_eq!(array, cv.O_dP_EE);
444 assert!(!cv.has_elbow);
445 assert!(cv.validate().is_ok());
446 }
447
448 #[test]
450 fn cartesian_velocities_can_construct_from_array_with_elbow() {
451 let array = [0., 1., 2., 3., 4., 5.];
452 let elbow = [0., 1.];
453 let cv = CartesianVelocities::with_elbow(array, elbow);
454 assert_eq!(array, cv.O_dP_EE);
455 assert_eq!(elbow, cv.elbow);
456 assert!(cv.has_elbow);
457 assert!(cv.validate().is_ok());
458 }
459
460 #[test]
462 fn zero_elbow_is_no_elbow() {
463 assert!(!CartesianPose::with_elbow(IDENTITY, [0., 0.]).has_elbow);
464 assert!(!CartesianVelocities::with_elbow([0.; 6], [0., 0.]).has_elbow);
465 }
466
467 #[test]
468 fn motion_finished_sets_the_flag() {
469 assert!(motion_finished(Torques::new([0.; 7])).motion_finished);
470 assert!(motion_finished(JointPositions::new([0.; 7])).motion_finished);
471 assert!(motion_finished(JointVelocities::new([0.; 7])).motion_finished);
472 assert!(motion_finished(CartesianPose::new(IDENTITY)).motion_finished);
473 assert!(motion_finished(CartesianVelocities::new([0.; 6])).motion_finished);
474 }
475
476 #[test]
477 fn motion_generator_kinds() {
478 assert_eq!(JointPositions::KIND, MotionGeneratorKind::JointPosition);
479 assert_eq!(JointVelocities::KIND, MotionGeneratorKind::JointVelocity);
480 assert_eq!(CartesianPose::KIND, MotionGeneratorKind::CartesianPosition);
481 assert_eq!(
482 CartesianVelocities::KIND,
483 MotionGeneratorKind::CartesianVelocity
484 );
485 }
486
487 #[test]
489 fn non_finite_values_are_rejected() {
490 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
491 let mut q = [0.; 7];
492 q[3] = bad;
493 assert_eq!(
494 message(Torques::new(q).validate()),
495 "Commanding value is infinite or NaN."
496 );
497 assert_eq!(
498 message(JointPositions::new(q).validate()),
499 "Commanding value is infinite or NaN."
500 );
501 assert_eq!(
502 message(JointVelocities::new(q).validate()),
503 "Commanding value is infinite or NaN."
504 );
505 let mut dx = [0.; 6];
506 dx[2] = bad;
507 assert_eq!(
508 message(CartesianVelocities::new(dx).validate()),
509 "Commanding value is infinite or NaN."
510 );
511 let mut pose = IDENTITY;
512 pose[0] = bad;
513 assert_eq!(
514 message(CartesianPose::new(pose).validate()),
515 "Commanding value is infinite or NaN."
516 );
517 }
518 }
519
520 #[test]
522 fn invalid_transformation_is_rejected() {
523 assert!(is_homogeneous_transformation(&IDENTITY));
524
525 let mut pose = IDENTITY;
527 pose[3] = 1.0;
528 assert!(!is_homogeneous_transformation(&pose));
529 assert_eq!(
530 message(CartesianPose::new(pose).validate()),
531 "libfranka: Attempt to set invalid transformation in motion generator. Has to be \
532 column major!"
533 );
534
535 for idx in [7usize, 11] {
536 let mut pose = IDENTITY;
537 pose[idx] = 1.0;
538 assert!(!is_homogeneous_transformation(&pose));
539 }
540 let mut pose = IDENTITY;
541 pose[15] = 0.0;
542 assert!(!is_homogeneous_transformation(&pose));
543
544 let mut pose = IDENTITY;
546 pose[0] = 2.0;
547 assert!(!is_homogeneous_transformation(&pose));
548
549 let pose = [
551 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
552 ];
553 assert!(is_homogeneous_transformation(&pose));
554 let pose = [
555 0.6, 0.8, 0., 0., 0.8, -0.6, 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
556 ];
557 assert!(is_homogeneous_transformation(&pose));
558
559 let mut pose = IDENTITY;
561 pose[0] = 1.0 + 9e-6;
562 assert!(is_homogeneous_transformation(&pose));
563 pose[0] = 1.0 + 2e-5;
564 assert!(!is_homogeneous_transformation(&pose));
565 }
566
567 #[test]
569 fn invalid_elbow_is_rejected() {
570 assert!(is_valid_elbow(&[0.5, 1.0]));
571 assert!(is_valid_elbow(&[0.5, -1.0]));
572 assert!(!is_valid_elbow(&[0.5, 0.5]));
573
574 let expected = "Invalid elbow configuration given! Only +1 or -1 are allowed for the sign \
575 of the 4th joint.";
576 assert_eq!(
577 message(CartesianPose::with_elbow(IDENTITY, [0.5, 0.5]).validate()),
578 expected
579 );
580 assert_eq!(
581 message(CartesianVelocities::with_elbow([0.; 6], [0.5, 0.5]).validate()),
582 expected
583 );
584 assert_eq!(
586 message(CartesianPose::with_elbow(IDENTITY, [f64::NAN, 1.0]).validate()),
587 "Commanding value is infinite or NaN."
588 );
589 assert!(CartesianPose::new(IDENTITY).validate().is_ok());
591 assert!(CartesianVelocities::new([0.; 6]).validate().is_ok());
592 }
593}