Skip to main content

piper_client/state/
machine.rs

1//! Type State Machine - 编译期状态安全
2//!
3//! 使用零大小类型(ZST)标记实现状态机,在编译期防止非法状态转换。
4
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8use crate::types::*;
9use crate::{observer::Observer, raw_commander::RawCommander};
10use piper_protocol::control::InstallPosition;
11use semver::Version;
12use tracing::{debug, info, trace, warn};
13
14// ==================== 状态类型(零大小类型)====================
15
16/// 未连接状态
17///
18/// 这是初始状态,在此状态下无法进行任何操作。
19pub struct Disconnected;
20
21/// 待机状态
22///
23/// 已连接但未使能。可以读取状态,但不能发送运动命令。
24pub struct Standby;
25
26/// 活动状态(带控制模式)
27///
28/// 机械臂已使能,可以发送运动命令。
29pub struct Active<Mode>(Mode);
30
31// ==================== 控制模式类型(零大小类型)====================
32
33/// MIT 模式
34///
35/// 支持位置、速度、力矩的混合控制。
36pub struct MitMode;
37
38/// 位置模式
39///
40/// 纯位置控制模式。
41pub struct PositionMode {
42    /// 发送策略配置
43    pub(crate) send_strategy: SendStrategy,
44}
45
46impl PositionMode {
47    /// 使用指定策略创建位置模式
48    pub(crate) fn with_strategy(send_strategy: SendStrategy) -> Self {
49        Self { send_strategy }
50    }
51}
52
53/// 错误状态
54///
55/// 急停或其他错误发生后进入此状态。
56/// 在此状态下,不允许发送任何运动控制命令。
57pub struct ErrorState;
58
59/// 回放模式状态
60///
61/// 用于安全地回放预先录制的 CAN 帧。
62///
63/// # 设计目的
64///
65/// - 暂停 TX 线程的周期性发送
66/// - 避免双控制流冲突
67/// - 允许精确控制帧发送时机
68///
69/// # 转换规则
70///
71/// - **进入**: 从 `Standby` 通过 `enter_replay_mode()` 进入
72/// - **退出**: 通过 `stop_replay()` 返回到 `Standby`
73///
74/// # 安全特性
75///
76/// - 在 ReplayMode 下,无法调用 `enable_*()` 方法
77/// - 所有周期性发送的控制指令都会被暂停
78/// - 只能通过 `replay_recording()` 发送预先录制的帧
79///
80/// # 使用场景
81///
82/// - 回放预先录制的运动轨迹
83/// - 测试和验证录制的 CAN 帧序列
84/// - 调试和分析工具
85///
86/// # 示例
87///
88/// ```rust,ignore
89/// # use piper_client::{PiperBuilder};
90/// # fn main() -> anyhow::Result<()> {
91/// let robot = PiperBuilder::new()
92///     .interface("can0")
93///     .build()?;
94///
95/// let standby = robot.connect()?;
96///
97/// // 进入回放模式
98/// let replay = standby.enter_replay_mode()?;
99///
100/// // 回放录制(1.0x 速度,原始速度)
101/// let standby = replay.replay_recording("recording.bin", 1.0)?;
102///
103/// // 回放完成后自动返回 Standby 状态
104/// # Ok(())
105/// # }
106/// ```
107pub struct ReplayMode;
108
109// ==================== 运动类型 ====================
110
111/// 运动类型
112///
113/// 决定机械臂如何规划运动轨迹。
114///
115/// **注意**:此枚举用于配置 `PositionModeConfig`,与 `MoveMode` 协议枚举对应。
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
117pub enum MotionType {
118    /// 关节空间运动
119    ///
120    /// 各关节独立运动到目标角度,末端轨迹不可预测。
121    /// 对应 MoveMode::MoveJ (0x01),使用指令 0x155-0x157。
122    #[default]
123    Joint,
124
125    /// 笛卡尔空间运动(点位模式)
126    ///
127    /// 末端从当前位置运动到目标位姿,轨迹由机械臂内部规划。
128    /// 对应 MoveMode::MoveP (0x00),使用指令 0x152-0x154。
129    Cartesian,
130
131    /// 直线运动
132    ///
133    /// 末端沿直线轨迹运动到目标位姿。
134    /// 对应 MoveMode::MoveL (0x02),使用指令 0x152-0x154。
135    Linear,
136
137    /// 圆弧运动
138    ///
139    /// 末端沿圆弧轨迹运动,需要指定起点、中点、终点。
140    /// 对应 MoveMode::MoveC (0x03),使用指令 0x152-0x154 + 0x158。
141    Circular,
142
143    /// 连续位置速度模式(V1.8-1+)
144    ///
145    /// 连续的位置和速度控制,适用于轨迹跟踪等场景。
146    /// 对应 MoveMode::MoveCpv (0x05)。
147    ///
148    /// **注意**:此模式也属于 `Active<PositionMode>` 状态。
149    ContinuousPositionVelocity,
150}
151
152impl From<MotionType> for piper_protocol::feedback::MoveMode {
153    fn from(motion_type: MotionType) -> Self {
154        use piper_protocol::feedback::MoveMode;
155        match motion_type {
156            MotionType::Joint => MoveMode::MoveJ,
157            MotionType::Cartesian => MoveMode::MoveP,
158            MotionType::Linear => MoveMode::MoveL,
159            MotionType::Circular => MoveMode::MoveC,
160            MotionType::ContinuousPositionVelocity => MoveMode::MoveCpv,
161        }
162    }
163}
164
165// ==================== 辅助函数 ====================
166
167/// 解析固件版本字符串
168///
169/// 将 "S-V1.6-3" 格式的字符串解析为 semver::Version
170fn parse_firmware_version(version_str: &str) -> Option<Version> {
171    let version_str = version_str.trim();
172    let version_part = version_str.strip_prefix("S-V")?;
173    let normalized = version_part.replace('-', ".");
174    Version::parse(&normalized).ok()
175}
176
177// ==================== 连接配置 ====================
178
179/// 连接配置
180#[derive(Debug, Clone)]
181pub struct ConnectionConfig {
182    /// CAN 接口名称(如 "can0")
183    pub interface: String,
184    /// 连接超时
185    pub timeout: Duration,
186}
187
188impl Default for ConnectionConfig {
189    fn default() -> Self {
190        ConnectionConfig {
191            interface: "can0".to_string(),
192            timeout: Duration::from_secs(5),
193        }
194    }
195}
196
197/// MIT 模式配置(带 Debounce 参数)
198#[derive(Debug, Clone)]
199pub struct MitModeConfig {
200    /// 使能超时
201    pub timeout: Duration,
202    /// Debounce 阈值:连续 N 次读到 Enabled 才认为成功
203    pub debounce_threshold: usize,
204    /// 轮询间隔
205    pub poll_interval: Duration,
206    /// 运动速度百分比(0-100)
207    ///
208    /// 用于设置 0x151 指令的 Byte 2(speed_percent)。
209    /// 默认值为 100,表示 100% 的运动速度。
210    /// **重要**:不应设为 0,否则某些固件版本可能会锁死关节或报错。
211    /// 虽然在纯 MIT 模式下(0x15A-0x15F),速度通常由控制指令本身携带,
212    /// 但在发送 0x151 切换模式时,speed_percent 可能会作为安全限速或预设速度生效。
213    pub speed_percent: u8,
214}
215
216impl Default for MitModeConfig {
217    fn default() -> Self {
218        Self {
219            timeout: Duration::from_secs(2),
220            debounce_threshold: 3,
221            poll_interval: Duration::from_millis(10),
222            speed_percent: 100,
223        }
224    }
225}
226
227/// 位置模式配置(带 Debounce 参数)
228///
229/// **术语说明**:虽然名为 "PositionMode",但实际支持多种运动规划模式
230/// (关节空间、笛卡尔空间、直线、圆弧等),与 MIT 混合控制模式相对。
231#[derive(Debug, Clone)]
232pub struct PositionModeConfig {
233    /// 使能超时
234    pub timeout: Duration,
235    /// Debounce 阈值:连续 N 次读到 Enabled 才认为成功
236    pub debounce_threshold: usize,
237    /// 轮询间隔
238    pub poll_interval: Duration,
239    /// 运动速度百分比(0-100)
240    ///
241    /// 用于设置 0x151 指令的 Byte 2(speed_percent)。
242    /// 默认值为 50,表示 50% 的运动速度。
243    /// 设置为 0 会导致机械臂不运动。
244    pub speed_percent: u8,
245    /// 安装位置
246    ///
247    /// 用于设置 0x151 指令的 Byte 5(installation_pos)。
248    /// 默认值为 `InstallPosition::Invalid` (0x00),表示无效值(不设置安装位置)。
249    ///
250    /// 根据官方 Python SDK,安装位置选项:
251    /// - `InstallPosition::Invalid` (0x00): 无效值(默认)
252    /// - `InstallPosition::Horizontal` (0x01): 水平正装
253    /// - `InstallPosition::SideLeft` (0x02): 侧装左
254    /// - `InstallPosition::SideRight` (0x03): 侧装右
255    ///
256    /// 注意:此参数基于 V1.5-2 版本后支持,注意接线朝后。
257    pub install_position: InstallPosition,
258    /// 运动类型(新增)
259    ///
260    /// 默认为 `Joint`(关节空间运动),保持向后兼容。
261    ///
262    /// **重要**:必须根据 `motion_type` 使用对应的控制方法:
263    /// - `Joint`: 使用 `command_joint_positions()` 或 `motion_commander().send_position_command()`
264    /// - `Cartesian`/`Linear`: 使用 `command_cartesian_pose()`
265    /// - `Circular`: 使用 `move_circular()` 方法
266    /// - `ContinuousPositionVelocity`: 待实现
267    pub motion_type: MotionType,
268    /// 发送策略(新增)
269    ///
270    /// 默认为 `SendStrategy::Auto`,根据命令类型自动选择:
271    /// - 位置命令:使用 Reliable(队列模式,不丢失)
272    /// - MIT 力控命令:使用 Realtime(邮箱模式,零延迟)
273    ///
274    /// **配置建议**:
275    /// - 轨迹控制:保持 `Auto` 或显式设置为 `Reliable`
276    /// - 高频力控:仅在 MIT 模式下设置为 `Realtime`
277    pub send_strategy: SendStrategy,
278}
279
280impl Default for PositionModeConfig {
281    fn default() -> Self {
282        Self {
283            timeout: Duration::from_secs(2),
284            debounce_threshold: 3,
285            poll_interval: Duration::from_millis(10),
286            speed_percent: 50,                          // 默认 50% 速度
287            install_position: InstallPosition::Invalid, // 默认无效值(不设置安装位置)
288            motion_type: MotionType::Joint,             // ✅ 默认关节模式,向后兼容
289            send_strategy: SendStrategy::Auto,          // 默认自动选择策略
290        }
291    }
292}
293
294/// 发送策略配置
295///
296/// 决定不同类型命令的发送方式:
297/// - **Realtime**:邮箱模式,零延迟,可覆盖(适用于高频力控)
298/// - **Reliable**:队列模式,按顺序,不丢失(适用于轨迹控制)
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
300pub enum SendStrategy {
301    /// 自动选择(推荐)
302    ///
303    /// 根据控制模式自动选择最优策略:
304    /// - MIT 模式:使用 Realtime
305    /// - Position 模式:使用 Reliable
306    #[default]
307    Auto,
308
309    /// 强制实时模式
310    ///
311    /// **使用场景**:超高频力控(>1kHz)
312    /// **风险**:命令可能被覆盖
313    Realtime,
314
315    /// 强制可靠模式
316    ///
317    /// **使用场景**:轨迹控制、序列指令
318    /// **保证**:命令按顺序发送,不丢失
319    /// **配置**:可设置超时和到达确认
320    Reliable {
321        /// 单个命令发送超时(默认 10ms)
322        timeout: Duration,
323
324        /// 是否确认到达(默认 true)
325        ///
326        /// 如果启用,会阻塞等待每个命令完成(增加延迟)
327        /// 如果禁用,只保证进入队列,不保证已发送
328        check_arrival: bool,
329    },
330}
331
332/// 失能配置(带 Debounce 参数)
333#[derive(Debug, Clone)]
334pub struct DisableConfig {
335    /// 失能超时
336    pub timeout: Duration,
337    /// Debounce 阈值:连续 N 次读到 Disabled 才认为成功
338    pub debounce_threshold: usize,
339    /// 轮询间隔
340    pub poll_interval: Duration,
341}
342
343impl Default for DisableConfig {
344    fn default() -> Self {
345        Self {
346            timeout: Duration::from_secs(2),
347            debounce_threshold: 3,
348            poll_interval: Duration::from_millis(10),
349        }
350    }
351}
352
353// ==================== Piper 状态机 ====================
354
355/// Piper 机械臂(Type State Pattern)
356///
357/// 使用泛型参数 `State` 在编译期强制执行正确的状态转换。
358///
359/// # 类型参数
360///
361/// - `State`: 当前状态(`Disconnected`, `Standby`, `Active<Mode>`)
362///
363/// # 内存开销
364///
365/// 大部分状态是零大小类型(ZST),除了 `Active<PositionMode>` 包含 `send_strategy` 配置。
366pub struct Piper<State = Disconnected> {
367    pub(crate) driver: Arc<piper_driver::Piper>,
368    pub(crate) observer: Observer,
369    pub(crate) quirks: DeviceQuirks,
370    pub(crate) _state: State, // 改为直接存储状态(不再使用 PhantomData)
371}
372
373// ==================== Disconnected 状态 ====================
374
375impl Piper<Disconnected> {
376    /// 连接到机械臂
377    ///
378    /// # 参数
379    ///
380    /// - `can_adapter`: 可分离的 CAN 适配器(必须已启动)
381    /// - `config`: 连接配置
382    ///
383    /// # 错误
384    ///
385    /// - `HighLevelError::Infrastructure`: CAN 设备初始化失败
386    /// - `HighLevelError::Timeout`: 等待反馈超时
387    pub fn connect<C>(can_adapter: C, config: ConnectionConfig) -> Result<Piper<Standby>>
388    where
389        C: piper_can::SplittableAdapter + Send + 'static,
390        C::RxAdapter: Send + 'static,
391        C::TxAdapter: Send + 'static,
392    {
393        use piper_driver::Piper as RobotPiper;
394
395        // ✅ 使用 driver 模块创建双线程模式的 Piper
396        let driver = Arc::new(RobotPiper::new_dual_thread(can_adapter, None)?);
397
398        // 等待接收到第一个有效反馈
399        driver.wait_for_feedback(config.timeout)?;
400
401        // 查询固件版本(用于 DeviceQuirks)
402        let _ = driver.query_firmware_version();
403
404        // 创建 DeviceQuirks
405        let quirks = if let Some(version_str) = driver.get_firmware_version() {
406            match parse_firmware_version(&version_str) {
407                Some(version) => DeviceQuirks::from_firmware_version(version),
408                None => {
409                    warn!(
410                        "Failed to parse firmware version '{}'. Using default quirks.",
411                        version_str
412                    );
413                    DeviceQuirks::from_firmware_version(Version::new(1, 9, 0))
414                },
415            }
416        } else {
417            DeviceQuirks::from_firmware_version(Version::new(1, 9, 0))
418        };
419
420        // 创建 Observer(View 模式)
421        let observer = Observer::new(driver.clone());
422
423        Ok(Piper {
424            driver,
425            observer,
426            quirks,
427            _state: Standby,
428        })
429    }
430
431    /// 重新连接到机械臂(用于连接丢失后重新建立连接)
432    ///
433    /// # 参数
434    ///
435    /// - `can_adapter`: 新的 CAN 适配器(或重用现有的)
436    /// - `config`: 连接配置
437    ///
438    /// # 返回
439    ///
440    /// - `Ok(Piper<Standby>)`: 成功重新连接
441    /// - `Err(RobotError)`: 重新连接失败
442    ///
443    /// # 示例
444    ///
445    /// ```rust,ignore
446    /// # use piper_client::state::*;
447    /// # fn example() -> Result<()> {
448    /// let robot = Piper::connect(can_adapter, config)?;
449    /// // ... 连接丢失 ...
450    /// // 在某些情况下,你可能需要手动重新连接
451    /// // 注意:这需要一个处于 Disconnected 状态的 Piper 实例
452    /// # Ok(())
453    /// # }
454    /// ```
455    ///
456    /// **注意**: 由于 `Disconnected` 是 ZST,`self` 参数本质上只是类型标记。
457    /// 此方法与 `connect()` 功能相同,但语义上表示"重新连接"操作。
458    pub fn reconnect<C>(self, can_adapter: C, config: ConnectionConfig) -> Result<Piper<Standby>>
459    where
460        C: piper_can::SplittableAdapter + Send + 'static,
461        C::RxAdapter: Send + 'static,
462        C::TxAdapter: Send + 'static,
463    {
464        info!("Attempting to reconnect to robot");
465
466        // 1. 创建新的 driver 实例
467        use piper_driver::Piper as RobotPiper;
468        let driver = Arc::new(RobotPiper::new_dual_thread(can_adapter, None)?);
469
470        // 2. 等待反馈
471        driver.wait_for_feedback(config.timeout)?;
472
473        // 3. 查询固件版本并创建 DeviceQuirks
474        let _ = driver.query_firmware_version();
475        let quirks = if let Some(version_str) = driver.get_firmware_version() {
476            match parse_firmware_version(&version_str) {
477                Some(version) => DeviceQuirks::from_firmware_version(version),
478                None => {
479                    warn!(
480                        "Failed to parse firmware version '{}'. Using default quirks.",
481                        version_str
482                    );
483                    DeviceQuirks::from_firmware_version(Version::new(1, 9, 0))
484                },
485            }
486        } else {
487            DeviceQuirks::from_firmware_version(Version::new(1, 9, 0))
488        };
489
490        // 4. 创建 observer
491        let observer = Observer::new(driver.clone());
492
493        // 5. 返回到 Standby 状态
494        info!("Reconnection successful");
495        Ok(Piper {
496            driver,
497            observer,
498            quirks,
499            _state: Standby,
500        })
501    }
502}
503
504// ==================== Standby 状态 ====================
505
506impl Piper<Standby> {
507    /// 使能 MIT 模式
508    ///
509    /// # 错误
510    ///
511    /// - `RobotError::Timeout`: 使能超时
512    /// - `RobotError::HardwareError`: 硬件响应异常
513    ///
514    /// # 示例
515    ///
516    /// ```rust,ignore
517    /// # use piper_client::state::*;
518    /// # use piper_client::types::*;
519    /// # fn example(robot: Piper<Standby>) -> Result<()> {
520    /// let robot = robot.enable_mit_mode(MitModeConfig::default())?;
521    /// // 现在可以发送力矩命令
522    /// # Ok(())
523    /// # }
524    /// ```
525    pub fn enable_mit_mode(self, config: MitModeConfig) -> Result<Piper<Active<MitMode>>> {
526        use piper_protocol::control::*;
527        use piper_protocol::feedback::MoveMode;
528
529        debug!("Enabling MIT mode (speed_percent={})", config.speed_percent);
530
531        // === PHASE 1: All operations that can panic ===
532
533        // 1. 发送使能指令
534        let enable_cmd = MotorEnableCommand::enable_all();
535        self.driver.send_reliable(enable_cmd.to_frame())?;
536        debug!("Motor enable command sent");
537
538        // 2. 等待使能完成(带 Debounce)
539        self.wait_for_enabled(
540            config.timeout,
541            config.debounce_threshold,
542            config.poll_interval,
543        )?;
544        debug!("All motors enabled (debounced)");
545
546        // 3. 设置 MIT 模式
547        // ✅ 关键修正:MoveMode 必须设为 MoveM (0x04)
548        // 注意:需要固件版本 >= V1.5-2
549        let control_cmd = ControlModeCommandFrame::new(
550            ControlModeCommand::CanControl,
551            MoveMode::MoveM,      // ✅ 修正:从 MoveP 改为 MoveM
552            config.speed_percent, // ✅ 修正:使用配置的速度(默认100),避免设为0导致锁死
553            MitMode::Mit,         // MIT 控制器 (0xAD)
554            0,
555            InstallPosition::Invalid,
556        );
557        self.driver.send_reliable(control_cmd.to_frame())?;
558        info!("Robot enabled - Active<MitMode>");
559
560        // === PHASE 2: No-panic zone - must not panic after this point ===
561
562        // Use ManuallyDrop to prevent Drop, then extract fields without cloning
563        let this = std::mem::ManuallyDrop::new(self);
564
565        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
566        // We're moving it out of ManuallyDrop, which prevents the original
567        // `self` from being dropped. This is safe because:
568        // 1. `this.driver` is immediately moved into the returned Piper
569        // 2. No other access to `this.driver` occurs after this read
570        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
571        let driver = unsafe { std::ptr::read(&this.driver) };
572
573        // SAFETY: `this.observer` is a valid Arc<Observer>.
574        // Same safety reasoning as driver above.
575        let observer = unsafe { std::ptr::read(&this.observer) };
576
577        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
578        let quirks = this.quirks.clone();
579
580        // `this` is dropped here, but since it's ManuallyDrop,
581        // the inner `self` is NOT dropped, preventing double-disable
582
583        // Construct new state (no Arc ref count increase!)
584        Ok(Piper {
585            driver,
586            observer,
587            quirks,
588            _state: Active(MitMode),
589        })
590    }
591
592    /// 使能位置模式
593    ///
594    /// # 错误
595    ///
596    /// - `RobotError::Timeout`: 使能超时
597    /// - `RobotError::HardwareError`: 硬件响应异常
598    pub fn enable_position_mode(
599        self,
600        config: PositionModeConfig,
601    ) -> Result<Piper<Active<PositionMode>>> {
602        use piper_protocol::control::*;
603        use piper_protocol::feedback::MoveMode;
604
605        debug!(
606            "Enabling Position mode (motion_type={:?}, speed_percent={})",
607            config.motion_type, config.speed_percent
608        );
609
610        // === PHASE 1: All operations that can panic ===
611
612        // 1. 发送使能指令
613        let enable_cmd = MotorEnableCommand::enable_all();
614        self.driver.send_reliable(enable_cmd.to_frame())?;
615        debug!("Motor enable command sent");
616
617        // 2. 等待使能完成(带 Debounce)
618        self.wait_for_enabled(
619            config.timeout,
620            config.debounce_threshold,
621            config.poll_interval,
622        )?;
623        debug!("All motors enabled (debounced)");
624
625        // 3. 设置位置模式
626        // ✅ 修改:使用配置的 motion_type
627        let move_mode: MoveMode = config.motion_type.into();
628
629        let control_cmd = ControlModeCommandFrame::new(
630            ControlModeCommand::CanControl,
631            move_mode, // ✅ 使用配置的运动类型
632            config.speed_percent,
633            MitMode::PositionVelocity,
634            0,
635            config.install_position,
636        );
637        self.driver.send_reliable(control_cmd.to_frame())?;
638        info!("Robot enabled - Active<PositionMode>");
639
640        // === PHASE 2: No-panic zone - must not panic after this point ===
641
642        // Use ManuallyDrop to prevent Drop, then extract fields without cloning
643        let this = std::mem::ManuallyDrop::new(self);
644
645        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
646        // We're moving it out of ManuallyDrop, which prevents the original
647        // `self` from being dropped. This is safe because:
648        // 1. `this.driver` is immediately moved into the returned Piper
649        // 2. No other access to `this.driver` occurs after this read
650        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
651        let driver = unsafe { std::ptr::read(&this.driver) };
652
653        // SAFETY: `this.observer` is a valid Arc<Observer>.
654        // Same safety reasoning as driver above.
655        let observer = unsafe { std::ptr::read(&this.observer) };
656
657        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
658        let quirks = this.quirks.clone();
659
660        // `this` is dropped here, but since it's ManuallyDrop,
661        // the inner `self` is NOT dropped, preventing double-disable
662
663        // Construct new state with send_strategy from config (no Arc ref count increase!)
664        let position_mode = PositionMode::with_strategy(config.send_strategy);
665        Ok(Piper {
666            driver,
667            observer,
668            quirks,
669            _state: Active(position_mode),
670        })
671    }
672
673    /// 使能全部关节并切换到 MIT 模式
674    ///
675    /// 这是 `enable_mit_mode` 的便捷方法,使用默认配置。
676    pub fn enable_all(self) -> Result<Piper<Active<MitMode>>> {
677        self.enable_mit_mode(MitModeConfig::default())
678    }
679
680    /// 使能指定关节(保持 Standby 状态)
681    ///
682    /// # 参数
683    ///
684    /// - `joints`: 要使能的关节列表
685    ///
686    /// # 返回
687    ///
688    /// 返回 `Piper<Standby>`,因为只是部分使能,不转换到 Active 状态。
689    pub fn enable_joints(self, joints: &[Joint]) -> Result<Piper<Standby>> {
690        use piper_protocol::control::MotorEnableCommand;
691
692        for &joint in joints {
693            let cmd = MotorEnableCommand::enable(joint.index() as u8);
694            let frame = cmd.to_frame();
695            self.driver.send_reliable(frame)?;
696        }
697
698        // 不转换状态,仍保持 Standby(部分使能)
699        Ok(self)
700    }
701
702    /// 使能单个关节(保持 Standby 状态)
703    ///
704    /// # 参数
705    ///
706    /// - `joint`: 要使能的关节
707    ///
708    /// # 返回
709    ///
710    /// 返回 `Piper<Standby>`,因为只是部分使能,不转换到 Active 状态。
711    pub fn enable_joint(self, joint: Joint) -> Result<Piper<Standby>> {
712        use piper_protocol::control::MotorEnableCommand;
713
714        let cmd = MotorEnableCommand::enable(joint.index() as u8);
715        let frame = cmd.to_frame();
716        self.driver.send_reliable(frame)?;
717
718        Ok(self)
719    }
720
721    /// 失能全部关节
722    ///
723    /// # 返回
724    ///
725    /// 返回 `()`,因为失能后仍然保持 Standby 状态。
726    pub fn disable_all(self) -> Result<()> {
727        use piper_protocol::control::MotorEnableCommand;
728
729        self.driver.send_reliable(MotorEnableCommand::disable_all().to_frame())?;
730        Ok(())
731    }
732
733    /// 失能指定关节
734    ///
735    /// # 参数
736    ///
737    /// - `joints`: 要失能的关节列表
738    ///
739    /// # 返回
740    ///
741    /// 返回 `()`,因为失能后仍然保持 Standby 状态。
742    pub fn disable_joints(self, joints: &[Joint]) -> Result<()> {
743        use piper_protocol::control::MotorEnableCommand;
744
745        for &joint in joints {
746            let cmd = MotorEnableCommand::disable(joint.index() as u8);
747            let frame = cmd.to_frame();
748            self.driver.send_reliable(frame)?;
749        }
750
751        Ok(())
752    }
753
754    /// 获取 Observer(只读)
755    ///
756    /// 即使在 Standby 状态,也可以读取机械臂状态。
757    pub fn observer(&self) -> &Observer {
758        &self.observer
759    }
760
761    /// 等待机械臂使能完成(带 Debounce 机制)
762    ///
763    /// # 参数
764    ///
765    /// - `timeout`: 超时时间
766    /// - `debounce_threshold`: Debounce 阈值:连续 N 次读到 Enabled 才认为成功
767    /// - `poll_interval`: 轮询间隔
768    ///
769    /// # 错误
770    ///
771    /// - `RobotError::Timeout`: 等待超时
772    /// - `RobotError::HardwareFailure`: 硬件反馈异常
773    ///
774    /// # 阻塞行为
775    ///
776    /// 此方法是**阻塞的 (Blocking)**,会阻塞当前线程直到使能完成或超时。
777    /// 请不要在 `async` 上下文(如 Tokio)中直接调用此方法。
778    fn wait_for_enabled(
779        &self,
780        timeout: Duration,
781        debounce_threshold: usize,
782        poll_interval: Duration,
783    ) -> Result<()> {
784        let start = Instant::now();
785        let mut stable_count = 0;
786
787        loop {
788            // 细粒度超时检查
789            if start.elapsed() > timeout {
790                return Err(RobotError::Timeout {
791                    timeout_ms: timeout.as_millis() as u64,
792                });
793            }
794
795            // ✅ 直接从 Observer 读取状态(View 模式,零延迟)
796            let enabled_mask = self.observer.joint_enabled_mask();
797
798            if enabled_mask == 0b111111 {
799                // ✅ Debounce:连续 N 次读到 Enabled 才认为成功
800                stable_count += 1;
801                if stable_count >= debounce_threshold {
802                    return Ok(());
803                }
804            } else {
805                // 状态跳变,重置计数器
806                stable_count = 0;
807            }
808
809            // 检查剩余时间,避免不必要的 sleep
810            let remaining = timeout.saturating_sub(start.elapsed());
811            let sleep_duration = poll_interval.min(remaining);
812
813            if sleep_duration.is_zero() {
814                return Err(RobotError::Timeout {
815                    timeout_ms: timeout.as_millis() as u64,
816                });
817            }
818
819            std::thread::sleep(sleep_duration);
820        }
821    }
822
823    /// 启动录制(Standby 状态)
824    ///
825    /// # 参数
826    ///
827    /// - `config`: 录制配置
828    ///
829    /// # 返回
830    ///
831    /// 返回 `(Piper<Standby>, RecordingHandle)`
832    ///
833    /// # 示例
834    ///
835    /// ```rust,ignore
836    /// # use piper_client::{PiperBuilder, recording::{RecordingConfig, StopCondition}};
837    /// # fn example() -> Result<()> {
838    /// let builder = PiperBuilder::new()
839    ///     .interface("can0");
840    ///
841    /// let standby = Piper::connect(builder)?;
842    ///
843    /// // 启动录制
844    /// let (standby, handle) = standby.start_recording(RecordingConfig {
845    ///     output_path: "demo.bin".into(),
846    ///     stop_condition: StopCondition::Duration(10),
847    ///     metadata: RecordingMetadata {
848    ///         notes: "Test recording".to_string(),
849    ///         operator: "Alice".to_string(),
850    ///     },
851    /// })?;
852    ///
853    /// // 执行操作(会被录制)
854    /// // ...
855    ///
856    /// // 停止录制并保存
857    /// let _standby = standby.stop_recording(handle)?;
858    /// # Ok(())
859    /// # }
860    /// ```
861    pub fn start_recording(
862        self,
863        config: crate::recording::RecordingConfig,
864    ) -> Result<(Self, crate::recording::RecordingHandle)> {
865        use crate::recording::{RecordingHandle, StopCondition};
866
867        // ✅ 提取 stop_on_id 从 StopCondition
868        let stop_on_id = match &config.stop_condition {
869            StopCondition::OnCanId(id) => Some(*id),
870            _ => None,
871        };
872
873        // ✅ 根据是否需要停止条件选择构造函数
874        let (hook, rx) = if let Some(id) = stop_on_id {
875            tracing::info!("Recording with stop condition: CAN ID 0x{:X}", id);
876            piper_driver::recording::AsyncRecordingHook::with_stop_condition(Some(id))
877        } else {
878            piper_driver::recording::AsyncRecordingHook::new()
879        };
880
881        let dropped = hook.dropped_frames().clone();
882        let counter = hook.frame_counter().clone();
883        let stop_requested = hook.stop_requested().clone(); // ✅ 获取停止标志引用
884
885        // 注册钩子
886        let callback = std::sync::Arc::new(hook) as std::sync::Arc<dyn piper_driver::FrameCallback>;
887        self.driver
888            .hooks()
889            .write()
890            .map_err(|_e| {
891                crate::RobotError::Infrastructure(piper_driver::DriverError::PoisonedLock)
892            })?
893            .add_callback(callback);
894
895        // ✅ 传递 stop_requested(OnCanId 时使用 Driver 层的标志,其他情况使用 None)
896        let handle = RecordingHandle::new(
897            rx,
898            dropped,
899            counter,
900            config.output_path.clone(),
901            std::time::Instant::now(),
902            if stop_on_id.is_some() {
903                Some(stop_requested)
904            } else {
905                None
906            },
907        );
908
909        tracing::info!("Recording started: {:?}", config.output_path);
910
911        Ok((self, handle))
912    }
913
914    /// 停止录制并保存文件
915    ///
916    /// # 参数
917    ///
918    /// - `handle`: 录制句柄
919    ///
920    /// # 返回
921    ///
922    /// 返回 `(Piper<Standby>, 录制统计)`
923    ///
924    /// # 示例
925    ///
926    /// ```rust,ignore
927    /// # use piper_client::{Piper, PiperBuilder};
928    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
929    /// let builder = PiperBuilder::new()
930    ///     .interface("can0");
931    ///
932    /// let standby = Piper::connect(builder)?;
933    ///
934    /// let (standby, handle) = standby.start_recording(config)?;
935    ///
936    /// // ... 等待一段时间 ...
937    ///
938    /// // 停止录制并保存
939    /// let (standby, stats) = standby.stop_recording(handle)?;
940    ///
941    /// println!("录制完成: {} 帧", stats.frame_count);
942    /// # Ok(())
943    /// # }
944    /// ```
945    pub fn stop_recording(
946        self,
947        handle: crate::recording::RecordingHandle,
948    ) -> Result<(Self, crate::recording::RecordingStats)> {
949        use piper_tools::{PiperRecording, TimestampSource, TimestampedFrame};
950
951        // 创建录制对象
952        let mut recording = PiperRecording::new(piper_tools::RecordingMetadata::new(
953            self.driver.interface(),
954            self.driver.bus_speed(),
955        ));
956
957        // 收集所有帧(转换为 piper_tools 格式)
958        let mut frame_count = 0;
959        while let Ok(driver_frame) = handle.receiver().try_recv() {
960            // 转换 piper_driver::TimestampedFrame -> piper_tools::TimestampedFrame
961            let tools_frame = TimestampedFrame::new(
962                driver_frame.timestamp_us,
963                driver_frame.id,
964                driver_frame.data,
965                TimestampSource::Hardware, // 使用硬件时间戳
966            );
967            recording.add_frame(tools_frame);
968            frame_count += 1;
969        }
970
971        // 保存文件
972        recording.save(handle.output_path()).map_err(|e| {
973            crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(e.to_string()))
974        })?;
975
976        let stats = crate::recording::RecordingStats {
977            frame_count,
978            duration: handle.elapsed(),
979            dropped_frames: handle.dropped_count(),
980            output_path: handle.output_path().clone(),
981        };
982
983        tracing::info!(
984            "Recording saved: {} frames, {:.2}s, {} dropped",
985            stats.frame_count,
986            stats.duration.as_secs_f64(),
987            stats.dropped_frames
988        );
989
990        Ok((self, stats))
991    }
992
993    /// 进入回放模式
994    ///
995    /// # 功能
996    ///
997    /// 将 Driver 切换到 Replay 模式,暂停 TX 线程的周期性发送,
998    /// 准备回放预先录制的 CAN 帧。
999    ///
1000    /// # 安全保证
1001    ///
1002    /// - Driver 进入 Replay 模式后,TX 线程暂停周期性发送
1003    /// - 避免双控制流冲突
1004    /// - 只能通过 `replay_recording()` 发送预先录制的帧
1005    ///
1006    /// # ⚠️ 安全警告
1007    ///
1008    /// - 进入 Replay 模式前,应确保机器人处于 Standby 状态
1009    /// - 回放时应遵守安全速度限制(建议 ≤ 2.0x)
1010    /// - 回放过程中应有人工急停准备
1011    ///
1012    /// # 返回
1013    ///
1014    /// 返回 `Piper<ReplayMode>` 实例
1015    ///
1016    /// # 示例
1017    ///
1018    /// ```rust,ignore
1019    /// # use piper_client::{Piper, PiperBuilder};
1020    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1021    /// let builder = PiperBuilder::new()
1022    ///     .interface("can0");
1023    ///
1024    /// let standby = Piper::connect(builder)?;
1025    ///
1026    /// // 进入回放模式
1027    /// let replay = standby.enter_replay_mode()?;
1028    ///
1029    /// // 回放录制(1.0x 速度,原始速度)
1030    /// let standby = replay.replay_recording("recording.bin", 1.0)?;
1031    /// # Ok(())
1032    /// # }
1033    /// ```
1034    pub fn enter_replay_mode(self) -> Result<Piper<ReplayMode>> {
1035        use piper_driver::mode::DriverMode;
1036
1037        // 切换 Driver 到 Replay 模式
1038        self.driver.set_mode(DriverMode::Replay);
1039
1040        tracing::info!("Entered ReplayMode - TX thread periodic sending paused");
1041
1042        // 状态转换:Standby -> ReplayMode
1043        let this = std::mem::ManuallyDrop::new(self);
1044
1045        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>
1046        let driver = unsafe { std::ptr::read(&this.driver) };
1047        let observer = unsafe { std::ptr::read(&this.observer) };
1048        let quirks = this.quirks.clone();
1049
1050        Ok(Piper {
1051            driver,
1052            observer,
1053            quirks,
1054            _state: ReplayMode,
1055        })
1056    }
1057
1058    /// 设置碰撞保护级别
1059    ///
1060    /// 设置6个关节的碰撞防护等级(0~8,等级0代表不检测碰撞)。
1061    ///
1062    /// # 参数
1063    ///
1064    /// - `levels`: 6个关节的碰撞防护等级数组,每个值范围 0~8
1065    ///
1066    /// # 示例
1067    ///
1068    /// ```rust,ignore
1069    /// # use piper_client::PiperBuilder;
1070    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1071    /// let standby = PiperBuilder::new().interface("can0").build()?;
1072    ///
1073    /// // 所有关节设置为等级 5(中等保护)
1074    /// standby.set_collision_protection([5, 5, 5, 5, 5, 5])?;
1075    ///
1076    /// // 为不同关节设置不同等级
1077    /// // J1-J3 基座关节使用较高保护,J4-J6 末端关节使用较低保护
1078    /// standby.set_collision_protection([6, 6, 6, 4, 4, 4])?;
1079    ///
1080    /// // 禁用碰撞保护(谨慎使用)
1081    /// standby.set_collision_protection([0, 0, 0, 0, 0, 0])?;
1082    /// # Ok(())
1083    /// # }
1084    /// ```
1085    pub fn set_collision_protection(&self, levels: [u8; 6]) -> Result<()> {
1086        let raw = RawCommander::new(&self.driver);
1087        raw.set_collision_protection(levels)
1088    }
1089
1090    /// 设置关节零位
1091    ///
1092    /// 设置指定关节的当前位置为零点。
1093    ///
1094    /// **⚠️ 安全警告**:
1095    /// - 设置零位前,确保关节已移动到预期的零点位置
1096    /// - 建议在机械臂安装或重新校准时使用
1097    /// - 设置后应验证关节位置是否正确
1098    ///
1099    /// # 参数
1100    ///
1101    /// - `joints`: 要设置零位的关节数组(0-based,0-5 对应 J1-J6)
1102    ///
1103    /// # 示例
1104    ///
1105    /// ```rust,ignore
1106    /// # use piper_client::PiperBuilder;
1107    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1108    /// let standby = PiperBuilder::new().interface("can0").build()?;
1109    ///
1110    /// // 设置 J1 的当前位置为零点
1111    /// // 注意:确保 J1 已移动到预期的零点位置
1112    /// standby.set_joint_zero_positions(&[0])?;
1113    ///
1114    /// // 设置多个关节的零位
1115    /// standby.set_joint_zero_positions(&[0, 1, 2])?;
1116    ///
1117    /// // 设置所有关节的零位
1118    /// standby.set_joint_zero_positions(&[0, 1, 2, 3, 4, 5])?;
1119    /// # Ok(())
1120    /// # }
1121    /// ```
1122    pub fn set_joint_zero_positions(&self, joints: &[usize]) -> Result<()> {
1123        let raw = RawCommander::new(&self.driver);
1124        raw.set_joint_zero_positions(joints)
1125    }
1126}
1127
1128// ==================== 所有状态共享的辅助方法 ====================
1129
1130impl<State> Piper<State> {
1131    /// 获取固件特性(DeviceQuirks)
1132    ///
1133    /// # 返回
1134    ///
1135    /// 返回当前机械臂的固件特性,包括:
1136    /// - `firmware_version`: 固件版本号
1137    /// - `joint_flip_map`: 关节 flip 标志
1138    /// - `torque_scaling`: 力矩缩放因子
1139    ///
1140    /// # 示例
1141    ///
1142    /// ```rust,ignore
1143    /// # use piper_client::PiperBuilder;
1144    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1145    /// let robot = PiperBuilder::new().interface("can0").build()?;
1146    ///
1147    /// // 获取固件特性
1148    /// let quirks = robot.quirks();
1149    /// println!("Firmware version: {}", quirks.firmware_version);
1150    ///
1151    /// // 在控制循环中使用 quirks
1152    /// for joint in [Joint::J1, Joint::J2, Joint::J3, Joint::J4, Joint::J5, Joint::J6] {
1153    ///     let needs_flip = quirks.needs_flip(joint);
1154    ///     let scaling = quirks.torque_scaling_factor(joint);
1155    ///     println!("Joint {:?}: flip={}, scaling={}", joint, needs_flip, scaling);
1156    /// }
1157    /// # Ok(())
1158    /// # }
1159    /// ```
1160    pub fn quirks(&self) -> DeviceQuirks {
1161        self.quirks.clone()
1162    }
1163
1164    /// 急停:发送急停指令,并转换到 ErrorState(之后不允许继续 command_*)
1165    ///
1166    /// # 设计说明
1167    ///
1168    /// - 急停属于"立即禁止后续指令"的软状态,若依赖硬件反馈会有窗口期
1169    /// - Type State 能在编译期/所有权层面强制禁止继续使用旧实例
1170    /// - 通过消耗 `self` 并返回 `Piper<ErrorState>`,确保无法继续发送控制命令
1171    ///
1172    /// # 参数
1173    ///
1174    /// - `self`: 消耗当前状态实例
1175    ///
1176    /// # 返回
1177    ///
1178    /// - `Ok(Piper<ErrorState>)`: 成功发送急停指令,返回错误状态
1179    /// - `Err(RobotError)`: 发送急停指令失败
1180    ///
1181    /// # 示例
1182    ///
1183    /// ```rust,ignore
1184    /// let robot = robot.enable_all()?;
1185    /// // 发生紧急情况,立即急停
1186    /// let robot = robot.emergency_stop()?;
1187    /// // robot 现在是 Piper<ErrorState>,无法调用 command_torques 等方法
1188    /// // robot.command_torques(...); // ❌ 编译错误
1189    /// ```
1190    pub fn emergency_stop(self) -> Result<Piper<ErrorState>> {
1191        // === PHASE 1: All operations that can panic ===
1192
1193        // 发送急停指令(可靠队列,安全优先)
1194        let raw_commander = RawCommander::new(&self.driver);
1195        raw_commander.emergency_stop()?;
1196
1197        // === PHASE 2: No-panic zone - must not panic after this point ===
1198
1199        // Use ManuallyDrop to prevent Drop, then extract fields without cloning
1200        let this = std::mem::ManuallyDrop::new(self);
1201
1202        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
1203        // We're moving it out of ManuallyDrop, which prevents the original
1204        // `self` from being dropped. This is safe because:
1205        // 1. `this.driver` is immediately moved into the returned Piper
1206        // 2. No other access to `this.driver` occurs after this read
1207        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
1208        let driver = unsafe { std::ptr::read(&this.driver) };
1209
1210        // SAFETY: `this.observer` is a valid Arc<Observer>.
1211        // Same safety reasoning as driver above.
1212        let observer = unsafe { std::ptr::read(&this.observer) };
1213
1214        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
1215        let quirks = this.quirks.clone();
1216
1217        // `this` is dropped here, but since it's ManuallyDrop,
1218        // the inner `self` is NOT dropped, preventing double-disable
1219
1220        // Construct new state (no Arc ref count increase!)
1221        Ok(Piper {
1222            driver,
1223            observer,
1224            quirks,
1225            _state: ErrorState,
1226        })
1227    }
1228
1229    /// 等待机械臂失能完成(带 Debounce 机制)
1230    ///
1231    /// # 参数
1232    ///
1233    /// - `timeout`: 超时时间
1234    /// - `debounce_threshold`: Debounce 阈值:连续 N 次读到 Disabled 才认为成功
1235    /// - `poll_interval`: 轮询间隔
1236    ///
1237    /// # 错误
1238    ///
1239    /// - `RobotError::Timeout`: 等待超时
1240    /// - `RobotError::HardwareFailure`: 硬件反馈异常
1241    ///
1242    /// # 阻塞行为
1243    ///
1244    /// 此方法是**阻塞的 (Blocking)**,会阻塞当前线程直到失能完成或超时。
1245    /// 请不要在 `async` 上下文(如 Tokio)中直接调用此方法。
1246    fn wait_for_disabled(
1247        &self,
1248        timeout: Duration,
1249        debounce_threshold: usize,
1250        poll_interval: Duration,
1251    ) -> Result<()> {
1252        let start = Instant::now();
1253        let mut stable_count = 0;
1254
1255        loop {
1256            if start.elapsed() > timeout {
1257                return Err(RobotError::Timeout {
1258                    timeout_ms: timeout.as_millis() as u64,
1259                });
1260            }
1261
1262            let enabled_mask = self.observer.joint_enabled_mask();
1263
1264            if enabled_mask == 0 {
1265                stable_count += 1;
1266                if stable_count >= debounce_threshold {
1267                    return Ok(());
1268                }
1269            } else {
1270                stable_count = 0;
1271            }
1272
1273            let remaining = timeout.saturating_sub(start.elapsed());
1274            let sleep_duration = poll_interval.min(remaining);
1275
1276            if sleep_duration.is_zero() {
1277                return Err(RobotError::Timeout {
1278                    timeout_ms: timeout.as_millis() as u64,
1279                });
1280            }
1281
1282            std::thread::sleep(sleep_duration);
1283        }
1284    }
1285}
1286
1287// ==================== Active<Mode> 状态(通用方法) ====================
1288
1289impl<M> Piper<Active<M>> {
1290    /// 优雅关闭机械臂
1291    ///
1292    /// 执行完整的关闭序列:
1293    /// 1. 停止运动
1294    /// 2. 等待机器人停止(允许 CAN 命令传播)
1295    /// 3. 失能电机
1296    /// 4. 等待失能确认
1297    /// 5. 返回到 Standby 状态
1298    ///
1299    /// # 示例
1300    ///
1301    /// ```rust,ignore
1302    /// # use piper_client::state::*;
1303    /// # fn example(robot: Piper<Active<MitMode>>) -> Result<()> {
1304    /// let standby_robot = robot.shutdown()?;
1305    /// # Ok(())
1306    /// # }
1307    /// ```
1308    pub fn shutdown(self) -> Result<Piper<Standby>> {
1309        use piper_protocol::control::*;
1310        use std::time::Duration;
1311
1312        info!("Starting graceful robot shutdown");
1313
1314        // === PHASE 1: All operations that can panic ===
1315
1316        // 1. 停止运动
1317        trace!("Sending stop command");
1318        let raw = RawCommander::new(&self.driver);
1319        raw.stop_motion()?;
1320
1321        // 2. 等待机器人停止
1322        //
1323        // ⚠️ INTENTIONAL HARD WAIT:
1324        // 这 100ms 的 sleep 允许 CAN 命令通过总线传播,
1325        // 并让机器人硬件处理停止命令,然后我们才失能电机。
1326        // 在关闭上下文中,硬等待是可接受的,因为:
1327        // - 关闭不是性能关键路径
1328        // - 我们需要确保硬件达到安全状态
1329        // - 替代方案(轮询"已停止"状态)不可靠
1330        trace!("Waiting for robot to stop (allowing CAN command propagation)");
1331        std::thread::sleep(Duration::from_millis(100));
1332
1333        // 3. 失能电机
1334        trace!("Disabling motors");
1335        let disable_cmd = MotorEnableCommand::disable_all();
1336        self.driver.send_reliable(disable_cmd.to_frame())?;
1337
1338        // 4. 等待失能确认
1339        trace!("Waiting for disable confirmation");
1340        self.wait_for_disabled(
1341            Duration::from_secs(1),
1342            1, // debounce_threshold
1343            Duration::from_millis(10),
1344        )?;
1345
1346        info!("Robot shutdown complete");
1347
1348        // === PHASE 2: No-panic zone - must not panic after this point ===
1349
1350        // 使用 ManuallyDrop 模式转换到 Standby 状态
1351        let this = std::mem::ManuallyDrop::new(self);
1352
1353        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
1354        // We're moving it out of ManuallyDrop, which prevents the original
1355        // `self` from being dropped. This is safe because:
1356        // 1. `this.driver` is immediately moved into the returned Piper
1357        // 2. No other access to `this.driver` occurs after this read
1358        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
1359        let driver = unsafe { std::ptr::read(&this.driver) };
1360
1361        // SAFETY: `this.observer` is a valid Arc<Observer>.
1362        // Same safety reasoning as driver above.
1363        let observer = unsafe { std::ptr::read(&this.observer) };
1364
1365        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
1366        let quirks = this.quirks.clone();
1367
1368        Ok(Piper {
1369            driver,
1370            observer,
1371            quirks,
1372            _state: Standby,
1373        })
1374    }
1375
1376    /// 获取诊断接口(逃生舱)
1377    ///
1378    /// # 返回值
1379    ///
1380    /// 返回的 `PiperDiagnostics` 持有 `Arc<piper_driver::Piper>`:
1381    /// - ✅ 独立于当前 `Piper` 实例的生命周期
1382    /// - ✅ 可以安全地移动到其他线程
1383    /// - ✅ 可以在后台线程中长期持有
1384    ///
1385    /// # 使用场景
1386    ///
1387    /// - 自定义诊断工具
1388    /// - 高级抓包和调试
1389    /// - 性能分析和优化
1390    /// - 后台监控线程
1391    ///
1392    /// # 示例
1393    ///
1394    /// ```rust,no_run
1395    /// # use piper_client::{Piper, PiperBuilder};
1396    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1397    /// let robot = PiperBuilder::new()
1398    ///     .interface("can0")
1399    ///     .build()?;
1400    ///
1401    /// let active = robot.enable_position_mode(Default::default())?;
1402    ///
1403    /// // 获取诊断接口
1404    /// let diag = active.diagnostics();
1405    ///
1406    /// // diag 可以安全地移动到其他线程
1407    /// std::thread::spawn(move || {
1408    ///     // 在这里使用 diag...
1409    /// });
1410    ///
1411    /// // active 仍然可以正常使用
1412    /// # Ok(())
1413    /// # }
1414    /// ```
1415    ///
1416    /// # 安全注意事项
1417    ///
1418    /// 诊断接口提供了底层访问能力,使用时需注意:
1419    /// 1. **不要在 Active 状态下发送控制指令帧**(会导致双控制流冲突)
1420    /// 2. **确保回调执行时间 <1μs**(否则会影响实时性能)
1421    /// 3. **注意生命周期**:即使持有 `Arc`,也要确保关联的 `Piper` 实例未被销毁
1422    ///
1423    /// # 参考
1424    ///
1425    /// - [`PiperDiagnostics`](crate::PiperDiagnostics) - 诊断接口文档
1426    /// - [架构分析报告](../../../docs/architecture/piper-driver-client-mixing-analysis.md) - 方案 B 设计
1427    pub fn diagnostics(&self) -> crate::PiperDiagnostics {
1428        crate::PiperDiagnostics::new(self)
1429    }
1430
1431    /// 启动录制(Active 状态)
1432    ///
1433    /// # 参数
1434    ///
1435    /// - `config`: 录制配置
1436    ///
1437    /// # 返回
1438    ///
1439    /// 返回 `(Piper<Active<M>>, RecordingHandle)`
1440    ///
1441    /// # 注意
1442    ///
1443    /// Active 状态下的录制会包含控制指令帧(0x1A1-0x1FF)。
1444    ///
1445    /// # 示例
1446    ///
1447    /// ```rust,ignore
1448    /// # use piper_client::{PiperBuilder, recording::{RecordingConfig, StopCondition}};
1449    /// # fn example() -> Result<()> {
1450    /// let builder = PiperBuilder::new()
1451    ///     .interface("can0");
1452    ///
1453    /// let standby = Piper::connect(builder)?;
1454    /// let active = standby.enable_mit_mode(Default::default())?;
1455    ///
1456    /// // 启动录制(Active 状态)
1457    /// let (active, handle) = active.start_recording(RecordingConfig {
1458    ///     output_path: "demo.bin".into(),
1459    ///     stop_condition: StopCondition::Duration(10),
1460    ///     metadata: RecordingMetadata {
1461    ///         notes: "Test recording".to_string(),
1462    ///         operator: "Alice".to_string(),
1463    ///     },
1464    /// })?;
1465    ///
1466    /// // 执行操作(会被录制,包含控制指令帧)
1467    /// active.command_torques(...)?;
1468    ///
1469    /// // 停止录制并保存
1470    /// let (active, _stats) = active.stop_recording(handle)?;
1471    /// # Ok(())
1472    /// # }
1473    /// ```
1474    pub fn start_recording(
1475        self,
1476        config: crate::recording::RecordingConfig,
1477    ) -> Result<(Self, crate::recording::RecordingHandle)> {
1478        use crate::recording::{RecordingHandle, StopCondition};
1479
1480        // ✅ 提取 stop_on_id 从 StopCondition
1481        let stop_on_id = match &config.stop_condition {
1482            StopCondition::OnCanId(id) => Some(*id),
1483            _ => None,
1484        };
1485
1486        // ✅ 根据是否需要停止条件选择构造函数
1487        let (hook, rx) = if let Some(id) = stop_on_id {
1488            tracing::info!("Recording with stop condition: CAN ID 0x{:X}", id);
1489            piper_driver::recording::AsyncRecordingHook::with_stop_condition(Some(id))
1490        } else {
1491            piper_driver::recording::AsyncRecordingHook::new()
1492        };
1493
1494        let dropped = hook.dropped_frames().clone();
1495        let counter = hook.frame_counter().clone();
1496        let stop_requested = hook.stop_requested().clone(); // ✅ 获取停止标志引用
1497
1498        // 注册钩子
1499        let callback = std::sync::Arc::new(hook) as std::sync::Arc<dyn piper_driver::FrameCallback>;
1500        self.driver
1501            .hooks()
1502            .write()
1503            .map_err(|_e| {
1504                crate::RobotError::Infrastructure(piper_driver::DriverError::PoisonedLock)
1505            })?
1506            .add_callback(callback);
1507
1508        // ✅ 传递 stop_requested(OnCanId 时使用 Driver 层的标志,其他情况使用 None)
1509        let handle = RecordingHandle::new(
1510            rx,
1511            dropped,
1512            counter,
1513            config.output_path.clone(),
1514            std::time::Instant::now(),
1515            if stop_on_id.is_some() {
1516                Some(stop_requested)
1517            } else {
1518                None
1519            },
1520        );
1521
1522        tracing::info!("Recording started (Active): {:?}", config.output_path);
1523
1524        Ok((self, handle))
1525    }
1526
1527    /// 停止录制并保存文件
1528    ///
1529    /// # 参数
1530    ///
1531    /// - `handle`: 录制句柄
1532    ///
1533    /// # 返回
1534    ///
1535    /// 返回 `(Piper<Active<M>>, 录制统计)`
1536    ///
1537    /// # 示例
1538    ///
1539    /// ```rust,ignore
1540    /// # use piper_client::{Piper, PiperBuilder};
1541    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1542    /// let builder = PiperBuilder::new()
1543    ///     .interface("can0");
1544    ///
1545    /// let standby = Piper::connect(builder)?;
1546    /// let active = standby.enable_mit_mode(Default::default())?;
1547    ///
1548    /// let (active, handle) = active.start_recording(config)?;
1549    ///
1550    /// // ... 执行操作 ...
1551    ///
1552    /// // 停止录制并保存
1553    /// let (active, stats) = active.stop_recording(handle)?;
1554    ///
1555    /// println!("录制完成: {} 帧", stats.frame_count);
1556    /// # Ok(())
1557    /// # }
1558    /// ```
1559    pub fn stop_recording(
1560        self,
1561        handle: crate::recording::RecordingHandle,
1562    ) -> Result<(Self, crate::recording::RecordingStats)> {
1563        use piper_tools::{PiperRecording, TimestampSource, TimestampedFrame};
1564
1565        // 创建录制对象
1566        let mut recording = PiperRecording::new(piper_tools::RecordingMetadata::new(
1567            self.driver.interface(),
1568            self.driver.bus_speed(),
1569        ));
1570
1571        // 收集所有帧(转换为 piper_tools 格式)
1572        let mut frame_count = 0;
1573        while let Ok(driver_frame) = handle.receiver().try_recv() {
1574            // 转换 piper_driver::TimestampedFrame -> piper_tools::TimestampedFrame
1575            let tools_frame = TimestampedFrame::new(
1576                driver_frame.timestamp_us,
1577                driver_frame.id,
1578                driver_frame.data,
1579                TimestampSource::Hardware, // 使用硬件时间戳
1580            );
1581            recording.add_frame(tools_frame);
1582            frame_count += 1;
1583        }
1584
1585        // 保存文件
1586        recording.save(handle.output_path()).map_err(|e| {
1587            crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(e.to_string()))
1588        })?;
1589
1590        let stats = crate::recording::RecordingStats {
1591            frame_count,
1592            duration: handle.elapsed(),
1593            dropped_frames: handle.dropped_count(),
1594            output_path: handle.output_path().clone(),
1595        };
1596
1597        tracing::info!(
1598            "Recording saved: {} frames, {:.2}s, {} dropped",
1599            stats.frame_count,
1600            stats.duration.as_secs_f64(),
1601            stats.dropped_frames
1602        );
1603
1604        Ok((self, stats))
1605    }
1606
1607    /// 设置碰撞保护级别
1608    ///
1609    /// 设置6个关节的碰撞防护等级(0~8,等级0代表不检测碰撞)。
1610    ///
1611    /// **注意**:此方法在 Active 状态下也可调用,允许运行时调整碰撞保护级别。
1612    ///
1613    /// # 参数
1614    ///
1615    /// - `levels`: 6个关节的碰撞防护等级数组,每个值范围 0~8
1616    ///
1617    /// # 示例
1618    ///
1619    /// ```rust,ignore
1620    /// # use piper_client::PiperBuilder;
1621    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1622    /// let standby = PiperBuilder::new().interface("can0").build()?;
1623    /// let active = standby.enable_position_mode(Default::default())?;
1624    ///
1625    /// // 运行时提高碰撞保护级别(例如进入精密操作区域)
1626    /// active.set_collision_protection([7, 7, 7, 7, 7, 7])?;
1627    ///
1628    /// // 运行时降低碰撞保护级别(例如需要更大的力矩)
1629    /// active.set_collision_protection([3, 3, 3, 3, 3, 3])?;
1630    /// # Ok(())
1631    /// # }
1632    /// ```
1633    pub fn set_collision_protection(&self, levels: [u8; 6]) -> Result<()> {
1634        let raw = RawCommander::new(&self.driver);
1635        raw.set_collision_protection(levels)
1636    }
1637}
1638
1639// ==================== Active<MitMode> 状态 ====================
1640
1641impl Piper<Active<MitMode>> {
1642    /// 发送 MIT 模式控制指令
1643    ///
1644    /// 对所有关节发送位置、速度、力矩的混合控制指令。
1645    ///
1646    /// # 参数
1647    ///
1648    /// - `positions`: 各关节目标位置(Rad)
1649    /// - `velocities`: 各关节目标速度(rad/s)
1650    /// - `kp`: 位置增益(每个关节独立)
1651    /// - `kd`: 速度增益(每个关节独立)
1652    /// - `torques`: 各关节前馈力矩(NewtonMeter)
1653    ///
1654    /// # 示例
1655    ///
1656    /// ```rust,ignore
1657    /// # use piper_client::state::*;
1658    /// # use piper_client::types::*;
1659    /// # fn example(robot: Piper<Active<MitMode>>) -> Result<()> {
1660    /// let positions = JointArray::from([
1661    ///     Rad(1.0), Rad(0.5), Rad(0.0), Rad(0.0), Rad(0.0), Rad(0.0)
1662    /// ]);
1663    /// let velocities = JointArray::from([0.5, 0.0, 0.0, 0.0, 0.0, 0.0]);
1664    /// let kp = JointArray::from([10.0; 6]);  // 每个关节独立的 kp
1665    /// let kd = JointArray::from([2.0; 6]);   // 每个关节独立的 kd
1666    /// let torques = JointArray::from([
1667    ///     NewtonMeter(5.0), NewtonMeter(0.0), NewtonMeter(0.0),
1668    ///     NewtonMeter(0.0), NewtonMeter(0.0), NewtonMeter(0.0)
1669    /// ]);
1670    /// robot.command_torques(&positions, &velocities, &kp, &kd, &torques)?;
1671    /// # Ok(())
1672    /// # }
1673    /// ```
1674    pub fn command_torques(
1675        &self,
1676        positions: &JointArray<Rad>,
1677        velocities: &JointArray<f64>,
1678        kp: &JointArray<f64>,
1679        kd: &JointArray<f64>,
1680        torques: &JointArray<NewtonMeter>,
1681    ) -> Result<()> {
1682        // ✅ 直接使用 RawCommander,避免创建 MotionCommander
1683        let raw = RawCommander::new(&self.driver);
1684        raw.send_mit_command_batch(positions, velocities, kp, kd, torques)
1685    }
1686
1687    /// 控制夹爪
1688    ///
1689    /// # 参数
1690    ///
1691    /// - `position`: 夹爪开口(0.0-1.0,1.0 = 完全打开)
1692    /// - `effort`: 夹持力度(0.0-1.0,1.0 = 最大力度)
1693    ///
1694    /// # 示例
1695    ///
1696    /// ```rust,ignore
1697    /// # use piper_client::state::*;
1698    /// # use piper_client::types::*;
1699    /// # fn example(robot: Piper<Active<MitMode>>) -> Result<()> {
1700    /// // 完全打开,低力度
1701    /// robot.set_gripper(1.0, 0.3)?;
1702    ///
1703    /// // 夹取物体,中等力度
1704    /// robot.set_gripper(0.2, 0.5)?;
1705    /// # Ok(())
1706    /// # }
1707    /// ```
1708    pub fn set_gripper(&self, position: f64, effort: f64) -> Result<()> {
1709        // 参数验证
1710        if !(0.0..=1.0).contains(&position) {
1711            return Err(RobotError::ConfigError(
1712                "Gripper position must be in [0.0, 1.0]".to_string(),
1713            ));
1714        }
1715        if !(0.0..=1.0).contains(&effort) {
1716            return Err(RobotError::ConfigError(
1717                "Gripper effort must be in [0.0, 1.0]".to_string(),
1718            ));
1719        }
1720
1721        let raw = RawCommander::new(&self.driver);
1722        raw.send_gripper_command(position, effort)
1723    }
1724
1725    /// 打开夹爪
1726    ///
1727    /// 便捷方法,相当于 `set_gripper(1.0, 0.3)`
1728    pub fn open_gripper(&self) -> Result<()> {
1729        self.set_gripper(1.0, 0.3)
1730    }
1731
1732    /// 关闭夹爪
1733    ///
1734    /// 便捷方法,相当于 `set_gripper(0.0, effort)`
1735    pub fn close_gripper(&self, effort: f64) -> Result<()> {
1736        self.set_gripper(0.0, effort)
1737    }
1738
1739    /// 获取 Observer(只读)
1740    pub fn observer(&self) -> &Observer {
1741        &self.observer
1742    }
1743
1744    /// 失能机械臂(返回 Standby 状态)
1745    ///
1746    /// # 参数
1747    ///
1748    /// - `config`: 失能配置(包含超时、Debounce 参数等)
1749    ///
1750    /// # 示例
1751    ///
1752    /// ```rust,ignore
1753    /// # use piper_client::state::*;
1754    /// # fn example(robot: Piper<Active<MitMode>>) -> Result<()> {
1755    /// let robot = robot.disable(DisableConfig::default())?;  // Piper<Standby>
1756    /// # Ok(())
1757    /// # }
1758    /// ```
1759    pub fn disable(self, config: DisableConfig) -> Result<Piper<Standby>> {
1760        use piper_protocol::control::*;
1761
1762        debug!("Disabling robot");
1763
1764        // === PHASE 1: All operations that can panic ===
1765
1766        // 1. 失能机械臂
1767        let disable_cmd = MotorEnableCommand::disable_all();
1768        self.driver.send_reliable(disable_cmd.to_frame())?;
1769        debug!("Motor disable command sent");
1770
1771        // 2. 等待失能完成(带 Debounce)
1772        self.wait_for_disabled(
1773            config.timeout,
1774            config.debounce_threshold,
1775            config.poll_interval,
1776        )?;
1777        info!("Robot disabled - Standby mode");
1778
1779        // === PHASE 2: No-panic zone - must not panic after this point ===
1780
1781        // Use ManuallyDrop to prevent Drop, then extract fields without cloning
1782        let this = std::mem::ManuallyDrop::new(self);
1783
1784        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
1785        // We're moving it out of ManuallyDrop, which prevents the original
1786        // `self` from being dropped. This is safe because:
1787        // 1. `this.driver` is immediately moved into the returned Piper
1788        // 2. No other access to `this.driver` occurs after this read
1789        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
1790        let driver = unsafe { std::ptr::read(&this.driver) };
1791
1792        // SAFETY: `this.observer` is a valid Arc<Observer>.
1793        // Same safety reasoning as driver above.
1794        let observer = unsafe { std::ptr::read(&this.observer) };
1795
1796        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
1797        let quirks = this.quirks.clone();
1798
1799        // `this` is dropped here, but since it's ManuallyDrop,
1800        // the inner `self` is NOT dropped, preventing double-disable
1801
1802        // Construct new state (no Arc ref count increase!)
1803        Ok(Piper {
1804            driver,
1805            observer,
1806            quirks,
1807            _state: Standby,
1808        })
1809    }
1810}
1811
1812// ==================== Active<PositionMode> 状态 ====================
1813
1814impl Piper<Active<PositionMode>> {
1815    /// 发送位置命令(批量发送所有关节)
1816    ///
1817    /// 一次性发送所有 6 个关节的目标位置。
1818    ///
1819    /// # 参数
1820    ///
1821    /// - `positions`: 各关节目标位置
1822    ///
1823    /// # 示例
1824    ///
1825    /// ```rust,ignore
1826    /// # use piper_client::state::*;
1827    /// # use piper_client::types::*;
1828    /// # fn example(robot: Piper<Active<PositionMode>>) -> Result<()> {
1829    /// let positions = JointArray::from([
1830    ///     Rad(1.0), Rad(0.5), Rad(0.0), Rad(0.0), Rad(0.0), Rad(0.0)
1831    /// ]);
1832    /// robot.send_position_command(&positions)?;
1833    /// # Ok(())
1834    /// # }
1835    /// ```
1836    pub fn send_position_command(&self, positions: &JointArray<Rad>) -> Result<()> {
1837        let raw = RawCommander::new(&self.driver);
1838        raw.send_position_command_batch(positions, self._state.0.send_strategy)
1839    }
1840
1841    /// 发送末端位姿命令(笛卡尔空间控制)
1842    ///
1843    /// **前提条件**:必须使用 `MotionType::Cartesian` 或 `MotionType::Linear` 配置。
1844    ///
1845    /// # 参数
1846    ///
1847    /// - `position`: 末端位置(米)
1848    /// - `orientation`: 末端姿态(欧拉角,度)
1849    ///
1850    /// # 示例
1851    ///
1852    /// ```rust,ignore
1853    /// let config = PositionModeConfig {
1854    ///     motion_type: MotionType::Cartesian,
1855    ///     ..Default::default()
1856    /// };
1857    /// let robot = robot.enable_position_mode(config)?;
1858    ///
1859    /// // 发送末端位姿
1860    /// robot.command_cartesian_pose(
1861    ///     Position3D::new(0.3, 0.0, 0.2),           // x, y, z (米)
1862    ///     EulerAngles::new(0.0, 180.0, 0.0),        // roll, pitch, yaw (度)
1863    /// )?;
1864    /// ```
1865    pub fn command_cartesian_pose(
1866        &self,
1867        position: Position3D,
1868        orientation: EulerAngles,
1869    ) -> Result<()> {
1870        let raw = RawCommander::new(&self.driver);
1871        raw.send_end_pose_command(position, orientation, self._state.0.send_strategy)
1872    }
1873
1874    /// 发送直线运动命令
1875    ///
1876    /// 末端沿直线轨迹运动到目标位姿。
1877    ///
1878    /// **前提条件**:必须使用 `MotionType::Linear` 配置。
1879    ///
1880    /// # 参数
1881    ///
1882    /// - `position`: 目标位置(米)
1883    /// - `orientation`: 目标姿态(欧拉角,度)
1884    ///
1885    /// # 示例
1886    ///
1887    /// ```rust,ignore
1888    /// let config = PositionModeConfig {
1889    ///     motion_type: MotionType::Linear,
1890    ///     ..Default::default()
1891    /// };
1892    /// let robot = robot.enable_position_mode(config)?;
1893    ///
1894    /// // 发送直线运动
1895    /// robot.move_linear(
1896    ///     Position3D::new(0.3, 0.0, 0.2),           // x, y, z (米)
1897    ///     EulerAngles::new(0.0, 180.0, 0.0),        // roll, pitch, yaw (度)
1898    /// )?;
1899    /// ```
1900    pub fn move_linear(&self, position: Position3D, orientation: EulerAngles) -> Result<()> {
1901        let raw = RawCommander::new(&self.driver);
1902        raw.send_end_pose_command(position, orientation, self._state.0.send_strategy)
1903    }
1904
1905    /// 发送圆弧运动命令
1906    ///
1907    /// 末端沿圆弧轨迹运动,需要指定中间点和终点。
1908    ///
1909    /// **前提条件**:必须使用 `MotionType::Circular` 配置。
1910    ///
1911    /// # 参数
1912    ///
1913    /// - `via_position`: 中间点位置(米)
1914    /// - `via_orientation`: 中间点姿态(欧拉角,度)
1915    /// - `target_position`: 终点位置(米)
1916    /// - `target_orientation`: 终点姿态(欧拉角,度)
1917    ///
1918    /// # 示例
1919    ///
1920    /// ```rust,ignore
1921    /// let config = PositionModeConfig {
1922    ///     motion_type: MotionType::Circular,
1923    ///     ..Default::default()
1924    /// };
1925    /// let robot = robot.enable_position_mode(config)?;
1926    ///
1927    /// // 发送圆弧运动
1928    /// robot.move_circular(
1929    ///     Position3D::new(0.2, 0.1, 0.2),          // via: 中间点
1930    ///     EulerAngles::new(0.0, 90.0, 0.0),
1931    ///     Position3D::new(0.3, 0.0, 0.2),          // target: 终点
1932    ///     EulerAngles::new(0.0, 180.0, 0.0),
1933    /// )?;
1934    /// ```
1935    pub fn move_circular(
1936        &self,
1937        via_position: Position3D,
1938        via_orientation: EulerAngles,
1939        target_position: Position3D,
1940        target_orientation: EulerAngles,
1941    ) -> Result<()> {
1942        let raw = RawCommander::new(&self.driver);
1943        raw.send_circular_motion(
1944            via_position,
1945            via_orientation,
1946            target_position,
1947            target_orientation,
1948            self._state.0.send_strategy,
1949        )
1950    }
1951    /// 更新单个关节位置(保持其他关节不变)
1952    ///
1953    /// **注意**:此方法会先读取当前所有关节位置,然后只更新目标关节。
1954    /// 如果需要更新多个关节,请使用 `motion_commander().send_position_command_batch()` 方法。
1955    ///
1956    /// **为什么需要读取当前位置?**
1957    /// - 每个 CAN 帧(0x155, 0x156, 0x157)包含两个关节的角度
1958    /// - 如果只发送单个关节,另一个关节会被错误地设置为 0.0
1959    /// - 因此必须先读取当前位置,然后更新目标关节,最后批量发送
1960    ///
1961    /// # 参数
1962    ///
1963    /// - `joint`: 目标关节
1964    /// - `position`: 目标位置(Rad)
1965    ///
1966    /// # 示例
1967    ///
1968    /// ```rust,ignore
1969    /// // 只更新 J1,保持其他关节不变
1970    /// robot.command_position(Joint::J1, Rad(1.57))?;
1971    ///
1972    /// // 更新多个关节,使用批量方法
1973    /// let mut positions = robot.observer().joint_positions();
1974    /// positions[Joint::J1] = Rad(1.0);
1975    /// positions[Joint::J2] = Rad(0.5);
1976    /// robot.motion_commander().send_position_command(&positions)?;
1977    /// ```
1978    pub fn command_position(&self, joint: Joint, position: Rad) -> Result<()> {
1979        // 读取当前所有关节位置
1980        let mut positions = self.observer.joint_positions();
1981        // 只更新目标关节
1982        positions[joint] = position;
1983        // 批量发送所有关节(包括更新的和未更新的)
1984        self.send_position_command(&positions)
1985    }
1986
1987    /// 控制夹爪
1988    ///
1989    /// # 参数
1990    ///
1991    /// - `position`: 夹爪开口(0.0-1.0,1.0 = 完全打开)
1992    /// - `effort`: 夹持力度(0.0-1.0,1.0 = 最大力度)
1993    pub fn set_gripper(&self, position: f64, effort: f64) -> Result<()> {
1994        // 参数验证
1995        if !(0.0..=1.0).contains(&position) {
1996            return Err(RobotError::ConfigError(
1997                "Gripper position must be in [0.0, 1.0]".to_string(),
1998            ));
1999        }
2000        if !(0.0..=1.0).contains(&effort) {
2001            return Err(RobotError::ConfigError(
2002                "Gripper effort must be in [0.0, 1.0]".to_string(),
2003            ));
2004        }
2005
2006        let raw = RawCommander::new(&self.driver);
2007        raw.send_gripper_command(position, effort)
2008    }
2009
2010    /// 打开夹爪
2011    ///
2012    /// 便捷方法,相当于 `set_gripper(1.0, 0.3)`
2013    pub fn open_gripper(&self) -> Result<()> {
2014        self.set_gripper(1.0, 0.3)
2015    }
2016
2017    /// 关闭夹爪
2018    ///
2019    /// 便捷方法,相当于 `set_gripper(0.0, effort)`
2020    pub fn close_gripper(&self, effort: f64) -> Result<()> {
2021        self.set_gripper(0.0, effort)
2022    }
2023
2024    /// 获取 Observer(只读)
2025    pub fn observer(&self) -> &Observer {
2026        &self.observer
2027    }
2028
2029    /// 失能机械臂(返回 Standby 状态)
2030    ///
2031    /// # 参数
2032    ///
2033    /// - `config`: 失能配置(包含超时、Debounce 参数等)
2034    ///
2035    /// # 示例
2036    ///
2037    /// ```rust,ignore
2038    /// let robot = robot.disable(DisableConfig::default())?;
2039    /// ```
2040    pub fn disable(self, config: DisableConfig) -> Result<Piper<Standby>> {
2041        use piper_protocol::control::*;
2042
2043        debug!("Disabling robot");
2044
2045        // === PHASE 1: All operations that can panic ===
2046
2047        // 1. 失能机械臂
2048        let disable_cmd = MotorEnableCommand::disable_all();
2049        self.driver.send_reliable(disable_cmd.to_frame())?;
2050        debug!("Motor disable command sent");
2051
2052        // 2. 等待失能完成(带 Debounce)
2053        self.wait_for_disabled(
2054            config.timeout,
2055            config.debounce_threshold,
2056            config.poll_interval,
2057        )?;
2058        info!("Robot disabled - Standby mode");
2059
2060        // === PHASE 2: No-panic zone - must not panic after this point ===
2061
2062        // Use ManuallyDrop to prevent Drop, then extract fields without cloning
2063        let this = std::mem::ManuallyDrop::new(self);
2064
2065        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>.
2066        // We're moving it out of ManuallyDrop, which prevents the original
2067        // `self` from being dropped. This is safe because:
2068        // 1. `this.driver` is immediately moved into the returned Piper
2069        // 2. No other access to `this.driver` occurs after this read
2070        // 3. The original `self` is never dropped (ManuallyDrop guarantees this)
2071        let driver = unsafe { std::ptr::read(&this.driver) };
2072
2073        // SAFETY: `this.observer` is a valid Arc<Observer>.
2074        // Same safety reasoning as driver above.
2075        let observer = unsafe { std::ptr::read(&this.observer) };
2076
2077        // SAFETY: `this.quirks` is a valid DeviceQuirks (Copy type).
2078        let quirks = this.quirks.clone();
2079
2080        // `this` is dropped here, but since it's ManuallyDrop,
2081        // the inner `self` is NOT dropped, preventing double-disable
2082
2083        // Construct new state (no Arc ref count increase!)
2084        Ok(Piper {
2085            driver,
2086            observer,
2087            quirks,
2088            _state: Standby,
2089        })
2090    }
2091}
2092
2093// ==================== ReplayMode 状态 ====================
2094
2095impl Piper<ReplayMode> {
2096    /// 回放预先录制的 CAN 帧
2097    ///
2098    /// # 参数
2099    ///
2100    /// - `recording_path`: 录制文件路径
2101    /// - `speed_factor`: 速度倍数(1.0 = 原始速度,建议范围 0.1 ~ 2.0)
2102    ///
2103    /// # 功能
2104    ///
2105    /// 从录制文件中读取 CAN 帧序列,并按照原始时间间隔发送。
2106    /// 支持变速回放,但建议速度 ≤ 2.0x 以确保安全。
2107    ///
2108    /// # 安全保证
2109    ///
2110    /// - Driver 处于 Replay 模式,TX 线程暂停周期性发送
2111    /// - 按照录制的时间戳顺序发送帧
2112    /// - 速度限制:建议 ≤ 2.0x,最大值 5.0x
2113    ///
2114    /// # ⚠️ 安全警告
2115    ///
2116    /// - **速度限制**: 建议使用 1.0x(原始速度),最高不超过 2.0x
2117    /// - **人工监控**: 回放过程中应有人工急停准备
2118    /// - **环境确认**: 确保回放环境安全,无人员/障碍物
2119    /// - **文件验证**: 只回放可信来源的录制文件
2120    ///
2121    /// # 返回
2122    ///
2123    /// 返回 `Piper<Standby>`,自动退出 Replay 模式
2124    ///
2125    /// # 示例
2126    ///
2127    /// ```rust,ignore
2128    /// # use piper_client::{Piper, PiperBuilder};
2129    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2130    /// let builder = PiperBuilder::new()
2131    ///     .interface("can0");
2132    ///
2133    /// let standby = Piper::connect(builder)?;
2134    /// let replay = standby.enter_replay_mode()?;
2135    ///
2136    /// // 回放录制(1.0x 速度,原始速度)
2137    /// let standby = replay.replay_recording("recording.bin", 1.0)?;
2138    ///
2139    /// // 回放完成后自动返回 Standby 状态
2140    /// # Ok(())
2141    /// # }
2142    /// ```
2143    pub fn replay_recording(
2144        self,
2145        recording_path: impl AsRef<std::path::Path>,
2146        speed_factor: f64,
2147    ) -> Result<Piper<Standby>> {
2148        use piper_driver::mode::DriverMode;
2149        use piper_tools::PiperRecording;
2150        use std::thread;
2151        use std::time::Duration;
2152
2153        // === 安全检查 ===
2154
2155        // 速度限制验证
2156        const MAX_SPEED_FACTOR: f64 = 5.0;
2157        const RECOMMENDED_SPEED_FACTOR: f64 = 2.0;
2158
2159        if speed_factor <= 0.0 {
2160            return Err(crate::RobotError::InvalidParameter {
2161                param: "speed_factor".to_string(),
2162                reason: "must be positive".to_string(),
2163            });
2164        }
2165
2166        if speed_factor > MAX_SPEED_FACTOR {
2167            return Err(crate::RobotError::InvalidParameter {
2168                param: "speed_factor".to_string(),
2169                reason: format!("exceeds maximum {}", MAX_SPEED_FACTOR),
2170            });
2171        }
2172
2173        if speed_factor > RECOMMENDED_SPEED_FACTOR {
2174            tracing::warn!(
2175                "Speed factor {} exceeds recommended limit {}. \
2176                 Ensure safe environment and emergency stop ready.",
2177                speed_factor,
2178                RECOMMENDED_SPEED_FACTOR
2179            );
2180        }
2181
2182        tracing::info!(
2183            "Starting replay: file={:?}, speed={:.2}x",
2184            recording_path.as_ref(),
2185            speed_factor
2186        );
2187
2188        // === 加载录制文件 ===
2189
2190        let recording = PiperRecording::load(recording_path.as_ref()).map_err(|e| {
2191            crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(e.to_string()))
2192        })?;
2193
2194        if recording.frames.is_empty() {
2195            tracing::warn!("Recording file is empty");
2196            // 即使是空录制,也要正常退出 Replay 模式
2197        } else {
2198            tracing::info!(
2199                "Loaded {} frames, duration: {:.2}s",
2200                recording.frames.len(),
2201                recording.duration().map(|d| d.as_secs_f64()).unwrap_or(0.0)
2202            );
2203        }
2204
2205        // === 回放帧序列 ===
2206
2207        let mut first_frame = true;
2208        let mut last_timestamp_us = 0u64;
2209
2210        for frame in recording.frames {
2211            // 计算时间间隔(考虑速度因子)
2212            let delay_us = if first_frame {
2213                first_frame = false;
2214                0 // 第一帧立即发送
2215            } else {
2216                let elapsed_us = frame.timestamp_us.saturating_sub(last_timestamp_us);
2217                // 应用速度因子:速度越快,延迟越短
2218                (elapsed_us as f64 / speed_factor) as u64
2219            };
2220
2221            last_timestamp_us = frame.timestamp_us;
2222
2223            // 等待适当的延迟
2224            if delay_us > 0 {
2225                let delay = Duration::from_micros(delay_us);
2226                thread::sleep(delay);
2227            }
2228
2229            // 发送帧
2230            let piper_frame = piper_can::PiperFrame {
2231                id: frame.can_id,
2232                data: {
2233                    let mut data = [0u8; 8];
2234                    data.copy_from_slice(&frame.data);
2235                    data
2236                },
2237                len: frame.data.len() as u8,
2238                is_extended: frame.can_id > 0x7FF,
2239                timestamp_us: frame.timestamp_us,
2240            };
2241
2242            self.driver.send_frame(piper_frame).map_err(|e| {
2243                crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(
2244                    e.to_string(),
2245                ))
2246            })?;
2247
2248            // 跟踪进度(每 1000 帧打印一次)
2249            if frame.timestamp_us % 1_000_000 < 1000 {
2250                trace!(
2251                    "Replayed frame at {:.3}s",
2252                    frame.timestamp_us as f64 / 1_000_000.0
2253                );
2254            }
2255        }
2256
2257        tracing::info!("Replay completed successfully");
2258
2259        // === 退出 Replay 模式 ===
2260
2261        // 恢复 Driver 到 Normal 模式
2262        self.driver.set_mode(DriverMode::Normal);
2263
2264        tracing::info!("Exited ReplayMode - TX thread normal operation resumed");
2265
2266        // 状态转换:ReplayMode -> Standby
2267        let this = std::mem::ManuallyDrop::new(self);
2268
2269        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>
2270        let driver = unsafe { std::ptr::read(&this.driver) };
2271        let observer = unsafe { std::ptr::read(&this.observer) };
2272        let quirks = this.quirks.clone();
2273
2274        Ok(Piper {
2275            driver,
2276            observer,
2277            quirks,
2278            _state: Standby,
2279        })
2280    }
2281
2282    /// 回放录制(带取消支持)
2283    ///
2284    /// # 功能
2285    ///
2286    /// 回放预先录制的 CAN 帧序列,支持协作式取消。
2287    ///
2288    /// # 参数
2289    ///
2290    /// * `recording_path` - 录制文件路径
2291    /// * `speed_factor` - 回放速度倍数(1.0 = 原始速度)
2292    /// * `cancel_signal` - 停止信号(`AtomicBool`),检查是否需要取消
2293    ///
2294    /// # 返回
2295    ///
2296    /// * `Ok(Piper<Standby>)` - 回放完成或被取消后返回 Standby 状态
2297    /// * `Err(RobotError)` - 回放失败
2298    ///
2299    /// # 取消机制
2300    ///
2301    /// 此方法支持协作式取消:
2302    /// - 每一帧都会检查 `cancel_signal`
2303    /// - 如果 `cancel_signal` 为 `false`,立即停止回放
2304    /// - 停止后会安全退出回放模式(恢复 Driver 到 Normal 模式)
2305    ///
2306    /// # 示例
2307    ///
2308    /// ```rust,no_run
2309    /// # use piper_client::PiperBuilder;
2310    /// # use std::sync::atomic::{AtomicBool, Ordering};
2311    /// # use std::sync::Arc;
2312    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2313    /// let robot = PiperBuilder::new()
2314    ///     .interface("can0")
2315    ///     .build()?;
2316    ///
2317    /// let replay = robot.enter_replay_mode()?;
2318    ///
2319    /// // 创建停止信号
2320    /// let running = Arc::new(AtomicBool::new(true));
2321    ///
2322    /// // 在另一个线程中设置停止信号(例如 Ctrl-C 处理器)
2323    /// // running.store(false, Ordering::SeqCst);
2324    ///
2325    /// // 回放(可被取消)
2326    /// let standby = replay.replay_recording_with_cancel(
2327    ///     "recording.bin",
2328    ///     1.0,
2329    ///     &running
2330    /// )?;
2331    /// # Ok(())
2332    /// # }
2333    /// ```
2334    pub fn replay_recording_with_cancel(
2335        self,
2336        recording_path: impl AsRef<std::path::Path>,
2337        speed_factor: f64,
2338        cancel_signal: &std::sync::atomic::AtomicBool,
2339    ) -> Result<Piper<Standby>> {
2340        use piper_driver::mode::DriverMode;
2341        use piper_tools::PiperRecording;
2342        use std::thread;
2343        use std::time::Duration;
2344
2345        // === 安全检查 ===
2346
2347        // 速度限制验证
2348        const MAX_SPEED_FACTOR: f64 = 5.0;
2349        const RECOMMENDED_SPEED_FACTOR: f64 = 2.0;
2350
2351        if speed_factor <= 0.0 {
2352            return Err(crate::RobotError::InvalidParameter {
2353                param: "speed_factor".to_string(),
2354                reason: "must be positive".to_string(),
2355            });
2356        }
2357
2358        if speed_factor > MAX_SPEED_FACTOR {
2359            return Err(crate::RobotError::InvalidParameter {
2360                param: "speed_factor".to_string(),
2361                reason: format!("exceeds maximum {}", MAX_SPEED_FACTOR),
2362            });
2363        }
2364
2365        if speed_factor > RECOMMENDED_SPEED_FACTOR {
2366            tracing::warn!(
2367                "Speed factor {} exceeds recommended limit {}. \
2368                 Ensure safe environment and emergency stop ready.",
2369                speed_factor,
2370                RECOMMENDED_SPEED_FACTOR
2371            );
2372        }
2373
2374        tracing::info!(
2375            "Starting replay (with cancel support): file={:?}, speed={:.2}x",
2376            recording_path.as_ref(),
2377            speed_factor
2378        );
2379
2380        // === 加载录制文件 ===
2381
2382        let recording = PiperRecording::load(recording_path.as_ref()).map_err(|e| {
2383            crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(e.to_string()))
2384        })?;
2385
2386        if recording.frames.is_empty() {
2387            tracing::warn!("Recording file is empty");
2388            // 即使是空录制,也要正常退出 Replay 模式
2389        } else {
2390            tracing::info!(
2391                "Loaded {} frames, duration: {:.2}s",
2392                recording.frames.len(),
2393                recording.duration().map(|d| d.as_secs_f64()).unwrap_or(0.0)
2394            );
2395        }
2396
2397        // === 回放帧序列(带取消检查) ===
2398
2399        let mut first_frame = true;
2400        let mut last_timestamp_us = 0u64;
2401
2402        for frame in recording.frames {
2403            // ✅ 每一帧都检查取消信号
2404            if !cancel_signal.load(std::sync::atomic::Ordering::Relaxed) {
2405                tracing::warn!("Replay cancelled by user signal");
2406
2407                // ⚠️ 安全停止:退出前必须恢复 Driver 到 Normal 模式
2408                self.driver.set_mode(DriverMode::Normal);
2409                tracing::info!("Safely exited ReplayMode due to cancellation");
2410
2411                // 状态转换:ReplayMode -> Standby
2412                let this = std::mem::ManuallyDrop::new(self);
2413
2414                // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>
2415                let _driver = unsafe { std::ptr::read(&this.driver) };
2416                let _observer = unsafe { std::ptr::read(&this.observer) };
2417
2418                return Err(crate::RobotError::Infrastructure(
2419                    piper_driver::DriverError::IoThread("Replay cancelled by user".to_string()),
2420                ));
2421            }
2422
2423            // 计算时间间隔(考虑速度因子)
2424            let delay_us = if first_frame {
2425                first_frame = false;
2426                0 // 第一帧立即发送
2427            } else {
2428                let elapsed_us = frame.timestamp_us.saturating_sub(last_timestamp_us);
2429                // 应用速度因子:速度越快,延迟越短
2430                (elapsed_us as f64 / speed_factor) as u64
2431            };
2432
2433            last_timestamp_us = frame.timestamp_us;
2434
2435            // 等待适当的延迟
2436            if delay_us > 0 {
2437                let delay = Duration::from_micros(delay_us);
2438                thread::sleep(delay);
2439            }
2440
2441            // 发送帧
2442            let piper_frame = piper_can::PiperFrame {
2443                id: frame.can_id,
2444                data: {
2445                    let mut data = [0u8; 8];
2446                    data.copy_from_slice(&frame.data);
2447                    data
2448                },
2449                len: frame.data.len() as u8,
2450                is_extended: frame.can_id > 0x7FF,
2451                timestamp_us: frame.timestamp_us,
2452            };
2453
2454            self.driver.send_frame(piper_frame).map_err(|e| {
2455                crate::RobotError::Infrastructure(piper_driver::DriverError::IoThread(
2456                    e.to_string(),
2457                ))
2458            })?;
2459
2460            // 跟踪进度(每 1000 帧打印一次)
2461            if frame.timestamp_us % 1_000_000 < 1000 {
2462                trace!(
2463                    "Replayed frame at {:.3}s",
2464                    frame.timestamp_us as f64 / 1_000_000.0
2465                );
2466            }
2467        }
2468
2469        tracing::info!("Replay completed successfully");
2470
2471        // === 退出 Replay 模式 ===
2472
2473        // 恢复 Driver 到 Normal 模式
2474        self.driver.set_mode(DriverMode::Normal);
2475
2476        tracing::info!("Exited ReplayMode - TX thread normal operation resumed");
2477
2478        // 状态转换:ReplayMode -> Standby
2479        let this = std::mem::ManuallyDrop::new(self);
2480
2481        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>
2482        let driver = unsafe { std::ptr::read(&this.driver) };
2483        let observer = unsafe { std::ptr::read(&this.observer) };
2484        let quirks = this.quirks.clone();
2485
2486        Ok(Piper {
2487            driver,
2488            observer,
2489            quirks,
2490            _state: Standby,
2491        })
2492    }
2493
2494    /// 退出回放模式(返回 Standby)
2495    ///
2496    /// # 功能
2497    ///
2498    /// 提前终止回放,恢复 Driver 到 Normal 模式。
2499    ///
2500    /// # 示例
2501    ///
2502    /// ```rust,ignore
2503    /// # use piper_client::{Piper, PiperBuilder};
2504    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2505    /// let builder = PiperBuilder::new()
2506    ///     .interface("can0");
2507    ///
2508    /// let standby = Piper::connect(builder)?;
2509    /// let replay = standby.enter_replay_mode()?;
2510    ///
2511    /// // 提前退出回放模式
2512    /// let standby = replay.stop_replay()?;
2513    /// # Ok(())
2514    /// # }
2515    /// ```
2516    pub fn stop_replay(self) -> Result<Piper<Standby>> {
2517        use piper_driver::mode::DriverMode;
2518
2519        tracing::info!("Stopping replay - exiting ReplayMode");
2520
2521        // 恢复 Driver 到 Normal 模式
2522        self.driver.set_mode(DriverMode::Normal);
2523
2524        // 状态转换:ReplayMode -> Standby
2525        let this = std::mem::ManuallyDrop::new(self);
2526
2527        // SAFETY: `this.driver` is a valid Arc<piper_driver::Piper>
2528        let driver = unsafe { std::ptr::read(&this.driver) };
2529        let observer = unsafe { std::ptr::read(&this.observer) };
2530        let quirks = this.quirks.clone();
2531
2532        Ok(Piper {
2533            driver,
2534            observer,
2535            quirks,
2536            _state: Standby,
2537        })
2538    }
2539}
2540
2541// ==================== ErrorState 状态 ====================
2542
2543impl Piper<ErrorState> {
2544    /// 获取 Observer(只读)
2545    ///
2546    /// 即使在错误状态,也可以读取机械臂状态。
2547    pub fn observer(&self) -> &Observer {
2548        &self.observer
2549    }
2550
2551    /// 检查是否处于错误状态
2552    ///
2553    /// 此方法总是返回 `true`,因为 `Piper<ErrorState>` 类型本身就表示错误状态。
2554    pub fn is_error_state(&self) -> bool {
2555        true
2556    }
2557
2558    // 注意:ErrorState 不实现任何 command_* 方法,确保无法继续发送控制命令
2559    // 如果需要恢复,可以添加 `recover()` 方法返回 `Piper<Standby>`
2560}
2561
2562// ==================== Drop 实现(安全关闭)====================
2563
2564impl<State> Drop for Piper<State> {
2565    fn drop(&mut self) {
2566        // 尝试失能(忽略错误,因为可能已经失能)
2567        use piper_protocol::control::MotorEnableCommand;
2568        let _ = self.driver.send_reliable(MotorEnableCommand::disable_all().to_frame());
2569
2570        // 注意:HeartbeatManager 已确认不需要(根据 HEARTBEAT_ANALYSIS_REPORT.md)
2571        // StateMonitor 已移除
2572    }
2573}
2574
2575// ==================== 测试 ====================
2576
2577#[cfg(test)]
2578mod tests {
2579    use super::*;
2580
2581    #[test]
2582    fn test_state_type_sizes() {
2583        // 大部分状态类型是 ZST(零大小类型)
2584        assert_eq!(std::mem::size_of::<Disconnected>(), 0);
2585        assert_eq!(std::mem::size_of::<Standby>(), 0);
2586        assert_eq!(std::mem::size_of::<MitMode>(), 0);
2587        assert_eq!(std::mem::size_of::<ErrorState>(), 0);
2588
2589        // Active<MitMode> 包含 MitMode(ZST),所以也是 ZST
2590        assert_eq!(std::mem::size_of::<Active<MitMode>>(), 0);
2591
2592        // PositionMode 包含 SendStrategy,不是 ZST
2593        assert!(std::mem::size_of::<PositionMode>() > 0);
2594        assert!(std::mem::size_of::<Active<PositionMode>>() > 0);
2595    }
2596
2597    #[test]
2598    fn test_mit_mode_config_default() {
2599        let config = MitModeConfig::default();
2600        assert_eq!(config.timeout, Duration::from_secs(2));
2601        assert_eq!(config.debounce_threshold, 3);
2602        assert_eq!(config.poll_interval, Duration::from_millis(10));
2603        assert_eq!(config.speed_percent, 100);
2604    }
2605
2606    #[test]
2607    fn test_motion_type_to_move_mode() {
2608        use piper_protocol::feedback::MoveMode;
2609
2610        assert_eq!(MoveMode::from(MotionType::Joint), MoveMode::MoveJ);
2611        assert_eq!(MoveMode::from(MotionType::Cartesian), MoveMode::MoveP);
2612        assert_eq!(MoveMode::from(MotionType::Linear), MoveMode::MoveL);
2613        assert_eq!(MoveMode::from(MotionType::Circular), MoveMode::MoveC);
2614        assert_eq!(
2615            MoveMode::from(MotionType::ContinuousPositionVelocity),
2616            MoveMode::MoveCpv
2617        );
2618    }
2619
2620    #[test]
2621    fn test_position_mode_config_default() {
2622        let config = PositionModeConfig::default();
2623        assert_eq!(config.motion_type, MotionType::Joint); // 向后兼容
2624        assert_eq!(config.speed_percent, 50);
2625    }
2626
2627    #[test]
2628    fn test_motion_type_default() {
2629        assert_eq!(MotionType::default(), MotionType::Joint);
2630    }
2631
2632    // 注意:集成测试位于 tests/ 目录
2633}