Skip to main content

piper_protocol/
config.rs

1//! 配置帧结构体定义
2//!
3//! 包含配置查询和设置指令的结构体,以及配置反馈帧。
4
5use crate::can::PiperFrame;
6use crate::{ProtocolError, bytes_to_i16_be, i16_to_bytes_be, ids::*};
7
8// ============================================================================
9// 随动主从模式设置指令
10// ============================================================================
11
12/// 联动设置指令
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LinkSetting {
15    /// 无效
16    Invalid = 0x00,
17    /// 设置为示教输入臂
18    TeachInputArm = 0xFA,
19    /// 设置为运动输出臂
20    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/// 反馈指令偏移值
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum FeedbackIdOffset {
42    /// 不偏移/恢复默认
43    None = 0x00,
44    /// 反馈指令基ID由2Ax偏移为2Bx
45    Offset2Bx = 0x10,
46    /// 反馈指令基ID由2Ax偏移为2Cx
47    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/// 控制指令偏移值
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum ControlIdOffset {
69    /// 不偏移/恢复默认
70    None = 0x00,
71    /// 控制指令基ID由15x偏移为16x
72    Offset16x = 0x10,
73    /// 控制指令基ID由15x偏移为17x
74    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/// 随动主从模式设置指令 (0x470)
94///
95/// 用于设置机械臂为示教输入臂或运动输出臂,并配置 ID 偏移值。
96/// 协议长度:4 字节,但 CAN 帧为 8 字节(后 4 字节保留)。
97#[derive(Debug, Clone, Copy)]
98pub struct MasterSlaveModeCommand {
99    pub link_setting: LinkSetting,            // Byte 0: 联动设置指令
100    pub feedback_id_offset: FeedbackIdOffset, // Byte 1: 反馈指令偏移值
101    pub control_id_offset: ControlIdOffset,   // Byte 2: 控制指令偏移值
102    pub target_id_offset: ControlIdOffset,    // Byte 3: 联动模式控制目标地址偏移值
103}
104
105impl MasterSlaveModeCommand {
106    /// 创建设置为示教输入臂的指令
107    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    /// 创建设置为运动输出臂的指令(恢复常规状态)
121    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    /// 转换为 CAN 帧
131    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        // Byte 4-7: 保留,已初始化为 0
138
139        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); // TeachInputArm
203        assert_eq!(frame.data[1], 0x10); // Offset2Bx
204        assert_eq!(frame.data[2], 0x10); // Offset16x
205        assert_eq!(frame.data[3], 0x10); // Offset16x
206        assert_eq!(frame.data[4], 0x00); // 保留
207    }
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); // MotionOutputArm
216        assert_eq!(frame.data[1], 0x00); // None
217        assert_eq!(frame.data[2], 0x00); // None
218        assert_eq!(frame.data[3], 0x00); // None
219    }
220}
221
222// ============================================================================
223// 查询电机限制指令和反馈
224// ============================================================================
225
226/// 查询内容类型
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum QueryType {
229    /// 查询电机角度/最大速度
230    AngleAndMaxVelocity = 0x01,
231    /// 查询电机最大加速度限制
232    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/// 查询电机限制指令 (0x472)
251#[derive(Debug, Clone, Copy)]
252pub struct QueryMotorLimitCommand {
253    pub joint_index: u8,       // Byte 0: 关节电机序号(1-6)
254    pub query_type: QueryType, // Byte 1: 查询内容
255}
256
257impl QueryMotorLimitCommand {
258    /// 创建查询电机角度/最大速度指令
259    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    /// 创建查询电机最大加速度限制指令
267    pub fn query_max_acceleration(joint_index: u8) -> Self {
268        Self {
269            joint_index,
270            query_type: QueryType::MaxAcceleration,
271        }
272    }
273
274    /// 转换为 CAN 帧
275    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        // Byte 2-7: 保留,已初始化为 0
280
281        PiperFrame::new_standard(ID_QUERY_MOTOR_LIMIT as u16, &data)
282    }
283}
284
285/// 反馈当前电机限制角度/最大速度 (0x473)
286///
287/// 单位:
288/// - 角度限制:0.1°(原始值)
289/// - 最大关节速度:0.01rad/s(原始值)
290#[derive(Debug, Clone, Copy, Default)]
291pub struct MotorLimitFeedback {
292    pub joint_index: u8,    // Byte 0: 关节电机序号(1-6)
293    pub max_angle_deg: i16, // Byte 1-2: 最大角度限制,单位 0.1°
294    pub min_angle_deg: i16, // Byte 3-4: 最小角度限制,单位 0.1°
295    pub max_velocity_rad_s: u16, // Byte 5-6: 最大关节速度,单位 0.01rad/s
296                            // Byte 7: 保留
297}
298
299impl MotorLimitFeedback {
300    /// 获取最大角度(度)
301    pub fn max_angle(&self) -> f64 {
302        self.max_angle_deg as f64 / 10.0
303    }
304
305    /// 获取最小角度(度)
306    pub fn min_angle(&self) -> f64 {
307        self.min_angle_deg as f64 / 10.0
308    }
309
310    /// 获取最大速度(rad/s)
311    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        // 验证 CAN ID
321        if frame.id != ID_MOTOR_LIMIT_FEEDBACK {
322            return Err(ProtocolError::InvalidCanId { id: frame.id });
323        }
324
325        // 验证数据长度
326        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        // 大端字节序
336        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); // AngleAndMaxVelocity
378    }
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); // MaxAcceleration
388    }
389
390    #[test]
391    fn test_motor_limit_feedback_parse() {
392        // 测试数据:
393        // - 最大角度: 180.0° = 1800 (0.1° 单位)
394        // - 最小角度: -180.0° = -1800 (0.1° 单位)
395        // - 最大速度: 5.0 rad/s = 500 (0.01rad/s 单位)
396        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; // 关节 1
402        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// ============================================================================
434// 设置电机限制指令
435// ============================================================================
436
437/// 电机角度限制/最大速度设置指令 (0x474)
438///
439/// 用于设置电机的角度限制和最大速度。
440/// 单位:
441/// - 角度限制:0.1°(原始值),无效值:0x7FFF
442/// - 最大关节速度:0.01rad/s(原始值),无效值:0x7FFF
443#[derive(Debug, Clone, Copy)]
444pub struct SetMotorLimitCommand {
445    pub joint_index: u8,                 // Byte 0: 关节电机序号(1-6)
446    pub max_angle_deg: Option<i16>,      // Byte 1-2: 最大角度限制,单位 0.1°,None 表示无效值
447    pub min_angle_deg: Option<i16>,      // Byte 3-4: 最小角度限制,单位 0.1°,None 表示无效值
448    pub max_velocity_rad_s: Option<u16>, // Byte 5-6: 最大关节速度,单位 0.01rad/s,None 表示无效值
449}
450
451impl SetMotorLimitCommand {
452    /// 从物理量创建设置指令
453    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    /// 转换为 CAN 帧
468    pub fn to_frame(self) -> PiperFrame {
469        let mut data = [0u8; 8];
470        data[0] = self.joint_index;
471
472        // 大端字节序
473        let max_angle_bytes = if let Some(angle) = self.max_angle_deg {
474            i16_to_bytes_be(angle)
475        } else {
476            [0x7F, 0xFF] // 无效值
477        };
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] // 无效值
484        };
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] // 无效值
491        };
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),  // 最大角度 180°
507            Some(-180.0), // 最小角度 -180°
508            Some(5.0),    // 最大速度 5.0 rad/s
509        );
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        // 验证大端字节序
525        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        // 测试无效值(None)
536        let cmd = SetMotorLimitCommand::new(1, None, None, None);
537        let frame = cmd.to_frame();
538
539        // 验证无效值编码为 0x7FFF
540        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        // 测试部分有效值
551        let cmd = SetMotorLimitCommand::new(
552            2,
553            Some(90.0), // 只设置最大角度
554            None,       // 最小角度无效
555            Some(3.0),  // 设置最大速度
556        );
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); // 无效值
565        assert_eq!(max_velocity, 300);
566    }
567}
568
569// ============================================================================
570// 关节设置指令
571// ============================================================================
572
573/// 关节设置指令 (0x475)
574///
575/// 用于设置关节的零点、加速度参数和清除错误代码。
576/// 单位:
577/// - 最大关节加速度:0.01rad/s²(原始值),无效值:0x7FFF
578///   特殊值:
579/// - 0xAE: 表示设置生效/清除错误
580#[derive(Debug, Clone, Copy)]
581pub struct JointSettingCommand {
582    pub joint_index: u8,               // Byte 0: 关节电机序号(1-7,7代表全部)
583    pub set_zero_point: bool,          // Byte 1: 设置当前位置为零点(0xAE 表示设置)
584    pub accel_param_enable: bool,      // Byte 2: 加速度参数设置是否生效(0xAE 表示生效)
585    pub max_accel_rad_s2: Option<u16>, // Byte 3-4: 最大关节加速度,单位 0.01rad/s²,None 表示无效值
586    pub clear_error: bool,             // Byte 5: 清除关节错误代码(0xAE 表示清除)
587}
588
589impl JointSettingCommand {
590    /// 创建设置零点指令
591    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    /// 创建设置加速度参数指令
602    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    /// 创建清除错误指令
613    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    /// 转换为 CAN 帧
624    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        // 大端字节序
631        let accel_bytes = if let Some(accel) = self.max_accel_rad_s2 {
632            accel.to_be_bytes()
633        } else {
634            [0x7F, 0xFF] // 无效值
635        };
636        data[3..5].copy_from_slice(&accel_bytes);
637
638        data[5] = if self.clear_error { 0xAE } else { 0x00 };
639        // Byte 6-7: 保留,已初始化为 0
640
641        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); // 设置零点
657        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); // 10.0 rad/s²
664        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); // 加速度参数生效
670
671        let max_accel = u16::from_be_bytes([frame.data[3], frame.data[4]]);
672        assert_eq!(max_accel, 1000); // 10.0 * 100 = 1000
673    }
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); // 清除错误
685    }
686
687    #[test]
688    fn test_joint_setting_command_all_joints() {
689        // 测试全部关节(joint_index = 7)
690        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        // 测试无效加速度值(None)
698        let mut cmd = JointSettingCommand::set_acceleration(1, 10.0);
699        cmd.max_accel_rad_s2 = None;
700        let frame = cmd.to_frame();
701
702        // 验证无效值编码为 0x7FFF
703        assert_eq!(frame.data[3], 0x7F);
704        assert_eq!(frame.data[4], 0xFF);
705    }
706}
707
708// ============================================================================
709// 设置指令应答
710// ============================================================================
711
712/// 轨迹包传输完成应答状态(Byte 3)
713#[derive(Debug, Clone, Copy, PartialEq, Eq)]
714pub enum TrajectoryPackCompleteStatus {
715    /// 传输完成且校验成功
716    Success,
717    /// 校验失败,需要整包重传
718    ChecksumFailed,
719    /// 其他状态值(未定义)
720    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/// 设置指令应答 (0x476)
744///
745/// 用于应答设置指令的执行结果。
746/// 注意:此帧有两种用途:
747/// 1. 设置指令应答(Byte 0 = 设置指令 ID 的最后一个字节,如 0x471 -> 0x71)
748/// 2. 轨迹传输应答(Byte 0 = 0x50,Byte 2 = 轨迹点索引,Byte 3 = 轨迹包传输完成应答)
749#[derive(Debug, Clone, Copy)]
750pub struct SettingResponse {
751    pub response_index: u8,       // Byte 0: 应答指令索引
752    pub zero_point_success: bool, // Byte 1: 零点是否设置成功(0x01: 成功,0x00: 失败/未设置)
753    pub trajectory_index: u8,     // Byte 2: 轨迹点索引(仅用于轨迹传输应答)
754    pub pack_complete_status: Option<TrajectoryPackCompleteStatus>, // Byte 3: 轨迹包传输完成应答(0xAE: 成功, 0xEE: 失败)
755    pub name_index: u16, // Byte 4-5: 轨迹包名称索引(仅用于轨迹传输应答)
756    pub crc16: u16,      // Byte 6-7: CRC16 校验(仅用于轨迹传输应答)
757}
758
759impl SettingResponse {
760    /// 判断是否为轨迹传输应答
761    pub fn is_trajectory_response(&self) -> bool {
762        self.response_index == 0x50
763    }
764
765    /// 判断是否为设置指令应答
766    pub fn is_setting_response(&self) -> bool {
767        !self.is_trajectory_response()
768    }
769
770    /// 获取轨迹包传输完成状态(如果是轨迹传输应答)
771    pub fn trajectory_pack_complete_status(&self) -> Option<TrajectoryPackCompleteStatus> {
772        self.pack_complete_status
773    }
774
775    /// 获取轨迹点索引(如果是轨迹传输应答)
776    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    /// 获取轨迹包名称索引(如果是轨迹传输应答)
785    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    /// 获取轨迹包 CRC16(如果是轨迹传输应答)
794    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        // 验证 CAN ID
808        if frame.id != ID_SETTING_RESPONSE {
809            return Err(ProtocolError::InvalidCanId { id: frame.id });
810        }
811
812        // 验证数据长度(至少需要 8 字节)
813        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        // Byte 3: 轨迹包传输完成应答(仅在轨迹传输应答时有效)
825        let pack_complete_status = if response_index == 0x50 {
826            Some(TrajectoryPackCompleteStatus::from(frame.data[3]))
827        } else {
828            None
829        };
830
831        // Byte 4-5: NameIndex(大端字节序)
832        let name_index = u16::from_be_bytes([frame.data[4], frame.data[5]]);
833
834        // Byte 6-7: CRC16(大端字节序)
835        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        // 测试设置指令应答(应答 0x471 电机使能指令)
855        let mut data = [0u8; 8];
856        data[0] = 0x71; // 0x471 的最后一个字节
857        data[1] = 0x00; // 不是零点设置
858
859        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        // 测试零点设置成功应答
871        let mut data = [0u8; 8];
872        data[0] = 0x75; // 0x475 关节设置指令的最后一个字节
873        data[1] = 0x01; // 零点设置成功
874
875        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        // 测试轨迹传输应答
885        let mut data = [0u8; 8];
886        data[0] = 0x50; // 轨迹传输应答标识
887        data[1] = 0x00; // 不相关
888        data[2] = 5; // 轨迹点索引
889        data[3] = 0xAE; // 轨迹包传输完成且校验成功
890        data[4] = 0x12; // NameIndex_H
891        data[5] = 0x34; // NameIndex_L
892        data[6] = 0x56; // CRC16_H
893        data[7] = 0x78; // CRC16_L
894
895        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        // 测试轨迹包校验失败
913        let mut data = [0u8; 8];
914        data[0] = 0x50; // 轨迹传输应答标识
915        data[1] = 0x00;
916        data[2] = 10; // 轨迹点索引
917        data[3] = 0xEE; // 校验失败
918
919        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// ============================================================================
944// 参数查询与设置指令
945// ============================================================================
946
947/// 参数查询类型
948#[derive(Debug, Clone, Copy, PartialEq, Eq)]
949pub enum ParameterQueryType {
950    /// 查询末端V/acc参数
951    EndVelocityAccel = 0x01,
952    /// 查询碰撞防护等级
953    CollisionProtectionLevel = 0x02,
954    /// 查询当前轨迹索引
955    CurrentTrajectoryIndex = 0x03,
956    /// 查询夹爪/示教器参数索引
957    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/// 参数设置类型
978#[derive(Debug, Clone, Copy, PartialEq, Eq)]
979pub enum ParameterSetType {
980    /// 设置末端V/acc参数为初始值
981    EndVelocityAccelToDefault = 0x01,
982    /// 设置全部关节限位、关节最大速度、关节加速度为默认值
983    AllJointLimitsToDefault = 0x02,
984}
985
986/// 0x48X报文反馈设置
987#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
988pub enum Feedback48XSetting {
989    /// 无效
990    #[default]
991    Invalid = 0x00,
992    /// 开启周期反馈(开启后周期上报1~6号关节当前末端速度/加速度)
993    Enable = 0x01,
994    /// 关闭周期反馈
995    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/// 末端负载设置
1015#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1016#[allow(clippy::enum_variant_names)]
1017pub enum EndLoadSetting {
1018    /// 空载
1019    #[default]
1020    NoLoad = 0x00,
1021    /// 半载
1022    HalfLoad = 0x01,
1023    /// 满载
1024    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/// 机械臂参数查询与设置指令 (0x477)
1059///
1060/// 注意:查询和设置是互斥的,不能同时设置。
1061#[derive(Debug, Clone, Copy)]
1062pub struct ParameterQuerySetCommand {
1063    pub query_type: Option<ParameterQueryType>, // Byte 0: 参数查询(0x00 表示不查询)
1064    pub set_type: Option<ParameterSetType>,     // Byte 1: 参数设置(0x00 表示不设置)
1065    pub feedback_48x_setting: Feedback48XSetting, // Byte 2: 0x48X报文反馈设置
1066    pub load_param_enable: bool,                // Byte 3: 末端负载参数设置是否生效(0xAE=有效值)
1067    pub end_load: EndLoadSetting,               // Byte 4: 设置末端负载
1068                                                // Byte 5-7: 保留
1069}
1070
1071impl ParameterQuerySetCommand {
1072    /// 创建查询指令
1073    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    /// 创建设置指令
1084    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    /// 设置0x48X报文反馈
1095    pub fn with_feedback_48x(mut self, setting: Feedback48XSetting) -> Self {
1096        self.feedback_48x_setting = setting;
1097        self
1098    }
1099
1100    /// 设置末端负载
1101    pub fn with_end_load(mut self, load: EndLoadSetting) -> Self {
1102        self.load_param_enable = true; // 设置负载时自动启用
1103        self.end_load = load;
1104        self
1105    }
1106
1107    /// 验证互斥性(查询和设置不能同时进行)
1108    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    /// 转换为 CAN 帧
1123    pub fn to_frame(self) -> Result<PiperFrame, ProtocolError> {
1124        // 验证互斥性
1125        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        // Byte 5-7: 保留,已初始化为 0
1134
1135        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); // EndVelocityAccel
1185        assert_eq!(frame.data[1], 0x00); // 不设置
1186    }
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); // 不查询
1195        assert_eq!(frame.data[1], 0x02); // AllJointLimitsToDefault
1196    }
1197
1198    #[test]
1199    fn test_parameter_query_set_command_validate_mutually_exclusive() {
1200        // 测试互斥性:同时设置查询和设置应该失败
1201        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        // 测试:既不查询也不设置应该失败
1211        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); // EndVelocityAccel
1263        assert_eq!(frame.data[1], 0x00); // 不设置
1264        assert_eq!(frame.data[2], 0x01); // Enable
1265    }
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); // 不查询
1275        assert_eq!(frame.data[1], 0x01); // EndVelocityAccelToDefault
1276        assert_eq!(frame.data[3], 0xAE); // 负载参数生效
1277        assert_eq!(frame.data[4], 0x02); // FullLoad
1278    }
1279}
1280
1281// ============================================================================
1282// 反馈末端速度/加速度参数
1283// ============================================================================
1284
1285/// 反馈当前末端速度/加速度参数 (0x478)
1286///
1287/// 单位:
1288/// - 线速度:0.001m/s(原始值)
1289/// - 角速度:0.001rad/s(原始值)
1290/// - 线加速度:0.001m/s²(原始值)
1291/// - 角加速度:0.001rad/s²(原始值)
1292#[derive(Debug, Clone, Copy, Default)]
1293pub struct EndVelocityAccelFeedback {
1294    pub max_linear_velocity: u16,  // Byte 0-1: 末端最大线速度,单位 0.001m/s
1295    pub max_angular_velocity: u16, // Byte 2-3: 末端最大角速度,单位 0.001rad/s
1296    pub max_linear_accel: u16,     // Byte 4-5: 末端最大线加速度,单位 0.001m/s²
1297    pub max_angular_accel: u16,    // Byte 6-7: 末端最大角加速度,单位 0.001rad/s²
1298}
1299
1300impl EndVelocityAccelFeedback {
1301    /// 获取最大线速度(m/s)
1302    pub fn max_linear_velocity(&self) -> f64 {
1303        self.max_linear_velocity as f64 / 1000.0
1304    }
1305
1306    /// 获取最大角速度(rad/s)
1307    pub fn max_angular_velocity(&self) -> f64 {
1308        self.max_angular_velocity as f64 / 1000.0
1309    }
1310
1311    /// 获取最大线加速度(m/s²)
1312    pub fn max_linear_accel(&self) -> f64 {
1313        self.max_linear_accel as f64 / 1000.0
1314    }
1315
1316    /// 获取最大角加速度(rad/s²)
1317    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        // 验证 CAN ID
1327        if frame.id != ID_END_VELOCITY_ACCEL_FEEDBACK {
1328            return Err(ProtocolError::InvalidCanId { id: frame.id });
1329        }
1330
1331        // 验证数据长度
1332        if frame.len < 8 {
1333            return Err(ProtocolError::InvalidLength {
1334                expected: 8,
1335                actual: frame.len as usize,
1336            });
1337        }
1338
1339        // 大端字节序
1340        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        // 测试数据:
1361        // - 最大线速度: 1.0 m/s = 1000 (0.001m/s 单位)
1362        // - 最大角速度: 2.0 rad/s = 2000 (0.001rad/s 单位)
1363        // - 最大线加速度: 0.5 m/s² = 500 (0.001m/s² 单位)
1364        // - 最大角加速度: 1.5 rad/s² = 1500 (0.001rad/s² 单位)
1365        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// ============================================================================
1400// 设置末端速度/加速度参数指令
1401// ============================================================================
1402
1403/// 末端速度/加速度参数设置指令 (0x479)
1404///
1405/// 用于设置末端的最大线速度、角速度、线加速度和角加速度。
1406/// 单位:
1407/// - 线速度:0.001m/s(原始值),无效值:0x7FFF
1408/// - 角速度:0.001rad/s(原始值),无效值:0x7FFF
1409/// - 线加速度:0.001m/s²(原始值),无效值:0x7FFF
1410/// - 角加速度:0.001rad/s²(原始值),无效值:0x7FFF
1411#[derive(Debug, Clone, Copy)]
1412pub struct SetEndVelocityAccelCommand {
1413    pub max_linear_velocity: Option<u16>, // Byte 0-1: 末端最大线速度,单位 0.001m/s,None 表示无效值
1414    pub max_angular_velocity: Option<u16>, // Byte 2-3: 末端最大角速度,单位 0.001rad/s,None 表示无效值
1415    pub max_linear_accel: Option<u16>, // Byte 4-5: 末端最大线加速度,单位 0.001m/s²,None 表示无效值
1416    pub max_angular_accel: Option<u16>, // Byte 6-7: 末端最大角加速度,单位 0.001rad/s²,None 表示无效值
1417}
1418
1419impl SetEndVelocityAccelCommand {
1420    /// 从物理量创建设置指令
1421    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    /// 转换为 CAN 帧
1436    pub fn to_frame(self) -> PiperFrame {
1437        let mut data = [0u8; 8];
1438
1439        // 大端字节序
1440        let linear_vel_bytes = if let Some(vel) = self.max_linear_velocity {
1441            vel.to_be_bytes()
1442        } else {
1443            [0x7F, 0xFF] // 无效值
1444        };
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] // 无效值
1451        };
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] // 无效值
1458        };
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] // 无效值
1465        };
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), // 最大线速度 1.0 m/s
1480            Some(2.0), // 最大角速度 2.0 rad/s
1481            Some(0.5), // 最大线加速度 0.5 m/s²
1482            Some(1.5), // 最大角加速度 1.5 rad/s²
1483        );
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        // 验证大端字节序
1498        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        // 测试无效值(None)
1512        let cmd = SetEndVelocityAccelCommand::new(None, None, None, None);
1513        let frame = cmd.to_frame();
1514
1515        // 验证无效值编码为 0x7FFF
1516        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        // 测试部分有效值
1529        let cmd = SetEndVelocityAccelCommand::new(
1530            Some(1.0), // 只设置线速度
1531            None,      // 角速度无效
1532            Some(0.5), // 设置线加速度
1533            None,      // 角加速度无效
1534        );
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); // 无效值
1544        assert_eq!(linear_accel, 500);
1545        assert_eq!(angular_accel, 0x7FFF); // 无效值
1546    }
1547}
1548
1549// ============================================================================
1550// 碰撞防护等级设置和反馈
1551// ============================================================================
1552
1553/// 碰撞防护等级设置指令 (0x47A)
1554///
1555/// 用于设置6个关节的碰撞防护等级(0~8,等级0代表不检测碰撞)。
1556#[derive(Debug, Clone, Copy)]
1557pub struct CollisionProtectionLevelCommand {
1558    pub levels: [u8; 6], // Byte 0-5: 1~6号关节碰撞防护等级(0~8)
1559}
1560
1561impl CollisionProtectionLevelCommand {
1562    /// 创建设置指令
1563    pub fn new(levels: [u8; 6]) -> Self {
1564        // 验证等级范围(0~8)
1565        for &level in &levels {
1566            if level > 8 {
1567                panic!("碰撞防护等级必须在0~8之间");
1568            }
1569        }
1570        Self { levels }
1571    }
1572
1573    /// 设置所有关节为相同等级
1574    pub fn all_joints(level: u8) -> Self {
1575        if level > 8 {
1576            panic!("碰撞防护等级必须在0~8之间");
1577        }
1578        Self { levels: [level; 6] }
1579    }
1580
1581    /// 转换为 CAN 帧
1582    pub fn to_frame(self) -> PiperFrame {
1583        let mut data = [0u8; 8];
1584        data[0..6].copy_from_slice(&self.levels);
1585        // Byte 6-7: 保留,已初始化为 0
1586
1587        PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL as u16, &data)
1588    }
1589}
1590
1591/// 碰撞防护等级设置反馈 (0x47B)
1592///
1593/// 反馈6个关节的碰撞防护等级(0~8,等级0代表不检测碰撞)。
1594#[derive(Debug, Clone, Copy, Default)]
1595pub struct CollisionProtectionLevelFeedback {
1596    pub levels: [u8; 6], // Byte 0-5: 1~6号关节碰撞防护等级(0~8)
1597}
1598
1599impl TryFrom<PiperFrame> for CollisionProtectionLevelFeedback {
1600    type Error = ProtocolError;
1601
1602    fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1603        // 验证 CAN ID
1604        if frame.id != ID_COLLISION_PROTECTION_LEVEL_FEEDBACK {
1605            return Err(ProtocolError::InvalidCanId { id: frame.id });
1606        }
1607
1608        // 验证数据长度
1609        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); // 保留
1647        assert_eq!(frame.data[7], 0x00); // 保留
1648    }
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        // 测试等级0(不检测碰撞)
1679        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        // 测试最大等级8
1687        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// ============================================================================
1694// 反馈电机最大加速度限制
1695// ============================================================================
1696
1697/// 反馈当前电机最大加速度限制 (0x47C)
1698///
1699/// 单位:0.001rad/s²(原始值)
1700#[derive(Debug, Clone, Copy, Default)]
1701pub struct MotorMaxAccelFeedback {
1702    pub joint_index: u8,       // Byte 0: 关节电机序号(1-6)
1703    pub max_accel_rad_s2: u16, // Byte 1-2: 最大关节加速度,单位 0.001rad/s²
1704}
1705
1706impl MotorMaxAccelFeedback {
1707    /// 获取最大加速度(rad/s²)
1708    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        // 验证 CAN ID
1718        if frame.id != ID_MOTOR_MAX_ACCEL_FEEDBACK {
1719            return Err(ProtocolError::InvalidCanId { id: frame.id });
1720        }
1721
1722        // 验证数据长度
1723        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        // 测试数据:
1748        // - 关节序号: 1
1749        // - 最大加速度: 10.0 rad/s² = 10000 (0.001rad/s² 单位)
1750        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        // 测试所有 6 个关节
1765        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// ============================================================================
1792// 夹爪/示教器参数设置和反馈
1793// ============================================================================
1794
1795/// 夹爪/示教器参数设置指令 (0x47D)
1796///
1797/// 用于设置示教器行程系数、夹爪行程系数和夹爪扭矩系数。
1798/// 注意:根据协议文档,实际字段与实现计划可能有所不同。
1799/// 协议文档显示:
1800/// - Byte 0: 示教器行程系数设置(100~200,单位%)
1801/// - Byte 1: 夹爪/示教器最大控制行程限制值设置(单位mm)
1802/// - Byte 2: 示教器摩擦系数设置(1-10)
1803#[derive(Debug, Clone, Copy)]
1804pub struct GripperTeachParamsCommand {
1805    pub teach_travel_coeff: u8, // Byte 0: 示教器行程系数设置(100~200,单位%)
1806    pub max_travel_limit: u8,   // Byte 1: 夹爪/示教器最大控制行程限制值(单位mm)
1807    pub friction_coeff: u8,     // Byte 2: 示教器摩擦系数设置(1-10)
1808                                // Byte 3-7: 保留
1809}
1810
1811impl GripperTeachParamsCommand {
1812    /// 创建设置指令
1813    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    /// 转换为 CAN 帧
1822    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        // Byte 3-7: 保留,已初始化为 0
1828
1829        PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS as u16, &data)
1830    }
1831}
1832
1833/// 夹爪/示教器参数反馈 (0x47E)
1834///
1835/// 反馈示教器行程系数、夹爪行程系数和夹爪扭矩系数。
1836#[derive(Debug, Clone, Copy, Default)]
1837pub struct GripperTeachParamsFeedback {
1838    pub teach_travel_coeff: u8, // Byte 0: 示教器行程系数反馈(100~200,单位%)
1839    pub max_travel_limit: u8,   // Byte 1: 夹爪/示教器最大控制行程限制值反馈(单位mm)
1840    pub friction_coeff: u8,     // Byte 2: 示教器摩擦系数反馈(1-10)
1841                                // Byte 3-7: 保留
1842}
1843
1844impl TryFrom<PiperFrame> for GripperTeachParamsFeedback {
1845    type Error = ProtocolError;
1846
1847    fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
1848        // 验证 CAN ID
1849        if frame.id != ID_GRIPPER_TEACH_PARAMS_FEEDBACK {
1850            return Err(ProtocolError::InvalidCanId { id: frame.id });
1851        }
1852
1853        // 验证数据长度
1854        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); // 保留
1891    }
1892
1893    #[test]
1894    fn test_gripper_teach_params_feedback_parse() {
1895        let mut data = [0u8; 8];
1896        data[0] = 150; // 示教器行程系数
1897        data[1] = 70; // 最大控制行程限制值
1898        data[2] = 5; // 摩擦系数
1899
1900        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// ============================================================================
1924// 固件升级模式设定指令
1925// ============================================================================
1926
1927/// 固件升级模式
1928#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
1929#[repr(u8)]
1930pub enum FirmwareUpgradeMode {
1931    /// 退出固件升级模式(默认状态)
1932    #[default]
1933    Exit = 0x00,
1934    /// 进入 CAN 升级外部总线静默模式(用于升级主控)
1935    CanUpgradeSilent = 0x01,
1936    /// 进入内外网组合升级模式(用于升级主控以及内网设备)
1937    /// 内外网总线静默,主控进入内外网 CAN 透传模式,退出后恢复数据反馈
1938    CombinedUpgrade = 0x02,
1939}
1940
1941/// 固件升级模式设定指令 (0x422)
1942///
1943/// 用于进入/退出固件升级模式。
1944#[derive(Debug, Clone, Copy)]
1945pub struct FirmwareUpgradeCommand {
1946    pub mode: FirmwareUpgradeMode,
1947}
1948
1949impl FirmwareUpgradeCommand {
1950    /// 创建固件升级模式设定指令
1951    pub fn new(mode: FirmwareUpgradeMode) -> Self {
1952        Self { mode }
1953    }
1954
1955    /// 创建退出固件升级模式指令
1956    pub fn exit() -> Self {
1957        Self {
1958            mode: FirmwareUpgradeMode::Exit,
1959        }
1960    }
1961
1962    /// 创建进入 CAN 升级外部总线静默模式指令
1963    pub fn can_upgrade_silent() -> Self {
1964        Self {
1965            mode: FirmwareUpgradeMode::CanUpgradeSilent,
1966        }
1967    }
1968
1969    /// 创建进入内外网组合升级模式指令
1970    pub fn combined_upgrade() -> Self {
1971        Self {
1972            mode: FirmwareUpgradeMode::CombinedUpgrade,
1973        }
1974    }
1975
1976    /// 转换为 CAN 帧
1977    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); // 默认退出
1999    }
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        // 测试所有模式
2038        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// ============================================================================
2054// 固件版本查询指令
2055// ============================================================================
2056
2057/// 固件版本查询指令 (0x4AF)
2058///
2059/// 用于查询机械臂固件版本信息。
2060/// 查询和反馈使用相同的 CAN ID (0x4AF)。
2061/// 查询命令的数据负载为固定值:`[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]`
2062///
2063/// **注意**:发送查询命令前应清空固件数据缓存,以便接收新的反馈数据。
2064#[derive(Debug, Clone, Copy)]
2065pub struct FirmwareVersionQueryCommand;
2066
2067impl FirmwareVersionQueryCommand {
2068    /// 创建固件版本查询指令
2069    ///
2070    /// 返回一个查询命令,用于向机械臂查询固件版本信息。
2071    pub fn new() -> Self {
2072        Self
2073    }
2074
2075    /// 转换为 CAN 帧
2076    ///
2077    /// 根据 Python SDK 的实现,查询命令的数据为:
2078    /// `[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]`
2079    pub fn to_frame(self) -> PiperFrame {
2080        // 与 Python SDK 对齐:data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
2081        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        // 验证数据格式与 Python SDK 一致
2119        let cmd = FirmwareVersionQueryCommand::new();
2120        let frame = cmd.to_frame();
2121
2122        // Python SDK: [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
2123        let expected_data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2124        assert_eq!(frame.data, expected_data);
2125    }
2126}