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 axis_requested_state: AxisState,
63 },
64 #[deku(id = "0x09")]
65 GetEncoderEstimates {
66 pos_estimate: f32,
68 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 input_pos: f32,
82 vel_ff: i16,
84 torque_ff: i16,
86 },
87 #[deku(id = "0x0D")]
88 SetInputVel {
89 input_vel: f32,
91 input_torque_ff: f32,
93 },
94 #[deku(id = "0x0E")]
95 SetInputTorque {
96 input_torque: f32,
98 },
99 #[deku(id = "0x0F")]
100 SetLimits {
101 velocity_limit: f32,
103 current_limit: f32,
105 },
106 #[deku(id = "0x11")]
107 SetTrajVelLimit {
108 traj_vel_limit: f32,
110 },
111 #[deku(id = "0x12")]
112 SetTrajAccelLimits {
113 traj_accel_limit: f32,
115 traj_decel_limit: f32,
117 },
118 #[deku(id = "0x13")]
119 SetTrajInertia {
120 traj_inertia: f32,
122 },
123 #[deku(id = "0x14")]
124 GetIq {
125 iq_setpoint: f32,
127 iq_measured: f32,
129 },
130 #[deku(id = "0x15")]
131 GetTemperature {
132 fet_temperature: f32,
134 motor_temperature: f32,
136 },
137 #[deku(id = "0x16")]
138 Reboot { action: RebootAction },
139 #[deku(id = "0x17")]
140 GetBusVoltageCurrent {
141 bus_voltage: f32,
143 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 position: f32,
155 },
156 #[deku(id = "0x1A")]
157 SetPosGain {
158 pos_gain: f32,
160 },
161 #[deku(id = "0x1B")]
162 SetVelGains {
163 vel_gain: f32,
165 vel_integrator_gain: f32,
167 },
168 #[deku(id = "0x1C")]
169 GetTorques {
170 torque_target: f32,
172 torque_estimate: f32,
174 },
175 #[deku(id = "0x1D")]
176 GetPowers {
177 electrical_power: f32,
179 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 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!(), };
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 let full_data = [&id.to_be_bytes(), data].concat();
226
227 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 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 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]); 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 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]); 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}