odrive_messages/
messages.rs

1use deku::prelude::*;
2use embedded_can::{Id, StandardId};
3
4use crate::sdo::ConfigValue;
5
6#[derive(Debug, Clone, DekuRead, DekuWrite, PartialEq)]
7#[deku(id_type = "u8", bits = 5)]
8pub enum CanMessage {
9    #[deku(id = "0x00")]
10    GetVersion {
11        protocol_version: u8,
12        hw_version_major: u8,
13        hw_version_minor: u8,
14        hw_version_variant: u8,
15        fw_version_major: u8,
16        fw_version_minor: u8,
17        fw_version_revision: u8,
18        fw_version_unreleased: u8,
19    },
20    #[deku(id = "0x01")]
21    Heartbeat {
22        #[deku(endian = "little")]
23        axis_error: u32,
24        axis_state: AxisState,
25        procedure_result: u8,
26        trajectory_done_flag: u8,
27        _reserved: u8,
28    },
29    #[deku(id = "0x02")]
30    Estop,
31    #[deku(id = "0x03")]
32    GetError {
33        #[deku(endian = "little")]
34        active_errors: u32,
35        #[deku(endian = "little")]
36        disarm_reason: u32,
37    },
38    #[deku(id = "0x04")]
39    RxSdo {
40        opcode: SdoOpcode,
41        #[deku(endian = "little")]
42        endpoint_id: u16,
43        reserved: u8,
44        #[deku(read_all)]
45        value: Vec<u8>,
46    },
47    #[deku(id = "0x05")]
48    TxSdo {
49        reserved0: u8,
50        #[deku(endian = "little")]
51        endpoint_id: u16,
52        reserved1: u8,
53        #[deku(read_all)]
54        value: Vec<u8>,
55    },
56    #[deku(id = "0x06")]
57    Address {
58        node_id: u8,
59        #[deku(bytes = "6")]
60        serial_number: [u8; 6],
61        connection_id: u8,
62    },
63    #[deku(id = "0x07")]
64    SetAxisState {
65        #[deku(pad_bytes_after = "3")]
66        // for some reason, in SetAxisState, the axis state is supposed to be 4 bytes
67        axis_requested_state: AxisState,
68    },
69    #[deku(id = "0x09")]
70    GetEncoderEstimates {
71        /// Unit: rev
72        #[deku(endian = "little")]
73        pos_estimate: f32,
74        /// Unit: rev/s
75        #[deku(endian = "little")]
76        vel_estimate: f32,
77    },
78    #[deku(id = "0x0B")]
79    SetControllerMode {
80        #[deku(pad_bytes_after = "3")]
81        control_mode: ControlMode,
82        #[deku(pad_bytes_after = "3")]
83        input_mode: InputMode,
84    },
85    #[deku(id = "0x0C")]
86    SetInputPos {
87        /// Unit: rev
88        #[deku(endian = "little")]
89        input_pos: f32,
90        /// Unit: 0.001 rev/s (default, configurable)
91        #[deku(endian = "little")]
92        vel_ff: i16,
93        /// Unit: 0.001 Nm (default, configurable)
94        #[deku(endian = "little")]
95        torque_ff: i16,
96    },
97    #[deku(id = "0x0D")]
98    SetInputVel {
99        /// Unit: rev/s
100        #[deku(endian = "little")]
101        input_vel: f32,
102        /// Unit: Nm
103        #[deku(endian = "little")]
104        input_torque_ff: f32,
105    },
106    #[deku(id = "0x0E")]
107    SetInputTorque {
108        /// Unit: Nm
109        #[deku(endian = "little")]
110        input_torque: f32,
111    },
112    #[deku(id = "0x0F")]
113    SetLimits {
114        /// Unit: rev/s
115        #[deku(endian = "little")]
116        velocity_limit: f32,
117        /// Unit: A
118        #[deku(endian = "little")]
119        current_limit: f32,
120    },
121    #[deku(id = "0x11")]
122    SetTrajVelLimit {
123        /// Unit: rev/s
124        #[deku(endian = "little")]
125        traj_vel_limit: f32,
126    },
127    #[deku(id = "0x12")]
128    SetTrajAccelLimits {
129        /// Unit: rev/s²
130        #[deku(endian = "little")]
131        traj_accel_limit: f32,
132        /// Unit: rev/s²
133        #[deku(endian = "little")]
134        traj_decel_limit: f32,
135    },
136    #[deku(id = "0x13")]
137    SetTrajInertia {
138        /// Unit: Nm/(rev/s²)
139        #[deku(endian = "little")]
140        traj_inertia: f32,
141    },
142    #[deku(id = "0x14")]
143    GetIq {
144        /// Unit: A
145        #[deku(endian = "little")]
146        iq_setpoint: f32,
147        /// Unit: A
148        #[deku(endian = "little")]
149        iq_measured: f32,
150    },
151    #[deku(id = "0x15")]
152    GetTemperature {
153        /// Unit: °C
154        #[deku(endian = "little")]
155        fet_temperature: f32,
156        /// Unit: °C
157        #[deku(endian = "little")]
158        motor_temperature: f32,
159    },
160    #[deku(id = "0x16")]
161    Reboot { action: RebootAction },
162    #[deku(id = "0x17")]
163    GetBusVoltageCurrent {
164        /// Unit: V
165        #[deku(endian = "little")]
166        bus_voltage: f32,
167        /// Unit: A
168        #[deku(endian = "little")]
169        bus_current: f32,
170    },
171    #[deku(id = "0x18")]
172    ClearErrors {
173        #[deku(bits = 8)]
174        identify: bool,
175    },
176    #[deku(id = "0x19")]
177    SetAbsolutePosition {
178        /// Unit: rev
179        #[deku(endian = "little")]
180        position: f32,
181    },
182    #[deku(id = "0x1A")]
183    SetPosGain {
184        /// Unit: (rev/s)/rev
185        #[deku(endian = "little")]
186        pos_gain: f32,
187    },
188    #[deku(id = "0x1B")]
189    SetVelGains {
190        /// Unit: Nm/(rev/s)
191        #[deku(endian = "little")]
192        vel_gain: f32,
193        /// Unit: Nm/rev
194        #[deku(endian = "little")]
195        vel_integrator_gain: f32,
196    },
197    #[deku(id = "0x1C")]
198    GetTorques {
199        /// Unit: Nm
200        #[deku(endian = "little")]
201        torque_target: f32,
202        /// Unit: Nm
203        #[deku(endian = "little")]
204        torque_estimate: f32,
205    },
206    #[deku(id = "0x1D")]
207    GetPowers {
208        /// Unit: W
209        #[deku(endian = "little")]
210        electrical_power: f32,
211        /// Unit: W
212        #[deku(endian = "little")]
213        mechanical_power: f32,
214    },
215    #[deku(id = "0x1F")]
216    EnterDfuMode,
217}
218
219#[derive(Debug, Clone, DekuRead, DekuWrite, PartialEq)]
220pub struct CanMessageWithId {
221    #[deku(bits = 6, pad_bits_before = "5")]
222    // since the id is actually separate from the data in socketcan, a bit of a hack to let deku handle it
223    pub node_id: u8,
224    pub message: CanMessage,
225}
226
227impl CanMessageWithId {
228    pub fn id(&self) -> StandardId {
229        let bytes: Vec<u8> = self.to_bytes().unwrap();
230        Self::id_from_bytes(&bytes)
231    }
232
233    pub fn data(&self) -> Vec<u8> {
234        let bytes: Vec<u8> = self.to_bytes().unwrap();
235        Self::data_from_bytes(&bytes).to_vec()
236    }
237
238    fn id_from_bytes(bytes: &[u8]) -> StandardId {
239        StandardId::new(u16::from_be_bytes(bytes[..2].try_into().unwrap())).unwrap()
240    }
241
242    fn data_from_bytes(bytes: &[u8]) -> &[u8] {
243        &bytes[2..]
244    }
245
246    pub fn from_frame<F: embedded_can::Frame>(frame: &F) -> Result<Self, DekuError> {
247        let id = match frame.id() {
248            Id::Standard(std_id) => std_id.as_raw(),
249            _ => unreachable!(), // ODrive uses standard IDs only
250        };
251        let data = frame.data();
252
253        Self::from_id_and_data(id, data)
254    }
255
256    pub fn from_id_and_data(id: u16, data: &[u8]) -> Result<Self, DekuError> {
257        // Prepend node_id and cmd_id to the data
258        let full_data = [&id.to_be_bytes(), data].concat();
259
260        // Parse using deku
261        let (_, message_with_id) = CanMessageWithId::from_bytes((&full_data, 0))?;
262
263        Ok(message_with_id)
264    }
265
266    pub fn to_frame<F: embedded_can::Frame>(self) -> F {
267        let bytes = self.to_bytes().unwrap();
268        F::new(Self::id_from_bytes(&bytes), Self::data_from_bytes(&bytes)).unwrap()
269    }
270
271    pub fn to_rtr_frame<F: embedded_can::Frame>(self) -> F {
272        F::new_remote(self.id(), 0).unwrap()
273    }
274
275    pub fn get_version(node_id: u8) -> Self {
276        CanMessageWithId {
277            node_id,
278            message: CanMessage::GetVersion {
279                protocol_version: 0,
280                hw_version_major: 0,
281                hw_version_minor: 0,
282                hw_version_variant: 0,
283                fw_version_major: 0,
284                fw_version_minor: 0,
285                fw_version_revision: 0,
286                fw_version_unreleased: 0,
287            },
288        }
289    }
290
291    pub fn heartbeat(node_id: u8) -> Self {
292        CanMessageWithId {
293            node_id,
294            message: CanMessage::Heartbeat {
295                axis_error: 0,
296                axis_state: AxisState::Undefined,
297                procedure_result: 0,
298                trajectory_done_flag: 0,
299                _reserved: 0,
300            },
301        }
302    }
303
304    pub fn estop(node_id: u8) -> Self {
305        CanMessageWithId {
306            node_id,
307            message: CanMessage::Estop,
308        }
309    }
310
311    pub fn get_error(node_id: u8) -> Self {
312        CanMessageWithId {
313            node_id,
314            message: CanMessage::GetError {
315                active_errors: 0,
316                disarm_reason: 0,
317            },
318        }
319    }
320
321    pub fn rx_sdo(node_id: u8, opcode: SdoOpcode, endpoint_id: u16, value: ConfigValue) -> Self {
322        CanMessageWithId {
323            node_id,
324            message: CanMessage::RxSdo {
325                opcode,
326                endpoint_id,
327                reserved: 0,
328                value: value.to_le_byte_vec(),
329            },
330        }
331    }
332
333    pub fn set_address(
334        node_id: u8,
335        set_node_id: u8,
336        serial_number: [u8; 6],
337        connection_id: u8,
338    ) -> Self {
339        CanMessageWithId {
340            node_id,
341            message: CanMessage::Address {
342                node_id: set_node_id,
343                serial_number,
344                connection_id,
345            },
346        }
347    }
348
349    pub fn get_address(node_id: u8) -> Self {
350        // should be sent as rtr frame
351        CanMessageWithId {
352            node_id,
353            message: CanMessage::Address {
354                node_id: 0,
355                serial_number: [0; 6],
356                connection_id: 0,
357            },
358        }
359    }
360
361    pub fn get_all_addresses() -> Self {
362        // should be sent as rtr frame
363        CanMessageWithId {
364            node_id: 0x3f,
365            message: CanMessage::Address {
366                node_id: 0,
367                serial_number: [0; 6],
368                connection_id: 0,
369            },
370        }
371    }
372
373    pub fn set_axis_state(node_id: u8, state: AxisState) -> Self {
374        CanMessageWithId {
375            node_id,
376            message: CanMessage::SetAxisState {
377                axis_requested_state: state,
378            },
379        }
380    }
381
382    pub fn get_encoder_estimates(node_id: u8) -> Self {
383        CanMessageWithId {
384            node_id,
385            message: CanMessage::GetEncoderEstimates {
386                pos_estimate: 0.0,
387                vel_estimate: 0.0,
388            },
389        }
390    }
391
392    pub fn set_controller_mode(
393        node_id: u8,
394        control_mode: ControlMode,
395        input_mode: InputMode,
396    ) -> Self {
397        CanMessageWithId {
398            node_id,
399            message: CanMessage::SetControllerMode {
400                control_mode,
401                input_mode,
402            },
403        }
404    }
405
406    pub fn set_input_pos(node_id: u8, input_pos: f32, vel_ff: i16, torque_ff: i16) -> Self {
407        CanMessageWithId {
408            node_id,
409            message: CanMessage::SetInputPos {
410                input_pos,
411                vel_ff,
412                torque_ff,
413            },
414        }
415    }
416
417    pub fn set_input_vel(node_id: u8, input_vel: f32, input_torque_ff: f32) -> Self {
418        CanMessageWithId {
419            node_id,
420            message: CanMessage::SetInputVel {
421                input_vel,
422                input_torque_ff,
423            },
424        }
425    }
426
427    pub fn set_input_torque(node_id: u8, input_torque: f32) -> Self {
428        CanMessageWithId {
429            node_id,
430            message: CanMessage::SetInputTorque { input_torque },
431        }
432    }
433
434    pub fn set_limits(node_id: u8, velocity_limit: f32, current_limit: f32) -> Self {
435        CanMessageWithId {
436            node_id,
437            message: CanMessage::SetLimits {
438                velocity_limit,
439                current_limit,
440            },
441        }
442    }
443
444    pub fn set_traj_vel_limit(node_id: u8, traj_vel_limit: f32) -> Self {
445        CanMessageWithId {
446            node_id,
447            message: CanMessage::SetTrajVelLimit { traj_vel_limit },
448        }
449    }
450
451    pub fn set_traj_accel_limits(
452        node_id: u8,
453        traj_accel_limit: f32,
454        traj_decel_limit: f32,
455    ) -> Self {
456        CanMessageWithId {
457            node_id,
458            message: CanMessage::SetTrajAccelLimits {
459                traj_accel_limit,
460                traj_decel_limit,
461            },
462        }
463    }
464
465    pub fn set_traj_inertia(node_id: u8, traj_inertia: f32) -> Self {
466        CanMessageWithId {
467            node_id,
468            message: CanMessage::SetTrajInertia { traj_inertia },
469        }
470    }
471
472    pub fn get_iq(node_id: u8) -> Self {
473        CanMessageWithId {
474            node_id,
475            message: CanMessage::GetIq {
476                iq_setpoint: 0.0,
477                iq_measured: 0.0,
478            },
479        }
480    }
481
482    pub fn get_temperature(node_id: u8) -> Self {
483        CanMessageWithId {
484            node_id,
485            message: CanMessage::GetTemperature {
486                fet_temperature: 0.0,
487                motor_temperature: 0.0,
488            },
489        }
490    }
491
492    pub fn reboot(node_id: u8, action: RebootAction) -> Self {
493        CanMessageWithId {
494            node_id,
495            message: CanMessage::Reboot { action },
496        }
497    }
498
499    pub fn get_bus_voltage_current(node_id: u8) -> Self {
500        CanMessageWithId {
501            node_id,
502            message: CanMessage::GetBusVoltageCurrent {
503                bus_voltage: 0.0,
504                bus_current: 0.0,
505            },
506        }
507    }
508
509    pub fn clear_errors(node_id: u8, identify: bool) -> Self {
510        CanMessageWithId {
511            node_id,
512            message: CanMessage::ClearErrors { identify },
513        }
514    }
515
516    pub fn set_absolute_position(node_id: u8, position: f32) -> Self {
517        CanMessageWithId {
518            node_id,
519            message: CanMessage::SetAbsolutePosition { position },
520        }
521    }
522
523    pub fn set_pos_gain(node_id: u8, pos_gain: f32) -> Self {
524        CanMessageWithId {
525            node_id,
526            message: CanMessage::SetPosGain { pos_gain },
527        }
528    }
529
530    pub fn set_vel_gains(node_id: u8, vel_gain: f32, vel_integrator_gain: f32) -> Self {
531        CanMessageWithId {
532            node_id,
533            message: CanMessage::SetVelGains {
534                vel_gain,
535                vel_integrator_gain,
536            },
537        }
538    }
539
540    pub fn get_torques(node_id: u8) -> Self {
541        CanMessageWithId {
542            node_id,
543            message: CanMessage::GetTorques {
544                torque_target: 0.0,
545                torque_estimate: 0.0,
546            },
547        }
548    }
549
550    pub fn get_powers(node_id: u8) -> Self {
551        CanMessageWithId {
552            node_id,
553            message: CanMessage::GetPowers {
554                electrical_power: 0.0,
555                mechanical_power: 0.0,
556            },
557        }
558    }
559
560    pub fn enter_dfu_mode(node_id: u8) -> Self {
561        CanMessageWithId {
562            node_id,
563            message: CanMessage::EnterDfuMode,
564        }
565    }
566}
567
568#[repr(u8)]
569#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuWrite, DekuRead)]
570#[deku(id_type = "u8")]
571pub enum AxisState {
572    Undefined = 0,
573    Idle = 1,
574    StartupSequence = 2,
575    FullCalibration = 3,
576    MotorCalibration = 4,
577    EncoderIndexSearch = 6,
578    EncoderOffsetCalibration = 7,
579    ClosedLoopControl = 8,
580    LockinSpin = 9,
581    EncoderDirFind = 10,
582    Homing = 11,
583    EncoderHallPolarityCalibration = 12,
584    EncoderHallPhaseCalibration = 13,
585    AnticoggingCalibration = 14,
586    HarmonicCalibration = 15,
587    HarmonicCalibrationCommutation = 16,
588}
589
590impl std::fmt::Display for AxisState {
591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592        match self {
593            AxisState::Undefined => write!(f, "UNDEFINED"),
594            AxisState::Idle => write!(f, "IDLE"),
595            AxisState::StartupSequence => write!(f, "STARTUP_SEQUENCE"),
596            AxisState::FullCalibration => write!(f, "FULL_CALIBRATION"),
597            AxisState::MotorCalibration => write!(f, "MOTOR_CALIBRATION"),
598            AxisState::EncoderIndexSearch => write!(f, "ENCODER_INDEX_SEARCH"),
599            AxisState::EncoderOffsetCalibration => write!(f, "ENCODER_OFFSET_CALIBRATION"),
600            AxisState::ClosedLoopControl => write!(f, "CLOSED_LOOP_CONTROL"),
601            AxisState::LockinSpin => write!(f, "LOCKIN_SPIN"),
602            AxisState::EncoderDirFind => write!(f, "ENCODER_DIR_FIND"),
603            AxisState::Homing => write!(f, "HOMING"),
604            AxisState::EncoderHallPolarityCalibration => {
605                write!(f, "ENCODER_HALL_POLARITY_CALIBRATION")
606            }
607            AxisState::EncoderHallPhaseCalibration => write!(f, "ENCODER_HALL_PHASE_CALIBRATION"),
608            AxisState::AnticoggingCalibration => write!(f, "ANTICOGGING_CALIBRATION"),
609            AxisState::HarmonicCalibration => write!(f, "HARMONIC_CALIBRATION"),
610            AxisState::HarmonicCalibrationCommutation => {
611                write!(f, "HARMONIC_CALIBRATION_COMMUTATION")
612            }
613        }
614    }
615}
616
617#[repr(u8)]
618#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuWrite, DekuRead)]
619#[deku(id_type = "u8")]
620pub enum RebootAction {
621    Reboot = 0,
622    SaveConfiguration = 1,
623    EraseConfiguration = 2,
624    EnterDfuMode2 = 3,
625}
626
627#[repr(u8)]
628#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuWrite, DekuRead)]
629#[deku(id_type = "u8")]
630pub enum ControlMode {
631    VoltageControl = 0,
632    TorqueControl = 1,
633    VelocityControl = 2,
634    PositionControl = 3,
635}
636
637#[repr(u8)]
638#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuWrite, DekuRead)]
639#[deku(id_type = "u8")]
640pub enum InputMode {
641    Inactive = 0,
642    Passthrough = 1,
643    VelRamp = 2,
644    PosFilter = 3,
645    MixChannels = 4,
646    TrapTraj = 5,
647    TorqueRamp = 6,
648    Mirror = 7,
649    Tuning = 8,
650}
651
652#[repr(u8)]
653#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuWrite, DekuRead)]
654#[deku(id_type = "u8", endian = "little")]
655pub enum SdoOpcode {
656    Read = 0,
657    Write = 1,
658}
659
660#[cfg(test)]
661mod tests {
662    use super::*;
663
664    #[test]
665    pub fn test_set_closed_loop_control() {
666        let msg = CanMessageWithId::set_axis_state(1, AxisState::ClosedLoopControl);
667        let id = msg.id().as_raw();
668        let data = msg.data();
669        assert_eq!(id, 1 << 5 | 0x07);
670        assert_eq!(data, vec![0x08, 0x00, 0x00, 0x00]);
671        assert_eq!(msg, CanMessageWithId::from_id_and_data(id, &data).unwrap());
672    }
673
674    #[test]
675    pub fn test_docs_example_read_vel_integrator_limit() {
676        let msg = CanMessageWithId::rx_sdo(0, SdoOpcode::Read, 0x0182, ConfigValue::Empty);
677        let id = msg.id().as_raw();
678        let data = msg.data();
679        assert_eq!(id, 0x04);
680        assert_eq!(data, vec![0x00, 0x82, 0x01, 0x00]); // read has no data
681        assert_eq!(msg, CanMessageWithId::from_id_and_data(id, &data).unwrap());
682    }
683
684    #[test]
685    pub fn test_docs_example_read_vel_integrator_limit_response() {
686        let resp_id = 0x05;
687        // Raw messages, assuming node_id = 0, endpoint_id = 0x0182, return_value = inf:
688        let resp_data = vec![0x00, 0x82, 0x01, 0x00, 0x00, 0x00, 0x80, 0x7f];
689        let resp_msg = CanMessageWithId::from_id_and_data(resp_id, &resp_data).unwrap();
690        match resp_msg.message {
691            CanMessage::TxSdo {
692                endpoint_id, value, ..
693            } => {
694                assert_eq!(endpoint_id, 0x0182);
695                let config_value = ConfigValue::from_le_bytes(&value, "float").unwrap();
696                match config_value {
697                    ConfigValue::Float(f) => {
698                        assert!(f.is_infinite() && f.is_sign_positive());
699                    }
700                    _ => panic!("Expected float ConfigValue"),
701                }
702            }
703            _ => panic!("Expected TxSdo message"),
704        }
705    }
706
707    #[test]
708    pub fn test_docs_example_function_call() {
709        let msg = CanMessageWithId::rx_sdo(0, SdoOpcode::Write, 0x0253, ConfigValue::List(vec![]));
710        let id = msg.id().as_raw();
711        let data = msg.data();
712        assert_eq!(id, 0x04);
713        assert_eq!(data, vec![0x01, 0x53, 0x02, 0x00]);
714        assert_eq!(msg, CanMessageWithId::from_id_and_data(id, &data).unwrap());
715    }
716
717    #[test]
718    pub fn test_docs_example_write_vel_integrator_limit() {
719        let msg = CanMessageWithId::rx_sdo(0, SdoOpcode::Write, 0x0182, ConfigValue::Float(1.234));
720        let id = msg.id().as_raw();
721        let data = msg.data();
722        assert_eq!(id, 0x04);
723        assert_eq!(data, vec![0x01, 0x82, 0x01, 0x00, 0xb6, 0xf3, 0x9d, 0x3f]); // the docs have an error here, the first byte should be 0x01 for write
724        assert_eq!(msg, CanMessageWithId::from_id_and_data(id, &data).unwrap());
725    }
726
727    #[test]
728    pub fn test_set_control_mode() {
729        let msg = CanMessageWithId::set_controller_mode(
730            2,
731            ControlMode::TorqueControl,
732            InputMode::Passthrough,
733        );
734        let id = msg.id().as_raw();
735        let data = msg.data();
736        assert_eq!(id, 2 << 5 | 0x0B);
737        assert_eq!(data, vec![0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00])
738    }
739}