1use crate::errors::Errors;
4use crate::robot_state::RobotState;
5
6pub type FrankaResult<T> = Result<T, FrankaError>;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum MoveStatus {
13 Success,
16 MotionStarted,
19 Preempted,
21 PreemptedDueToActivatedSafetyFunctions,
24 CommandRejectedDueToActivatedSafetyFunctions,
27 CommandNotPossibleRejected,
30 StartAtSingularPoseRejected,
33 InvalidArgumentRejected,
35 ReflexAborted,
38 EmergencyAborted,
40 InputErrorAborted,
43 Aborted,
45}
46
47impl MoveStatus {
48 pub const fn to_u8(self) -> u8 {
50 self as u8
51 }
52
53 pub const fn from_u8(v: u8) -> Option<MoveStatus> {
55 Some(match v {
56 0 => MoveStatus::Success,
57 1 => MoveStatus::MotionStarted,
58 2 => MoveStatus::Preempted,
59 3 => MoveStatus::PreemptedDueToActivatedSafetyFunctions,
60 4 => MoveStatus::CommandRejectedDueToActivatedSafetyFunctions,
61 5 => MoveStatus::CommandNotPossibleRejected,
62 6 => MoveStatus::StartAtSingularPoseRejected,
63 7 => MoveStatus::InvalidArgumentRejected,
64 8 => MoveStatus::ReflexAborted,
65 9 => MoveStatus::EmergencyAborted,
66 10 => MoveStatus::InputErrorAborted,
67 11 => MoveStatus::Aborted,
68 _ => return None,
69 })
70 }
71}
72
73#[derive(Debug, Clone, Copy, Default, PartialEq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct RobotCommandLog {
77 pub q_c: [f64; 7],
79 pub dq_c: [f64; 7],
81 pub O_T_EE_c: [f64; 16],
84 pub O_dP_EE_c: [f64; 6],
86 pub elbow_c: [f64; 2],
88 pub tau_J_d: [f64; 7],
90}
91
92#[derive(Debug, Clone, PartialEq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct Record {
98 pub state: RobotState,
100 pub command: Option<RobotCommandLog>,
102}
103
104#[derive(Debug, Clone)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub struct ControlException {
111 pub message: String,
114 pub move_status: Option<MoveStatus>,
116 pub last_motion_errors: Errors,
118 pub log: Vec<Record>,
120}
121
122impl ControlException {
123 pub fn new(message: impl Into<String>) -> Self {
125 ControlException {
126 message: message.into(),
127 move_status: None,
128 last_motion_errors: Errors::default(),
129 log: Vec::new(),
130 }
131 }
132}
133
134impl std::fmt::Display for ControlException {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 f.write_str(&self.message)
137 }
138}
139
140impl std::error::Error for ControlException {}
141
142#[derive(Debug, Clone, thiserror::Error)]
144pub enum FrankaError {
145 #[error("{0}")]
147 Network(String),
148 #[error("{0}")]
150 Protocol(String),
151 #[error("libfranka: Incompatible library version (server version: {server_version}, library version: {library_version}).")]
153 IncompatibleVersion {
154 server_version: u16,
156 library_version: u16,
158 },
159 #[error("{0}")]
161 Command(String),
162 #[error("{0}")]
164 Control(ControlException),
165 #[error("{0}")]
167 Realtime(String),
168 #[error("{0}")]
170 InvalidOperation(String),
171 #[error("{0}")]
173 InvalidArgument(String),
174 #[error("{0}")]
176 Model(String),
177}
178
179impl From<ControlException> for FrankaError {
180 fn from(e: ControlException) -> Self {
181 FrankaError::Control(e)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn incompatible_version_text_matches_libfranka() {
191 let e = FrankaError::IncompatibleVersion {
192 server_version: 10,
193 library_version: 9,
194 };
195 assert_eq!(
196 e.to_string(),
197 "libfranka: Incompatible library version (server version: 10, library version: 9)."
198 );
199 }
200
201 #[test]
202 fn move_status_roundtrip() {
203 for v in 0..=11u8 {
204 let s = MoveStatus::from_u8(v).unwrap();
205 assert_eq!(s.to_u8(), v);
206 }
207 assert!(MoveStatus::from_u8(12).is_none());
208 }
209
210 #[cfg(feature = "serde")]
211 #[test]
212 fn record_and_exception_round_trip_through_json() {
213 use crate::duration::Duration;
214 use crate::robot_state::RobotMode;
215
216 let mut state = RobotState {
217 time: Duration::from_millis(123_456),
218 robot_mode: RobotMode::Reflex,
219 ..RobotState::default()
220 };
221 state.q = [0.1, -0.2, 0.3, -2.0, 0.5, 1.6, 0.7];
222 state.joint_contact[3] = 1.0;
223 state.O_F_ext_hat_K = [1.0, 2.0, 3.0, 0.1, 0.2, 0.3];
224 state.current_errors.0[Errors::index_of("cartesian_reflex").unwrap()] = true;
225 let record = Record {
226 state,
227 command: Some(RobotCommandLog {
228 q_c: [0.1, -0.2, 0.3, -2.0, 0.5, 1.6, 0.7],
229 ..RobotCommandLog::default()
230 }),
231 };
232 let json = serde_json::to_string(&record).unwrap();
233 assert!(
234 json.contains("\"current_errors\":[\"cartesian_reflex\"]"),
235 "{json}"
236 );
237 assert!(json.contains("\"time\":123456"), "{json}");
238 let back: Record = serde_json::from_str(&json).unwrap();
239 assert_eq!(back, record);
240 assert_eq!(back.state.q[3], -2.0);
241 assert_eq!(back.state.joint_contact[3], 1.0);
242 assert_eq!(back.state.current_errors, state.current_errors);
243 assert_eq!(back.command.unwrap().q_c[5], 1.6);
244
245 let exception = ControlException {
246 message: "libfranka: Move command aborted: motion aborted by reflex!".into(),
247 move_status: Some(MoveStatus::ReflexAborted),
248 last_motion_errors: state.current_errors,
249 log: vec![record.clone(), record],
250 };
251 let json = serde_json::to_string(&exception).unwrap();
252 let back: ControlException = serde_json::from_str(&json).unwrap();
253 assert_eq!(back.message, exception.message);
254 assert_eq!(back.move_status, Some(MoveStatus::ReflexAborted));
255 assert_eq!(back.last_motion_errors, exception.last_motion_errors);
256 assert_eq!(back.log, exception.log);
257 }
258}