Skip to main content

piper_protocol/
control.rs

1//! 控制帧结构体定义
2//!
3//! 包含所有控制指令帧的结构体,提供构建控制帧的方法
4//! 和转换为 `PiperFrame` 的方法。
5
6use crate::can::PiperFrame;
7use crate::{ProtocolError, i16_to_bytes_be, i32_to_bytes_be, ids::*};
8use bilge::prelude::*;
9
10// ============================================================================
11// 控制模式指令相关枚举
12// ============================================================================
13
14/// 控制模式(控制指令版本,0x151)
15///
16/// 注意:控制指令的 ControlMode 与反馈帧的 ControlMode 不同。
17/// 控制指令只支持部分值(0x00, 0x01, 0x02, 0x03, 0x04, 0x07),
18/// 不支持 0x05(Remote)和 0x06(LinkTeach)。
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum ControlModeCommand {
21    /// 待机模式
22    #[default]
23    Standby = 0x00,
24    /// CAN指令控制模式
25    CanControl = 0x01,
26    /// 示教模式
27    Teach = 0x02,
28    /// 以太网控制模式
29    Ethernet = 0x03,
30    /// wifi控制模式
31    Wifi = 0x04,
32    /// 离线轨迹模式
33    OfflineTrajectory = 0x07,
34}
35
36impl TryFrom<u8> for ControlModeCommand {
37    type Error = ProtocolError;
38
39    fn try_from(value: u8) -> Result<Self, Self::Error> {
40        match value {
41            0x00 => Ok(ControlModeCommand::Standby),
42            0x01 => Ok(ControlModeCommand::CanControl),
43            0x02 => Ok(ControlModeCommand::Teach),
44            0x03 => Ok(ControlModeCommand::Ethernet),
45            0x04 => Ok(ControlModeCommand::Wifi),
46            0x07 => Ok(ControlModeCommand::OfflineTrajectory),
47            _ => Err(ProtocolError::InvalidValue {
48                field: "ControlModeCommand".to_string(),
49                value,
50            }),
51        }
52    }
53}
54
55/// MIT 模式
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
57pub enum MitMode {
58    /// 位置速度模式(默认)
59    #[default]
60    PositionVelocity = 0x00,
61    /// MIT模式(用于主从模式)
62    Mit = 0xAD,
63}
64
65impl TryFrom<u8> for MitMode {
66    type Error = ProtocolError;
67
68    fn try_from(value: u8) -> Result<Self, Self::Error> {
69        match value {
70            0x00 => Ok(MitMode::PositionVelocity),
71            0xAD => Ok(MitMode::Mit),
72            _ => Err(ProtocolError::InvalidValue {
73                field: "MitMode".to_string(),
74                value,
75            }),
76        }
77    }
78}
79
80/// 安装位置
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum InstallPosition {
83    /// 无效值
84    #[default]
85    Invalid = 0x00,
86    /// 水平正装
87    Horizontal = 0x01,
88    /// 侧装左
89    SideLeft = 0x02,
90    /// 侧装右
91    SideRight = 0x03,
92}
93
94impl TryFrom<u8> for InstallPosition {
95    type Error = ProtocolError;
96
97    fn try_from(value: u8) -> Result<Self, Self::Error> {
98        match value {
99            0x00 => Ok(InstallPosition::Invalid),
100            0x01 => Ok(InstallPosition::Horizontal),
101            0x02 => Ok(InstallPosition::SideLeft),
102            0x03 => Ok(InstallPosition::SideRight),
103            _ => Err(ProtocolError::InvalidValue {
104                field: "InstallPosition".to_string(),
105                value,
106            }),
107        }
108    }
109}
110
111// 从 feedback 模块导入 MoveMode(控制指令和反馈帧共用)
112use crate::feedback::MoveMode;
113
114// ============================================================================
115// 控制模式指令结构体
116// ============================================================================
117
118/// 控制模式指令 (0x151)
119///
120/// 用于切换机械臂的控制模式、MOVE 模式、运动速度等参数。
121#[derive(Debug, Clone, Copy, Default)]
122pub struct ControlModeCommandFrame {
123    pub control_mode: ControlModeCommand, // Byte 0
124    pub move_mode: MoveMode,              // Byte 1
125    pub speed_percent: u8,                // Byte 2 (0-100)
126    pub mit_mode: MitMode,                // Byte 3: 0x00 或 0xAD
127    pub trajectory_stay_time: u8,         // Byte 4: 0~254(单位s),255表示轨迹终止
128    pub install_position: InstallPosition, // Byte 5: 安装位置
129                                          // Byte 6-7: 保留
130}
131
132impl ControlModeCommandFrame {
133    /// 创建模式切换指令(仅切换控制模式,其他字段填充 0x0)
134    ///
135    /// 用于快速切换控制模式,其他参数使用默认值。
136    pub fn mode_switch(control_mode: ControlModeCommand) -> Self {
137        Self {
138            control_mode,
139            move_mode: MoveMode::MoveP, // 默认值
140            speed_percent: 0,
141            mit_mode: MitMode::PositionVelocity,
142            trajectory_stay_time: 0,
143            install_position: InstallPosition::Invalid,
144        }
145    }
146
147    /// 创建完整的控制指令(包含所有参数)
148    pub fn new(
149        control_mode: ControlModeCommand,
150        move_mode: MoveMode,
151        speed_percent: u8,
152        mit_mode: MitMode,
153        trajectory_stay_time: u8,
154        install_position: InstallPosition,
155    ) -> Self {
156        Self {
157            control_mode,
158            move_mode,
159            speed_percent,
160            mit_mode,
161            trajectory_stay_time,
162            install_position,
163        }
164    }
165
166    /// 转换为 CAN 帧
167    pub fn to_frame(self) -> PiperFrame {
168        let mut data = [0u8; 8];
169        data[0] = self.control_mode as u8;
170        data[1] = self.move_mode as u8;
171        data[2] = self.speed_percent;
172        data[3] = self.mit_mode as u8;
173        data[4] = self.trajectory_stay_time;
174        data[5] = self.install_position as u8;
175        // Byte 6-7: 保留,已初始化为 0
176
177        PiperFrame::new_standard(ID_CONTROL_MODE as u16, &data)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    // ========================================================================
186    // ControlModeCommand 枚举测试
187    // ========================================================================
188
189    #[test]
190    fn test_control_mode_command_from_u8() {
191        assert_eq!(
192            ControlModeCommand::try_from(0x00).unwrap(),
193            ControlModeCommand::Standby
194        );
195        assert_eq!(
196            ControlModeCommand::try_from(0x01).unwrap(),
197            ControlModeCommand::CanControl
198        );
199        assert_eq!(
200            ControlModeCommand::try_from(0x02).unwrap(),
201            ControlModeCommand::Teach
202        );
203        assert_eq!(
204            ControlModeCommand::try_from(0x03).unwrap(),
205            ControlModeCommand::Ethernet
206        );
207        assert_eq!(
208            ControlModeCommand::try_from(0x04).unwrap(),
209            ControlModeCommand::Wifi
210        );
211        assert_eq!(
212            ControlModeCommand::try_from(0x07).unwrap(),
213            ControlModeCommand::OfflineTrajectory
214        );
215    }
216
217    #[test]
218    fn test_control_mode_command_invalid_values() {
219        // 测试无效值(0x05, 0x06 未定义)
220        assert!(ControlModeCommand::try_from(0x05).is_err());
221        assert!(ControlModeCommand::try_from(0x06).is_err());
222        assert!(ControlModeCommand::try_from(0xFF).is_err());
223    }
224
225    // ========================================================================
226    // MitMode 枚举测试
227    // ========================================================================
228
229    #[test]
230    fn test_mit_mode_from_u8() {
231        assert_eq!(MitMode::try_from(0x00).unwrap(), MitMode::PositionVelocity);
232        assert_eq!(MitMode::try_from(0xAD).unwrap(), MitMode::Mit);
233    }
234
235    #[test]
236    fn test_mit_mode_invalid_values() {
237        assert!(MitMode::try_from(0x01).is_err());
238        assert!(MitMode::try_from(0xFF).is_err());
239    }
240
241    // ========================================================================
242    // InstallPosition 枚举测试
243    // ========================================================================
244
245    #[test]
246    fn test_install_position_from_u8() {
247        assert_eq!(
248            InstallPosition::try_from(0x00).unwrap(),
249            InstallPosition::Invalid
250        );
251        assert_eq!(
252            InstallPosition::try_from(0x01).unwrap(),
253            InstallPosition::Horizontal
254        );
255        assert_eq!(
256            InstallPosition::try_from(0x02).unwrap(),
257            InstallPosition::SideLeft
258        );
259        assert_eq!(
260            InstallPosition::try_from(0x03).unwrap(),
261            InstallPosition::SideRight
262        );
263    }
264
265    #[test]
266    fn test_install_position_invalid_values() {
267        assert!(InstallPosition::try_from(0x04).is_err());
268        assert!(InstallPosition::try_from(0xFF).is_err());
269    }
270
271    // ========================================================================
272    // ControlModeCommandFrame 测试
273    // ========================================================================
274
275    #[test]
276    fn test_control_mode_command_frame_mode_switch() {
277        let cmd = ControlModeCommandFrame::mode_switch(ControlModeCommand::CanControl);
278        let frame = cmd.to_frame();
279
280        assert_eq!(frame.id, ID_CONTROL_MODE);
281        assert_eq!(frame.data[0], 0x01); // CanControl
282        assert_eq!(frame.data[1], 0x00); // MoveP (默认)
283        assert_eq!(frame.data[2], 0x00); // speed_percent = 0
284        assert_eq!(frame.data[3], 0x00); // PositionVelocity (默认)
285        assert_eq!(frame.data[4], 0x00); // trajectory_stay_time = 0
286        assert_eq!(frame.data[5], 0x00); // Invalid (默认)
287        assert_eq!(frame.data[6], 0x00); // 保留
288        assert_eq!(frame.data[7], 0x00); // 保留
289    }
290
291    #[test]
292    fn test_control_mode_command_frame_new() {
293        let cmd = ControlModeCommandFrame::new(
294            ControlModeCommand::CanControl,
295            MoveMode::MoveJ,
296            50, // 50% 速度
297            MitMode::Mit,
298            10, // 停留 10 秒
299            InstallPosition::Horizontal,
300        );
301        let frame = cmd.to_frame();
302
303        assert_eq!(frame.id, ID_CONTROL_MODE);
304        assert_eq!(frame.data[0], 0x01); // CanControl
305        assert_eq!(frame.data[1], 0x01); // MoveJ
306        assert_eq!(frame.data[2], 50); // speed_percent
307        assert_eq!(frame.data[3], 0xAD); // Mit
308        assert_eq!(frame.data[4], 10); // trajectory_stay_time
309        assert_eq!(frame.data[5], 0x01); // Horizontal
310    }
311
312    #[test]
313    fn test_control_mode_command_frame_trajectory_terminate() {
314        // 测试轨迹终止(trajectory_stay_time = 255)
315        let cmd = ControlModeCommandFrame::new(
316            ControlModeCommand::OfflineTrajectory,
317            MoveMode::MoveP,
318            0,
319            MitMode::PositionVelocity,
320            255, // 轨迹终止
321            InstallPosition::Invalid,
322        );
323        let frame = cmd.to_frame();
324
325        assert_eq!(frame.data[4], 255); // 轨迹终止标志
326    }
327}
328
329// ============================================================================
330// 关节控制指令结构体
331// ============================================================================
332
333/// 机械臂臂部关节控制指令12 (0x155)
334///
335/// 用于控制 J1 和 J2 关节的目标角度。
336/// 单位:0.001°(原始值),可通过 `new()` 方法从物理量(度)创建。
337#[derive(Debug, Clone, Copy, Default)]
338pub struct JointControl12 {
339    pub j1_deg: i32, // Byte 0-3: J1角度,单位 0.001°
340    pub j2_deg: i32, // Byte 4-7: J2角度,单位 0.001°
341}
342
343impl JointControl12 {
344    /// 从物理量(度)创建关节控制指令
345    ///
346    /// **注意**:使用 `round()` 进行四舍五入,与 Python SDK 保持一致。
347    /// Python SDK 使用 `round(pos_deg * 1e3)` 进行转换。
348    pub fn new(j1: f64, j2: f64) -> Self {
349        Self {
350            j1_deg: (j1 * 1000.0).round() as i32,
351            j2_deg: (j2 * 1000.0).round() as i32,
352        }
353    }
354
355    /// 转换为 CAN 帧
356    pub fn to_frame(self) -> PiperFrame {
357        let mut data = [0u8; 8];
358        let j1_bytes = i32_to_bytes_be(self.j1_deg);
359        let j2_bytes = i32_to_bytes_be(self.j2_deg);
360        data[0..4].copy_from_slice(&j1_bytes);
361        data[4..8].copy_from_slice(&j2_bytes);
362
363        PiperFrame::new_standard(ID_JOINT_CONTROL_12 as u16, &data)
364    }
365}
366
367/// 机械臂腕部关节控制指令34 (0x156)
368///
369/// 用于控制 J3 和 J4 关节的目标角度。
370#[derive(Debug, Clone, Copy, Default)]
371pub struct JointControl34 {
372    pub j3_deg: i32, // Byte 0-3: J3角度,单位 0.001°
373    pub j4_deg: i32, // Byte 4-7: J4角度,单位 0.001°
374}
375
376impl JointControl34 {
377    /// 从物理量(度)创建关节控制指令
378    ///
379    /// **注意**:使用 `round()` 进行四舍五入,与 Python SDK 保持一致。
380    /// Python SDK 使用 `round(pos_deg * 1e3)` 进行转换。
381    pub fn new(j3: f64, j4: f64) -> Self {
382        Self {
383            j3_deg: (j3 * 1000.0).round() as i32,
384            j4_deg: (j4 * 1000.0).round() as i32,
385        }
386    }
387
388    /// 转换为 CAN 帧
389    pub fn to_frame(self) -> PiperFrame {
390        let mut data = [0u8; 8];
391        let j3_bytes = i32_to_bytes_be(self.j3_deg);
392        let j4_bytes = i32_to_bytes_be(self.j4_deg);
393        data[0..4].copy_from_slice(&j3_bytes);
394        data[4..8].copy_from_slice(&j4_bytes);
395
396        PiperFrame::new_standard(ID_JOINT_CONTROL_34 as u16, &data)
397    }
398}
399
400/// 机械臂腕部关节控制指令56 (0x157)
401///
402/// 用于控制 J5 和 J6 关节的目标角度。
403#[derive(Debug, Clone, Copy, Default)]
404pub struct JointControl56 {
405    pub j5_deg: i32, // Byte 0-3: J5角度,单位 0.001°
406    pub j6_deg: i32, // Byte 4-7: J6角度,单位 0.001°
407}
408
409impl JointControl56 {
410    /// 从物理量(度)创建关节控制指令
411    ///
412    /// **注意**:使用 `round()` 进行四舍五入,与 Python SDK 保持一致。
413    /// Python SDK 使用 `round(pos_deg * 1e3)` 进行转换。
414    pub fn new(j5: f64, j6: f64) -> Self {
415        Self {
416            j5_deg: (j5 * 1000.0).round() as i32,
417            j6_deg: (j6 * 1000.0).round() as i32,
418        }
419    }
420
421    /// 转换为 CAN 帧
422    pub fn to_frame(self) -> PiperFrame {
423        let mut data = [0u8; 8];
424        let j5_bytes = i32_to_bytes_be(self.j5_deg);
425        let j6_bytes = i32_to_bytes_be(self.j6_deg);
426        data[0..4].copy_from_slice(&j5_bytes);
427        data[4..8].copy_from_slice(&j6_bytes);
428
429        PiperFrame::new_standard(ID_JOINT_CONTROL_56 as u16, &data)
430    }
431}
432
433#[cfg(test)]
434mod joint_control_tests {
435    use super::*;
436
437    #[test]
438    fn test_joint_control12_new() {
439        let cmd = JointControl12::new(90.0, -45.0);
440        assert_eq!(cmd.j1_deg, 90000);
441        assert_eq!(cmd.j2_deg, -45000);
442    }
443
444    #[test]
445    fn test_joint_control12_to_frame() {
446        let cmd = JointControl12::new(90.0, -45.0);
447        let frame = cmd.to_frame();
448
449        assert_eq!(frame.id, ID_JOINT_CONTROL_12);
450        // 验证大端字节序编码
451        let j1_decoded =
452            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
453        let j2_decoded =
454            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
455        assert_eq!(j1_decoded, 90000);
456        assert_eq!(j2_decoded, -45000);
457    }
458
459    #[test]
460    fn test_joint_control12_roundtrip() {
461        // 测试编码-解码循环(直接验证编码后的字节值)
462        let cmd = JointControl12::new(90.0, -45.0);
463        let frame = cmd.to_frame();
464
465        // 验证 CAN ID
466        assert_eq!(frame.id, ID_JOINT_CONTROL_12);
467
468        // 验证编码后的字节值(大端字节序)
469        let j1_decoded =
470            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
471        let j2_decoded =
472            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
473        assert_eq!(j1_decoded, 90000);
474        assert_eq!(j2_decoded, -45000);
475
476        // 验证原始值
477        assert_eq!(cmd.j1_deg, 90000);
478        assert_eq!(cmd.j2_deg, -45000);
479    }
480
481    #[test]
482    fn test_joint_control34_new() {
483        let cmd = JointControl34::new(30.0, -60.0);
484        assert_eq!(cmd.j3_deg, 30000);
485        assert_eq!(cmd.j4_deg, -60000);
486    }
487
488    #[test]
489    fn test_joint_control34_to_frame() {
490        let cmd = JointControl34::new(30.0, -60.0);
491        let frame = cmd.to_frame();
492
493        assert_eq!(frame.id, ID_JOINT_CONTROL_34);
494        let j3_decoded =
495            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
496        let j4_decoded =
497            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
498        assert_eq!(j3_decoded, 30000);
499        assert_eq!(j4_decoded, -60000);
500    }
501
502    #[test]
503    fn test_joint_control56_new() {
504        let cmd = JointControl56::new(180.0, -90.0);
505        assert_eq!(cmd.j5_deg, 180000);
506        assert_eq!(cmd.j6_deg, -90000);
507    }
508
509    #[test]
510    fn test_joint_control56_to_frame() {
511        let cmd = JointControl56::new(180.0, -90.0);
512        let frame = cmd.to_frame();
513
514        assert_eq!(frame.id, ID_JOINT_CONTROL_56);
515        let j5_decoded =
516            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
517        let j6_decoded =
518            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
519        assert_eq!(j5_decoded, 180000);
520        assert_eq!(j6_decoded, -90000);
521    }
522
523    #[test]
524    fn test_joint_control_precision() {
525        // 测试精度:0.5° = 500 (0.001° 单位)
526        let cmd = JointControl12::new(0.5, -0.5);
527        assert_eq!(cmd.j1_deg, 500);
528        assert_eq!(cmd.j2_deg, -500);
529    }
530}
531
532// ============================================================================
533// 快速急停/轨迹指令结构体
534// ============================================================================
535
536/// 快速急停动作
537#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
538pub enum EmergencyStopAction {
539    /// 无效
540    #[default]
541    Invalid = 0x00,
542    /// 快速急停
543    EmergencyStop = 0x01,
544    /// 恢复
545    Resume = 0x02,
546}
547
548impl TryFrom<u8> for EmergencyStopAction {
549    type Error = ProtocolError;
550
551    fn try_from(value: u8) -> Result<Self, Self::Error> {
552        match value {
553            0x00 => Ok(EmergencyStopAction::Invalid),
554            0x01 => Ok(EmergencyStopAction::EmergencyStop),
555            0x02 => Ok(EmergencyStopAction::Resume),
556            _ => Err(ProtocolError::InvalidValue {
557                field: "EmergencyStopAction".to_string(),
558                value,
559            }),
560        }
561    }
562}
563
564/// 轨迹指令
565#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
566pub enum TrajectoryCommand {
567    /// 关闭
568    #[default]
569    Closed = 0x00,
570    /// 暂停当前规划
571    PausePlanning = 0x01,
572    /// 开始/继续当前轨迹
573    StartContinue = 0x02,
574    /// 清除当前轨迹
575    ClearCurrent = 0x03,
576    /// 清除所有轨迹
577    ClearAll = 0x04,
578    /// 获取当前规划轨迹
579    GetCurrentPlanning = 0x05,
580    /// 终止执行
581    Terminate = 0x06,
582    /// 轨迹传输
583    Transmit = 0x07,
584    /// 轨迹传输结束
585    TransmitEnd = 0x08,
586}
587
588impl TryFrom<u8> for TrajectoryCommand {
589    type Error = ProtocolError;
590
591    fn try_from(value: u8) -> Result<Self, Self::Error> {
592        match value {
593            0x00 => Ok(TrajectoryCommand::Closed),
594            0x01 => Ok(TrajectoryCommand::PausePlanning),
595            0x02 => Ok(TrajectoryCommand::StartContinue),
596            0x03 => Ok(TrajectoryCommand::ClearCurrent),
597            0x04 => Ok(TrajectoryCommand::ClearAll),
598            0x05 => Ok(TrajectoryCommand::GetCurrentPlanning),
599            0x06 => Ok(TrajectoryCommand::Terminate),
600            0x07 => Ok(TrajectoryCommand::Transmit),
601            0x08 => Ok(TrajectoryCommand::TransmitEnd),
602            _ => Err(ProtocolError::InvalidValue {
603                field: "TrajectoryCommand".to_string(),
604                value,
605            }),
606        }
607    }
608}
609
610/// 拖动示教指令
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
612pub enum TeachCommand {
613    /// 关闭
614    #[default]
615    Closed = 0x00,
616    /// 开始示教记录(进入拖动示教模式)
617    StartRecord = 0x01,
618    /// 结束示教记录(退出拖动示教模式)
619    EndRecord = 0x02,
620    /// 执行示教轨迹(拖动示教轨迹复现)
621    Execute = 0x03,
622    /// 暂停执行
623    Pause = 0x04,
624    /// 继续执行(轨迹复现继续)
625    Continue = 0x05,
626    /// 终止执行
627    Terminate = 0x06,
628    /// 运动到轨迹起点
629    MoveToStart = 0x07,
630}
631
632impl TryFrom<u8> for TeachCommand {
633    type Error = ProtocolError;
634
635    fn try_from(value: u8) -> Result<Self, Self::Error> {
636        match value {
637            0x00 => Ok(TeachCommand::Closed),
638            0x01 => Ok(TeachCommand::StartRecord),
639            0x02 => Ok(TeachCommand::EndRecord),
640            0x03 => Ok(TeachCommand::Execute),
641            0x04 => Ok(TeachCommand::Pause),
642            0x05 => Ok(TeachCommand::Continue),
643            0x06 => Ok(TeachCommand::Terminate),
644            0x07 => Ok(TeachCommand::MoveToStart),
645            _ => Err(ProtocolError::InvalidValue {
646                field: "TeachCommand".to_string(),
647                value,
648            }),
649        }
650    }
651}
652
653/// 快速急停/轨迹指令 (0x150)
654///
655/// 用于快速急停、轨迹控制和拖动示教控制。
656/// 注意:在离线轨迹模式下,Byte 3-7 用于轨迹传输(轨迹点索引、NameIndex、CRC16),
657/// 其他模式下这些字段全部填充 0x0。
658#[derive(Debug, Clone, Copy, Default)]
659pub struct EmergencyStopCommand {
660    pub emergency_stop: EmergencyStopAction,   // Byte 0
661    pub trajectory_command: TrajectoryCommand, // Byte 1
662    pub teach_command: TeachCommand,           // Byte 2
663    pub trajectory_index: u8,                  // Byte 3: 轨迹点索引 (0~255)
664    // 以下字段用于离线轨迹模式下的轨迹传输,其它模式下全部填充 0x0
665    pub name_index: u16, // Byte 4-5: 轨迹包名称索引
666    pub crc16: u16,      // Byte 6-7: CRC16 校验
667}
668
669impl EmergencyStopCommand {
670    /// 创建快速急停指令
671    pub fn emergency_stop() -> Self {
672        Self {
673            emergency_stop: EmergencyStopAction::EmergencyStop,
674            trajectory_command: TrajectoryCommand::Closed,
675            teach_command: TeachCommand::Closed,
676            trajectory_index: 0,
677            name_index: 0,
678            crc16: 0,
679        }
680    }
681
682    /// 创建恢复指令
683    pub fn resume() -> Self {
684        Self {
685            emergency_stop: EmergencyStopAction::Resume,
686            trajectory_command: TrajectoryCommand::Closed,
687            teach_command: TeachCommand::Closed,
688            trajectory_index: 0,
689            name_index: 0,
690            crc16: 0,
691        }
692    }
693
694    /// 创建轨迹传输指令(用于离线轨迹模式)
695    pub fn trajectory_transmit(trajectory_index: u8, name_index: u16, crc16: u16) -> Self {
696        Self {
697            emergency_stop: EmergencyStopAction::Invalid,
698            trajectory_command: TrajectoryCommand::Transmit,
699            teach_command: TeachCommand::Closed,
700            trajectory_index,
701            name_index,
702            crc16,
703        }
704    }
705
706    /// 转换为 CAN 帧
707    pub fn to_frame(self) -> PiperFrame {
708        let mut data = [0u8; 8];
709        data[0] = self.emergency_stop as u8;
710        data[1] = self.trajectory_command as u8;
711        data[2] = self.teach_command as u8;
712        data[3] = self.trajectory_index;
713
714        // 大端字节序
715        let name_index_bytes = self.name_index.to_be_bytes();
716        data[4] = name_index_bytes[0];
717        data[5] = name_index_bytes[1];
718
719        let crc_bytes = self.crc16.to_be_bytes();
720        data[6] = crc_bytes[0];
721        data[7] = crc_bytes[1];
722
723        PiperFrame::new_standard(ID_EMERGENCY_STOP as u16, &data)
724    }
725}
726
727#[cfg(test)]
728mod emergency_stop_tests {
729    use super::*;
730
731    #[test]
732    fn test_emergency_stop_action_from_u8() {
733        assert_eq!(
734            EmergencyStopAction::try_from(0x00).unwrap(),
735            EmergencyStopAction::Invalid
736        );
737        assert_eq!(
738            EmergencyStopAction::try_from(0x01).unwrap(),
739            EmergencyStopAction::EmergencyStop
740        );
741        assert_eq!(
742            EmergencyStopAction::try_from(0x02).unwrap(),
743            EmergencyStopAction::Resume
744        );
745    }
746
747    #[test]
748    fn test_trajectory_command_from_u8() {
749        assert_eq!(
750            TrajectoryCommand::try_from(0x00).unwrap(),
751            TrajectoryCommand::Closed
752        );
753        assert_eq!(
754            TrajectoryCommand::try_from(0x07).unwrap(),
755            TrajectoryCommand::Transmit
756        );
757        assert_eq!(
758            TrajectoryCommand::try_from(0x08).unwrap(),
759            TrajectoryCommand::TransmitEnd
760        );
761    }
762
763    #[test]
764    fn test_teach_command_from_u8() {
765        assert_eq!(TeachCommand::try_from(0x00).unwrap(), TeachCommand::Closed);
766        assert_eq!(
767            TeachCommand::try_from(0x01).unwrap(),
768            TeachCommand::StartRecord
769        );
770        assert_eq!(
771            TeachCommand::try_from(0x07).unwrap(),
772            TeachCommand::MoveToStart
773        );
774    }
775
776    #[test]
777    fn test_emergency_stop_command_emergency_stop() {
778        let cmd = EmergencyStopCommand::emergency_stop();
779        let frame = cmd.to_frame();
780
781        assert_eq!(frame.id, ID_EMERGENCY_STOP);
782        assert_eq!(frame.data[0], 0x01); // EmergencyStop
783        assert_eq!(frame.data[1], 0x00); // Closed
784        assert_eq!(frame.data[2], 0x00); // Closed
785        assert_eq!(frame.data[3], 0x00); // trajectory_index = 0
786    }
787
788    #[test]
789    fn test_emergency_stop_command_resume() {
790        let cmd = EmergencyStopCommand::resume();
791        let frame = cmd.to_frame();
792
793        assert_eq!(frame.id, ID_EMERGENCY_STOP);
794        assert_eq!(frame.data[0], 0x02); // Resume
795    }
796
797    #[test]
798    fn test_emergency_stop_command_trajectory_transmit() {
799        let cmd = EmergencyStopCommand::trajectory_transmit(5, 0x1234, 0x5678);
800        let frame = cmd.to_frame();
801
802        assert_eq!(frame.id, ID_EMERGENCY_STOP);
803        assert_eq!(frame.data[0], 0x00); // Invalid
804        assert_eq!(frame.data[1], 0x07); // Transmit
805        assert_eq!(frame.data[3], 5); // trajectory_index
806
807        // 验证大端字节序
808        let name_index = u16::from_be_bytes([frame.data[4], frame.data[5]]);
809        let crc16 = u16::from_be_bytes([frame.data[6], frame.data[7]]);
810        assert_eq!(name_index, 0x1234);
811        assert_eq!(crc16, 0x5678);
812    }
813}
814
815// ============================================================================
816// 电机使能指令结构体
817// ============================================================================
818
819/// 电机使能/失能设置指令 (0x471)
820///
821/// 用于使能或失能指定的关节电机。
822#[derive(Debug, Clone, Copy)]
823pub struct MotorEnableCommand {
824    pub joint_index: u8, // Byte 0: 1-6 代表关节驱动器序号,7 代表全部关节电机
825    pub enable: bool,    // Byte 1: true = 使能 (0x02), false = 失能 (0x01)
826}
827
828impl MotorEnableCommand {
829    /// 创建使能指令
830    pub fn enable(joint_index: u8) -> Self {
831        Self {
832            joint_index,
833            enable: true,
834        }
835    }
836
837    /// 创建失能指令
838    pub fn disable(joint_index: u8) -> Self {
839        Self {
840            joint_index,
841            enable: false,
842        }
843    }
844
845    /// 使能全部关节电机
846    pub fn enable_all() -> Self {
847        Self {
848            joint_index: 7,
849            enable: true,
850        }
851    }
852
853    /// 失能全部关节电机
854    pub fn disable_all() -> Self {
855        Self {
856            joint_index: 7,
857            enable: false,
858        }
859    }
860
861    /// 转换为 CAN 帧
862    pub fn to_frame(self) -> PiperFrame {
863        let mut data = [0u8; 8];
864        data[0] = self.joint_index;
865        data[1] = if self.enable { 0x02 } else { 0x01 };
866        // Byte 2-7: 保留,已初始化为 0
867
868        PiperFrame::new_standard(ID_MOTOR_ENABLE as u16, &data)
869    }
870}
871
872#[cfg(test)]
873mod motor_enable_tests {
874    use super::*;
875
876    #[test]
877    fn test_motor_enable_command_enable() {
878        let cmd = MotorEnableCommand::enable(1);
879        let frame = cmd.to_frame();
880
881        assert_eq!(frame.id, ID_MOTOR_ENABLE);
882        assert_eq!(frame.data[0], 1);
883        assert_eq!(frame.data[1], 0x02); // 使能
884    }
885
886    #[test]
887    fn test_motor_enable_command_disable() {
888        let cmd = MotorEnableCommand::disable(2);
889        let frame = cmd.to_frame();
890
891        assert_eq!(frame.id, ID_MOTOR_ENABLE);
892        assert_eq!(frame.data[0], 2);
893        assert_eq!(frame.data[1], 0x01); // 失能
894    }
895
896    #[test]
897    fn test_motor_enable_command_enable_all() {
898        let cmd = MotorEnableCommand::enable_all();
899        let frame = cmd.to_frame();
900
901        assert_eq!(frame.id, ID_MOTOR_ENABLE);
902        assert_eq!(frame.data[0], 7); // 全部关节
903        assert_eq!(frame.data[1], 0x02); // 使能
904    }
905
906    #[test]
907    fn test_motor_enable_command_all_joints() {
908        // 测试所有关节序号(1-6)
909        for i in 1..=6 {
910            let cmd = MotorEnableCommand::enable(i);
911            let frame = cmd.to_frame();
912            assert_eq!(frame.data[0], i);
913            assert_eq!(frame.data[1], 0x02);
914        }
915    }
916}
917
918// ============================================================================
919// 夹爪控制指令结构体
920// ============================================================================
921
922/// 夹爪控制标志位域(Byte 6: 8 位)
923///
924/// 协议定义:
925/// - Bit 0: 置1使能,0失能
926/// - Bit 1: 置1清除错误
927/// - Bit 2-7: 保留
928#[bitsize(8)]
929#[derive(FromBits, DebugBits, Clone, Copy, Default)]
930pub struct GripperControlFlags {
931    pub enable: bool,      // Bit 0: 置1使能,0失能
932    pub clear_error: bool, // Bit 1: 置1清除错误
933    pub reserved: u6,      // Bit 2-7: 保留
934}
935
936/// 夹爪控制指令 (0x159)
937///
938/// 用于控制夹爪的行程、扭矩、使能状态和零点设置。
939/// - 行程单位:0.001mm(原始值),0值表示完全闭合
940/// - 扭矩单位:0.001N·m(原始值)
941#[derive(Debug, Clone, Copy)]
942pub struct GripperControlCommand {
943    pub travel_mm: i32, // Byte 0-3: 夹爪行程,单位 0.001mm(0值表示完全闭合)
944    pub torque_nm: i16, // Byte 4-5: 夹爪扭矩,单位 0.001N·m
945    pub control_flags: GripperControlFlags, // Byte 6: 控制标志位域
946    pub zero_setting: u8, // Byte 7: 零点设置(0x00: 无效,0xAE: 设置当前为零点)
947}
948
949impl GripperControlCommand {
950    /// 从物理量创建夹爪控制指令
951    pub fn new(travel_mm: f64, torque_nm: f64, enable: bool) -> Self {
952        Self {
953            travel_mm: (travel_mm * 1000.0) as i32,
954            torque_nm: (torque_nm * 1000.0) as i16,
955            control_flags: {
956                let mut flags = GripperControlFlags::from(u8::new(0));
957                flags.set_enable(enable);
958                flags
959            },
960            zero_setting: 0x00,
961        }
962    }
963
964    /// 设置零点(设置当前为零点)
965    pub fn set_zero_point(mut self) -> Self {
966        self.zero_setting = 0xAE;
967        // 设置零点时,Byte 6 应该填充 0x0(失能)
968        let mut flags = GripperControlFlags::from(u8::new(0));
969        flags.set_enable(false);
970        self.control_flags = flags;
971        self
972    }
973
974    /// 清除错误
975    pub fn clear_error(mut self) -> Self {
976        let mut flags = self.control_flags;
977        flags.set_clear_error(true);
978        self.control_flags = flags;
979        self
980    }
981
982    /// 转换为 CAN 帧
983    pub fn to_frame(self) -> PiperFrame {
984        let mut data = [0u8; 8];
985
986        // 大端字节序
987        let travel_bytes = i32_to_bytes_be(self.travel_mm);
988        data[0..4].copy_from_slice(&travel_bytes);
989
990        let torque_bytes = i16_to_bytes_be(self.torque_nm);
991        data[4..6].copy_from_slice(&torque_bytes);
992
993        data[6] = u8::from(self.control_flags).value();
994        data[7] = self.zero_setting;
995
996        PiperFrame::new_standard(ID_GRIPPER_CONTROL as u16, &data)
997    }
998}
999
1000#[cfg(test)]
1001mod gripper_control_tests {
1002    use super::*;
1003
1004    #[test]
1005    fn test_gripper_control_flags_parse() {
1006        // 测试:Bit 0 = 1(使能),Bit 1 = 1(清除错误)
1007        let byte = 0b0000_0011;
1008        let flags = GripperControlFlags::from(u8::new(byte));
1009
1010        assert!(flags.enable());
1011        assert!(flags.clear_error());
1012    }
1013
1014    #[test]
1015    fn test_gripper_control_flags_encode() {
1016        let mut flags = GripperControlFlags::from(u8::new(0));
1017        flags.set_enable(true);
1018        flags.set_clear_error(true);
1019
1020        let encoded = u8::from(flags).value();
1021        assert_eq!(encoded, 0b0000_0011);
1022    }
1023
1024    #[test]
1025    fn test_gripper_control_command_new() {
1026        let cmd = GripperControlCommand::new(50.0, 2.5, true);
1027        assert_eq!(cmd.travel_mm, 50000);
1028        assert_eq!(cmd.torque_nm, 2500);
1029        assert!(cmd.control_flags.enable());
1030        assert_eq!(cmd.zero_setting, 0x00);
1031    }
1032
1033    #[test]
1034    fn test_gripper_control_command_to_frame() {
1035        let cmd = GripperControlCommand::new(50.0, 2.5, true);
1036        let frame = cmd.to_frame();
1037
1038        assert_eq!(frame.id, ID_GRIPPER_CONTROL);
1039
1040        // 验证大端字节序
1041        let travel_decoded =
1042            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1043        let torque_decoded = i16::from_be_bytes([frame.data[4], frame.data[5]]);
1044        assert_eq!(travel_decoded, 50000);
1045        assert_eq!(torque_decoded, 2500);
1046        assert_eq!(frame.data[6] & 0x01, 0x01); // Bit 0 = 1(使能)
1047        assert_eq!(frame.data[7], 0x00); // 零点设置无效
1048    }
1049
1050    #[test]
1051    fn test_gripper_control_command_set_zero_point() {
1052        let cmd = GripperControlCommand::new(0.0, 0.0, false).set_zero_point();
1053        let frame = cmd.to_frame();
1054
1055        assert_eq!(frame.data[6], 0x00); // 失能(设置零点时)
1056        assert_eq!(frame.data[7], 0xAE); // 设置零点标志
1057    }
1058
1059    #[test]
1060    fn test_gripper_control_command_clear_error() {
1061        let cmd = GripperControlCommand::new(50.0, 2.5, true).clear_error();
1062        let frame = cmd.to_frame();
1063
1064        assert_eq!(frame.data[6] & 0x03, 0x03); // Bit 0 和 Bit 1 都是 1
1065    }
1066
1067    #[test]
1068    fn test_gripper_control_command_fully_closed() {
1069        // 测试完全闭合(travel = 0)
1070        let cmd = GripperControlCommand::new(0.0, 1.0, true);
1071        assert_eq!(cmd.travel_mm, 0);
1072    }
1073}
1074
1075// ============================================================================
1076// 末端位姿控制指令结构体
1077// ============================================================================
1078
1079/// 机械臂运动控制直角坐标指令1 (0x152)
1080///
1081/// 用于控制末端执行器的 X 和 Y 坐标。
1082/// 单位:0.001mm(原始值)
1083#[derive(Debug, Clone, Copy, Default)]
1084pub struct EndPoseControl1 {
1085    pub x_mm: i32, // Byte 0-3: X坐标,单位 0.001mm
1086    pub y_mm: i32, // Byte 4-7: Y坐标,单位 0.001mm
1087}
1088
1089impl EndPoseControl1 {
1090    /// 从物理量(mm)创建末端位姿控制指令
1091    pub fn new(x: f64, y: f64) -> Self {
1092        Self {
1093            x_mm: (x * 1000.0) as i32,
1094            y_mm: (y * 1000.0) as i32,
1095        }
1096    }
1097
1098    /// 转换为 CAN 帧
1099    pub fn to_frame(self) -> PiperFrame {
1100        let mut data = [0u8; 8];
1101        let x_bytes = i32_to_bytes_be(self.x_mm);
1102        let y_bytes = i32_to_bytes_be(self.y_mm);
1103        data[0..4].copy_from_slice(&x_bytes);
1104        data[4..8].copy_from_slice(&y_bytes);
1105
1106        PiperFrame::new_standard(ID_END_POSE_CONTROL_1 as u16, &data)
1107    }
1108}
1109
1110/// 机械臂运动控制旋转坐标指令2 (0x153)
1111///
1112/// 用于控制末端执行器的 Z 坐标和 RX 角度。
1113/// - Z 单位:0.001mm(原始值)
1114/// - RX 单位:0.001°(原始值)
1115#[derive(Debug, Clone, Copy, Default)]
1116pub struct EndPoseControl2 {
1117    pub z_mm: i32,   // Byte 0-3: Z坐标,单位 0.001mm
1118    pub rx_deg: i32, // Byte 4-7: RX角度,单位 0.001°
1119}
1120
1121impl EndPoseControl2 {
1122    /// 从物理量创建末端位姿控制指令
1123    pub fn new(z: f64, rx: f64) -> Self {
1124        Self {
1125            z_mm: (z * 1000.0) as i32,
1126            rx_deg: (rx * 1000.0) as i32,
1127        }
1128    }
1129
1130    /// 转换为 CAN 帧
1131    pub fn to_frame(self) -> PiperFrame {
1132        let mut data = [0u8; 8];
1133        let z_bytes = i32_to_bytes_be(self.z_mm);
1134        let rx_bytes = i32_to_bytes_be(self.rx_deg);
1135        data[0..4].copy_from_slice(&z_bytes);
1136        data[4..8].copy_from_slice(&rx_bytes);
1137
1138        PiperFrame::new_standard(ID_END_POSE_CONTROL_2 as u16, &data)
1139    }
1140}
1141
1142/// 机械臂运动控制旋转坐标指令3 (0x154)
1143///
1144/// 用于控制末端执行器的 RY 和 RZ 角度。
1145/// 单位:0.001°(原始值)
1146#[derive(Debug, Clone, Copy, Default)]
1147pub struct EndPoseControl3 {
1148    pub ry_deg: i32, // Byte 0-3: RY角度,单位 0.001°
1149    pub rz_deg: i32, // Byte 4-7: RZ角度,单位 0.001°
1150}
1151
1152impl EndPoseControl3 {
1153    /// 从物理量(度)创建末端位姿控制指令
1154    pub fn new(ry: f64, rz: f64) -> Self {
1155        Self {
1156            ry_deg: (ry * 1000.0) as i32,
1157            rz_deg: (rz * 1000.0) as i32,
1158        }
1159    }
1160
1161    /// 转换为 CAN 帧
1162    pub fn to_frame(self) -> PiperFrame {
1163        let mut data = [0u8; 8];
1164        let ry_bytes = i32_to_bytes_be(self.ry_deg);
1165        let rz_bytes = i32_to_bytes_be(self.rz_deg);
1166        data[0..4].copy_from_slice(&ry_bytes);
1167        data[4..8].copy_from_slice(&rz_bytes);
1168
1169        PiperFrame::new_standard(ID_END_POSE_CONTROL_3 as u16, &data)
1170    }
1171}
1172
1173#[cfg(test)]
1174mod end_pose_control_tests {
1175    use super::*;
1176
1177    #[test]
1178    fn test_end_pose_control1_new() {
1179        let cmd = EndPoseControl1::new(100.0, -50.0);
1180        assert_eq!(cmd.x_mm, 100000);
1181        assert_eq!(cmd.y_mm, -50000);
1182    }
1183
1184    #[test]
1185    fn test_end_pose_control1_to_frame() {
1186        let cmd = EndPoseControl1::new(100.0, -50.0);
1187        let frame = cmd.to_frame();
1188
1189        assert_eq!(frame.id, ID_END_POSE_CONTROL_1);
1190        let x_decoded =
1191            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1192        let y_decoded =
1193            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1194        assert_eq!(x_decoded, 100000);
1195        assert_eq!(y_decoded, -50000);
1196    }
1197
1198    #[test]
1199    fn test_end_pose_control2_new() {
1200        let cmd = EndPoseControl2::new(200.0, 90.0);
1201        assert_eq!(cmd.z_mm, 200000);
1202        assert_eq!(cmd.rx_deg, 90000);
1203    }
1204
1205    #[test]
1206    fn test_end_pose_control2_to_frame() {
1207        let cmd = EndPoseControl2::new(200.0, 90.0);
1208        let frame = cmd.to_frame();
1209
1210        assert_eq!(frame.id, ID_END_POSE_CONTROL_2);
1211        let z_decoded =
1212            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1213        let rx_decoded =
1214            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1215        assert_eq!(z_decoded, 200000);
1216        assert_eq!(rx_decoded, 90000);
1217    }
1218
1219    #[test]
1220    fn test_end_pose_control3_new() {
1221        let cmd = EndPoseControl3::new(-45.0, 180.0);
1222        assert_eq!(cmd.ry_deg, -45000);
1223        assert_eq!(cmd.rz_deg, 180000);
1224    }
1225
1226    #[test]
1227    fn test_end_pose_control3_to_frame() {
1228        let cmd = EndPoseControl3::new(-45.0, 180.0);
1229        let frame = cmd.to_frame();
1230
1231        assert_eq!(frame.id, ID_END_POSE_CONTROL_3);
1232        let ry_decoded =
1233            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1234        let rz_decoded =
1235            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1236        assert_eq!(ry_decoded, -45000);
1237        assert_eq!(rz_decoded, 180000);
1238    }
1239
1240    #[test]
1241    fn test_end_pose_control_precision() {
1242        // 测试精度:0.5mm = 500 (0.001mm 单位)
1243        let cmd = EndPoseControl1::new(0.5, -0.5);
1244        assert_eq!(cmd.x_mm, 500);
1245        assert_eq!(cmd.y_mm, -500);
1246    }
1247}
1248
1249// ============================================================================
1250// 圆弧模式坐标序号更新指令结构体
1251// ============================================================================
1252
1253/// 圆弧模式坐标序号
1254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1255pub enum ArcPointIndex {
1256    /// 无效
1257    Invalid = 0x00,
1258    /// 起点
1259    Start = 0x01,
1260    /// 中间点
1261    Middle = 0x02,
1262    /// 终点
1263    End = 0x03,
1264}
1265
1266impl TryFrom<u8> for ArcPointIndex {
1267    type Error = ProtocolError;
1268
1269    fn try_from(value: u8) -> Result<Self, Self::Error> {
1270        match value {
1271            0x00 => Ok(ArcPointIndex::Invalid),
1272            0x01 => Ok(ArcPointIndex::Start),
1273            0x02 => Ok(ArcPointIndex::Middle),
1274            0x03 => Ok(ArcPointIndex::End),
1275            _ => Err(ProtocolError::InvalidValue {
1276                field: "ArcPointIndex".to_string(),
1277                value,
1278            }),
1279        }
1280    }
1281}
1282
1283/// 圆弧模式坐标序号更新指令 (0x158)
1284///
1285/// 用于在圆弧模式(MOVE C)下更新坐标序号。
1286/// 只有 Byte 0 有效,其他字节填充 0x0。
1287#[derive(Debug, Clone, Copy)]
1288pub struct ArcPointCommand {
1289    pub point_index: ArcPointIndex,
1290}
1291
1292impl ArcPointCommand {
1293    /// 创建起点指令
1294    pub fn start() -> Self {
1295        Self {
1296            point_index: ArcPointIndex::Start,
1297        }
1298    }
1299
1300    /// 创建中间点指令
1301    pub fn middle() -> Self {
1302        Self {
1303            point_index: ArcPointIndex::Middle,
1304        }
1305    }
1306
1307    /// 创建终点指令
1308    pub fn end() -> Self {
1309        Self {
1310            point_index: ArcPointIndex::End,
1311        }
1312    }
1313
1314    /// 从枚举值创建
1315    pub fn new(point_index: ArcPointIndex) -> Self {
1316        Self { point_index }
1317    }
1318
1319    /// 转换为 CAN 帧
1320    pub fn to_frame(self) -> PiperFrame {
1321        let mut data = [0u8; 8];
1322        data[0] = self.point_index as u8;
1323        // Byte 1-7: 保留,已初始化为 0
1324
1325        PiperFrame::new_standard(ID_ARC_POINT as u16, &data)
1326    }
1327}
1328
1329#[cfg(test)]
1330mod arc_point_tests {
1331    use super::*;
1332
1333    #[test]
1334    fn test_arc_point_index_from_u8() {
1335        assert_eq!(
1336            ArcPointIndex::try_from(0x00).unwrap(),
1337            ArcPointIndex::Invalid
1338        );
1339        assert_eq!(ArcPointIndex::try_from(0x01).unwrap(), ArcPointIndex::Start);
1340        assert_eq!(
1341            ArcPointIndex::try_from(0x02).unwrap(),
1342            ArcPointIndex::Middle
1343        );
1344        assert_eq!(ArcPointIndex::try_from(0x03).unwrap(), ArcPointIndex::End);
1345    }
1346
1347    #[test]
1348    fn test_arc_point_index_invalid() {
1349        assert!(ArcPointIndex::try_from(0x04).is_err());
1350        assert!(ArcPointIndex::try_from(0xFF).is_err());
1351    }
1352
1353    #[test]
1354    fn test_arc_point_command_start() {
1355        let cmd = ArcPointCommand::start();
1356        let frame = cmd.to_frame();
1357
1358        assert_eq!(frame.id, ID_ARC_POINT);
1359        assert_eq!(frame.data[0], 0x01);
1360        assert_eq!(frame.data[1], 0x00); // 保留字段
1361    }
1362
1363    #[test]
1364    fn test_arc_point_command_middle() {
1365        let cmd = ArcPointCommand::middle();
1366        let frame = cmd.to_frame();
1367
1368        assert_eq!(frame.id, ID_ARC_POINT);
1369        assert_eq!(frame.data[0], 0x02);
1370    }
1371
1372    #[test]
1373    fn test_arc_point_command_end() {
1374        let cmd = ArcPointCommand::end();
1375        let frame = cmd.to_frame();
1376
1377        assert_eq!(frame.id, ID_ARC_POINT);
1378        assert_eq!(frame.data[0], 0x03);
1379    }
1380}
1381
1382// ============================================================================
1383// MIT 控制指令结构体
1384// ============================================================================
1385
1386/// MIT 控制指令 (0x15A~0x15F)
1387///
1388/// 用于控制机械臂关节的 MIT 模式(主从模式)。
1389/// 包含位置参考、速度参考、比例增益、微分增益、力矩参考。
1390///
1391/// 注意:此指令使用复杂的跨字节位域打包,需要仔细处理。
1392///
1393/// **CRC 计算优化**(v2.1):
1394/// - CRC 属于衍生属性,不在结构体中存储
1395/// - 在 `to_frame` 时即时计算,确保数据一致性
1396/// - 避免了 "Stale CRC" 问题(修改字段后 CRC 过期)
1397#[derive(Debug, Clone, Copy)]
1398pub struct MitControlCommand {
1399    pub joint_index: u8, // 从 ID 推导:0x15A -> 1, 0x15B -> 2, ...
1400    pub pos_ref: f32,    // 位置参考值
1401    pub vel_ref: f32,    // 速度参考值
1402    pub kp: f32,         // 比例增益(参考值:10)
1403    pub kd: f32,         // 微分增益(参考值:0.8)
1404    pub t_ref: f32,      // 力矩参考值
1405}
1406
1407impl MitControlCommand {
1408    // ✅ 常量定义:消除魔法数字
1409    /// 位置参考范围(弧度)
1410    const P_MIN: f32 = -12.5;
1411    const P_MAX: f32 = 12.5;
1412    /// 速度参考范围(弧度/秒)
1413    const V_MIN: f32 = -45.0;
1414    const V_MAX: f32 = 45.0;
1415    /// 比例增益范围
1416    const KP_MIN: f32 = 0.0;
1417    const KP_MAX: f32 = 500.0;
1418    /// 微分增益范围
1419    const KD_MIN: f32 = -5.0;
1420    const KD_MAX: f32 = 5.0;
1421    /// 力矩参考范围(牛顿·米)
1422    const T_MIN: f32 = -18.0;
1423    const T_MAX: f32 = 18.0;
1424
1425    /// 辅助函数:将浮点数转换为无符号整数(根据协议公式)
1426    ///
1427    /// 公式:`(x - x_min) * ((1 << bits) - 1) / (x_max - x_min)`
1428    fn float_to_uint(x: f32, x_min: f32, x_max: f32, bits: u32) -> u32 {
1429        let span = x_max - x_min;
1430        let offset = x_min;
1431        if span <= 0.0 {
1432            return 0;
1433        }
1434        let result = ((x - offset) * ((1u32 << bits) - 1) as f32 / span) as u32;
1435        result.min((1u32 << bits) - 1)
1436    }
1437
1438    /// 协议解码辅助函数(预留用于解析 MIT 控制反馈)
1439    ///
1440    /// 将无符号整数转换为浮点数(根据协议公式)
1441    ///
1442    /// # 公式
1443    ///
1444    /// ```text
1445    /// x_float = x_int * (x_max - x_min) / ((1 << bits) - 1) + x_min
1446    /// ```
1447    ///
1448    /// # 用途
1449    ///
1450    /// **当前状态**:此函数主要用于测试,保留作为公共 API。
1451    ///
1452    /// **未来用途**:如果将来需要从电机反馈帧中解析 MIT 控制参数(如 `kp`, `kd`, `t_ref`),
1453    /// 可以使用此函数进行解码。例如,电机可能会发送 MIT 控制状态的反馈帧,
1454    /// 其中包含当前的阻抗控制参数。
1455    ///
1456    /// # 参数
1457    ///
1458    /// - `x_int`: 编码后的整数值(从 CAN 帧中读取)
1459    /// - `x_min`: 物理量最小值(如位置的最小弧度)
1460    /// - `x_max`: 物理量最大值(如位置的最大弧度)
1461    /// - `bits`: 编码位数(如 12 位或 16 位)
1462    ///
1463    /// # 示例
1464    ///
1465    /// ```ignore
1466    /// // 解析位置反馈(假设使用 16 位编码,范围 -12.5 ~ 12.5 弧度)
1467    /// let position_raw = 0x8000u32; // 原始值
1468    /// let position_rad = uint_to_float(position_raw, -12.5, 12.5, 16);
1469    /// ```
1470    #[allow(dead_code)]
1471    pub fn uint_to_float(x_int: u32, x_min: f32, x_max: f32, bits: u32) -> f32 {
1472        let span = x_max - x_min;
1473        let offset = x_min;
1474        (x_int as f32) * span / ((1u32 << bits) - 1) as f32 + offset
1475    }
1476
1477    /// 创建 MIT 控制指令
1478    ///
1479    /// **参数范围**(根据官方 SDK,固定值,不要更改):
1480    /// - pos_ref: -12.5 ~ 12.5(弧度)
1481    /// - vel_ref: -45.0 ~ 45.0 rad/s
1482    /// - kp: 0.0 ~ 500.0
1483    /// - kd: -5.0 ~ 5.0
1484    /// - t_ref: -18.0 ~ 18.0 N·m
1485    ///
1486    /// # Arguments
1487    ///
1488    /// * `joint_index` - 关节序号 [1, 6]
1489    /// * `pos_ref` - 设定期望的目标位置
1490    /// * `vel_ref` - 设定电机运动的速度
1491    /// * `kp` - 比例增益,控制位置误差对输出力矩的影响
1492    /// * `kd` - 微分增益,控制速度误差对输出力矩的影响
1493    /// * `t_ref` - 目标力矩参考值,用于控制电机施加的力矩或扭矩
1494    ///
1495    /// # 版本变更
1496    ///
1497    /// **v2.1**:移除了 `crc` 参数,CRC 在 `to_frame` 时即时计算。
1498    /// 这样可以避免"Stale CRC"问题(修改字段后 CRC 过期)。
1499    pub fn new(joint_index: u8, pos_ref: f32, vel_ref: f32, kp: f32, kd: f32, t_ref: f32) -> Self {
1500        Self {
1501            joint_index,
1502            pos_ref,
1503            vel_ref,
1504            kp,
1505            kd,
1506            t_ref,
1507        }
1508    }
1509
1510    /// 核心编码逻辑:将控制参数编码为完整的 8 字节(CRC 位预留为 0)
1511    ///
1512    /// **优化(v2.1)**:一次性完成所有数据的编码,包括 `T_ref` 的高位和低位。
1513    /// 这样避免了在 `to_frame` 中重复计算 `T_ref`,减少浮点运算开销。
1514    ///
1515    /// **职责**:负责**内容**(Payload)的编码,不包含 CRC。
1516    fn encode_to_bytes(&self) -> [u8; 8] {
1517        let mut data = [0u8; 8];
1518
1519        // Byte 0-1: Pos_ref (16位)
1520        let pos_ref_uint = Self::float_to_uint(self.pos_ref, Self::P_MIN, Self::P_MAX, 16);
1521        data[0] = ((pos_ref_uint >> 8) & 0xFF) as u8;
1522        data[1] = (pos_ref_uint & 0xFF) as u8;
1523
1524        // Byte 2-3: Vel_ref (12位) 和 Kp (12位) 的跨字节打包
1525        let vel_ref_uint = Self::float_to_uint(self.vel_ref, Self::V_MIN, Self::V_MAX, 12);
1526        data[2] = ((vel_ref_uint >> 4) & 0xFF) as u8; // Vel_ref [bit11~bit4]
1527
1528        let kp_uint = Self::float_to_uint(self.kp, Self::KP_MIN, Self::KP_MAX, 12);
1529        let vel_ref_low = (vel_ref_uint & 0x0F) as u8;
1530        let kp_high = ((kp_uint >> 8) & 0x0F) as u8;
1531        data[3] = (vel_ref_low << 4) | kp_high;
1532
1533        // Byte 4: Kp [bit7~bit0]
1534        data[4] = (kp_uint & 0xFF) as u8;
1535
1536        // Byte 5-6: Kd (12位) 和 T_ref (8位) 的跨字节打包
1537        let kd_uint = Self::float_to_uint(self.kd, Self::KD_MIN, Self::KD_MAX, 12);
1538        data[5] = ((kd_uint >> 4) & 0xFF) as u8; // Kd [bit11~bit4]
1539
1540        // ✅ 优化:只计算一次 T_ref,同时处理高位和低位
1541        let t_ref_uint = Self::float_to_uint(self.t_ref, Self::T_MIN, Self::T_MAX, 8);
1542
1543        // Byte 6: Kd [bit3~bit0] | T_ref [bit7~bit4]
1544        let kd_low = (kd_uint & 0x0F) as u8;
1545        let t_ref_high = ((t_ref_uint >> 4) & 0x0F) as u8;
1546        data[6] = (kd_low << 4) | t_ref_high;
1547
1548        // Byte 7: T_ref [bit3~bit0] | CRC (预留为 0)
1549        let t_ref_low = (t_ref_uint & 0x0F) as u8;
1550        data[7] = t_ref_low << 4; // 后 4 位留给 CRC,目前为 0
1551
1552        data
1553    }
1554
1555    /// 计算 CRC 校验值(4位)
1556    ///
1557    /// 根据官方 SDK:对前 7 个字节进行异或运算,然后取低 4 位。
1558    ///
1559    /// **参数**
1560    ///
1561    /// * `data` - 前 7 字节的编码数据(不含 CRC)
1562    /// * `_joint_index` - 关节索引(1-6),当前未使用(保留用于未来扩展)
1563    ///
1564    /// **返回**
1565    ///
1566    /// 4 位 CRC 值(0-15)
1567    fn calculate_crc(data: &[u8; 7], _joint_index: u8) -> u8 {
1568        let crc = data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[6];
1569        crc & 0x0F
1570    }
1571
1572    /// 转换为 CAN 帧
1573    ///
1574    /// **职责**:负责**校验**(Checksum)和**封装**(Packet)。
1575    /// 在序列化时即时计算 CRC,确保数据一致性。
1576    ///
1577    /// 协议位域布局:
1578    /// - Byte 0-1: Pos_ref (16位)
1579    /// - Byte 2: Vel_ref [bit11~bit4] (8位)
1580    /// - Byte 3: Vel_ref [bit3~bit0] | Kp [bit11~bit8] (跨字节打包)
1581    /// - Byte 4: Kp [bit7~bit0] (8位)
1582    /// - Byte 5: Kd [bit11~bit4] (8位)
1583    /// - Byte 6: Kd [bit3~bit0] | T_ref [bit7~bit4] (跨字节打包)
1584    /// - Byte 7: T_ref [bit3~bit0] | CRC [bit3~bit0] (跨字节打包)
1585    ///
1586    /// **参数范围(根据官方 SDK)**:
1587    /// - Pos_ref: -12.5 ~ 12.5 (16位)
1588    /// - Vel_ref: -45.0 ~ 45.0 rad/s (12位)
1589    /// - Kp: 0.0 ~ 500.0 (12位)
1590    /// - Kd: -5.0 ~ 5.0 (12位)
1591    /// - T_ref: -18.0 ~ 18.0 N·m (8位)
1592    ///
1593    /// **版本变更**
1594    ///
1595    /// **v2.1**:
1596    /// - 使用 `encode_to_bytes` 获取完整 8 字节数据(CRC 位预留为 0)
1597    /// - 基于前 7 字节计算 CRC
1598    /// - 将 CRC 填入第 8 字节的低 4 位
1599    /// - 避免 `T_ref` 的双重计算,性能优化
1600    pub fn to_frame(self) -> PiperFrame {
1601        // 1. 获取完整数据(CRC 位目前是 0)
1602        let mut data = self.encode_to_bytes();
1603
1604        // 2. 基于前 7 字节计算 CRC
1605        // 注意:data[0..7] 包含了 T_ref 的高 4 位,这是正确的,
1606        // 因为 CRC 通常覆盖所有数据位(除了 CRC 本身)
1607        let crc = Self::calculate_crc(data[0..7].try_into().unwrap(), self.joint_index);
1608
1609        // 3. 将 CRC 填入第 8 字节的低 4 位
1610        // 使用 | 操作符,因为 encode_to_bytes 已经把低 4 位清零了
1611        data[7] |= crc & 0x0F;
1612
1613        let can_id = ID_MIT_CONTROL_BASE + (self.joint_index - 1) as u32;
1614        PiperFrame::new_standard(can_id as u16, &data)
1615    }
1616
1617    /// 测试专用:允许注入自定义 CRC
1618    ///
1619    /// 用于测试场景,验证硬件对错误 CRC 的处理。
1620    ///
1621    /// **参数**
1622    ///
1623    /// * `custom_crc` - 自定义 CRC 值(仅低 4 位有效)
1624    ///
1625    /// **版本变更**
1626    ///
1627    /// **v2.1**:复用 `encode_to_bytes`,只需修改 CRC 位。
1628    #[cfg(test)]
1629    pub fn to_frame_with_custom_crc(self, custom_crc: u8) -> PiperFrame {
1630        // 复用 encode_to_bytes,只需修改 CRC 位
1631        let mut data = self.encode_to_bytes();
1632
1633        // 强制使用指定 CRC(替换低 4 位)
1634        data[7] = (data[7] & 0xF0) | (custom_crc & 0x0F);
1635
1636        let can_id = ID_MIT_CONTROL_BASE + (self.joint_index - 1) as u32;
1637        PiperFrame::new_standard(can_id as u16, &data)
1638    }
1639}
1640
1641#[cfg(test)]
1642mod mit_control_tests {
1643    use super::*;
1644
1645    #[test]
1646    fn test_float_to_uint() {
1647        // 测试转换公式:范围 0.0 ~ 10.0,12位
1648        let result = MitControlCommand::float_to_uint(5.0, 0.0, 10.0, 12);
1649        // 期望:5.0 / 10.0 * 4095 = 2047.5 ≈ 2047
1650        assert_eq!(result, 2047);
1651    }
1652
1653    #[test]
1654    fn test_float_to_uint_boundary() {
1655        // 测试边界值
1656        let min = MitControlCommand::float_to_uint(0.0, 0.0, 10.0, 12);
1657        let max = MitControlCommand::float_to_uint(10.0, 0.0, 10.0, 12);
1658        assert_eq!(min, 0);
1659        assert_eq!(max, 4095);
1660    }
1661
1662    #[test]
1663    fn test_uint_to_float() {
1664        // 测试转换公式:范围 0.0 ~ 10.0,12位
1665        let result = MitControlCommand::uint_to_float(2047, 0.0, 10.0, 12);
1666        // 期望:2047 / 4095 * 10.0 ≈ 5.0
1667        assert!((result - 5.0).abs() < 0.01);
1668    }
1669
1670    #[test]
1671    fn test_uint_to_float_boundary() {
1672        // 测试边界值
1673        let min = MitControlCommand::uint_to_float(0, 0.0, 10.0, 12);
1674        let max = MitControlCommand::uint_to_float(4095, 0.0, 10.0, 12);
1675        assert!((min - 0.0).abs() < 0.001);
1676        assert!((max - 10.0).abs() < 0.001);
1677    }
1678
1679    #[test]
1680    fn test_mit_control_command_new() {
1681        // v2.1: 移除了 crc 参数,CRC 在 to_frame 时自动计算
1682        let cmd = MitControlCommand::new(1, 1.0, 2.0, 10.0, 0.8, 5.0);
1683        assert_eq!(cmd.joint_index, 1);
1684        assert_eq!(cmd.pos_ref, 1.0);
1685        assert_eq!(cmd.vel_ref, 2.0);
1686        assert_eq!(cmd.kp, 10.0);
1687        assert_eq!(cmd.kd, 0.8);
1688        assert_eq!(cmd.t_ref, 5.0);
1689    }
1690
1691    #[test]
1692    fn test_mit_control_command_calculate_crc() {
1693        // 测试 CRC 计算逻辑(私有方法,通过 to_frame 间接测试)
1694
1695        // 测试用例 1:所有输入都为 0(包括 kp 和 kd)
1696        // 注意:由于编码时的位操作,即使输入全 0,
1697        // 编码后的字节可能不全为 0,因此 CRC 可能不为 0
1698        let cmd = MitControlCommand::new(1, 0.0, 0.0, 0.0, 0.0, 0.0);
1699        let frame = cmd.to_frame();
1700        // 只验证 CRC 在有效范围内(0-15)
1701        let crc = frame.data[7] & 0x0F;
1702        assert!(crc <= 0x0F, "CRC 应该在 0-15 范围内");
1703
1704        // 测试用例 2:有输入值(kp=10.0, kd=0.8)
1705        let cmd2 = MitControlCommand::new(1, 0.0, 0.0, 10.0, 0.8, 0.0);
1706        let frame2 = cmd2.to_frame();
1707        let crc2 = frame2.data[7] & 0x0F;
1708        assert!(crc2 <= 0x0F, "CRC 应该在 0-15 范围内");
1709    }
1710
1711    #[test]
1712    fn test_mit_control_command_roundtrip() {
1713        // 测试转换函数的往返转换
1714        let original = 5.0f32;
1715        let x_min = 0.0f32;
1716        let x_max = 10.0f32;
1717        let bits = 12u32;
1718
1719        let uint_val = MitControlCommand::float_to_uint(original, x_min, x_max, bits);
1720        let float_val = MitControlCommand::uint_to_float(uint_val, x_min, x_max, bits);
1721
1722        // 由于精度损失,允许一定误差
1723        assert!((float_val - original).abs() < 0.01);
1724    }
1725}
1726
1727// ============================================================================
1728// 灯光控制指令
1729// ============================================================================
1730
1731/// 灯光控制使能标志
1732#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
1733#[repr(u8)]
1734pub enum LightControlEnable {
1735    /// 控制指令无效
1736    #[default]
1737    Disabled = 0x00,
1738    /// 灯光控制使能
1739    Enabled = 0x01,
1740}
1741
1742/// 灯光控制指令 (0x121)
1743///
1744/// 用于控制关节上的 LED 灯光。
1745#[derive(Debug, Clone, Copy)]
1746pub struct LightControlCommand {
1747    pub enable: LightControlEnable, // Byte 0: 灯光控制使能标志
1748    pub joint_index: u8,            // Byte 1: 关节序号 (1~6)
1749    pub led_index: u8,              // Byte 2: 灯珠序号 (0-254, 0xFF表示同时操作全部)
1750    pub r: u8,                      // Byte 3: R通道灰度值 (0~255)
1751    pub g: u8,                      // Byte 4: G通道灰度值 (0~255)
1752    pub b: u8,                      // Byte 5: B通道灰度值 (0~255)
1753    pub counter: u8,                // Byte 7: 计数校验 (0-255循环计数)
1754                                    // Byte 6: 保留
1755}
1756
1757impl LightControlCommand {
1758    /// 创建灯光控制指令
1759    pub fn new(
1760        enable: LightControlEnable,
1761        joint_index: u8,
1762        led_index: u8,
1763        r: u8,
1764        g: u8,
1765        b: u8,
1766        counter: u8,
1767    ) -> Self {
1768        Self {
1769            enable,
1770            joint_index,
1771            led_index,
1772            r,
1773            g,
1774            b,
1775            counter,
1776        }
1777    }
1778
1779    /// 转换为 CAN 帧
1780    pub fn to_frame(self) -> PiperFrame {
1781        let mut data = [0u8; 8];
1782        data[0] = self.enable as u8;
1783        data[1] = self.joint_index;
1784        data[2] = self.led_index;
1785        data[3] = self.r;
1786        data[4] = self.g;
1787        data[5] = self.b;
1788        // Byte 6: 保留,已初始化为 0
1789        data[7] = self.counter;
1790
1791        PiperFrame::new_standard(ID_LIGHT_CONTROL as u16, &data)
1792    }
1793}
1794
1795#[cfg(test)]
1796mod light_control_tests {
1797    use super::*;
1798
1799    #[test]
1800    fn test_light_control_enable_from() {
1801        assert_eq!(LightControlEnable::from(0x00), LightControlEnable::Disabled);
1802        assert_eq!(LightControlEnable::from(0x01), LightControlEnable::Enabled);
1803        assert_eq!(LightControlEnable::from(0xFF), LightControlEnable::Disabled);
1804        // 默认无效
1805    }
1806
1807    #[test]
1808    fn test_light_control_command_new() {
1809        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 1, 0xFF, 255, 128, 0, 10);
1810        assert_eq!(cmd.enable, LightControlEnable::Enabled);
1811        assert_eq!(cmd.joint_index, 1);
1812        assert_eq!(cmd.led_index, 0xFF);
1813        assert_eq!(cmd.r, 255);
1814        assert_eq!(cmd.g, 128);
1815        assert_eq!(cmd.b, 0);
1816        assert_eq!(cmd.counter, 10);
1817    }
1818
1819    #[test]
1820    fn test_light_control_command_to_frame() {
1821        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 2, 5, 100, 200, 50, 42);
1822        let frame = cmd.to_frame();
1823
1824        assert_eq!(frame.id, ID_LIGHT_CONTROL);
1825        assert_eq!(frame.data[0], 0x01); // Enabled
1826        assert_eq!(frame.data[1], 2); // joint_index
1827        assert_eq!(frame.data[2], 5); // led_index
1828        assert_eq!(frame.data[3], 100); // R
1829        assert_eq!(frame.data[4], 200); // G
1830        assert_eq!(frame.data[5], 50); // B
1831        assert_eq!(frame.data[6], 0x00); // 保留
1832        assert_eq!(frame.data[7], 42); // counter
1833    }
1834
1835    #[test]
1836    fn test_light_control_command_all_leds() {
1837        // 测试 0xFF 表示同时操作全部灯珠
1838        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 3, 0xFF, 255, 255, 255, 0);
1839        let frame = cmd.to_frame();
1840        assert_eq!(frame.data[2], 0xFF);
1841    }
1842
1843    #[test]
1844    fn test_light_control_command_disabled() {
1845        let cmd = LightControlCommand::new(LightControlEnable::Disabled, 1, 0, 0, 0, 0, 0);
1846        let frame = cmd.to_frame();
1847        assert_eq!(frame.data[0], 0x00);
1848    }
1849}