1use crate::can::PiperFrame;
6use crate::{ProtocolError, bytes_to_i16_be, i16_to_bytes_be, ids::*};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LinkSetting {
15 Invalid = 0x00,
17 TeachInputArm = 0xFA,
19 MotionOutputArm = 0xFC,
21}
22
23impl TryFrom<u8> for LinkSetting {
24 type Error = ProtocolError;
25
26 fn try_from(value: u8) -> Result<Self, Self::Error> {
27 match value {
28 0x00 => Ok(LinkSetting::Invalid),
29 0xFA => Ok(LinkSetting::TeachInputArm),
30 0xFC => Ok(LinkSetting::MotionOutputArm),
31 _ => Err(ProtocolError::InvalidValue {
32 field: "LinkSetting".to_string(),
33 value,
34 }),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum FeedbackIdOffset {
42 None = 0x00,
44 Offset2Bx = 0x10,
46 Offset2Cx = 0x20,
48}
49
50impl TryFrom<u8> for FeedbackIdOffset {
51 type Error = ProtocolError;
52
53 fn try_from(value: u8) -> Result<Self, Self::Error> {
54 match value {
55 0x00 => Ok(FeedbackIdOffset::None),
56 0x10 => Ok(FeedbackIdOffset::Offset2Bx),
57 0x20 => Ok(FeedbackIdOffset::Offset2Cx),
58 _ => Err(ProtocolError::InvalidValue {
59 field: "FeedbackIdOffset".to_string(),
60 value,
61 }),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum ControlIdOffset {
69 None = 0x00,
71 Offset16x = 0x10,
73 Offset17x = 0x20,
75}
76
77impl TryFrom<u8> for ControlIdOffset {
78 type Error = ProtocolError;
79
80 fn try_from(value: u8) -> Result<Self, Self::Error> {
81 match value {
82 0x00 => Ok(ControlIdOffset::None),
83 0x10 => Ok(ControlIdOffset::Offset16x),
84 0x20 => Ok(ControlIdOffset::Offset17x),
85 _ => Err(ProtocolError::InvalidValue {
86 field: "ControlIdOffset".to_string(),
87 value,
88 }),
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy)]
98pub struct MasterSlaveModeCommand {
99 pub link_setting: LinkSetting, pub feedback_id_offset: FeedbackIdOffset, pub control_id_offset: ControlIdOffset, pub target_id_offset: ControlIdOffset, }
104
105impl MasterSlaveModeCommand {
106 pub fn set_teach_input_arm(
108 feedback_id_offset: FeedbackIdOffset,
109 control_id_offset: ControlIdOffset,
110 target_id_offset: ControlIdOffset,
111 ) -> Self {
112 Self {
113 link_setting: LinkSetting::TeachInputArm,
114 feedback_id_offset,
115 control_id_offset,
116 target_id_offset,
117 }
118 }
119
120 pub fn set_motion_output_arm() -> Self {
122 Self {
123 link_setting: LinkSetting::MotionOutputArm,
124 feedback_id_offset: FeedbackIdOffset::None,
125 control_id_offset: ControlIdOffset::None,
126 target_id_offset: ControlIdOffset::None,
127 }
128 }
129
130 pub fn to_frame(self) -> PiperFrame {
132 let mut data = [0u8; 8];
133 data[0] = self.link_setting as u8;
134 data[1] = self.feedback_id_offset as u8;
135 data[2] = self.control_id_offset as u8;
136 data[3] = self.target_id_offset as u8;
137 PiperFrame::new_standard(ID_MASTER_SLAVE_MODE as u16, &data)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_link_setting_from_u8() {
149 assert_eq!(LinkSetting::try_from(0x00).unwrap(), LinkSetting::Invalid);
150 assert_eq!(
151 LinkSetting::try_from(0xFA).unwrap(),
152 LinkSetting::TeachInputArm
153 );
154 assert_eq!(
155 LinkSetting::try_from(0xFC).unwrap(),
156 LinkSetting::MotionOutputArm
157 );
158 }
159
160 #[test]
161 fn test_feedback_id_offset_from_u8() {
162 assert_eq!(
163 FeedbackIdOffset::try_from(0x00).unwrap(),
164 FeedbackIdOffset::None
165 );
166 assert_eq!(
167 FeedbackIdOffset::try_from(0x10).unwrap(),
168 FeedbackIdOffset::Offset2Bx
169 );
170 assert_eq!(
171 FeedbackIdOffset::try_from(0x20).unwrap(),
172 FeedbackIdOffset::Offset2Cx
173 );
174 }
175
176 #[test]
177 fn test_control_id_offset_from_u8() {
178 assert_eq!(
179 ControlIdOffset::try_from(0x00).unwrap(),
180 ControlIdOffset::None
181 );
182 assert_eq!(
183 ControlIdOffset::try_from(0x10).unwrap(),
184 ControlIdOffset::Offset16x
185 );
186 assert_eq!(
187 ControlIdOffset::try_from(0x20).unwrap(),
188 ControlIdOffset::Offset17x
189 );
190 }
191
192 #[test]
193 fn test_master_slave_mode_command_set_teach_input_arm() {
194 let cmd = MasterSlaveModeCommand::set_teach_input_arm(
195 FeedbackIdOffset::Offset2Bx,
196 ControlIdOffset::Offset16x,
197 ControlIdOffset::Offset16x,
198 );
199 let frame = cmd.to_frame();
200
201 assert_eq!(frame.id, ID_MASTER_SLAVE_MODE);
202 assert_eq!(frame.data[0], 0xFA); assert_eq!(frame.data[1], 0x10); assert_eq!(frame.data[2], 0x10); assert_eq!(frame.data[3], 0x10); assert_eq!(frame.data[4], 0x00); }
208
209 #[test]
210 fn test_master_slave_mode_command_set_motion_output_arm() {
211 let cmd = MasterSlaveModeCommand::set_motion_output_arm();
212 let frame = cmd.to_frame();
213
214 assert_eq!(frame.id, ID_MASTER_SLAVE_MODE);
215 assert_eq!(frame.data[0], 0xFC); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x00); assert_eq!(frame.data[3], 0x00); }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum QueryType {
229 AngleAndMaxVelocity = 0x01,
231 MaxAcceleration = 0x02,
233}
234
235impl TryFrom<u8> for QueryType {
236 type Error = ProtocolError;
237
238 fn try_from(value: u8) -> Result<Self, Self::Error> {
239 match value {
240 0x01 => Ok(QueryType::AngleAndMaxVelocity),
241 0x02 => Ok(QueryType::MaxAcceleration),
242 _ => Err(ProtocolError::InvalidValue {
243 field: "QueryType".to_string(),
244 value,
245 }),
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy)]
252pub struct QueryMotorLimitCommand {
253 pub joint_index: u8, pub query_type: QueryType, }
256
257impl QueryMotorLimitCommand {
258 pub fn query_angle_and_max_velocity(joint_index: u8) -> Self {
260 Self {
261 joint_index,
262 query_type: QueryType::AngleAndMaxVelocity,
263 }
264 }
265
266 pub fn query_max_acceleration(joint_index: u8) -> Self {
268 Self {
269 joint_index,
270 query_type: QueryType::MaxAcceleration,
271 }
272 }
273
274 pub fn to_frame(self) -> PiperFrame {
276 let mut data = [0u8; 8];
277 data[0] = self.joint_index;
278 data[1] = self.query_type as u8;
279 PiperFrame::new_standard(ID_QUERY_MOTOR_LIMIT as u16, &data)
282 }
283}
284
285#[derive(Debug, Clone, Copy, Default)]
291pub struct MotorLimitFeedback {
292 pub joint_index: u8, pub max_angle_deg: i16, pub min_angle_deg: i16, pub max_velocity_rad_s: u16, }
298
299impl MotorLimitFeedback {
300 pub fn max_angle(&self) -> f64 {
302 self.max_angle_deg as f64 / 10.0
303 }
304
305 pub fn min_angle(&self) -> f64 {
307 self.min_angle_deg as f64 / 10.0
308 }
309
310 pub fn max_velocity(&self) -> f64 {
312 self.max_velocity_rad_s as f64 / 100.0
313 }
314}
315
316impl TryFrom<PiperFrame> for MotorLimitFeedback {
317 type Error = ProtocolError;
318
319 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
320 if frame.id != ID_MOTOR_LIMIT_FEEDBACK {
322 return Err(ProtocolError::InvalidCanId { id: frame.id });
323 }
324
325 if frame.len < 7 {
327 return Err(ProtocolError::InvalidLength {
328 expected: 7,
329 actual: frame.len as usize,
330 });
331 }
332
333 let joint_index = frame.data[0];
334
335 let max_angle_bytes = [frame.data[1], frame.data[2]];
337 let max_angle_deg = bytes_to_i16_be(max_angle_bytes);
338
339 let min_angle_bytes = [frame.data[3], frame.data[4]];
340 let min_angle_deg = bytes_to_i16_be(min_angle_bytes);
341
342 let max_velocity_bytes = [frame.data[5], frame.data[6]];
343 let max_velocity_rad_s = u16::from_be_bytes(max_velocity_bytes);
344
345 Ok(Self {
346 joint_index,
347 max_angle_deg,
348 min_angle_deg,
349 max_velocity_rad_s,
350 })
351 }
352}
353
354#[cfg(test)]
355mod motor_limit_tests {
356 use super::*;
357
358 #[test]
359 fn test_query_type_from_u8() {
360 assert_eq!(
361 QueryType::try_from(0x01).unwrap(),
362 QueryType::AngleAndMaxVelocity
363 );
364 assert_eq!(
365 QueryType::try_from(0x02).unwrap(),
366 QueryType::MaxAcceleration
367 );
368 }
369
370 #[test]
371 fn test_query_motor_limit_command_query_angle_and_max_velocity() {
372 let cmd = QueryMotorLimitCommand::query_angle_and_max_velocity(1);
373 let frame = cmd.to_frame();
374
375 assert_eq!(frame.id, ID_QUERY_MOTOR_LIMIT);
376 assert_eq!(frame.data[0], 1);
377 assert_eq!(frame.data[1], 0x01); }
379
380 #[test]
381 fn test_query_motor_limit_command_query_max_acceleration() {
382 let cmd = QueryMotorLimitCommand::query_max_acceleration(2);
383 let frame = cmd.to_frame();
384
385 assert_eq!(frame.id, ID_QUERY_MOTOR_LIMIT);
386 assert_eq!(frame.data[0], 2);
387 assert_eq!(frame.data[1], 0x02); }
389
390 #[test]
391 fn test_motor_limit_feedback_parse() {
392 let max_angle_val = 1800i16;
397 let min_angle_val = -1800i16;
398 let max_velocity_val = 500u16;
399
400 let mut data = [0u8; 8];
401 data[0] = 1; data[1..3].copy_from_slice(&max_angle_val.to_be_bytes());
403 data[3..5].copy_from_slice(&min_angle_val.to_be_bytes());
404 data[5..7].copy_from_slice(&max_velocity_val.to_be_bytes());
405
406 let frame = PiperFrame::new_standard(ID_MOTOR_LIMIT_FEEDBACK as u16, &data);
407 let feedback = MotorLimitFeedback::try_from(frame).unwrap();
408
409 assert_eq!(feedback.joint_index, 1);
410 assert_eq!(feedback.max_angle_deg, 1800);
411 assert_eq!(feedback.min_angle_deg, -1800);
412 assert_eq!(feedback.max_velocity_rad_s, 500);
413 assert!((feedback.max_angle() - 180.0).abs() < 0.0001);
414 assert!((feedback.min_angle() - (-180.0)).abs() < 0.0001);
415 assert!((feedback.max_velocity() - 5.0).abs() < 0.0001);
416 }
417
418 #[test]
419 fn test_motor_limit_feedback_invalid_id() {
420 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
421 let result = MotorLimitFeedback::try_from(frame);
422 assert!(result.is_err());
423 }
424
425 #[test]
426 fn test_motor_limit_feedback_invalid_length() {
427 let frame = PiperFrame::new_standard(ID_MOTOR_LIMIT_FEEDBACK as u16, &[0; 4]);
428 let result = MotorLimitFeedback::try_from(frame);
429 assert!(result.is_err());
430 }
431}
432
433#[derive(Debug, Clone, Copy)]
444pub struct SetMotorLimitCommand {
445 pub joint_index: u8, pub max_angle_deg: Option<i16>, pub min_angle_deg: Option<i16>, pub max_velocity_rad_s: Option<u16>, }
450
451impl SetMotorLimitCommand {
452 pub fn new(
454 joint_index: u8,
455 max_angle: Option<f64>,
456 min_angle: Option<f64>,
457 max_velocity: Option<f64>,
458 ) -> Self {
459 Self {
460 joint_index,
461 max_angle_deg: max_angle.map(|a| (a * 10.0) as i16),
462 min_angle_deg: min_angle.map(|a| (a * 10.0) as i16),
463 max_velocity_rad_s: max_velocity.map(|v| (v * 100.0) as u16),
464 }
465 }
466
467 pub fn to_frame(self) -> PiperFrame {
469 let mut data = [0u8; 8];
470 data[0] = self.joint_index;
471
472 let max_angle_bytes = if let Some(angle) = self.max_angle_deg {
474 i16_to_bytes_be(angle)
475 } else {
476 [0x7F, 0xFF] };
478 data[1..3].copy_from_slice(&max_angle_bytes);
479
480 let min_angle_bytes = if let Some(angle) = self.min_angle_deg {
481 i16_to_bytes_be(angle)
482 } else {
483 [0x7F, 0xFF] };
485 data[3..5].copy_from_slice(&min_angle_bytes);
486
487 let max_velocity_bytes = if let Some(velocity) = self.max_velocity_rad_s {
488 velocity.to_be_bytes()
489 } else {
490 [0x7F, 0xFF] };
492 data[5..7].copy_from_slice(&max_velocity_bytes);
493
494 PiperFrame::new_standard(ID_SET_MOTOR_LIMIT as u16, &data)
495 }
496}
497
498#[cfg(test)]
499mod set_motor_limit_tests {
500 use super::*;
501
502 #[test]
503 fn test_set_motor_limit_command_new() {
504 let cmd = SetMotorLimitCommand::new(
505 1,
506 Some(180.0), Some(-180.0), Some(5.0), );
510 assert_eq!(cmd.joint_index, 1);
511 assert_eq!(cmd.max_angle_deg, Some(1800));
512 assert_eq!(cmd.min_angle_deg, Some(-1800));
513 assert_eq!(cmd.max_velocity_rad_s, Some(500));
514 }
515
516 #[test]
517 fn test_set_motor_limit_command_to_frame() {
518 let cmd = SetMotorLimitCommand::new(1, Some(180.0), Some(-180.0), Some(5.0));
519 let frame = cmd.to_frame();
520
521 assert_eq!(frame.id, ID_SET_MOTOR_LIMIT);
522 assert_eq!(frame.data[0], 1);
523
524 let max_angle = i16::from_be_bytes([frame.data[1], frame.data[2]]);
526 let min_angle = i16::from_be_bytes([frame.data[3], frame.data[4]]);
527 let max_velocity = u16::from_be_bytes([frame.data[5], frame.data[6]]);
528 assert_eq!(max_angle, 1800);
529 assert_eq!(min_angle, -1800);
530 assert_eq!(max_velocity, 500);
531 }
532
533 #[test]
534 fn test_set_motor_limit_command_invalid_values() {
535 let cmd = SetMotorLimitCommand::new(1, None, None, None);
537 let frame = cmd.to_frame();
538
539 assert_eq!(frame.data[1], 0x7F);
541 assert_eq!(frame.data[2], 0xFF);
542 assert_eq!(frame.data[3], 0x7F);
543 assert_eq!(frame.data[4], 0xFF);
544 assert_eq!(frame.data[5], 0x7F);
545 assert_eq!(frame.data[6], 0xFF);
546 }
547
548 #[test]
549 fn test_set_motor_limit_command_partial_values() {
550 let cmd = SetMotorLimitCommand::new(
552 2,
553 Some(90.0), None, Some(3.0), );
557 let frame = cmd.to_frame();
558
559 let max_angle = i16::from_be_bytes([frame.data[1], frame.data[2]]);
560 let min_angle = i16::from_be_bytes([frame.data[3], frame.data[4]]);
561 let max_velocity = u16::from_be_bytes([frame.data[5], frame.data[6]]);
562
563 assert_eq!(max_angle, 900);
564 assert_eq!(min_angle, 0x7FFF); assert_eq!(max_velocity, 300);
566 }
567}
568
569#[derive(Debug, Clone, Copy)]
581pub struct JointSettingCommand {
582 pub joint_index: u8, pub set_zero_point: bool, pub accel_param_enable: bool, pub max_accel_rad_s2: Option<u16>, pub clear_error: bool, }
588
589impl JointSettingCommand {
590 pub fn set_zero_point(joint_index: u8) -> Self {
592 Self {
593 joint_index,
594 set_zero_point: true,
595 accel_param_enable: false,
596 max_accel_rad_s2: None,
597 clear_error: false,
598 }
599 }
600
601 pub fn set_acceleration(joint_index: u8, max_accel: f64) -> Self {
603 Self {
604 joint_index,
605 set_zero_point: false,
606 accel_param_enable: true,
607 max_accel_rad_s2: Some((max_accel * 100.0) as u16),
608 clear_error: false,
609 }
610 }
611
612 pub fn clear_error(joint_index: u8) -> Self {
614 Self {
615 joint_index,
616 set_zero_point: false,
617 accel_param_enable: false,
618 max_accel_rad_s2: None,
619 clear_error: true,
620 }
621 }
622
623 pub fn to_frame(self) -> PiperFrame {
625 let mut data = [0u8; 8];
626 data[0] = self.joint_index;
627 data[1] = if self.set_zero_point { 0xAE } else { 0x00 };
628 data[2] = if self.accel_param_enable { 0xAE } else { 0x00 };
629
630 let accel_bytes = if let Some(accel) = self.max_accel_rad_s2 {
632 accel.to_be_bytes()
633 } else {
634 [0x7F, 0xFF] };
636 data[3..5].copy_from_slice(&accel_bytes);
637
638 data[5] = if self.clear_error { 0xAE } else { 0x00 };
639 PiperFrame::new_standard(ID_JOINT_SETTING as u16, &data)
642 }
643}
644
645#[cfg(test)]
646mod joint_setting_tests {
647 use super::*;
648
649 #[test]
650 fn test_joint_setting_command_set_zero_point() {
651 let cmd = JointSettingCommand::set_zero_point(1);
652 let frame = cmd.to_frame();
653
654 assert_eq!(frame.id, ID_JOINT_SETTING);
655 assert_eq!(frame.data[0], 1);
656 assert_eq!(frame.data[1], 0xAE); assert_eq!(frame.data[2], 0x00);
658 assert_eq!(frame.data[5], 0x00);
659 }
660
661 #[test]
662 fn test_joint_setting_command_set_acceleration() {
663 let cmd = JointSettingCommand::set_acceleration(2, 10.0); let frame = cmd.to_frame();
665
666 assert_eq!(frame.id, ID_JOINT_SETTING);
667 assert_eq!(frame.data[0], 2);
668 assert_eq!(frame.data[1], 0x00);
669 assert_eq!(frame.data[2], 0xAE); let max_accel = u16::from_be_bytes([frame.data[3], frame.data[4]]);
672 assert_eq!(max_accel, 1000); }
674
675 #[test]
676 fn test_joint_setting_command_clear_error() {
677 let cmd = JointSettingCommand::clear_error(3);
678 let frame = cmd.to_frame();
679
680 assert_eq!(frame.id, ID_JOINT_SETTING);
681 assert_eq!(frame.data[0], 3);
682 assert_eq!(frame.data[1], 0x00);
683 assert_eq!(frame.data[2], 0x00);
684 assert_eq!(frame.data[5], 0xAE); }
686
687 #[test]
688 fn test_joint_setting_command_all_joints() {
689 let cmd = JointSettingCommand::set_zero_point(7);
691 let frame = cmd.to_frame();
692 assert_eq!(frame.data[0], 7);
693 }
694
695 #[test]
696 fn test_joint_setting_command_invalid_accel() {
697 let mut cmd = JointSettingCommand::set_acceleration(1, 10.0);
699 cmd.max_accel_rad_s2 = None;
700 let frame = cmd.to_frame();
701
702 assert_eq!(frame.data[3], 0x7F);
704 assert_eq!(frame.data[4], 0xFF);
705 }
706}
707
708#[derive(Debug, Clone, Copy, PartialEq, Eq)]
714pub enum TrajectoryPackCompleteStatus {
715 Success,
717 ChecksumFailed,
719 Other(u8),
721}
722
723impl From<u8> for TrajectoryPackCompleteStatus {
724 fn from(value: u8) -> Self {
725 match value {
726 0xAE => TrajectoryPackCompleteStatus::Success,
727 0xEE => TrajectoryPackCompleteStatus::ChecksumFailed,
728 _ => TrajectoryPackCompleteStatus::Other(value),
729 }
730 }
731}
732
733impl From<TrajectoryPackCompleteStatus> for u8 {
734 fn from(status: TrajectoryPackCompleteStatus) -> Self {
735 match status {
736 TrajectoryPackCompleteStatus::Success => 0xAE,
737 TrajectoryPackCompleteStatus::ChecksumFailed => 0xEE,
738 TrajectoryPackCompleteStatus::Other(val) => val,
739 }
740 }
741}
742
743#[derive(Debug, Clone, Copy)]
750pub struct SettingResponse {
751 pub response_index: u8, pub zero_point_success: bool, pub trajectory_index: u8, pub pack_complete_status: Option<TrajectoryPackCompleteStatus>, pub name_index: u16, pub crc16: u16, }
758
759impl SettingResponse {
760 pub fn is_trajectory_response(&self) -> bool {
762 self.response_index == 0x50
763 }
764
765 pub fn is_setting_response(&self) -> bool {
767 !self.is_trajectory_response()
768 }
769
770 pub fn trajectory_pack_complete_status(&self) -> Option<TrajectoryPackCompleteStatus> {
772 self.pack_complete_status
773 }
774
775 pub fn trajectory_point_index(&self) -> Option<u8> {
777 if self.is_trajectory_response() {
778 Some(self.trajectory_index)
779 } else {
780 None
781 }
782 }
783
784 pub fn trajectory_name_index(&self) -> Option<u16> {
786 if self.is_trajectory_response() {
787 Some(self.name_index)
788 } else {
789 None
790 }
791 }
792
793 pub fn trajectory_crc16(&self) -> Option<u16> {
795 if self.is_trajectory_response() {
796 Some(self.crc16)
797 } else {
798 None
799 }
800 }
801}
802
803impl TryFrom<PiperFrame> for SettingResponse {
804 type Error = ProtocolError;
805
806 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
807 if frame.id != ID_SETTING_RESPONSE {
809 return Err(ProtocolError::InvalidCanId { id: frame.id });
810 }
811
812 if frame.len < 8 {
814 return Err(ProtocolError::InvalidLength {
815 expected: 8,
816 actual: frame.len as usize,
817 });
818 }
819
820 let response_index = frame.data[0];
821 let zero_point_success = frame.data[1] == 0x01;
822 let trajectory_index = frame.data[2];
823
824 let pack_complete_status = if response_index == 0x50 {
826 Some(TrajectoryPackCompleteStatus::from(frame.data[3]))
827 } else {
828 None
829 };
830
831 let name_index = u16::from_be_bytes([frame.data[4], frame.data[5]]);
833
834 let crc16 = u16::from_be_bytes([frame.data[6], frame.data[7]]);
836
837 Ok(Self {
838 response_index,
839 zero_point_success,
840 trajectory_index,
841 pack_complete_status,
842 name_index,
843 crc16,
844 })
845 }
846}
847
848#[cfg(test)]
849mod setting_response_tests {
850 use super::*;
851
852 #[test]
853 fn test_setting_response_setting_command() {
854 let mut data = [0u8; 8];
856 data[0] = 0x71; data[1] = 0x00; let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
860 let response = SettingResponse::try_from(frame).unwrap();
861
862 assert_eq!(response.response_index, 0x71);
863 assert!(!response.zero_point_success);
864 assert!(response.is_setting_response());
865 assert!(!response.is_trajectory_response());
866 }
867
868 #[test]
869 fn test_setting_response_zero_point_success() {
870 let mut data = [0u8; 8];
872 data[0] = 0x75; data[1] = 0x01; let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
876 let response = SettingResponse::try_from(frame).unwrap();
877
878 assert_eq!(response.response_index, 0x75);
879 assert!(response.zero_point_success);
880 }
881
882 #[test]
883 fn test_setting_response_trajectory_transmit() {
884 let mut data = [0u8; 8];
886 data[0] = 0x50; data[1] = 0x00; data[2] = 5; data[3] = 0xAE; data[4] = 0x12; data[5] = 0x34; data[6] = 0x56; data[7] = 0x78; let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
896 let response = SettingResponse::try_from(frame).unwrap();
897
898 assert_eq!(response.response_index, 0x50);
899 assert_eq!(response.trajectory_index, 5);
900 assert!(response.is_trajectory_response());
901 assert!(!response.is_setting_response());
902 assert_eq!(
903 response.trajectory_pack_complete_status(),
904 Some(TrajectoryPackCompleteStatus::Success)
905 );
906 assert_eq!(response.trajectory_name_index(), Some(0x1234));
907 assert_eq!(response.trajectory_crc16(), Some(0x5678));
908 }
909
910 #[test]
911 fn test_setting_response_trajectory_checksum_failed() {
912 let mut data = [0u8; 8];
914 data[0] = 0x50; data[1] = 0x00;
916 data[2] = 10; data[3] = 0xEE; let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
920 let response = SettingResponse::try_from(frame).unwrap();
921
922 assert_eq!(
923 response.trajectory_pack_complete_status(),
924 Some(TrajectoryPackCompleteStatus::ChecksumFailed)
925 );
926 }
927
928 #[test]
929 fn test_setting_response_invalid_id() {
930 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
931 let result = SettingResponse::try_from(frame);
932 assert!(result.is_err());
933 }
934
935 #[test]
936 fn test_setting_response_invalid_length() {
937 let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &[0; 2]);
938 let result = SettingResponse::try_from(frame);
939 assert!(result.is_err());
940 }
941}
942
943#[derive(Debug, Clone, Copy, PartialEq, Eq)]
949pub enum ParameterQueryType {
950 EndVelocityAccel = 0x01,
952 CollisionProtectionLevel = 0x02,
954 CurrentTrajectoryIndex = 0x03,
956 GripperTeachParamsIndex = 0x04,
958}
959
960impl TryFrom<u8> for ParameterQueryType {
961 type Error = ProtocolError;
962
963 fn try_from(value: u8) -> Result<Self, Self::Error> {
964 match value {
965 0x01 => Ok(ParameterQueryType::EndVelocityAccel),
966 0x02 => Ok(ParameterQueryType::CollisionProtectionLevel),
967 0x03 => Ok(ParameterQueryType::CurrentTrajectoryIndex),
968 0x04 => Ok(ParameterQueryType::GripperTeachParamsIndex),
969 _ => Err(ProtocolError::InvalidValue {
970 field: "ParameterQueryType".to_string(),
971 value,
972 }),
973 }
974 }
975}
976
977#[derive(Debug, Clone, Copy, PartialEq, Eq)]
979pub enum ParameterSetType {
980 EndVelocityAccelToDefault = 0x01,
982 AllJointLimitsToDefault = 0x02,
984}
985
986#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
988pub enum Feedback48XSetting {
989 #[default]
991 Invalid = 0x00,
992 Enable = 0x01,
994 Disable = 0x02,
996}
997
998impl TryFrom<u8> for Feedback48XSetting {
999 type Error = ProtocolError;
1000
1001 fn try_from(value: u8) -> Result<Self, Self::Error> {
1002 match value {
1003 0x00 => Ok(Feedback48XSetting::Invalid),
1004 0x01 => Ok(Feedback48XSetting::Enable),
1005 0x02 => Ok(Feedback48XSetting::Disable),
1006 _ => Err(ProtocolError::InvalidValue {
1007 field: "Feedback48XSetting".to_string(),
1008 value,
1009 }),
1010 }
1011 }
1012}
1013
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1016#[allow(clippy::enum_variant_names)]
1017pub enum EndLoadSetting {
1018 #[default]
1020 NoLoad = 0x00,
1021 HalfLoad = 0x01,
1023 FullLoad = 0x02,
1025}
1026
1027impl TryFrom<u8> for EndLoadSetting {
1028 type Error = ProtocolError;
1029
1030 fn try_from(value: u8) -> Result<Self, Self::Error> {
1031 match value {
1032 0x00 => Ok(EndLoadSetting::NoLoad),
1033 0x01 => Ok(EndLoadSetting::HalfLoad),
1034 0x02 => Ok(EndLoadSetting::FullLoad),
1035 _ => Err(ProtocolError::InvalidValue {
1036 field: "EndLoadSetting".to_string(),
1037 value,
1038 }),
1039 }
1040 }
1041}
1042
1043impl TryFrom<u8> for ParameterSetType {
1044 type Error = ProtocolError;
1045
1046 fn try_from(value: u8) -> Result<Self, Self::Error> {
1047 match value {
1048 0x01 => Ok(ParameterSetType::EndVelocityAccelToDefault),
1049 0x02 => Ok(ParameterSetType::AllJointLimitsToDefault),
1050 _ => Err(ProtocolError::InvalidValue {
1051 field: "ParameterSetType".to_string(),
1052 value,
1053 }),
1054 }
1055 }
1056}
1057
1058#[derive(Debug, Clone, Copy)]
1062pub struct ParameterQuerySetCommand {
1063 pub query_type: Option<ParameterQueryType>, pub set_type: Option<ParameterSetType>, pub feedback_48x_setting: Feedback48XSetting, pub load_param_enable: bool, pub end_load: EndLoadSetting, }
1070
1071impl ParameterQuerySetCommand {
1072 pub fn query(query_type: ParameterQueryType) -> Self {
1074 Self {
1075 query_type: Some(query_type),
1076 set_type: None,
1077 feedback_48x_setting: Feedback48XSetting::Invalid,
1078 load_param_enable: false,
1079 end_load: EndLoadSetting::NoLoad,
1080 }
1081 }
1082
1083 pub fn set(set_type: ParameterSetType) -> Self {
1085 Self {
1086 query_type: None,
1087 set_type: Some(set_type),
1088 feedback_48x_setting: Feedback48XSetting::Invalid,
1089 load_param_enable: false,
1090 end_load: EndLoadSetting::NoLoad,
1091 }
1092 }
1093
1094 pub fn with_feedback_48x(mut self, setting: Feedback48XSetting) -> Self {
1096 self.feedback_48x_setting = setting;
1097 self
1098 }
1099
1100 pub fn with_end_load(mut self, load: EndLoadSetting) -> Self {
1102 self.load_param_enable = true; self.end_load = load;
1104 self
1105 }
1106
1107 pub fn validate(&self) -> Result<(), ProtocolError> {
1109 if self.query_type.is_some() && self.set_type.is_some() {
1110 return Err(ProtocolError::ParseError(
1111 "查询和设置不能同时进行".to_string(),
1112 ));
1113 }
1114 if self.query_type.is_none() && self.set_type.is_none() {
1115 return Err(ProtocolError::ParseError(
1116 "必须指定查询或设置之一".to_string(),
1117 ));
1118 }
1119 Ok(())
1120 }
1121
1122 pub fn to_frame(self) -> Result<PiperFrame, ProtocolError> {
1124 self.validate()?;
1126
1127 let mut data = [0u8; 8];
1128 data[0] = self.query_type.map(|q| q as u8).unwrap_or(0x00);
1129 data[1] = self.set_type.map(|s| s as u8).unwrap_or(0x00);
1130 data[2] = self.feedback_48x_setting as u8;
1131 data[3] = if self.load_param_enable { 0xAE } else { 0x00 };
1132 data[4] = self.end_load as u8;
1133 Ok(PiperFrame::new_standard(
1136 ID_PARAMETER_QUERY_SET as u16,
1137 &data,
1138 ))
1139 }
1140}
1141
1142#[cfg(test)]
1143mod parameter_query_set_tests {
1144 use super::*;
1145
1146 #[test]
1147 fn test_parameter_query_type_from_u8() {
1148 assert_eq!(
1149 ParameterQueryType::try_from(0x01).unwrap(),
1150 ParameterQueryType::EndVelocityAccel
1151 );
1152 assert_eq!(
1153 ParameterQueryType::try_from(0x02).unwrap(),
1154 ParameterQueryType::CollisionProtectionLevel
1155 );
1156 assert_eq!(
1157 ParameterQueryType::try_from(0x03).unwrap(),
1158 ParameterQueryType::CurrentTrajectoryIndex
1159 );
1160 assert_eq!(
1161 ParameterQueryType::try_from(0x04).unwrap(),
1162 ParameterQueryType::GripperTeachParamsIndex
1163 );
1164 }
1165
1166 #[test]
1167 fn test_parameter_set_type_from_u8() {
1168 assert_eq!(
1169 ParameterSetType::try_from(0x01).unwrap(),
1170 ParameterSetType::EndVelocityAccelToDefault
1171 );
1172 assert_eq!(
1173 ParameterSetType::try_from(0x02).unwrap(),
1174 ParameterSetType::AllJointLimitsToDefault
1175 );
1176 }
1177
1178 #[test]
1179 fn test_parameter_query_set_command_query() {
1180 let cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel);
1181 let frame = cmd.to_frame().unwrap();
1182
1183 assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
1184 assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); }
1187
1188 #[test]
1189 fn test_parameter_query_set_command_set() {
1190 let cmd = ParameterQuerySetCommand::set(ParameterSetType::AllJointLimitsToDefault);
1191 let frame = cmd.to_frame().unwrap();
1192
1193 assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
1194 assert_eq!(frame.data[0], 0x00); assert_eq!(frame.data[1], 0x02); }
1197
1198 #[test]
1199 fn test_parameter_query_set_command_validate_mutually_exclusive() {
1200 let mut cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel);
1202 cmd.set_type = Some(ParameterSetType::AllJointLimitsToDefault);
1203
1204 assert!(cmd.validate().is_err());
1205 assert!(cmd.to_frame().is_err());
1206 }
1207
1208 #[test]
1209 fn test_parameter_query_set_command_validate_neither() {
1210 let cmd = ParameterQuerySetCommand {
1212 query_type: None,
1213 set_type: None,
1214 feedback_48x_setting: Feedback48XSetting::Invalid,
1215 load_param_enable: false,
1216 end_load: EndLoadSetting::NoLoad,
1217 };
1218
1219 assert!(cmd.validate().is_err());
1220 assert!(cmd.to_frame().is_err());
1221 }
1222
1223 #[test]
1224 fn test_feedback_48x_setting_from_u8() {
1225 assert_eq!(
1226 Feedback48XSetting::try_from(0x00).unwrap(),
1227 Feedback48XSetting::Invalid
1228 );
1229 assert_eq!(
1230 Feedback48XSetting::try_from(0x01).unwrap(),
1231 Feedback48XSetting::Enable
1232 );
1233 assert_eq!(
1234 Feedback48XSetting::try_from(0x02).unwrap(),
1235 Feedback48XSetting::Disable
1236 );
1237 }
1238
1239 #[test]
1240 fn test_end_load_setting_from_u8() {
1241 assert_eq!(
1242 EndLoadSetting::try_from(0x00).unwrap(),
1243 EndLoadSetting::NoLoad
1244 );
1245 assert_eq!(
1246 EndLoadSetting::try_from(0x01).unwrap(),
1247 EndLoadSetting::HalfLoad
1248 );
1249 assert_eq!(
1250 EndLoadSetting::try_from(0x02).unwrap(),
1251 EndLoadSetting::FullLoad
1252 );
1253 }
1254
1255 #[test]
1256 fn test_parameter_query_set_command_with_feedback_48x() {
1257 let cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel)
1258 .with_feedback_48x(Feedback48XSetting::Enable);
1259 let frame = cmd.to_frame().unwrap();
1260
1261 assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
1262 assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x01); }
1266
1267 #[test]
1268 fn test_parameter_query_set_command_with_end_load() {
1269 let cmd = ParameterQuerySetCommand::set(ParameterSetType::EndVelocityAccelToDefault)
1270 .with_end_load(EndLoadSetting::FullLoad);
1271 let frame = cmd.to_frame().unwrap();
1272
1273 assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
1274 assert_eq!(frame.data[0], 0x00); assert_eq!(frame.data[1], 0x01); assert_eq!(frame.data[3], 0xAE); assert_eq!(frame.data[4], 0x02); }
1279}
1280
1281#[derive(Debug, Clone, Copy, Default)]
1293pub struct EndVelocityAccelFeedback {
1294 pub max_linear_velocity: u16, pub max_angular_velocity: u16, pub max_linear_accel: u16, pub max_angular_accel: u16, }
1299
1300impl EndVelocityAccelFeedback {
1301 pub fn max_linear_velocity(&self) -> f64 {
1303 self.max_linear_velocity as f64 / 1000.0
1304 }
1305
1306 pub fn max_angular_velocity(&self) -> f64 {
1308 self.max_angular_velocity as f64 / 1000.0
1309 }
1310
1311 pub fn max_linear_accel(&self) -> f64 {
1313 self.max_linear_accel as f64 / 1000.0
1314 }
1315
1316 pub fn max_angular_accel(&self) -> f64 {
1318 self.max_angular_accel as f64 / 1000.0
1319 }
1320}
1321
1322impl TryFrom<PiperFrame> for EndVelocityAccelFeedback {
1323 type Error = ProtocolError;
1324
1325 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1326 if frame.id != ID_END_VELOCITY_ACCEL_FEEDBACK {
1328 return Err(ProtocolError::InvalidCanId { id: frame.id });
1329 }
1330
1331 if frame.len < 8 {
1333 return Err(ProtocolError::InvalidLength {
1334 expected: 8,
1335 actual: frame.len as usize,
1336 });
1337 }
1338
1339 let max_linear_velocity = u16::from_be_bytes([frame.data[0], frame.data[1]]);
1341 let max_angular_velocity = u16::from_be_bytes([frame.data[2], frame.data[3]]);
1342 let max_linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
1343 let max_angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
1344
1345 Ok(Self {
1346 max_linear_velocity,
1347 max_angular_velocity,
1348 max_linear_accel,
1349 max_angular_accel,
1350 })
1351 }
1352}
1353
1354#[cfg(test)]
1355mod end_velocity_accel_feedback_tests {
1356 use super::*;
1357
1358 #[test]
1359 fn test_end_velocity_accel_feedback_parse() {
1360 let mut data = [0u8; 8];
1366 data[0..2].copy_from_slice(&1000u16.to_be_bytes());
1367 data[2..4].copy_from_slice(&2000u16.to_be_bytes());
1368 data[4..6].copy_from_slice(&500u16.to_be_bytes());
1369 data[6..8].copy_from_slice(&1500u16.to_be_bytes());
1370
1371 let frame = PiperFrame::new_standard(ID_END_VELOCITY_ACCEL_FEEDBACK as u16, &data);
1372 let feedback = EndVelocityAccelFeedback::try_from(frame).unwrap();
1373
1374 assert_eq!(feedback.max_linear_velocity, 1000);
1375 assert_eq!(feedback.max_angular_velocity, 2000);
1376 assert_eq!(feedback.max_linear_accel, 500);
1377 assert_eq!(feedback.max_angular_accel, 1500);
1378 assert!((feedback.max_linear_velocity() - 1.0).abs() < 0.0001);
1379 assert!((feedback.max_angular_velocity() - 2.0).abs() < 0.0001);
1380 assert!((feedback.max_linear_accel() - 0.5).abs() < 0.0001);
1381 assert!((feedback.max_angular_accel() - 1.5).abs() < 0.0001);
1382 }
1383
1384 #[test]
1385 fn test_end_velocity_accel_feedback_invalid_id() {
1386 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
1387 let result = EndVelocityAccelFeedback::try_from(frame);
1388 assert!(result.is_err());
1389 }
1390
1391 #[test]
1392 fn test_end_velocity_accel_feedback_invalid_length() {
1393 let frame = PiperFrame::new_standard(ID_END_VELOCITY_ACCEL_FEEDBACK as u16, &[0; 4]);
1394 let result = EndVelocityAccelFeedback::try_from(frame);
1395 assert!(result.is_err());
1396 }
1397}
1398
1399#[derive(Debug, Clone, Copy)]
1412pub struct SetEndVelocityAccelCommand {
1413 pub max_linear_velocity: Option<u16>, pub max_angular_velocity: Option<u16>, pub max_linear_accel: Option<u16>, pub max_angular_accel: Option<u16>, }
1418
1419impl SetEndVelocityAccelCommand {
1420 pub fn new(
1422 max_linear_velocity: Option<f64>,
1423 max_angular_velocity: Option<f64>,
1424 max_linear_accel: Option<f64>,
1425 max_angular_accel: Option<f64>,
1426 ) -> Self {
1427 Self {
1428 max_linear_velocity: max_linear_velocity.map(|v| (v * 1000.0) as u16),
1429 max_angular_velocity: max_angular_velocity.map(|v| (v * 1000.0) as u16),
1430 max_linear_accel: max_linear_accel.map(|a| (a * 1000.0) as u16),
1431 max_angular_accel: max_angular_accel.map(|a| (a * 1000.0) as u16),
1432 }
1433 }
1434
1435 pub fn to_frame(self) -> PiperFrame {
1437 let mut data = [0u8; 8];
1438
1439 let linear_vel_bytes = if let Some(vel) = self.max_linear_velocity {
1441 vel.to_be_bytes()
1442 } else {
1443 [0x7F, 0xFF] };
1445 data[0..2].copy_from_slice(&linear_vel_bytes);
1446
1447 let angular_vel_bytes = if let Some(vel) = self.max_angular_velocity {
1448 vel.to_be_bytes()
1449 } else {
1450 [0x7F, 0xFF] };
1452 data[2..4].copy_from_slice(&angular_vel_bytes);
1453
1454 let linear_accel_bytes = if let Some(accel) = self.max_linear_accel {
1455 accel.to_be_bytes()
1456 } else {
1457 [0x7F, 0xFF] };
1459 data[4..6].copy_from_slice(&linear_accel_bytes);
1460
1461 let angular_accel_bytes = if let Some(accel) = self.max_angular_accel {
1462 accel.to_be_bytes()
1463 } else {
1464 [0x7F, 0xFF] };
1466 data[6..8].copy_from_slice(&angular_accel_bytes);
1467
1468 PiperFrame::new_standard(ID_SET_END_VELOCITY_ACCEL as u16, &data)
1469 }
1470}
1471
1472#[cfg(test)]
1473mod set_end_velocity_accel_tests {
1474 use super::*;
1475
1476 #[test]
1477 fn test_set_end_velocity_accel_command_new() {
1478 let cmd = SetEndVelocityAccelCommand::new(
1479 Some(1.0), Some(2.0), Some(0.5), Some(1.5), );
1484 assert_eq!(cmd.max_linear_velocity, Some(1000));
1485 assert_eq!(cmd.max_angular_velocity, Some(2000));
1486 assert_eq!(cmd.max_linear_accel, Some(500));
1487 assert_eq!(cmd.max_angular_accel, Some(1500));
1488 }
1489
1490 #[test]
1491 fn test_set_end_velocity_accel_command_to_frame() {
1492 let cmd = SetEndVelocityAccelCommand::new(Some(1.0), Some(2.0), Some(0.5), Some(1.5));
1493 let frame = cmd.to_frame();
1494
1495 assert_eq!(frame.id, ID_SET_END_VELOCITY_ACCEL);
1496
1497 let linear_vel = u16::from_be_bytes([frame.data[0], frame.data[1]]);
1499 let angular_vel = u16::from_be_bytes([frame.data[2], frame.data[3]]);
1500 let linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
1501 let angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
1502
1503 assert_eq!(linear_vel, 1000);
1504 assert_eq!(angular_vel, 2000);
1505 assert_eq!(linear_accel, 500);
1506 assert_eq!(angular_accel, 1500);
1507 }
1508
1509 #[test]
1510 fn test_set_end_velocity_accel_command_invalid_values() {
1511 let cmd = SetEndVelocityAccelCommand::new(None, None, None, None);
1513 let frame = cmd.to_frame();
1514
1515 assert_eq!(frame.data[0], 0x7F);
1517 assert_eq!(frame.data[1], 0xFF);
1518 assert_eq!(frame.data[2], 0x7F);
1519 assert_eq!(frame.data[3], 0xFF);
1520 assert_eq!(frame.data[4], 0x7F);
1521 assert_eq!(frame.data[5], 0xFF);
1522 assert_eq!(frame.data[6], 0x7F);
1523 assert_eq!(frame.data[7], 0xFF);
1524 }
1525
1526 #[test]
1527 fn test_set_end_velocity_accel_command_partial_values() {
1528 let cmd = SetEndVelocityAccelCommand::new(
1530 Some(1.0), None, Some(0.5), None, );
1535 let frame = cmd.to_frame();
1536
1537 let linear_vel = u16::from_be_bytes([frame.data[0], frame.data[1]]);
1538 let angular_vel = u16::from_be_bytes([frame.data[2], frame.data[3]]);
1539 let linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
1540 let angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
1541
1542 assert_eq!(linear_vel, 1000);
1543 assert_eq!(angular_vel, 0x7FFF); assert_eq!(linear_accel, 500);
1545 assert_eq!(angular_accel, 0x7FFF); }
1547}
1548
1549#[derive(Debug, Clone, Copy)]
1557pub struct CollisionProtectionLevelCommand {
1558 pub levels: [u8; 6], }
1560
1561impl CollisionProtectionLevelCommand {
1562 pub fn new(levels: [u8; 6]) -> Self {
1564 for &level in &levels {
1566 if level > 8 {
1567 panic!("碰撞防护等级必须在0~8之间");
1568 }
1569 }
1570 Self { levels }
1571 }
1572
1573 pub fn all_joints(level: u8) -> Self {
1575 if level > 8 {
1576 panic!("碰撞防护等级必须在0~8之间");
1577 }
1578 Self { levels: [level; 6] }
1579 }
1580
1581 pub fn to_frame(self) -> PiperFrame {
1583 let mut data = [0u8; 8];
1584 data[0..6].copy_from_slice(&self.levels);
1585 PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL as u16, &data)
1588 }
1589}
1590
1591#[derive(Debug, Clone, Copy, Default)]
1595pub struct CollisionProtectionLevelFeedback {
1596 pub levels: [u8; 6], }
1598
1599impl TryFrom<PiperFrame> for CollisionProtectionLevelFeedback {
1600 type Error = ProtocolError;
1601
1602 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1603 if frame.id != ID_COLLISION_PROTECTION_LEVEL_FEEDBACK {
1605 return Err(ProtocolError::InvalidCanId { id: frame.id });
1606 }
1607
1608 if frame.len < 6 {
1610 return Err(ProtocolError::InvalidLength {
1611 expected: 6,
1612 actual: frame.len as usize,
1613 });
1614 }
1615
1616 let mut levels = [0u8; 6];
1617 levels.copy_from_slice(&frame.data[0..6]);
1618
1619 Ok(Self { levels })
1620 }
1621}
1622
1623#[cfg(test)]
1624mod collision_protection_tests {
1625 use super::*;
1626
1627 #[test]
1628 fn test_collision_protection_level_command_new() {
1629 let cmd = CollisionProtectionLevelCommand::new([1, 2, 3, 4, 5, 6]);
1630 assert_eq!(cmd.levels, [1, 2, 3, 4, 5, 6]);
1631 }
1632
1633 #[test]
1634 fn test_collision_protection_level_command_all_joints() {
1635 let cmd = CollisionProtectionLevelCommand::all_joints(5);
1636 assert_eq!(cmd.levels, [5; 6]);
1637 }
1638
1639 #[test]
1640 fn test_collision_protection_level_command_to_frame() {
1641 let cmd = CollisionProtectionLevelCommand::new([1, 2, 3, 4, 5, 6]);
1642 let frame = cmd.to_frame();
1643
1644 assert_eq!(frame.id, ID_COLLISION_PROTECTION_LEVEL);
1645 assert_eq!(frame.data[0..6], [1, 2, 3, 4, 5, 6]);
1646 assert_eq!(frame.data[6], 0x00); assert_eq!(frame.data[7], 0x00); }
1649
1650 #[test]
1651 fn test_collision_protection_level_feedback_parse() {
1652 let mut data = [0u8; 8];
1653 data[0..6].copy_from_slice(&[1, 2, 3, 4, 5, 6]);
1654
1655 let frame = PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL_FEEDBACK as u16, &data);
1656 let feedback = CollisionProtectionLevelFeedback::try_from(frame).unwrap();
1657
1658 assert_eq!(feedback.levels, [1, 2, 3, 4, 5, 6]);
1659 }
1660
1661 #[test]
1662 fn test_collision_protection_level_feedback_invalid_id() {
1663 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
1664 let result = CollisionProtectionLevelFeedback::try_from(frame);
1665 assert!(result.is_err());
1666 }
1667
1668 #[test]
1669 fn test_collision_protection_level_feedback_invalid_length() {
1670 let frame =
1671 PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL_FEEDBACK as u16, &[0; 4]);
1672 let result = CollisionProtectionLevelFeedback::try_from(frame);
1673 assert!(result.is_err());
1674 }
1675
1676 #[test]
1677 fn test_collision_protection_level_zero() {
1678 let cmd = CollisionProtectionLevelCommand::all_joints(0);
1680 let frame = cmd.to_frame();
1681 assert_eq!(frame.data[0..6], [0; 6]);
1682 }
1683
1684 #[test]
1685 fn test_collision_protection_level_max() {
1686 let cmd = CollisionProtectionLevelCommand::all_joints(8);
1688 let frame = cmd.to_frame();
1689 assert_eq!(frame.data[0..6], [8; 6]);
1690 }
1691}
1692
1693#[derive(Debug, Clone, Copy, Default)]
1701pub struct MotorMaxAccelFeedback {
1702 pub joint_index: u8, pub max_accel_rad_s2: u16, }
1705
1706impl MotorMaxAccelFeedback {
1707 pub fn max_accel(&self) -> f64 {
1709 self.max_accel_rad_s2 as f64 / 1000.0
1710 }
1711}
1712
1713impl TryFrom<PiperFrame> for MotorMaxAccelFeedback {
1714 type Error = ProtocolError;
1715
1716 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1717 if frame.id != ID_MOTOR_MAX_ACCEL_FEEDBACK {
1719 return Err(ProtocolError::InvalidCanId { id: frame.id });
1720 }
1721
1722 if frame.len < 3 {
1724 return Err(ProtocolError::InvalidLength {
1725 expected: 3,
1726 actual: frame.len as usize,
1727 });
1728 }
1729
1730 let joint_index = frame.data[0];
1731 let max_accel_bytes = [frame.data[1], frame.data[2]];
1732 let max_accel_rad_s2 = u16::from_be_bytes(max_accel_bytes);
1733
1734 Ok(Self {
1735 joint_index,
1736 max_accel_rad_s2,
1737 })
1738 }
1739}
1740
1741#[cfg(test)]
1742mod motor_max_accel_feedback_tests {
1743 use super::*;
1744
1745 #[test]
1746 fn test_motor_max_accel_feedback_parse() {
1747 let mut data = [0u8; 8];
1751 data[0] = 1;
1752 data[1..3].copy_from_slice(&10000u16.to_be_bytes());
1753
1754 let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &data);
1755 let feedback = MotorMaxAccelFeedback::try_from(frame).unwrap();
1756
1757 assert_eq!(feedback.joint_index, 1);
1758 assert_eq!(feedback.max_accel_rad_s2, 10000);
1759 assert!((feedback.max_accel() - 10.0).abs() < 0.0001);
1760 }
1761
1762 #[test]
1763 fn test_motor_max_accel_feedback_all_joints() {
1764 for i in 1..=6 {
1766 let mut data = [0u8; 8];
1767 data[0] = i;
1768 data[1..3].copy_from_slice(&5000u16.to_be_bytes());
1769
1770 let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &data);
1771 let feedback = MotorMaxAccelFeedback::try_from(frame).unwrap();
1772 assert_eq!(feedback.joint_index, i);
1773 }
1774 }
1775
1776 #[test]
1777 fn test_motor_max_accel_feedback_invalid_id() {
1778 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
1779 let result = MotorMaxAccelFeedback::try_from(frame);
1780 assert!(result.is_err());
1781 }
1782
1783 #[test]
1784 fn test_motor_max_accel_feedback_invalid_length() {
1785 let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &[0; 2]);
1786 let result = MotorMaxAccelFeedback::try_from(frame);
1787 assert!(result.is_err());
1788 }
1789}
1790
1791#[derive(Debug, Clone, Copy)]
1804pub struct GripperTeachParamsCommand {
1805 pub teach_travel_coeff: u8, pub max_travel_limit: u8, pub friction_coeff: u8, }
1810
1811impl GripperTeachParamsCommand {
1812 pub fn new(teach_travel_coeff: u8, max_travel_limit: u8, friction_coeff: u8) -> Self {
1814 Self {
1815 teach_travel_coeff,
1816 max_travel_limit,
1817 friction_coeff,
1818 }
1819 }
1820
1821 pub fn to_frame(self) -> PiperFrame {
1823 let mut data = [0u8; 8];
1824 data[0] = self.teach_travel_coeff;
1825 data[1] = self.max_travel_limit;
1826 data[2] = self.friction_coeff;
1827 PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS as u16, &data)
1830 }
1831}
1832
1833#[derive(Debug, Clone, Copy, Default)]
1837pub struct GripperTeachParamsFeedback {
1838 pub teach_travel_coeff: u8, pub max_travel_limit: u8, pub friction_coeff: u8, }
1843
1844impl TryFrom<PiperFrame> for GripperTeachParamsFeedback {
1845 type Error = ProtocolError;
1846
1847 fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1848 if frame.id != ID_GRIPPER_TEACH_PARAMS_FEEDBACK {
1850 return Err(ProtocolError::InvalidCanId { id: frame.id });
1851 }
1852
1853 if frame.len < 3 {
1855 return Err(ProtocolError::InvalidLength {
1856 expected: 3,
1857 actual: frame.len as usize,
1858 });
1859 }
1860
1861 Ok(Self {
1862 teach_travel_coeff: frame.data[0],
1863 max_travel_limit: frame.data[1],
1864 friction_coeff: frame.data[2],
1865 })
1866 }
1867}
1868
1869#[cfg(test)]
1870mod gripper_teach_params_tests {
1871 use super::*;
1872
1873 #[test]
1874 fn test_gripper_teach_params_command_new() {
1875 let cmd = GripperTeachParamsCommand::new(150, 70, 5);
1876 assert_eq!(cmd.teach_travel_coeff, 150);
1877 assert_eq!(cmd.max_travel_limit, 70);
1878 assert_eq!(cmd.friction_coeff, 5);
1879 }
1880
1881 #[test]
1882 fn test_gripper_teach_params_command_to_frame() {
1883 let cmd = GripperTeachParamsCommand::new(150, 70, 5);
1884 let frame = cmd.to_frame();
1885
1886 assert_eq!(frame.id, ID_GRIPPER_TEACH_PARAMS);
1887 assert_eq!(frame.data[0], 150);
1888 assert_eq!(frame.data[1], 70);
1889 assert_eq!(frame.data[2], 5);
1890 assert_eq!(frame.data[3], 0x00); }
1892
1893 #[test]
1894 fn test_gripper_teach_params_feedback_parse() {
1895 let mut data = [0u8; 8];
1896 data[0] = 150; data[1] = 70; data[2] = 5; let frame = PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS_FEEDBACK as u16, &data);
1901 let feedback = GripperTeachParamsFeedback::try_from(frame).unwrap();
1902
1903 assert_eq!(feedback.teach_travel_coeff, 150);
1904 assert_eq!(feedback.max_travel_limit, 70);
1905 assert_eq!(feedback.friction_coeff, 5);
1906 }
1907
1908 #[test]
1909 fn test_gripper_teach_params_feedback_invalid_id() {
1910 let frame = PiperFrame::new_standard(0x999, &[0; 8]);
1911 let result = GripperTeachParamsFeedback::try_from(frame);
1912 assert!(result.is_err());
1913 }
1914
1915 #[test]
1916 fn test_gripper_teach_params_feedback_invalid_length() {
1917 let frame = PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS_FEEDBACK as u16, &[0; 2]);
1918 let result = GripperTeachParamsFeedback::try_from(frame);
1919 assert!(result.is_err());
1920 }
1921}
1922
1923#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
1929#[repr(u8)]
1930pub enum FirmwareUpgradeMode {
1931 #[default]
1933 Exit = 0x00,
1934 CanUpgradeSilent = 0x01,
1936 CombinedUpgrade = 0x02,
1939}
1940
1941#[derive(Debug, Clone, Copy)]
1945pub struct FirmwareUpgradeCommand {
1946 pub mode: FirmwareUpgradeMode,
1947}
1948
1949impl FirmwareUpgradeCommand {
1950 pub fn new(mode: FirmwareUpgradeMode) -> Self {
1952 Self { mode }
1953 }
1954
1955 pub fn exit() -> Self {
1957 Self {
1958 mode: FirmwareUpgradeMode::Exit,
1959 }
1960 }
1961
1962 pub fn can_upgrade_silent() -> Self {
1964 Self {
1965 mode: FirmwareUpgradeMode::CanUpgradeSilent,
1966 }
1967 }
1968
1969 pub fn combined_upgrade() -> Self {
1971 Self {
1972 mode: FirmwareUpgradeMode::CombinedUpgrade,
1973 }
1974 }
1975
1976 pub fn to_frame(self) -> PiperFrame {
1978 let data = [self.mode as u8];
1979 PiperFrame::new_standard(ID_FIRMWARE_UPGRADE as u16, &data)
1980 }
1981}
1982
1983#[cfg(test)]
1984mod firmware_upgrade_tests {
1985 use super::*;
1986
1987 #[test]
1988 fn test_firmware_upgrade_mode_from() {
1989 assert_eq!(FirmwareUpgradeMode::from(0x00), FirmwareUpgradeMode::Exit);
1990 assert_eq!(
1991 FirmwareUpgradeMode::from(0x01),
1992 FirmwareUpgradeMode::CanUpgradeSilent
1993 );
1994 assert_eq!(
1995 FirmwareUpgradeMode::from(0x02),
1996 FirmwareUpgradeMode::CombinedUpgrade
1997 );
1998 assert_eq!(FirmwareUpgradeMode::from(0xFF), FirmwareUpgradeMode::Exit); }
2000
2001 #[test]
2002 fn test_firmware_upgrade_command_new() {
2003 let cmd = FirmwareUpgradeCommand::new(FirmwareUpgradeMode::CanUpgradeSilent);
2004 assert_eq!(cmd.mode, FirmwareUpgradeMode::CanUpgradeSilent);
2005 }
2006
2007 #[test]
2008 fn test_firmware_upgrade_command_exit() {
2009 let cmd = FirmwareUpgradeCommand::exit();
2010 assert_eq!(cmd.mode, FirmwareUpgradeMode::Exit);
2011 }
2012
2013 #[test]
2014 fn test_firmware_upgrade_command_can_upgrade_silent() {
2015 let cmd = FirmwareUpgradeCommand::can_upgrade_silent();
2016 assert_eq!(cmd.mode, FirmwareUpgradeMode::CanUpgradeSilent);
2017 }
2018
2019 #[test]
2020 fn test_firmware_upgrade_command_combined_upgrade() {
2021 let cmd = FirmwareUpgradeCommand::combined_upgrade();
2022 assert_eq!(cmd.mode, FirmwareUpgradeMode::CombinedUpgrade);
2023 }
2024
2025 #[test]
2026 fn test_firmware_upgrade_command_to_frame() {
2027 let cmd = FirmwareUpgradeCommand::new(FirmwareUpgradeMode::CanUpgradeSilent);
2028 let frame = cmd.to_frame();
2029
2030 assert_eq!(frame.id, ID_FIRMWARE_UPGRADE);
2031 assert_eq!(frame.len, 1);
2032 assert_eq!(frame.data[0], 0x01);
2033 }
2034
2035 #[test]
2036 fn test_firmware_upgrade_command_all_modes() {
2037 let modes = [
2039 FirmwareUpgradeMode::Exit,
2040 FirmwareUpgradeMode::CanUpgradeSilent,
2041 FirmwareUpgradeMode::CombinedUpgrade,
2042 ];
2043
2044 for mode in modes.iter() {
2045 let cmd = FirmwareUpgradeCommand::new(*mode);
2046 let frame = cmd.to_frame();
2047 assert_eq!(frame.id, ID_FIRMWARE_UPGRADE);
2048 assert_eq!(frame.data[0], *mode as u8);
2049 }
2050 }
2051}
2052
2053#[derive(Debug, Clone, Copy)]
2065pub struct FirmwareVersionQueryCommand;
2066
2067impl FirmwareVersionQueryCommand {
2068 pub fn new() -> Self {
2072 Self
2073 }
2074
2075 pub fn to_frame(self) -> PiperFrame {
2080 let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2082 PiperFrame::new_standard(ID_FIRMWARE_READ as u16, &data)
2083 }
2084}
2085
2086impl Default for FirmwareVersionQueryCommand {
2087 fn default() -> Self {
2088 Self::new()
2089 }
2090}
2091
2092#[cfg(test)]
2093mod firmware_version_query_tests {
2094 use super::*;
2095
2096 #[test]
2097 fn test_firmware_version_query_command_new() {
2098 let cmd = FirmwareVersionQueryCommand::new();
2099 let frame = cmd.to_frame();
2100
2101 assert_eq!(frame.id, ID_FIRMWARE_READ);
2102 assert_eq!(frame.len, 8);
2103 assert_eq!(frame.data[0], 0x01);
2104 assert_eq!(frame.data[1..8], [0x00; 7]);
2105 }
2106
2107 #[test]
2108 fn test_firmware_version_query_command_default() {
2109 let cmd = FirmwareVersionQueryCommand;
2110 let frame = cmd.to_frame();
2111
2112 assert_eq!(frame.id, ID_FIRMWARE_READ);
2113 assert_eq!(frame.data, [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
2114 }
2115
2116 #[test]
2117 fn test_firmware_version_query_command_data_format() {
2118 let cmd = FirmwareVersionQueryCommand::new();
2120 let frame = cmd.to_frame();
2121
2122 let expected_data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2124 assert_eq!(frame.data, expected_data);
2125 }
2126}