piper_sdk/protocol/
control.rs

1//! 控制帧结构体定义
2//!
3//! 包含所有控制指令帧的结构体,提供构建控制帧的方法
4//! 和转换为 `PiperFrame` 的方法。
5
6use crate::can::PiperFrame;
7use crate::protocol::{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::protocol::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    pub fn new(j1: f64, j2: f64) -> Self {
346        Self {
347            j1_deg: (j1 * 1000.0) as i32,
348            j2_deg: (j2 * 1000.0) as i32,
349        }
350    }
351
352    /// 转换为 CAN 帧
353    pub fn to_frame(self) -> PiperFrame {
354        let mut data = [0u8; 8];
355        let j1_bytes = i32_to_bytes_be(self.j1_deg);
356        let j2_bytes = i32_to_bytes_be(self.j2_deg);
357        data[0..4].copy_from_slice(&j1_bytes);
358        data[4..8].copy_from_slice(&j2_bytes);
359
360        PiperFrame::new_standard(ID_JOINT_CONTROL_12 as u16, &data)
361    }
362}
363
364/// 机械臂腕部关节控制指令34 (0x156)
365///
366/// 用于控制 J3 和 J4 关节的目标角度。
367#[derive(Debug, Clone, Copy, Default)]
368pub struct JointControl34 {
369    pub j3_deg: i32, // Byte 0-3: J3角度,单位 0.001°
370    pub j4_deg: i32, // Byte 4-7: J4角度,单位 0.001°
371}
372
373impl JointControl34 {
374    /// 从物理量(度)创建关节控制指令
375    pub fn new(j3: f64, j4: f64) -> Self {
376        Self {
377            j3_deg: (j3 * 1000.0) as i32,
378            j4_deg: (j4 * 1000.0) as i32,
379        }
380    }
381
382    /// 转换为 CAN 帧
383    pub fn to_frame(self) -> PiperFrame {
384        let mut data = [0u8; 8];
385        let j3_bytes = i32_to_bytes_be(self.j3_deg);
386        let j4_bytes = i32_to_bytes_be(self.j4_deg);
387        data[0..4].copy_from_slice(&j3_bytes);
388        data[4..8].copy_from_slice(&j4_bytes);
389
390        PiperFrame::new_standard(ID_JOINT_CONTROL_34 as u16, &data)
391    }
392}
393
394/// 机械臂腕部关节控制指令56 (0x157)
395///
396/// 用于控制 J5 和 J6 关节的目标角度。
397#[derive(Debug, Clone, Copy, Default)]
398pub struct JointControl56 {
399    pub j5_deg: i32, // Byte 0-3: J5角度,单位 0.001°
400    pub j6_deg: i32, // Byte 4-7: J6角度,单位 0.001°
401}
402
403impl JointControl56 {
404    /// 从物理量(度)创建关节控制指令
405    pub fn new(j5: f64, j6: f64) -> Self {
406        Self {
407            j5_deg: (j5 * 1000.0) as i32,
408            j6_deg: (j6 * 1000.0) as i32,
409        }
410    }
411
412    /// 转换为 CAN 帧
413    pub fn to_frame(self) -> PiperFrame {
414        let mut data = [0u8; 8];
415        let j5_bytes = i32_to_bytes_be(self.j5_deg);
416        let j6_bytes = i32_to_bytes_be(self.j6_deg);
417        data[0..4].copy_from_slice(&j5_bytes);
418        data[4..8].copy_from_slice(&j6_bytes);
419
420        PiperFrame::new_standard(ID_JOINT_CONTROL_56 as u16, &data)
421    }
422}
423
424#[cfg(test)]
425mod joint_control_tests {
426    use super::*;
427
428    #[test]
429    fn test_joint_control12_new() {
430        let cmd = JointControl12::new(90.0, -45.0);
431        assert_eq!(cmd.j1_deg, 90000);
432        assert_eq!(cmd.j2_deg, -45000);
433    }
434
435    #[test]
436    fn test_joint_control12_to_frame() {
437        let cmd = JointControl12::new(90.0, -45.0);
438        let frame = cmd.to_frame();
439
440        assert_eq!(frame.id, ID_JOINT_CONTROL_12);
441        // 验证大端字节序编码
442        let j1_decoded =
443            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
444        let j2_decoded =
445            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
446        assert_eq!(j1_decoded, 90000);
447        assert_eq!(j2_decoded, -45000);
448    }
449
450    #[test]
451    fn test_joint_control12_roundtrip() {
452        // 测试编码-解码循环(直接验证编码后的字节值)
453        let cmd = JointControl12::new(90.0, -45.0);
454        let frame = cmd.to_frame();
455
456        // 验证 CAN ID
457        assert_eq!(frame.id, ID_JOINT_CONTROL_12);
458
459        // 验证编码后的字节值(大端字节序)
460        let j1_decoded =
461            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
462        let j2_decoded =
463            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
464        assert_eq!(j1_decoded, 90000);
465        assert_eq!(j2_decoded, -45000);
466
467        // 验证原始值
468        assert_eq!(cmd.j1_deg, 90000);
469        assert_eq!(cmd.j2_deg, -45000);
470    }
471
472    #[test]
473    fn test_joint_control34_new() {
474        let cmd = JointControl34::new(30.0, -60.0);
475        assert_eq!(cmd.j3_deg, 30000);
476        assert_eq!(cmd.j4_deg, -60000);
477    }
478
479    #[test]
480    fn test_joint_control34_to_frame() {
481        let cmd = JointControl34::new(30.0, -60.0);
482        let frame = cmd.to_frame();
483
484        assert_eq!(frame.id, ID_JOINT_CONTROL_34);
485        let j3_decoded =
486            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
487        let j4_decoded =
488            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
489        assert_eq!(j3_decoded, 30000);
490        assert_eq!(j4_decoded, -60000);
491    }
492
493    #[test]
494    fn test_joint_control56_new() {
495        let cmd = JointControl56::new(180.0, -90.0);
496        assert_eq!(cmd.j5_deg, 180000);
497        assert_eq!(cmd.j6_deg, -90000);
498    }
499
500    #[test]
501    fn test_joint_control56_to_frame() {
502        let cmd = JointControl56::new(180.0, -90.0);
503        let frame = cmd.to_frame();
504
505        assert_eq!(frame.id, ID_JOINT_CONTROL_56);
506        let j5_decoded =
507            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
508        let j6_decoded =
509            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
510        assert_eq!(j5_decoded, 180000);
511        assert_eq!(j6_decoded, -90000);
512    }
513
514    #[test]
515    fn test_joint_control_precision() {
516        // 测试精度:0.5° = 500 (0.001° 单位)
517        let cmd = JointControl12::new(0.5, -0.5);
518        assert_eq!(cmd.j1_deg, 500);
519        assert_eq!(cmd.j2_deg, -500);
520    }
521}
522
523// ============================================================================
524// 快速急停/轨迹指令结构体
525// ============================================================================
526
527/// 快速急停动作
528#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
529pub enum EmergencyStopAction {
530    /// 无效
531    #[default]
532    Invalid = 0x00,
533    /// 快速急停
534    EmergencyStop = 0x01,
535    /// 恢复
536    Resume = 0x02,
537}
538
539impl TryFrom<u8> for EmergencyStopAction {
540    type Error = ProtocolError;
541
542    fn try_from(value: u8) -> Result<Self, Self::Error> {
543        match value {
544            0x00 => Ok(EmergencyStopAction::Invalid),
545            0x01 => Ok(EmergencyStopAction::EmergencyStop),
546            0x02 => Ok(EmergencyStopAction::Resume),
547            _ => Err(ProtocolError::InvalidValue {
548                field: "EmergencyStopAction".to_string(),
549                value,
550            }),
551        }
552    }
553}
554
555/// 轨迹指令
556#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
557pub enum TrajectoryCommand {
558    /// 关闭
559    #[default]
560    Closed = 0x00,
561    /// 暂停当前规划
562    PausePlanning = 0x01,
563    /// 开始/继续当前轨迹
564    StartContinue = 0x02,
565    /// 清除当前轨迹
566    ClearCurrent = 0x03,
567    /// 清除所有轨迹
568    ClearAll = 0x04,
569    /// 获取当前规划轨迹
570    GetCurrentPlanning = 0x05,
571    /// 终止执行
572    Terminate = 0x06,
573    /// 轨迹传输
574    Transmit = 0x07,
575    /// 轨迹传输结束
576    TransmitEnd = 0x08,
577}
578
579impl TryFrom<u8> for TrajectoryCommand {
580    type Error = ProtocolError;
581
582    fn try_from(value: u8) -> Result<Self, Self::Error> {
583        match value {
584            0x00 => Ok(TrajectoryCommand::Closed),
585            0x01 => Ok(TrajectoryCommand::PausePlanning),
586            0x02 => Ok(TrajectoryCommand::StartContinue),
587            0x03 => Ok(TrajectoryCommand::ClearCurrent),
588            0x04 => Ok(TrajectoryCommand::ClearAll),
589            0x05 => Ok(TrajectoryCommand::GetCurrentPlanning),
590            0x06 => Ok(TrajectoryCommand::Terminate),
591            0x07 => Ok(TrajectoryCommand::Transmit),
592            0x08 => Ok(TrajectoryCommand::TransmitEnd),
593            _ => Err(ProtocolError::InvalidValue {
594                field: "TrajectoryCommand".to_string(),
595                value,
596            }),
597        }
598    }
599}
600
601/// 拖动示教指令
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
603pub enum TeachCommand {
604    /// 关闭
605    #[default]
606    Closed = 0x00,
607    /// 开始示教记录(进入拖动示教模式)
608    StartRecord = 0x01,
609    /// 结束示教记录(退出拖动示教模式)
610    EndRecord = 0x02,
611    /// 执行示教轨迹(拖动示教轨迹复现)
612    Execute = 0x03,
613    /// 暂停执行
614    Pause = 0x04,
615    /// 继续执行(轨迹复现继续)
616    Continue = 0x05,
617    /// 终止执行
618    Terminate = 0x06,
619    /// 运动到轨迹起点
620    MoveToStart = 0x07,
621}
622
623impl TryFrom<u8> for TeachCommand {
624    type Error = ProtocolError;
625
626    fn try_from(value: u8) -> Result<Self, Self::Error> {
627        match value {
628            0x00 => Ok(TeachCommand::Closed),
629            0x01 => Ok(TeachCommand::StartRecord),
630            0x02 => Ok(TeachCommand::EndRecord),
631            0x03 => Ok(TeachCommand::Execute),
632            0x04 => Ok(TeachCommand::Pause),
633            0x05 => Ok(TeachCommand::Continue),
634            0x06 => Ok(TeachCommand::Terminate),
635            0x07 => Ok(TeachCommand::MoveToStart),
636            _ => Err(ProtocolError::InvalidValue {
637                field: "TeachCommand".to_string(),
638                value,
639            }),
640        }
641    }
642}
643
644/// 快速急停/轨迹指令 (0x150)
645///
646/// 用于快速急停、轨迹控制和拖动示教控制。
647/// 注意:在离线轨迹模式下,Byte 3-7 用于轨迹传输(轨迹点索引、NameIndex、CRC16),
648/// 其他模式下这些字段全部填充 0x0。
649#[derive(Debug, Clone, Copy, Default)]
650pub struct EmergencyStopCommand {
651    pub emergency_stop: EmergencyStopAction,   // Byte 0
652    pub trajectory_command: TrajectoryCommand, // Byte 1
653    pub teach_command: TeachCommand,           // Byte 2
654    pub trajectory_index: u8,                  // Byte 3: 轨迹点索引 (0~255)
655    // 以下字段用于离线轨迹模式下的轨迹传输,其它模式下全部填充 0x0
656    pub name_index: u16, // Byte 4-5: 轨迹包名称索引
657    pub crc16: u16,      // Byte 6-7: CRC16 校验
658}
659
660impl EmergencyStopCommand {
661    /// 创建快速急停指令
662    pub fn emergency_stop() -> Self {
663        Self {
664            emergency_stop: EmergencyStopAction::EmergencyStop,
665            trajectory_command: TrajectoryCommand::Closed,
666            teach_command: TeachCommand::Closed,
667            trajectory_index: 0,
668            name_index: 0,
669            crc16: 0,
670        }
671    }
672
673    /// 创建恢复指令
674    pub fn resume() -> Self {
675        Self {
676            emergency_stop: EmergencyStopAction::Resume,
677            trajectory_command: TrajectoryCommand::Closed,
678            teach_command: TeachCommand::Closed,
679            trajectory_index: 0,
680            name_index: 0,
681            crc16: 0,
682        }
683    }
684
685    /// 创建轨迹传输指令(用于离线轨迹模式)
686    pub fn trajectory_transmit(trajectory_index: u8, name_index: u16, crc16: u16) -> Self {
687        Self {
688            emergency_stop: EmergencyStopAction::Invalid,
689            trajectory_command: TrajectoryCommand::Transmit,
690            teach_command: TeachCommand::Closed,
691            trajectory_index,
692            name_index,
693            crc16,
694        }
695    }
696
697    /// 转换为 CAN 帧
698    pub fn to_frame(self) -> PiperFrame {
699        let mut data = [0u8; 8];
700        data[0] = self.emergency_stop as u8;
701        data[1] = self.trajectory_command as u8;
702        data[2] = self.teach_command as u8;
703        data[3] = self.trajectory_index;
704
705        // 大端字节序
706        let name_index_bytes = self.name_index.to_be_bytes();
707        data[4] = name_index_bytes[0];
708        data[5] = name_index_bytes[1];
709
710        let crc_bytes = self.crc16.to_be_bytes();
711        data[6] = crc_bytes[0];
712        data[7] = crc_bytes[1];
713
714        PiperFrame::new_standard(ID_EMERGENCY_STOP as u16, &data)
715    }
716}
717
718#[cfg(test)]
719mod emergency_stop_tests {
720    use super::*;
721
722    #[test]
723    fn test_emergency_stop_action_from_u8() {
724        assert_eq!(
725            EmergencyStopAction::try_from(0x00).unwrap(),
726            EmergencyStopAction::Invalid
727        );
728        assert_eq!(
729            EmergencyStopAction::try_from(0x01).unwrap(),
730            EmergencyStopAction::EmergencyStop
731        );
732        assert_eq!(
733            EmergencyStopAction::try_from(0x02).unwrap(),
734            EmergencyStopAction::Resume
735        );
736    }
737
738    #[test]
739    fn test_trajectory_command_from_u8() {
740        assert_eq!(
741            TrajectoryCommand::try_from(0x00).unwrap(),
742            TrajectoryCommand::Closed
743        );
744        assert_eq!(
745            TrajectoryCommand::try_from(0x07).unwrap(),
746            TrajectoryCommand::Transmit
747        );
748        assert_eq!(
749            TrajectoryCommand::try_from(0x08).unwrap(),
750            TrajectoryCommand::TransmitEnd
751        );
752    }
753
754    #[test]
755    fn test_teach_command_from_u8() {
756        assert_eq!(TeachCommand::try_from(0x00).unwrap(), TeachCommand::Closed);
757        assert_eq!(
758            TeachCommand::try_from(0x01).unwrap(),
759            TeachCommand::StartRecord
760        );
761        assert_eq!(
762            TeachCommand::try_from(0x07).unwrap(),
763            TeachCommand::MoveToStart
764        );
765    }
766
767    #[test]
768    fn test_emergency_stop_command_emergency_stop() {
769        let cmd = EmergencyStopCommand::emergency_stop();
770        let frame = cmd.to_frame();
771
772        assert_eq!(frame.id, ID_EMERGENCY_STOP);
773        assert_eq!(frame.data[0], 0x01); // EmergencyStop
774        assert_eq!(frame.data[1], 0x00); // Closed
775        assert_eq!(frame.data[2], 0x00); // Closed
776        assert_eq!(frame.data[3], 0x00); // trajectory_index = 0
777    }
778
779    #[test]
780    fn test_emergency_stop_command_resume() {
781        let cmd = EmergencyStopCommand::resume();
782        let frame = cmd.to_frame();
783
784        assert_eq!(frame.id, ID_EMERGENCY_STOP);
785        assert_eq!(frame.data[0], 0x02); // Resume
786    }
787
788    #[test]
789    fn test_emergency_stop_command_trajectory_transmit() {
790        let cmd = EmergencyStopCommand::trajectory_transmit(5, 0x1234, 0x5678);
791        let frame = cmd.to_frame();
792
793        assert_eq!(frame.id, ID_EMERGENCY_STOP);
794        assert_eq!(frame.data[0], 0x00); // Invalid
795        assert_eq!(frame.data[1], 0x07); // Transmit
796        assert_eq!(frame.data[3], 5); // trajectory_index
797
798        // 验证大端字节序
799        let name_index = u16::from_be_bytes([frame.data[4], frame.data[5]]);
800        let crc16 = u16::from_be_bytes([frame.data[6], frame.data[7]]);
801        assert_eq!(name_index, 0x1234);
802        assert_eq!(crc16, 0x5678);
803    }
804}
805
806// ============================================================================
807// 电机使能指令结构体
808// ============================================================================
809
810/// 电机使能/失能设置指令 (0x471)
811///
812/// 用于使能或失能指定的关节电机。
813#[derive(Debug, Clone, Copy)]
814pub struct MotorEnableCommand {
815    pub joint_index: u8, // Byte 0: 1-6 代表关节驱动器序号,7 代表全部关节电机
816    pub enable: bool,    // Byte 1: true = 使能 (0x02), false = 失能 (0x01)
817}
818
819impl MotorEnableCommand {
820    /// 创建使能指令
821    pub fn enable(joint_index: u8) -> Self {
822        Self {
823            joint_index,
824            enable: true,
825        }
826    }
827
828    /// 创建失能指令
829    pub fn disable(joint_index: u8) -> Self {
830        Self {
831            joint_index,
832            enable: false,
833        }
834    }
835
836    /// 使能全部关节电机
837    pub fn enable_all() -> Self {
838        Self {
839            joint_index: 7,
840            enable: true,
841        }
842    }
843
844    /// 失能全部关节电机
845    pub fn disable_all() -> Self {
846        Self {
847            joint_index: 7,
848            enable: false,
849        }
850    }
851
852    /// 转换为 CAN 帧
853    pub fn to_frame(self) -> PiperFrame {
854        let mut data = [0u8; 8];
855        data[0] = self.joint_index;
856        data[1] = if self.enable { 0x02 } else { 0x01 };
857        // Byte 2-7: 保留,已初始化为 0
858
859        PiperFrame::new_standard(ID_MOTOR_ENABLE as u16, &data)
860    }
861}
862
863#[cfg(test)]
864mod motor_enable_tests {
865    use super::*;
866
867    #[test]
868    fn test_motor_enable_command_enable() {
869        let cmd = MotorEnableCommand::enable(1);
870        let frame = cmd.to_frame();
871
872        assert_eq!(frame.id, ID_MOTOR_ENABLE);
873        assert_eq!(frame.data[0], 1);
874        assert_eq!(frame.data[1], 0x02); // 使能
875    }
876
877    #[test]
878    fn test_motor_enable_command_disable() {
879        let cmd = MotorEnableCommand::disable(2);
880        let frame = cmd.to_frame();
881
882        assert_eq!(frame.id, ID_MOTOR_ENABLE);
883        assert_eq!(frame.data[0], 2);
884        assert_eq!(frame.data[1], 0x01); // 失能
885    }
886
887    #[test]
888    fn test_motor_enable_command_enable_all() {
889        let cmd = MotorEnableCommand::enable_all();
890        let frame = cmd.to_frame();
891
892        assert_eq!(frame.id, ID_MOTOR_ENABLE);
893        assert_eq!(frame.data[0], 7); // 全部关节
894        assert_eq!(frame.data[1], 0x02); // 使能
895    }
896
897    #[test]
898    fn test_motor_enable_command_all_joints() {
899        // 测试所有关节序号(1-6)
900        for i in 1..=6 {
901            let cmd = MotorEnableCommand::enable(i);
902            let frame = cmd.to_frame();
903            assert_eq!(frame.data[0], i);
904            assert_eq!(frame.data[1], 0x02);
905        }
906    }
907}
908
909// ============================================================================
910// 夹爪控制指令结构体
911// ============================================================================
912
913/// 夹爪控制标志位域(Byte 6: 8 位)
914///
915/// 协议定义:
916/// - Bit 0: 置1使能,0失能
917/// - Bit 1: 置1清除错误
918/// - Bit 2-7: 保留
919#[bitsize(8)]
920#[derive(FromBits, DebugBits, Clone, Copy, Default)]
921pub struct GripperControlFlags {
922    pub enable: bool,      // Bit 0: 置1使能,0失能
923    pub clear_error: bool, // Bit 1: 置1清除错误
924    pub reserved: u6,      // Bit 2-7: 保留
925}
926
927/// 夹爪控制指令 (0x159)
928///
929/// 用于控制夹爪的行程、扭矩、使能状态和零点设置。
930/// - 行程单位:0.001mm(原始值),0值表示完全闭合
931/// - 扭矩单位:0.001N·m(原始值)
932#[derive(Debug, Clone, Copy)]
933pub struct GripperControlCommand {
934    pub travel_mm: i32, // Byte 0-3: 夹爪行程,单位 0.001mm(0值表示完全闭合)
935    pub torque_nm: i16, // Byte 4-5: 夹爪扭矩,单位 0.001N·m
936    pub control_flags: GripperControlFlags, // Byte 6: 控制标志位域
937    pub zero_setting: u8, // Byte 7: 零点设置(0x00: 无效,0xAE: 设置当前为零点)
938}
939
940impl GripperControlCommand {
941    /// 从物理量创建夹爪控制指令
942    pub fn new(travel_mm: f64, torque_nm: f64, enable: bool) -> Self {
943        Self {
944            travel_mm: (travel_mm * 1000.0) as i32,
945            torque_nm: (torque_nm * 1000.0) as i16,
946            control_flags: {
947                let mut flags = GripperControlFlags::from(u8::new(0));
948                flags.set_enable(enable);
949                flags
950            },
951            zero_setting: 0x00,
952        }
953    }
954
955    /// 设置零点(设置当前为零点)
956    pub fn set_zero_point(mut self) -> Self {
957        self.zero_setting = 0xAE;
958        // 设置零点时,Byte 6 应该填充 0x0(失能)
959        let mut flags = GripperControlFlags::from(u8::new(0));
960        flags.set_enable(false);
961        self.control_flags = flags;
962        self
963    }
964
965    /// 清除错误
966    pub fn clear_error(mut self) -> Self {
967        let mut flags = self.control_flags;
968        flags.set_clear_error(true);
969        self.control_flags = flags;
970        self
971    }
972
973    /// 转换为 CAN 帧
974    pub fn to_frame(self) -> PiperFrame {
975        let mut data = [0u8; 8];
976
977        // 大端字节序
978        let travel_bytes = i32_to_bytes_be(self.travel_mm);
979        data[0..4].copy_from_slice(&travel_bytes);
980
981        let torque_bytes = i16_to_bytes_be(self.torque_nm);
982        data[4..6].copy_from_slice(&torque_bytes);
983
984        data[6] = u8::from(self.control_flags).value();
985        data[7] = self.zero_setting;
986
987        PiperFrame::new_standard(ID_GRIPPER_CONTROL as u16, &data)
988    }
989}
990
991#[cfg(test)]
992mod gripper_control_tests {
993    use super::*;
994
995    #[test]
996    fn test_gripper_control_flags_parse() {
997        // 测试:Bit 0 = 1(使能),Bit 1 = 1(清除错误)
998        let byte = 0b0000_0011;
999        let flags = GripperControlFlags::from(u8::new(byte));
1000
1001        assert!(flags.enable());
1002        assert!(flags.clear_error());
1003    }
1004
1005    #[test]
1006    fn test_gripper_control_flags_encode() {
1007        let mut flags = GripperControlFlags::from(u8::new(0));
1008        flags.set_enable(true);
1009        flags.set_clear_error(true);
1010
1011        let encoded = u8::from(flags).value();
1012        assert_eq!(encoded, 0b0000_0011);
1013    }
1014
1015    #[test]
1016    fn test_gripper_control_command_new() {
1017        let cmd = GripperControlCommand::new(50.0, 2.5, true);
1018        assert_eq!(cmd.travel_mm, 50000);
1019        assert_eq!(cmd.torque_nm, 2500);
1020        assert!(cmd.control_flags.enable());
1021        assert_eq!(cmd.zero_setting, 0x00);
1022    }
1023
1024    #[test]
1025    fn test_gripper_control_command_to_frame() {
1026        let cmd = GripperControlCommand::new(50.0, 2.5, true);
1027        let frame = cmd.to_frame();
1028
1029        assert_eq!(frame.id, ID_GRIPPER_CONTROL);
1030
1031        // 验证大端字节序
1032        let travel_decoded =
1033            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1034        let torque_decoded = i16::from_be_bytes([frame.data[4], frame.data[5]]);
1035        assert_eq!(travel_decoded, 50000);
1036        assert_eq!(torque_decoded, 2500);
1037        assert_eq!(frame.data[6] & 0x01, 0x01); // Bit 0 = 1(使能)
1038        assert_eq!(frame.data[7], 0x00); // 零点设置无效
1039    }
1040
1041    #[test]
1042    fn test_gripper_control_command_set_zero_point() {
1043        let cmd = GripperControlCommand::new(0.0, 0.0, false).set_zero_point();
1044        let frame = cmd.to_frame();
1045
1046        assert_eq!(frame.data[6], 0x00); // 失能(设置零点时)
1047        assert_eq!(frame.data[7], 0xAE); // 设置零点标志
1048    }
1049
1050    #[test]
1051    fn test_gripper_control_command_clear_error() {
1052        let cmd = GripperControlCommand::new(50.0, 2.5, true).clear_error();
1053        let frame = cmd.to_frame();
1054
1055        assert_eq!(frame.data[6] & 0x03, 0x03); // Bit 0 和 Bit 1 都是 1
1056    }
1057
1058    #[test]
1059    fn test_gripper_control_command_fully_closed() {
1060        // 测试完全闭合(travel = 0)
1061        let cmd = GripperControlCommand::new(0.0, 1.0, true);
1062        assert_eq!(cmd.travel_mm, 0);
1063    }
1064}
1065
1066// ============================================================================
1067// 末端位姿控制指令结构体
1068// ============================================================================
1069
1070/// 机械臂运动控制直角坐标指令1 (0x152)
1071///
1072/// 用于控制末端执行器的 X 和 Y 坐标。
1073/// 单位:0.001mm(原始值)
1074#[derive(Debug, Clone, Copy, Default)]
1075pub struct EndPoseControl1 {
1076    pub x_mm: i32, // Byte 0-3: X坐标,单位 0.001mm
1077    pub y_mm: i32, // Byte 4-7: Y坐标,单位 0.001mm
1078}
1079
1080impl EndPoseControl1 {
1081    /// 从物理量(mm)创建末端位姿控制指令
1082    pub fn new(x: f64, y: f64) -> Self {
1083        Self {
1084            x_mm: (x * 1000.0) as i32,
1085            y_mm: (y * 1000.0) as i32,
1086        }
1087    }
1088
1089    /// 转换为 CAN 帧
1090    pub fn to_frame(self) -> PiperFrame {
1091        let mut data = [0u8; 8];
1092        let x_bytes = i32_to_bytes_be(self.x_mm);
1093        let y_bytes = i32_to_bytes_be(self.y_mm);
1094        data[0..4].copy_from_slice(&x_bytes);
1095        data[4..8].copy_from_slice(&y_bytes);
1096
1097        PiperFrame::new_standard(ID_END_POSE_CONTROL_1 as u16, &data)
1098    }
1099}
1100
1101/// 机械臂运动控制旋转坐标指令2 (0x153)
1102///
1103/// 用于控制末端执行器的 Z 坐标和 RX 角度。
1104/// - Z 单位:0.001mm(原始值)
1105/// - RX 单位:0.001°(原始值)
1106#[derive(Debug, Clone, Copy, Default)]
1107pub struct EndPoseControl2 {
1108    pub z_mm: i32,   // Byte 0-3: Z坐标,单位 0.001mm
1109    pub rx_deg: i32, // Byte 4-7: RX角度,单位 0.001°
1110}
1111
1112impl EndPoseControl2 {
1113    /// 从物理量创建末端位姿控制指令
1114    pub fn new(z: f64, rx: f64) -> Self {
1115        Self {
1116            z_mm: (z * 1000.0) as i32,
1117            rx_deg: (rx * 1000.0) as i32,
1118        }
1119    }
1120
1121    /// 转换为 CAN 帧
1122    pub fn to_frame(self) -> PiperFrame {
1123        let mut data = [0u8; 8];
1124        let z_bytes = i32_to_bytes_be(self.z_mm);
1125        let rx_bytes = i32_to_bytes_be(self.rx_deg);
1126        data[0..4].copy_from_slice(&z_bytes);
1127        data[4..8].copy_from_slice(&rx_bytes);
1128
1129        PiperFrame::new_standard(ID_END_POSE_CONTROL_2 as u16, &data)
1130    }
1131}
1132
1133/// 机械臂运动控制旋转坐标指令3 (0x154)
1134///
1135/// 用于控制末端执行器的 RY 和 RZ 角度。
1136/// 单位:0.001°(原始值)
1137#[derive(Debug, Clone, Copy, Default)]
1138pub struct EndPoseControl3 {
1139    pub ry_deg: i32, // Byte 0-3: RY角度,单位 0.001°
1140    pub rz_deg: i32, // Byte 4-7: RZ角度,单位 0.001°
1141}
1142
1143impl EndPoseControl3 {
1144    /// 从物理量(度)创建末端位姿控制指令
1145    pub fn new(ry: f64, rz: f64) -> Self {
1146        Self {
1147            ry_deg: (ry * 1000.0) as i32,
1148            rz_deg: (rz * 1000.0) as i32,
1149        }
1150    }
1151
1152    /// 转换为 CAN 帧
1153    pub fn to_frame(self) -> PiperFrame {
1154        let mut data = [0u8; 8];
1155        let ry_bytes = i32_to_bytes_be(self.ry_deg);
1156        let rz_bytes = i32_to_bytes_be(self.rz_deg);
1157        data[0..4].copy_from_slice(&ry_bytes);
1158        data[4..8].copy_from_slice(&rz_bytes);
1159
1160        PiperFrame::new_standard(ID_END_POSE_CONTROL_3 as u16, &data)
1161    }
1162}
1163
1164#[cfg(test)]
1165mod end_pose_control_tests {
1166    use super::*;
1167
1168    #[test]
1169    fn test_end_pose_control1_new() {
1170        let cmd = EndPoseControl1::new(100.0, -50.0);
1171        assert_eq!(cmd.x_mm, 100000);
1172        assert_eq!(cmd.y_mm, -50000);
1173    }
1174
1175    #[test]
1176    fn test_end_pose_control1_to_frame() {
1177        let cmd = EndPoseControl1::new(100.0, -50.0);
1178        let frame = cmd.to_frame();
1179
1180        assert_eq!(frame.id, ID_END_POSE_CONTROL_1);
1181        let x_decoded =
1182            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1183        let y_decoded =
1184            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1185        assert_eq!(x_decoded, 100000);
1186        assert_eq!(y_decoded, -50000);
1187    }
1188
1189    #[test]
1190    fn test_end_pose_control2_new() {
1191        let cmd = EndPoseControl2::new(200.0, 90.0);
1192        assert_eq!(cmd.z_mm, 200000);
1193        assert_eq!(cmd.rx_deg, 90000);
1194    }
1195
1196    #[test]
1197    fn test_end_pose_control2_to_frame() {
1198        let cmd = EndPoseControl2::new(200.0, 90.0);
1199        let frame = cmd.to_frame();
1200
1201        assert_eq!(frame.id, ID_END_POSE_CONTROL_2);
1202        let z_decoded =
1203            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1204        let rx_decoded =
1205            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1206        assert_eq!(z_decoded, 200000);
1207        assert_eq!(rx_decoded, 90000);
1208    }
1209
1210    #[test]
1211    fn test_end_pose_control3_new() {
1212        let cmd = EndPoseControl3::new(-45.0, 180.0);
1213        assert_eq!(cmd.ry_deg, -45000);
1214        assert_eq!(cmd.rz_deg, 180000);
1215    }
1216
1217    #[test]
1218    fn test_end_pose_control3_to_frame() {
1219        let cmd = EndPoseControl3::new(-45.0, 180.0);
1220        let frame = cmd.to_frame();
1221
1222        assert_eq!(frame.id, ID_END_POSE_CONTROL_3);
1223        let ry_decoded =
1224            i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
1225        let rz_decoded =
1226            i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
1227        assert_eq!(ry_decoded, -45000);
1228        assert_eq!(rz_decoded, 180000);
1229    }
1230
1231    #[test]
1232    fn test_end_pose_control_precision() {
1233        // 测试精度:0.5mm = 500 (0.001mm 单位)
1234        let cmd = EndPoseControl1::new(0.5, -0.5);
1235        assert_eq!(cmd.x_mm, 500);
1236        assert_eq!(cmd.y_mm, -500);
1237    }
1238}
1239
1240// ============================================================================
1241// 圆弧模式坐标序号更新指令结构体
1242// ============================================================================
1243
1244/// 圆弧模式坐标序号
1245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1246pub enum ArcPointIndex {
1247    /// 无效
1248    Invalid = 0x00,
1249    /// 起点
1250    Start = 0x01,
1251    /// 中间点
1252    Middle = 0x02,
1253    /// 终点
1254    End = 0x03,
1255}
1256
1257impl TryFrom<u8> for ArcPointIndex {
1258    type Error = ProtocolError;
1259
1260    fn try_from(value: u8) -> Result<Self, Self::Error> {
1261        match value {
1262            0x00 => Ok(ArcPointIndex::Invalid),
1263            0x01 => Ok(ArcPointIndex::Start),
1264            0x02 => Ok(ArcPointIndex::Middle),
1265            0x03 => Ok(ArcPointIndex::End),
1266            _ => Err(ProtocolError::InvalidValue {
1267                field: "ArcPointIndex".to_string(),
1268                value,
1269            }),
1270        }
1271    }
1272}
1273
1274/// 圆弧模式坐标序号更新指令 (0x158)
1275///
1276/// 用于在圆弧模式(MOVE C)下更新坐标序号。
1277/// 只有 Byte 0 有效,其他字节填充 0x0。
1278#[derive(Debug, Clone, Copy)]
1279pub struct ArcPointCommand {
1280    pub point_index: ArcPointIndex,
1281}
1282
1283impl ArcPointCommand {
1284    /// 创建起点指令
1285    pub fn start() -> Self {
1286        Self {
1287            point_index: ArcPointIndex::Start,
1288        }
1289    }
1290
1291    /// 创建中间点指令
1292    pub fn middle() -> Self {
1293        Self {
1294            point_index: ArcPointIndex::Middle,
1295        }
1296    }
1297
1298    /// 创建终点指令
1299    pub fn end() -> Self {
1300        Self {
1301            point_index: ArcPointIndex::End,
1302        }
1303    }
1304
1305    /// 从枚举值创建
1306    pub fn new(point_index: ArcPointIndex) -> Self {
1307        Self { point_index }
1308    }
1309
1310    /// 转换为 CAN 帧
1311    pub fn to_frame(self) -> PiperFrame {
1312        let mut data = [0u8; 8];
1313        data[0] = self.point_index as u8;
1314        // Byte 1-7: 保留,已初始化为 0
1315
1316        PiperFrame::new_standard(ID_ARC_POINT as u16, &data)
1317    }
1318}
1319
1320#[cfg(test)]
1321mod arc_point_tests {
1322    use super::*;
1323
1324    #[test]
1325    fn test_arc_point_index_from_u8() {
1326        assert_eq!(
1327            ArcPointIndex::try_from(0x00).unwrap(),
1328            ArcPointIndex::Invalid
1329        );
1330        assert_eq!(ArcPointIndex::try_from(0x01).unwrap(), ArcPointIndex::Start);
1331        assert_eq!(
1332            ArcPointIndex::try_from(0x02).unwrap(),
1333            ArcPointIndex::Middle
1334        );
1335        assert_eq!(ArcPointIndex::try_from(0x03).unwrap(), ArcPointIndex::End);
1336    }
1337
1338    #[test]
1339    fn test_arc_point_index_invalid() {
1340        assert!(ArcPointIndex::try_from(0x04).is_err());
1341        assert!(ArcPointIndex::try_from(0xFF).is_err());
1342    }
1343
1344    #[test]
1345    fn test_arc_point_command_start() {
1346        let cmd = ArcPointCommand::start();
1347        let frame = cmd.to_frame();
1348
1349        assert_eq!(frame.id, ID_ARC_POINT);
1350        assert_eq!(frame.data[0], 0x01);
1351        assert_eq!(frame.data[1], 0x00); // 保留字段
1352    }
1353
1354    #[test]
1355    fn test_arc_point_command_middle() {
1356        let cmd = ArcPointCommand::middle();
1357        let frame = cmd.to_frame();
1358
1359        assert_eq!(frame.id, ID_ARC_POINT);
1360        assert_eq!(frame.data[0], 0x02);
1361    }
1362
1363    #[test]
1364    fn test_arc_point_command_end() {
1365        let cmd = ArcPointCommand::end();
1366        let frame = cmd.to_frame();
1367
1368        assert_eq!(frame.id, ID_ARC_POINT);
1369        assert_eq!(frame.data[0], 0x03);
1370    }
1371}
1372
1373// ============================================================================
1374// MIT 控制指令结构体
1375// ============================================================================
1376
1377/// MIT 控制指令 (0x15A~0x15F)
1378///
1379/// 用于控制机械臂关节的 MIT 模式(主从模式)。
1380/// 包含位置参考、速度参考、比例增益、微分增益、力矩参考和 CRC 校验。
1381///
1382/// 注意:此指令使用复杂的跨字节位域打包,需要仔细处理。
1383#[derive(Debug, Clone, Copy)]
1384pub struct MitControlCommand {
1385    pub joint_index: u8, // 从 ID 推导:0x15A -> 1, 0x15B -> 2, ...
1386    pub pos_ref: f32,    // 位置参考值
1387    pub vel_ref: f32,    // 速度参考值
1388    pub kp: f32,         // 比例增益(参考值:10)
1389    pub kd: f32,         // 微分增益(参考值:0.8)
1390    pub t_ref: f32,      // 力矩参考值
1391    pub crc: u8,         // CRC 校验(4位,但存储为 u8)
1392}
1393
1394impl MitControlCommand {
1395    /// 辅助函数:将浮点数转换为无符号整数(根据协议公式)
1396    ///
1397    /// 公式:`(x - x_min) * ((1 << bits) - 1) / (x_max - x_min)`
1398    fn float_to_uint(x: f32, x_min: f32, x_max: f32, bits: u32) -> u32 {
1399        let span = x_max - x_min;
1400        let offset = x_min;
1401        if span <= 0.0 {
1402            return 0;
1403        }
1404        let result = ((x - offset) * ((1u32 << bits) - 1) as f32 / span) as u32;
1405        result.min((1u32 << bits) - 1)
1406    }
1407
1408    /// 辅助函数:将无符号整数转换为浮点数(根据协议公式)
1409    ///
1410    /// 公式:`x_int * (x_max - x_min) / ((1 << bits) - 1) + x_min`
1411    ///
1412    /// 注意:此函数目前仅用于测试,保留作为公共 API 以便将来可能需要解析 MIT 控制反馈。
1413    #[allow(dead_code)]
1414    pub fn uint_to_float(x_int: u32, x_min: f32, x_max: f32, bits: u32) -> f32 {
1415        let span = x_max - x_min;
1416        let offset = x_min;
1417        (x_int as f32) * span / ((1u32 << bits) - 1) as f32 + offset
1418    }
1419
1420    /// 创建 MIT 控制指令
1421    ///
1422    /// 参数范围(根据官方 SDK,固定值,不要更改):
1423    /// - pos_ref: -12.5 ~ 12.5
1424    /// - vel_ref: -45.0 ~ 45.0 rad/s
1425    /// - kp: 0.0 ~ 500.0
1426    /// - kd: -5.0 ~ 5.0
1427    /// - t_ref: -18.0 ~ 18.0 N·m
1428    ///
1429    /// # Arguments
1430    ///
1431    /// * `joint_index` - 关节序号 [1, 6]
1432    /// * `pos_ref` - 设定期望的目标位置
1433    /// * `vel_ref` - 设定电机运动的速度
1434    /// * `kp` - 比例增益,控制位置误差对输出力矩的影响
1435    /// * `kd` - 微分增益,控制速度误差对输出力矩的影响
1436    /// * `t_ref` - 目标力矩参考值,用于控制电机施加的力矩或扭矩
1437    /// * `crc` - CRC 校验值(4位)
1438    pub fn new(
1439        joint_index: u8,
1440        pos_ref: f32,
1441        vel_ref: f32,
1442        kp: f32,
1443        kd: f32,
1444        t_ref: f32,
1445        crc: u8,
1446    ) -> Self {
1447        Self {
1448            joint_index,
1449            pos_ref,
1450            vel_ref,
1451            kp,
1452            kd,
1453            t_ref,
1454            crc: crc & 0x0F, // 只保留低4位
1455        }
1456    }
1457
1458    /// 转换为 CAN 帧
1459    ///
1460    /// 协议位域布局:
1461    /// - Byte 0-1: Pos_ref (16位)
1462    /// - Byte 2: Vel_ref [bit11~bit4] (8位)
1463    /// - Byte 3: Vel_ref [bit3~bit0] | Kp [bit11~bit8] (跨字节打包)
1464    /// - Byte 4: Kp [bit7~bit0] (8位)
1465    /// - Byte 5: Kd [bit11~bit4] (8位)
1466    /// - Byte 6: Kd [bit3~bit0] | T_ref [bit7~bit4] (跨字节打包)
1467    /// - Byte 7: T_ref [bit3~bit0] | CRC [bit3~bit0] (跨字节打包)
1468    ///
1469    /// 参数范围(根据官方 SDK):
1470    /// - Pos_ref: -12.5 ~ 12.5 (16位)
1471    /// - Vel_ref: -45.0 ~ 45.0 rad/s (12位)
1472    /// - Kp: 0.0 ~ 500.0 (12位)
1473    /// - Kd: -5.0 ~ 5.0 (12位)
1474    /// - T_ref: -18.0 ~ 18.0 N·m (8位)
1475    pub fn to_frame(self) -> PiperFrame {
1476        let mut data = [0u8; 8];
1477
1478        // Byte 0-1: Pos_ref (16位)
1479        // 范围:-12.5 ~ 12.5(根据官方 SDK)
1480        let pos_ref_uint = Self::float_to_uint(self.pos_ref, -12.5, 12.5, 16);
1481        data[0] = ((pos_ref_uint >> 8) & 0xFF) as u8;
1482        data[1] = (pos_ref_uint & 0xFF) as u8;
1483
1484        // Byte 2-3: Vel_ref (12位) 和 Kp (12位) 的跨字节打包
1485        // Vel_ref 范围:-45.0 ~ 45.0 rad/s(根据官方 SDK)
1486        let vel_ref_uint = Self::float_to_uint(self.vel_ref, -45.0, 45.0, 12);
1487        data[2] = ((vel_ref_uint >> 4) & 0xFF) as u8; // Vel_ref [bit11~bit4]
1488
1489        // Byte 3: Vel_ref [bit3~bit0] | Kp [bit11~bit8]
1490        // Kp 范围:0.0 ~ 500.0(根据官方 SDK)
1491        let kp_uint = Self::float_to_uint(self.kp, 0.0, 500.0, 12);
1492        let vel_ref_low = (vel_ref_uint & 0x0F) as u8;
1493        let kp_high = ((kp_uint >> 8) & 0x0F) as u8;
1494        data[3] = (vel_ref_low << 4) | kp_high;
1495
1496        // Byte 4: Kp [bit7~bit0]
1497        data[4] = (kp_uint & 0xFF) as u8;
1498
1499        // Byte 5-6: Kd (12位) 和 T_ref (8位) 的跨字节打包
1500        // Kd 范围:-5.0 ~ 5.0(根据官方 SDK)
1501        let kd_uint = Self::float_to_uint(self.kd, -5.0, 5.0, 12);
1502        data[5] = ((kd_uint >> 4) & 0xFF) as u8; // Kd [bit11~bit4]
1503
1504        // Byte 6: Kd [bit3~bit0] | T_ref [bit7~bit4]
1505        // T_ref 范围:-18.0 ~ 18.0 N·m(根据官方 SDK)
1506        let t_ref_uint = Self::float_to_uint(self.t_ref, -18.0, 18.0, 8);
1507        let kd_low = (kd_uint & 0x0F) as u8;
1508        let t_ref_high = ((t_ref_uint >> 4) & 0x0F) as u8;
1509        data[6] = (kd_low << 4) | t_ref_high;
1510
1511        // Byte 7: T_ref [bit3~bit0] | CRC [bit3~bit0]
1512        let t_ref_low = (t_ref_uint & 0x0F) as u8;
1513        let crc_low = self.crc & 0x0F;
1514        data[7] = (t_ref_low << 4) | crc_low;
1515
1516        let can_id = ID_MIT_CONTROL_BASE + (self.joint_index - 1) as u32;
1517        PiperFrame::new_standard(can_id as u16, &data)
1518    }
1519}
1520
1521#[cfg(test)]
1522mod mit_control_tests {
1523    use super::*;
1524
1525    #[test]
1526    fn test_float_to_uint() {
1527        // 测试转换公式:范围 0.0 ~ 10.0,12位
1528        let result = MitControlCommand::float_to_uint(5.0, 0.0, 10.0, 12);
1529        // 期望:5.0 / 10.0 * 4095 = 2047.5 ≈ 2047
1530        assert_eq!(result, 2047);
1531    }
1532
1533    #[test]
1534    fn test_float_to_uint_boundary() {
1535        // 测试边界值
1536        let min = MitControlCommand::float_to_uint(0.0, 0.0, 10.0, 12);
1537        let max = MitControlCommand::float_to_uint(10.0, 0.0, 10.0, 12);
1538        assert_eq!(min, 0);
1539        assert_eq!(max, 4095);
1540    }
1541
1542    #[test]
1543    fn test_uint_to_float() {
1544        // 测试转换公式:范围 0.0 ~ 10.0,12位
1545        let result = MitControlCommand::uint_to_float(2047, 0.0, 10.0, 12);
1546        // 期望:2047 / 4095 * 10.0 ≈ 5.0
1547        assert!((result - 5.0).abs() < 0.01);
1548    }
1549
1550    #[test]
1551    fn test_uint_to_float_boundary() {
1552        // 测试边界值
1553        let min = MitControlCommand::uint_to_float(0, 0.0, 10.0, 12);
1554        let max = MitControlCommand::uint_to_float(4095, 0.0, 10.0, 12);
1555        assert!((min - 0.0).abs() < 0.001);
1556        assert!((max - 10.0).abs() < 0.001);
1557    }
1558
1559    #[test]
1560    fn test_mit_control_command_new() {
1561        let cmd = MitControlCommand::new(1, 1.0, 2.0, 10.0, 0.8, 5.0, 0x0A);
1562        assert_eq!(cmd.joint_index, 1);
1563        assert_eq!(cmd.pos_ref, 1.0);
1564        assert_eq!(cmd.vel_ref, 2.0);
1565        assert_eq!(cmd.kp, 10.0);
1566        assert_eq!(cmd.kd, 0.8);
1567        assert_eq!(cmd.t_ref, 5.0);
1568        assert_eq!(cmd.crc, 0x0A);
1569    }
1570
1571    #[test]
1572    fn test_mit_control_command_crc_mask() {
1573        // 测试 CRC 只保留低4位
1574        let cmd = MitControlCommand::new(1, 0.0, 0.0, 0.0, 0.0, 0.0, 0xFF);
1575        assert_eq!(cmd.crc, 0x0F);
1576    }
1577
1578    #[test]
1579    fn test_mit_control_command_to_frame() {
1580        // 使用官方 SDK 的参考值:kp=10, kd=0.8
1581        let cmd = MitControlCommand::new(1, 0.0, 0.0, 10.0, 0.8, 0.0, 0x05);
1582        let frame = cmd.to_frame();
1583
1584        assert_eq!(frame.id, ID_MIT_CONTROL_BASE);
1585        // 验证 CRC 在 Byte 7 的低4位
1586        assert_eq!(frame.data[7] & 0x0F, 0x05);
1587    }
1588
1589    #[test]
1590    fn test_mit_control_command_with_official_ranges() {
1591        // 测试使用官方 SDK 的参数范围
1592        // pos_ref: -12.5 ~ 12.5
1593        // vel_ref: -45.0 ~ 45.0
1594        // kp: 0.0 ~ 500.0
1595        // kd: -5.0 ~ 5.0
1596        // t_ref: -18.0 ~ 18.0
1597
1598        // 测试边界值
1599        let cmd_min = MitControlCommand::new(1, -12.5, -45.0, 0.0, -5.0, -18.0, 0x00);
1600        let frame_min = cmd_min.to_frame();
1601        assert_eq!(frame_min.id, ID_MIT_CONTROL_BASE);
1602
1603        let cmd_max = MitControlCommand::new(1, 12.5, 45.0, 500.0, 5.0, 18.0, 0x0F);
1604        let frame_max = cmd_max.to_frame();
1605        assert_eq!(frame_max.id, ID_MIT_CONTROL_BASE);
1606
1607        // 测试参考值(根据协议文档)
1608        let cmd_ref = MitControlCommand::new(1, 0.0, 0.0, 10.0, 0.8, 0.0, 0x00);
1609        let frame_ref = cmd_ref.to_frame();
1610        assert_eq!(frame_ref.id, ID_MIT_CONTROL_BASE);
1611    }
1612
1613    #[test]
1614    fn test_mit_control_command_all_joints() {
1615        // 测试所有 6 个关节
1616        for i in 1..=6 {
1617            let cmd = MitControlCommand::new(i, 0.0, 0.0, 10.0, 0.8, 0.0, 0x00);
1618            let frame = cmd.to_frame();
1619            let expected_id = ID_MIT_CONTROL_BASE + (i - 1) as u32;
1620            assert_eq!(frame.id, expected_id);
1621        }
1622    }
1623
1624    #[test]
1625    fn test_mit_control_command_roundtrip() {
1626        // 测试转换函数的往返转换
1627        let original = 5.0f32;
1628        let x_min = 0.0f32;
1629        let x_max = 10.0f32;
1630        let bits = 12u32;
1631
1632        let uint_val = MitControlCommand::float_to_uint(original, x_min, x_max, bits);
1633        let float_val = MitControlCommand::uint_to_float(uint_val, x_min, x_max, bits);
1634
1635        // 由于精度损失,允许一定误差
1636        assert!((float_val - original).abs() < 0.01);
1637    }
1638}
1639
1640// ============================================================================
1641// 灯光控制指令
1642// ============================================================================
1643
1644/// 灯光控制使能标志
1645#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1646pub enum LightControlEnable {
1647    /// 控制指令无效
1648    #[default]
1649    Disabled = 0x00,
1650    /// 灯光控制使能
1651    Enabled = 0x01,
1652}
1653
1654impl From<u8> for LightControlEnable {
1655    fn from(value: u8) -> Self {
1656        match value {
1657            0x00 => Self::Disabled,
1658            0x01 => Self::Enabled,
1659            _ => Self::Disabled, // 默认无效
1660        }
1661    }
1662}
1663
1664/// 灯光控制指令 (0x121)
1665///
1666/// 用于控制关节上的 LED 灯光。
1667#[derive(Debug, Clone, Copy)]
1668pub struct LightControlCommand {
1669    pub enable: LightControlEnable, // Byte 0: 灯光控制使能标志
1670    pub joint_index: u8,            // Byte 1: 关节序号 (1~6)
1671    pub led_index: u8,              // Byte 2: 灯珠序号 (0-254, 0xFF表示同时操作全部)
1672    pub r: u8,                      // Byte 3: R通道灰度值 (0~255)
1673    pub g: u8,                      // Byte 4: G通道灰度值 (0~255)
1674    pub b: u8,                      // Byte 5: B通道灰度值 (0~255)
1675    pub counter: u8,                // Byte 7: 计数校验 (0-255循环计数)
1676                                    // Byte 6: 保留
1677}
1678
1679impl LightControlCommand {
1680    /// 创建灯光控制指令
1681    pub fn new(
1682        enable: LightControlEnable,
1683        joint_index: u8,
1684        led_index: u8,
1685        r: u8,
1686        g: u8,
1687        b: u8,
1688        counter: u8,
1689    ) -> Self {
1690        Self {
1691            enable,
1692            joint_index,
1693            led_index,
1694            r,
1695            g,
1696            b,
1697            counter,
1698        }
1699    }
1700
1701    /// 转换为 CAN 帧
1702    pub fn to_frame(self) -> PiperFrame {
1703        let mut data = [0u8; 8];
1704        data[0] = self.enable as u8;
1705        data[1] = self.joint_index;
1706        data[2] = self.led_index;
1707        data[3] = self.r;
1708        data[4] = self.g;
1709        data[5] = self.b;
1710        // Byte 6: 保留,已初始化为 0
1711        data[7] = self.counter;
1712
1713        PiperFrame::new_standard(ID_LIGHT_CONTROL as u16, &data)
1714    }
1715}
1716
1717#[cfg(test)]
1718mod light_control_tests {
1719    use super::*;
1720
1721    #[test]
1722    fn test_light_control_enable_from() {
1723        assert_eq!(LightControlEnable::from(0x00), LightControlEnable::Disabled);
1724        assert_eq!(LightControlEnable::from(0x01), LightControlEnable::Enabled);
1725        assert_eq!(LightControlEnable::from(0xFF), LightControlEnable::Disabled); // 默认无效
1726    }
1727
1728    #[test]
1729    fn test_light_control_command_new() {
1730        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 1, 0xFF, 255, 128, 0, 10);
1731        assert_eq!(cmd.enable, LightControlEnable::Enabled);
1732        assert_eq!(cmd.joint_index, 1);
1733        assert_eq!(cmd.led_index, 0xFF);
1734        assert_eq!(cmd.r, 255);
1735        assert_eq!(cmd.g, 128);
1736        assert_eq!(cmd.b, 0);
1737        assert_eq!(cmd.counter, 10);
1738    }
1739
1740    #[test]
1741    fn test_light_control_command_to_frame() {
1742        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 2, 5, 100, 200, 50, 42);
1743        let frame = cmd.to_frame();
1744
1745        assert_eq!(frame.id, ID_LIGHT_CONTROL);
1746        assert_eq!(frame.data[0], 0x01); // Enabled
1747        assert_eq!(frame.data[1], 2); // joint_index
1748        assert_eq!(frame.data[2], 5); // led_index
1749        assert_eq!(frame.data[3], 100); // R
1750        assert_eq!(frame.data[4], 200); // G
1751        assert_eq!(frame.data[5], 50); // B
1752        assert_eq!(frame.data[6], 0x00); // 保留
1753        assert_eq!(frame.data[7], 42); // counter
1754    }
1755
1756    #[test]
1757    fn test_light_control_command_all_leds() {
1758        // 测试 0xFF 表示同时操作全部灯珠
1759        let cmd = LightControlCommand::new(LightControlEnable::Enabled, 3, 0xFF, 255, 255, 255, 0);
1760        let frame = cmd.to_frame();
1761        assert_eq!(frame.data[2], 0xFF);
1762    }
1763
1764    #[test]
1765    fn test_light_control_command_disabled() {
1766        let cmd = LightControlCommand::new(LightControlEnable::Disabled, 1, 0, 0, 0, 0, 0);
1767        let frame = cmd.to_frame();
1768        assert_eq!(frame.data[0], 0x00);
1769    }
1770}