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