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}