1use motor_codec::{
4 BusCapabilities, CanFrame, CodecError, Command, CommandKind, Event, FrameFlags, Limits,
5 MotorCodec, MotorRef, MotorTypeId, ParamValue,
6};
7
8use crate::bitpack::{pack_mit_payload, unpack_state_payload};
9use crate::limits::limits_for;
10use crate::types::DamiaoMotorType;
11
12const VENDOR: &str = "damiao";
13
14#[derive(Default, Debug, Clone)]
20pub struct DamiaoCodec {
21 bound_caps: Option<BusCapabilities>,
22}
23
24impl DamiaoCodec {
25 pub fn new() -> Self {
27 Self { bound_caps: None }
28 }
29
30 fn motor_type(&self, id: MotorTypeId) -> Result<DamiaoMotorType, CodecError> {
31 match id {
32 MotorTypeId::Damiao(d) => {
33 DamiaoMotorType::from_discriminant(d).ok_or(CodecError::UnknownMotorType {
34 vendor: VENDOR,
35 type_id: d,
36 })
37 }
38 _ => Err(CodecError::UnknownMotorType {
39 vendor: VENDOR,
40 type_id: 0,
41 }),
42 }
43 }
44
45 fn encode_special(
46 &self,
47 motor: MotorRef<'_>,
48 trailing_byte: u8,
49 ) -> Result<CanFrame, CodecError> {
50 let payload = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, trailing_byte];
51 CanFrame::classical(motor.send_id, &payload).map_err(|_| CodecError::DecodeFailed {
52 reason: "classical frame construction failed",
53 })
54 }
55}
56
57impl MotorCodec for DamiaoCodec {
58 fn vendor_name(&self) -> &'static str {
59 VENDOR
60 }
61
62 fn supports(&self, motor_type: MotorTypeId) -> bool {
63 matches!(motor_type, MotorTypeId::Damiao(d) if DamiaoMotorType::from_discriminant(d).is_some())
64 }
65
66 fn limits(&self, motor_type: MotorTypeId) -> Result<Limits, CodecError> {
67 let t = self.motor_type(motor_type)?;
68 Ok(limits_for(t))
69 }
70
71 fn bind_to_bus(&mut self, caps: BusCapabilities) {
72 self.bound_caps = Some(caps);
77 }
78
79 fn encode_enable(&self, motor: MotorRef<'_>) -> Result<CanFrame, CodecError> {
80 self.encode_special(motor, 0xFC)
81 }
82
83 fn encode_disable(&self, motor: MotorRef<'_>) -> Result<CanFrame, CodecError> {
84 self.encode_special(motor, 0xFD)
85 }
86
87 fn encode_set_zero(&self, motor: MotorRef<'_>) -> Result<CanFrame, CodecError> {
88 self.encode_special(motor, 0xFE)
89 }
90
91 fn encode_command(&self, motor: MotorRef<'_>, cmd: &Command) -> Result<CanFrame, CodecError> {
92 let t = self.motor_type(motor.motor_type)?;
93 let lim = limits_for(t);
94 match cmd {
95 Command::Mit { kp, kd, q, dq, tau } => {
96 check_range("q", *q, lim.p_max)?;
97 check_range("dq", *dq, lim.v_max)?;
98 check_range("tau", *tau, lim.t_max)?;
99 check_unsigned("kp", *kp, 500.0)?;
100 check_unsigned("kd", *kd, 5.0)?;
101 let payload =
102 pack_mit_payload(*q, *dq, *kp, *kd, *tau, lim.p_max, lim.v_max, lim.t_max);
103 CanFrame::classical(motor.send_id, &payload).map_err(|_| CodecError::DecodeFailed {
104 reason: "MIT frame construction failed",
105 })
106 }
107 Command::PosVel { q, dq } => {
108 let mut payload = [0u8; 8];
109 payload[0..4].copy_from_slice(&(*q as f32).to_le_bytes());
110 payload[4..8].copy_from_slice(&(*dq as f32).to_le_bytes());
111 CanFrame::classical(0x100 + motor.send_id, &payload).map_err(|_| {
112 CodecError::DecodeFailed {
113 reason: "PosVel frame construction failed",
114 }
115 })
116 }
117 Command::Vel { dq } => {
118 let mut payload = [0u8; 8];
119 payload[0..4].copy_from_slice(&(*dq as f32).to_le_bytes());
120 CanFrame::classical(0x200 + motor.send_id, &payload).map_err(|_| {
121 CodecError::DecodeFailed {
122 reason: "Vel frame construction failed",
123 }
124 })
125 }
126 Command::PosForce { q, dq, i_pu } => {
127 let mut payload = [0u8; 8];
128 payload[0..4].copy_from_slice(&(*q as f32).to_le_bytes());
129 let dq_u = (dq * 100.0) as u16;
130 let i_u = (i_pu * 10000.0) as u16;
131 payload[4..6].copy_from_slice(&dq_u.to_le_bytes());
132 payload[6..8].copy_from_slice(&i_u.to_le_bytes());
133 CanFrame::classical(0x300 + motor.send_id, &payload).map_err(|_| {
134 CodecError::DecodeFailed {
135 reason: "PosForce frame construction failed",
136 }
137 })
138 }
139 _ => Err(CodecError::CommandNotSupported {
140 vendor: VENDOR,
141 mode: cmd.kind(),
142 }),
143 }
144 }
145
146 fn encode_refresh(&self, motor: MotorRef<'_>) -> Result<Option<CanFrame>, CodecError> {
147 Ok(Some(crate::DamiaoCodecExt::encode_refresh(self, motor)))
151 }
152
153 fn encode_set_mode(
154 &self,
155 motor: MotorRef<'_>,
156 mode: CommandKind,
157 ) -> Result<Option<CanFrame>, CodecError> {
158 let value: u32 = match mode {
160 CommandKind::Mit => 1,
161 CommandKind::PosVel => 2,
162 CommandKind::Vel => 3,
163 CommandKind::PosForce => 4,
164 _ => {
165 return Err(CodecError::CommandNotSupported {
166 vendor: VENDOR,
167 mode,
168 })
169 }
170 };
171 Ok(Some(crate::DamiaoCodecExt::encode_write_param(
172 self,
173 motor,
174 crate::DamiaoRid::CTRL_MODE,
175 ParamValue::UInt(value),
176 )))
177 }
178
179 fn encode_control_mode_readback(
180 &self,
181 motor: MotorRef<'_>,
182 ) -> Result<Option<CanFrame>, CodecError> {
183 Ok(Some(crate::DamiaoCodecExt::encode_read_param(
184 self,
185 motor,
186 crate::DamiaoRid::CTRL_MODE,
187 )))
188 }
189
190 fn decode_control_mode_readback(
191 &self,
192 frame: &CanFrame,
193 motor: MotorRef<'_>,
194 ) -> Result<Option<u32>, CodecError> {
195 if frame.len != 8 {
196 return Ok(None);
197 }
198 let p = frame.payload();
199 let send_id = u16::from_le_bytes([p[0], p[1]]) as u32;
200 if send_id != motor.send_id || p[2] != 0x33 || p[3] != u8::from(crate::DamiaoRid::CTRL_MODE)
201 {
202 return Ok(None);
203 }
204 Ok(Some(u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
205 }
206
207 fn decode(&self, frame: &CanFrame) -> Result<Option<Event>, CodecError> {
208 if frame.is_fd() && !self.bound_caps.is_some_and(|c| c.supports_fd) {
211 return Ok(None);
214 }
215 if frame.flags.contains(FrameFlags::REMOTE_REQUEST) {
216 return Ok(None);
217 }
218 if frame.id == 0x7FF || (frame.payload()[2] == 0x33 || frame.payload()[2] == 0x55) {
221 return Ok(None);
222 }
223 if frame.len != 8 {
224 return Ok(None);
226 }
227 let byte0 = frame.payload()[0];
228 let reported_id = byte0 & 0x0f;
229 let payload: [u8; 8] =
234 frame
235 .payload()
236 .try_into()
237 .map_err(|_| CodecError::DecodeFailed {
238 reason: "expected 8-byte payload",
239 })?;
240 let dm = limits_for(DamiaoMotorType::DM4340);
241 let (_, _, q, dq, tau, t_mos, t_rotor) =
242 unpack_state_payload(&payload, dm.p_max, dm.v_max, dm.t_max);
243 if reported_id == 0 && frame.id == 0 {
244 return Ok(None);
245 }
246 Ok(Some(Event::State {
247 motor_id: frame.id,
248 q,
249 dq,
250 tau,
251 t_mos,
252 t_rotor,
253 }))
254 }
255}
256
257fn check_range(field: &'static str, x: f64, magnitude: f64) -> Result<(), CodecError> {
258 if x.abs() > magnitude {
259 Err(CodecError::OutOfRange { field })
260 } else {
261 Ok(())
262 }
263}
264
265fn check_unsigned(field: &'static str, x: f64, max: f64) -> Result<(), CodecError> {
266 if !(0.0..=max).contains(&x) {
267 Err(CodecError::OutOfRange { field })
268 } else {
269 Ok(())
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use alloc::boxed::Box;
277
278 fn ref_motor(name: &str, mt: DamiaoMotorType, send: u32, recv: u32) -> MotorRef<'_> {
279 MotorRef {
280 motor_type: mt.into(),
281 send_id: send,
282 recv_id: recv,
283 name,
284 }
285 }
286
287 #[test]
288 fn constructible_as_trait_object() {
289 let c: Box<dyn MotorCodec> = Box::new(DamiaoCodec::new());
290 assert_eq!(c.vendor_name(), "damiao");
291 }
292
293 #[test]
294 fn supports_all_known_skus() {
295 let c = DamiaoCodec::new();
296 for d in 0..=12u16 {
297 assert!(c.supports(MotorTypeId::Damiao(d)), "discriminant {d}");
298 assert!(c.limits(MotorTypeId::Damiao(d)).is_ok());
299 }
300 }
301
302 #[test]
303 fn rejects_unknown_and_other_vendors() {
304 let c = DamiaoCodec::new();
305 assert!(!c.supports(MotorTypeId::Damiao(0xFFFF)));
306 assert!(!c.supports(MotorTypeId::Robostride(0)));
307 assert!(matches!(
308 c.limits(MotorTypeId::Damiao(0xFFFF)),
309 Err(CodecError::UnknownMotorType {
310 vendor: "damiao",
311 type_id: 0xFFFF,
312 })
313 ));
314 }
315
316 #[test]
317 fn enable_disable_setzero_byte_patterns() {
318 let c = DamiaoCodec::new();
319 let m = ref_motor("g", DamiaoMotorType::DM4310, 0x05, 0x18);
320 let e = c.encode_enable(m).unwrap();
321 assert_eq!(e.id, 0x05);
322 assert_eq!(e.len, 8);
323 assert_eq!(
324 e.payload(),
325 &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC]
326 );
327
328 let d = c.encode_disable(m).unwrap();
329 assert_eq!(d.payload()[7], 0xFD);
330 let z = c.encode_set_zero(m).unwrap();
331 assert_eq!(z.payload()[7], 0xFE);
332 }
333
334 #[test]
335 fn mit_frame_id_equals_send_id_and_not_fd() {
336 let c = DamiaoCodec::new();
337 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
338 let f = c
339 .encode_command(
340 m,
341 &Command::Mit {
342 kp: 50.0,
343 kd: 1.0,
344 q: 0.0,
345 dq: 0.0,
346 tau: 0.0,
347 },
348 )
349 .unwrap();
350 assert_eq!(f.id, 0x01);
351 assert_eq!(f.len, 8);
352 assert!(!f.is_fd());
353 }
354
355 #[test]
356 fn mit_out_of_range_tau() {
357 let c = DamiaoCodec::new();
358 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
359 let r = c.encode_command(
360 m,
361 &Command::Mit {
362 kp: 0.0,
363 kd: 0.0,
364 q: 0.0,
365 dq: 0.0,
366 tau: 1000.0,
367 },
368 );
369 assert!(matches!(r, Err(CodecError::OutOfRange { field: "tau" })));
370 }
371
372 #[test]
373 fn posvel_uses_0x100_offset() {
374 let c = DamiaoCodec::new();
375 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
376 let f = c
377 .encode_command(m, &Command::PosVel { q: 1.0, dq: 2.0 })
378 .unwrap();
379 assert_eq!(f.id, 0x101);
380 assert_eq!(&f.payload()[0..4], &1.0f32.to_le_bytes());
381 assert_eq!(&f.payload()[4..8], &2.0f32.to_le_bytes());
382 }
383
384 #[test]
385 fn vel_uses_0x200_offset() {
386 let c = DamiaoCodec::new();
387 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
388 let f = c.encode_command(m, &Command::Vel { dq: 1.5 }).unwrap();
389 assert_eq!(f.id, 0x201);
390 assert_eq!(&f.payload()[0..4], &1.5f32.to_le_bytes());
391 }
392
393 #[test]
394 fn posforce_uses_0x300_offset_with_integer_scaling() {
395 let c = DamiaoCodec::new();
396 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
397 let f = c
398 .encode_command(
399 m,
400 &Command::PosForce {
401 q: 1.0,
402 dq: 2.0,
403 i_pu: 0.5,
404 },
405 )
406 .unwrap();
407 assert_eq!(f.id, 0x301);
408 assert_eq!(&f.payload()[0..4], &1.0f32.to_le_bytes());
409 assert_eq!(&f.payload()[4..6], &200u16.to_le_bytes());
410 assert_eq!(&f.payload()[6..8], &5000u16.to_le_bytes());
411 }
412
413 #[test]
414 fn decode_foreign_frame_returns_none() {
415 let c = DamiaoCodec::new();
416 let f = CanFrame::classical(0x00, &[0x00; 8]).unwrap();
417 assert!(matches!(c.decode(&f), Ok(None)));
418 }
419
420 #[test]
421 fn decode_truncated_returns_none() {
422 let c = DamiaoCodec::new();
423 let f = CanFrame::classical(0x11, &[0x11, 0x00, 0x00]).unwrap();
424 assert!(matches!(c.decode(&f), Ok(None)));
425 }
426
427 #[test]
428 fn decode_state_frame_round_trips_within_lsb() {
429 let c = DamiaoCodec::new();
430 let lim = limits_for(DamiaoMotorType::DM4340);
431 let (q, dq, tau) = (1.0, 0.5, 5.0);
432 let q_u = crate::bitpack::float_to_uint(q, -lim.p_max, lim.p_max, 16);
433 let dq_u = crate::bitpack::float_to_uint(dq, -lim.v_max, lim.v_max, 12);
434 let tau_u = crate::bitpack::float_to_uint(tau, -lim.t_max, lim.t_max, 12);
435 let payload = [
436 0x01,
438 ((q_u >> 8) & 0xff) as u8,
439 (q_u & 0xff) as u8,
440 ((dq_u >> 4) & 0xff) as u8,
441 ((((dq_u & 0xf) << 4) | ((tau_u >> 8) & 0xf)) & 0xff) as u8,
442 (tau_u & 0xff) as u8,
443 30,
444 35,
445 ];
446 let f = CanFrame::classical(0x11, &payload).unwrap();
447 match c.decode(&f).unwrap().unwrap() {
448 Event::State {
449 motor_id,
450 q: qo,
451 dq: dqo,
452 tau: tauo,
453 t_mos,
454 t_rotor,
455 } => {
456 assert_eq!(motor_id, 0x11);
457 assert!((qo - q).abs() < 0.001);
458 assert!((dqo - dq).abs() < 0.01);
459 assert!((tauo - tau).abs() < 0.05);
460 assert_eq!(t_mos, 30);
461 assert_eq!(t_rotor, 35);
462 }
463 _ => panic!("expected State"),
464 }
465 }
466
467 #[test]
468 fn decode_dm4310_enable_disable_replies_from_socketcan_capture() {
469 let c = DamiaoCodec::new();
470 for payload in [
471 [0x18, 0x81, 0x07, 0x7F, 0xE7, 0xFF, 0x1D, 0x1B],
472 [0x08, 0x81, 0x07, 0x80, 0x07, 0xFF, 0x1D, 0x1B],
473 ] {
474 match c
475 .decode(&CanFrame::classical(0x18, &payload).unwrap())
476 .unwrap()
477 {
478 Some(Event::State {
479 motor_id,
480 t_mos,
481 t_rotor,
482 ..
483 }) => {
484 assert_eq!(motor_id, 0x18);
485 assert_eq!(t_mos, 29);
486 assert_eq!(t_rotor, 27);
487 }
488 other => panic!("expected State, got {other:?}"),
489 }
490 }
491 }
492
493 #[test]
494 fn classical_emission_regardless_of_bound_caps() {
495 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
496 for caps in [BusCapabilities::classical(), BusCapabilities::fd()] {
497 let mut c = DamiaoCodec::new();
498 c.bind_to_bus(caps);
499 let f = c
500 .encode_command(
501 m,
502 &Command::Mit {
503 kp: 0.0,
504 kd: 0.0,
505 q: 0.0,
506 dq: 0.0,
507 tau: 0.0,
508 },
509 )
510 .unwrap();
511 assert!(!f.is_fd(), "caps={caps:?} produced FD frame");
512 assert_eq!(f.len, 8);
513 }
514 }
515
516 #[test]
519 fn classical_binding_byte_identical_to_unbound() {
520 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
521 let cmd = Command::Mit {
522 kp: 50.0,
523 kd: 1.0,
524 q: 0.25,
525 dq: -0.5,
526 tau: 0.1,
527 };
528 let unbound = DamiaoCodec::new().encode_command(m, &cmd).unwrap();
529 let mut classical = DamiaoCodec::new();
530 classical.bind_to_bus(BusCapabilities::classical());
531 let mut fd = DamiaoCodec::new();
532 fd.bind_to_bus(BusCapabilities::fd());
533 let cf = classical.encode_command(m, &cmd).unwrap();
534 let ff = fd.encode_command(m, &cmd).unwrap();
535 assert!(!cf.is_fd() && !ff.is_fd() && !unbound.is_fd());
536 assert_eq!(cf.id, unbound.id);
537 assert_eq!(cf.payload(), unbound.payload());
538 assert_eq!(ff.payload(), unbound.payload());
539 }
540
541 #[test]
545 fn fd_state_frame_decoded_only_when_bound_fd() {
546 let lim = limits_for(DamiaoMotorType::DM4340);
547 let (q, dq, tau) = (1.0, 0.5, 5.0);
548 let q_u = crate::bitpack::float_to_uint(q, -lim.p_max, lim.p_max, 16);
549 let dq_u = crate::bitpack::float_to_uint(dq, -lim.v_max, lim.v_max, 12);
550 let tau_u = crate::bitpack::float_to_uint(tau, -lim.t_max, lim.t_max, 12);
551 let payload = [
552 0x01,
553 ((q_u >> 8) & 0xff) as u8,
554 (q_u & 0xff) as u8,
555 ((dq_u >> 4) & 0xff) as u8,
556 ((((dq_u & 0xf) << 4) | ((tau_u >> 8) & 0xf)) & 0xff) as u8,
557 (tau_u & 0xff) as u8,
558 30,
559 35,
560 ];
561 let fd_frame = CanFrame::fd(0x11, &payload).unwrap();
563 assert!(fd_frame.is_fd());
564
565 assert!(matches!(DamiaoCodec::new().decode(&fd_frame), Ok(None)));
567 let mut classical = DamiaoCodec::new();
568 classical.bind_to_bus(BusCapabilities::classical());
569 assert!(matches!(classical.decode(&fd_frame), Ok(None)));
570
571 let mut fd = DamiaoCodec::new();
573 fd.bind_to_bus(BusCapabilities::fd());
574 match fd.decode(&fd_frame).unwrap().unwrap() {
575 Event::State { motor_id, .. } => assert_eq!(motor_id, 0x11),
576 _ => panic!("expected State"),
577 }
578 }
579
580 #[test]
581 fn encode_refresh_matches_openarm_layout() {
582 let c = DamiaoCodec::new();
583 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
584 let f = MotorCodec::encode_refresh(&c, m)
586 .unwrap()
587 .expect("damiao supports refresh");
588 assert_eq!(f.id, 0x7FF);
589 assert_eq!(f.len, 8);
590 assert_eq!(f.payload(), &[0x01, 0x00, 0xCC, 0, 0, 0, 0, 0]);
591 assert!(!f.is_fd());
592 }
593
594 #[test]
595 fn encode_set_mode_writes_ctrl_mode_register() {
596 let c = DamiaoCodec::new();
597 let m = ref_motor("j0", DamiaoMotorType::DM4340, 0x01, 0x11);
598 let f = MotorCodec::encode_set_mode(&c, m, CommandKind::Mit)
599 .unwrap()
600 .expect("damiao supports set_mode");
601 assert_eq!(f.id, 0x7FF);
602 assert_eq!(f.len, 8);
603 let p = f.payload();
604 assert_eq!(&p[0..2], &0x01u16.to_le_bytes()); assert_eq!(p[2], 0x55); assert_eq!(p[3], 10); assert_eq!(&p[4..8], &1u32.to_le_bytes()); }
609
610 #[test]
611 fn ctrl_mode_readback_is_contextual_and_not_state() {
612 let c = DamiaoCodec::new();
613 let m = ref_motor("g", DamiaoMotorType::DM4310, 0x05, 0x18);
614 let query = c.encode_control_mode_readback(m).unwrap().unwrap();
615 assert_eq!(query.id, 0x7FF);
616 assert_eq!(&query.payload()[0..4], &[0x05, 0x00, 0x33, 0x0A]);
617
618 let mut response = [0u8; 8];
619 response[0..2].copy_from_slice(&0x05u16.to_le_bytes());
620 response[2] = 0x33;
621 response[3] = 0x0A;
622 response[4..8].copy_from_slice(&4u32.to_le_bytes());
623 let response = CanFrame::classical(0x18, &response).unwrap();
624 assert_eq!(
625 c.decode_control_mode_readback(&response, m).unwrap(),
626 Some(4)
627 );
628 assert!(c.decode(&response).unwrap().is_none());
629
630 let wrong_send = CanFrame::classical(0x18, &[0x06, 0, 0x33, 0x0A, 4, 0, 0, 0]).unwrap();
631 assert_eq!(
632 c.decode_control_mode_readback(&wrong_send, m).unwrap(),
633 None
634 );
635 let write_ack = CanFrame::classical(0x18, &[0x05, 0, 0x55, 0x0A, 4, 0, 0, 0]).unwrap();
636 assert_eq!(c.decode_control_mode_readback(&write_ack, m).unwrap(), None);
637 }
638
639 extern crate alloc;
640}