Skip to main content

piper_client/
diagnostics.rs

1//! 高级诊断接口(逃生舱)
2//!
3//! 本模块提供对底层 driver 的受限访问,用于高级诊断、调试和性能分析场景。
4//!
5//! # 设计理念
6//!
7//! 这是一个**受限的逃生舱**(Escape Hatch),暴露了底层 driver 的部分功能:
8//! - ✅ 可以访问 context.hooks(注册自定义回调)
9//! - ✅ 可以访问 send_frame(发送原始 CAN 帧)
10//! - ❌ 不能直接调用 enable/disable(保持状态机安全)
11//!
12//! # 线程安全
13//!
14//! `PiperDiagnostics` 持有 `Arc<piper_driver::Piper>`,可以安全地跨线程传递:
15//! - ✅ **独立生命周期**:不受原始 `Piper` 实例生命周期约束
16//! - ✅ **跨线程使用**:可以在诊断线程中长期持有
17//! - ✅ **`'static`**:可以存储在 `static` 变量或线程局部存储中
18//!
19//! # 权衡说明
20//!
21//! 由于持有 `Arc` 而非引用,`PiperDiagnostics` **脱离了 TypeState 的直接保护**。
22//! 这是逃生舱设计的**有意权衡**:
23//! - 优点:灵活性极高,适合复杂的诊断场景
24//! - 缺点:无法在编译时保证关联的 `Piper` 仍然处于特定状态
25//! - 缓解:通过运行时检查和文档警告来保证安全
26//!
27//! # 使用场景
28//!
29//! - 自定义诊断工具
30//! - 高级抓包和调试
31//! - 性能分析和优化
32//! - 非标准回放逻辑
33//! - 后台监控线程
34//!
35//! # 安全注意事项
36//!
37//! 此接口提供的底层能力**可能破坏状态机的不变性**。
38//! 使用时需注意:
39//! 1. **不要在 Active 状态下发送控制指令**(会导致双控制流冲突)
40//! 2. **不要手动调用 `disable()`**(应该通过 `Piper` 的 `Drop` 来处理)
41//! 3. **确保回调执行时间 <1μs**(否则会影响实时性能)
42//! 4. **注意生命周期**:即使持有 `Arc`,也要确保关联的 `Piper` 实例未被销毁
43//!
44//! # 示例
45//!
46//! ## 基础使用
47//!
48//! ```rust,no_run
49//! use piper_client::{PiperBuilder};
50//! use piper_driver::recording::AsyncRecordingHook;
51//! use std::sync::Arc;
52//!
53//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
54//! let robot = PiperBuilder::new()
55//!     .interface("can0")
56//!     .build()?;
57//!
58//! let active = robot.enable_position_mode(Default::default())?;
59//!
60//! // 获取诊断接口(持有 Arc,独立生命周期)
61//! let diag = active.diagnostics();
62//!
63//! // 创建自定义录制钩子
64//! let (hook, rx) = AsyncRecordingHook::new();
65//!
66//! // 注册钩子
67//! diag.register_callback(Arc::new(hook))?;
68//!
69//! // 在后台线程处理录制数据
70//! std::thread::spawn(move || {
71//!     while let Ok(frame) = rx.recv() {
72//!         println!("Received CAN frame: 0x{:03X}", frame.id);
73//!     }
74//! });
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! ## 跨线程长期持有
80//!
81//! ```rust,no_run
82//! use piper_client::{PiperBuilder};
83//! use std::thread;
84//!
85//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
86//! let robot = PiperBuilder::new()
87//!     .interface("can0")
88//!     .build()?;
89//!
90//! let active = robot.enable_position_mode(Default::default())?;
91//!
92//! // 获取诊断接口(可以安全地移动到其他线程)
93//! let diag = active.diagnostics();
94//!
95//! // 在另一个线程中长期持有
96//! thread::spawn(move || {
97//!     // diag 在这里完全独立,不受主线程影响
98//!     loop {
99//!         // 执行诊断逻辑...
100//!         std::thread::sleep(std::time::Duration::from_secs(1));
101//!     }
102//! });
103//!
104//! // 主线程可以继续使用 active
105//! // active.send_position_command(&target)?;
106//!
107//! # Ok(())
108//! # }
109//! ```
110
111use piper_can::PiperFrame;
112use piper_driver::FrameCallback;
113use std::sync::Arc;
114
115// 使用 Result 类型别名(使用 crate 的 RobotError)
116pub type Result<T> = std::result::Result<T, crate::RobotError>;
117
118/// 高级诊断接口(逃生舱)
119///
120/// # 持有 Arc 引用计数指针
121///
122/// `PiperDiagnostics` 持有 `Arc<piper_driver::Piper>`:
123/// - 轻量级克隆(仅增加引用计数)
124/// - 独立生命周期,不受原始 `Piper` 实例约束
125/// - 可以安全地跨线程传递
126///
127/// # 参考设计
128///
129/// 这与 Rust 社区成熟库的逃生舱设计一致:
130/// - `reqwest::Client`:持有 `Arc<ClientInner>`,可跨线程
131/// - `tokio::runtime::Handle`:持有 `Arc<Runtime>`,独立生命周期
132pub struct PiperDiagnostics {
133    /// 持有 driver 的 Arc 克隆
134    ///
135    /// **设计权衡**:
136    /// - 使用 `Arc` 而非引用 → 独立生命周期,可跨线程
137    /// - 脱离 TypeState 保护 → 依赖运行时检查
138    driver: Arc<piper_driver::Piper>,
139}
140
141impl PiperDiagnostics {
142    pub(super) fn new<M>(inner: &crate::state::Piper<crate::state::Active<M>>) -> Self {
143        // 克隆 Arc(轻量级操作,仅增加引用计数)
144        Self {
145            driver: Arc::clone(&inner.driver),
146        }
147    }
148
149    /// 注册自定义 FrameCallback
150    ///
151    /// # 性能要求
152    ///
153    /// 回调会在 Driver 层的 RX 线程中执行,必须保证:
154    /// - 执行时间 <1μs
155    /// - 不阻塞
156    /// - 线程安全(Send + Sync)
157    ///
158    /// # 示例
159    ///
160    /// ```rust,no_run
161    /// # use piper_client::PiperBuilder;
162    /// # use piper_driver::recording::AsyncRecordingHook;
163    /// # use std::sync::Arc;
164    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
165    /// let robot = PiperBuilder::new()
166    ///     .interface("can0")
167    ///     .build()?;
168    ///
169    /// let active = robot.enable_position_mode(Default::default())?;
170    /// let diag = active.diagnostics();
171    ///
172    /// let (hook, _rx) = AsyncRecordingHook::new();
173    /// diag.register_callback(Arc::new(hook))?;
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub fn register_callback(&self, callback: Arc<dyn FrameCallback>) -> Result<()> {
178        self.driver
179            .hooks()
180            .write()
181            .map_err(|_e| {
182                crate::RobotError::Infrastructure(piper_driver::DriverError::PoisonedLock)
183            })?
184            .add_callback(callback);
185        Ok(())
186    }
187
188    /// 发送原始 CAN 帧
189    ///
190    /// # ⚠️ 安全警告
191    ///
192    /// **严禁在 Active 状态下发送控制指令帧(0x1A1-0x1FF)**。
193    /// 这会导致与驱动层的周期性发送任务产生双控制流冲突。
194    ///
195    /// # 允许的使用场景
196    ///
197    /// - ✅ Standby 状态:发送配置帧(0x5A1-0x5FF)
198    /// - ✅ ReplayMode:回放预先录制的帧
199    /// - ✅ 调试:发送测试帧
200    ///
201    /// # 禁止的使用场景
202    ///
203    /// - ❌ `Active<MIT>`:发送 0x1A1-0x1A6(位置/速度/力矩指令)
204    /// - ❌ `Active<Position>`: 发送 0x1A1-0x1A6
205    ///
206    /// # 示例
207    ///
208    /// ```rust,no_run
209    /// # use piper_client::PiperBuilder;
210    /// # use piper_can::PiperFrame;
211    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
212    /// let robot = PiperBuilder::new()
213    ///     .interface("can0")
214    ///     .build()?;
215    ///
216    /// let active = robot.enable_position_mode(Default::default())?;
217    /// let diag = active.diagnostics();
218    ///
219    /// // 发送配置帧(安全)
220    /// let frame = PiperFrame {
221    ///     id: 0x5A1,
222    ///     data: [0, 1, 2, 3, 4, 5, 6, 7],
223    ///     len: 8,
224    ///     is_extended: false,
225    ///     timestamp_us: 0,
226    /// };
227    /// diag.send_frame(&frame)?;
228    /// # Ok(())
229    /// # }
230    /// ```
231    pub fn send_frame(&self, frame: &PiperFrame) -> Result<()> {
232        self.driver.send_frame(*frame)?;
233        Ok(())
234    }
235
236    /// 获取 driver 实例的 Arc 克隆(完全访问)
237    ///
238    /// # ⚠️ 高级逃生舱
239    ///
240    /// 此方法提供对底层 `piper_driver::Piper` 的完全访问。
241    /// 仅用于**极端特殊场景**,99% 的情况下应该使用上面的 `register_callback` 和 `send_frame`。
242    ///
243    /// # 使用前提
244    ///
245    /// 你必须完全理解以下文档:
246    /// - `piper_driver` 模块文档
247    /// - 类型状态机设计
248    /// - Driver 层 IO 线程模型
249    ///
250    /// # 安全保证
251    ///
252    /// 返回的是 `Arc` 引用计数指针,而非不可变引用:
253    /// - ✅ 可以跨线程传递
254    /// - ✅ 可以长期持有
255    /// - ❌ 无法直接调用 `enable/disable`(这些方法需要 `&mut self`)
256    ///
257    /// # 示例
258    ///
259    /// ```rust,no_run
260    /// # use piper_client::PiperBuilder;
261    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
262    /// let robot = PiperBuilder::new()
263    ///     .interface("can0")
264    ///     .build()?;
265    ///
266    /// let active = robot.enable_position_mode(Default::default())?;
267    /// let diag = active.diagnostics();
268    ///
269    /// // 获取完全访问权限(仅在极端特殊场景使用)
270    /// let driver = diag.driver();
271    ///
272    /// // 访问底层 hooks
273    /// let hooks = driver.hooks();
274    /// # Ok(())
275    /// # }
276    /// ```
277    pub fn driver(&self) -> Arc<piper_driver::Piper> {
278        Arc::clone(&self.driver)
279    }
280}
281
282// SAFETY: PiperDiagnostics 持有 Arc,可以安全地在线程间传递
283unsafe impl Send for PiperDiagnostics {}
284unsafe impl Sync for PiperDiagnostics {}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_diagnostics_send_sync() {
292        // 确保 PiperDiagnostics 实现 Send + Sync
293        fn assert_send_sync<T: Send + Sync>() {}
294        assert_send_sync::<PiperDiagnostics>();
295    }
296}