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}