piper_client/observer.rs
1//! Observer - 状态观察器(View 模式)
2//!
3//! 直接持有 `driver::Piper` 引用,零拷贝、零延迟地读取底层状态。
4//! 不再使用缓存层,避免数据延迟和锁竞争。
5//!
6//! # 设计目标
7//!
8//! - **零延迟**: 直接从 `driver::Piper` 读取,无缓存层
9//! - **零拷贝**: 使用 ArcSwap 的 wait-free 读取
10//! - **类型安全**: 返回强类型单位(Rad, RadPerSecond, NewtonMeter)
11//! - **逻辑一致性**: 提供 `snapshot()` 方法保证时间一致性
12//!
13//! # 使用示例
14//!
15//! ```rust,no_run
16//! # use piper_client::observer::Observer;
17//! # use piper_client::types::*;
18//! # fn example(observer: Observer) -> Result<()> {
19//! // 读取关节位置
20//! let positions = observer.joint_positions();
21//! println!("J1 position: {}", positions[Joint::J1].to_deg());
22//!
23//! // 使用 snapshot 获取时间一致的数据(推荐用于控制算法)
24//! let snapshot = observer.snapshot();
25//! println!("Position: {:?}, Velocity: {:?}", snapshot.position, snapshot.velocity);
26//!
27//! // 克隆 Observer 用于另一个线程
28//! let observer2 = observer.clone();
29//! std::thread::spawn(move || {
30//! loop {
31//! let snapshot = observer2.snapshot();
32//! // ... 监控状态 ...
33//! }
34//! });
35//! # Ok(())
36//! # }
37//! ```
38
39use std::sync::Arc;
40use std::time::Instant;
41
42use crate::types::*;
43use piper_driver::Piper as RobotPiper;
44use piper_protocol::constants::*;
45
46/// 状态观察器(只读接口,View 模式)
47///
48/// 直接持有 `driver::Piper` 引用,零拷贝、零延迟地读取底层状态。
49/// 不再使用缓存层,避免数据延迟和锁竞争。
50#[derive(Clone)]
51pub struct Observer {
52 /// Driver 实例(直接持有,零拷贝)
53 driver: Arc<RobotPiper>,
54}
55
56impl Observer {
57 /// 创建新的 Observer
58 ///
59 /// **注意:** 此方法通常不直接调用,Observer 应该通过 `Piper` 状态机的 `observer()` 方法获取。
60 /// 此方法为 `pub` 以支持内部测试和性能基准测试。
61 ///
62 /// **基准测试:** 为了支持性能基准测试,此方法在 benches 中也可访问。
63 pub fn new(driver: Arc<RobotPiper>) -> Self {
64 Observer { driver }
65 }
66
67 /// 获取运动快照(推荐用于控制算法)
68 ///
69 /// 此方法尽可能快地连续读取多个相关状态,减少时间偏斜。
70 /// 即使底层是分帧更新的,此方法也能提供逻辑上最一致的数据。
71 ///
72 /// # 性能
73 ///
74 /// - 延迟:~20ns(连续调用 3 次 ArcSwap::load)
75 /// - 无锁竞争(ArcSwap 是 Wait-Free 的)
76 ///
77 /// # 推荐使用场景
78 ///
79 /// - 高频控制算法(>100Hz)
80 /// - 阻抗控制、力矩控制等需要时间一致性的算法
81 pub fn snapshot(&self) -> MotionSnapshot {
82 // 在读取之前记录时间戳,更准确地反映"读取动作发生"的时刻
83 let timestamp = Instant::now();
84
85 // 连续读取,减少中间被抢占的概率
86 let pos = self.driver.get_joint_position();
87 let dyn_state = self.driver.get_joint_dynamic();
88
89 MotionSnapshot {
90 position: JointArray::new(pos.joint_pos.map(Rad)),
91 // ✅ 使用类型安全的单位
92 velocity: JointArray::new(dyn_state.joint_vel.map(RadPerSecond)),
93 torque: JointArray::new(dyn_state.get_all_torques().map(NewtonMeter)),
94 timestamp, // 使用读取前的时间戳
95 }
96 }
97
98 /// 获取关节位置(独立读取,可能与其他状态有时间偏斜)
99 ///
100 /// # 注意
101 ///
102 /// 如果需要与其他状态(如速度、力矩)保持时间一致性,
103 /// 请使用 `snapshot()` 方法。
104 pub fn joint_positions(&self) -> JointArray<Rad> {
105 let raw_pos = self.driver.get_joint_position();
106 JointArray::new(raw_pos.joint_pos.map(Rad))
107 }
108
109 /// 获取关节速度(独立读取,可能与其他状态有时间偏斜)
110 ///
111 /// # 注意
112 ///
113 /// 如果需要与其他状态(如位置、力矩)保持时间一致性,
114 /// 请使用 `snapshot()` 方法。
115 ///
116 /// # 返回值
117 ///
118 /// 返回 `JointArray<RadPerSecond>`,保持类型安全。
119 pub fn joint_velocities(&self) -> JointArray<RadPerSecond> {
120 let dyn_state = self.driver.get_joint_dynamic();
121 // ✅ 使用类型安全的单位
122 JointArray::new(dyn_state.joint_vel.map(RadPerSecond))
123 }
124
125 /// 获取关节力矩(独立读取,可能与其他状态有时间偏斜)
126 ///
127 /// # 注意
128 ///
129 /// 如果需要与其他状态(如位置、速度)保持时间一致性,
130 /// 请使用 `snapshot()` 方法。
131 pub fn joint_torques(&self) -> JointArray<NewtonMeter> {
132 let dyn_state = self.driver.get_joint_dynamic();
133 JointArray::new(dyn_state.get_all_torques().map(NewtonMeter))
134 }
135
136 /// 获取夹爪状态
137 pub fn gripper_state(&self) -> GripperState {
138 let gripper = self.driver.get_gripper();
139 GripperState {
140 position: (gripper.travel / GRIPPER_POSITION_SCALE).clamp(0.0, 1.0),
141 effort: (gripper.torque / GRIPPER_FORCE_SCALE).clamp(0.0, 1.0),
142 enabled: gripper.is_enabled(),
143 }
144 }
145
146 /// 获取夹爪位置 (0.0-1.0)
147 pub fn gripper_position(&self) -> f64 {
148 self.gripper_state().position
149 }
150
151 /// 获取夹爪力度 (0.0-1.0)
152 pub fn gripper_effort(&self) -> f64 {
153 self.gripper_state().effort
154 }
155
156 /// 检查夹爪是否使能
157 pub fn is_gripper_enabled(&self) -> bool {
158 self.driver.get_gripper().is_enabled()
159 }
160
161 /// 获取使能掩码(Bit 0-5 对应 J1-J6)
162 pub fn joint_enabled_mask(&self) -> u8 {
163 let driver_state = self.driver.get_joint_driver_low_speed();
164 driver_state.driver_enabled_mask
165 }
166
167 /// 检查指定关节是否使能
168 pub fn is_joint_enabled(&self, joint_index: usize) -> bool {
169 let driver_state = self.driver.get_joint_driver_low_speed();
170 (driver_state.driver_enabled_mask & (1 << joint_index)) != 0
171 }
172
173 /// 获取末端位姿(独立读取,可能与其他状态有时间偏斜)
174 ///
175 /// # 返回值
176 ///
177 /// 返回 `EndPoseState`,包含:
178 /// - `end_pose`: [X, Y, Z, Rx, Ry, Rz]
179 /// - X, Y, Z: 位置(米)
180 /// - Rx, Ry, Rz: 姿态角(弧度)
181 ///
182 /// # 示例
183 ///
184 /// ```rust,no_run
185 /// # use piper_client::observer::Observer;
186 /// # fn example(observer: Observer) {
187 /// let end_pose = observer.end_pose();
188 /// println!("Position: X={:.4}, Y={:.4}, Z={:.4}",
189 /// end_pose.end_pose[0], end_pose.end_pose[1], end_pose.end_pose[2]);
190 /// # }
191 /// ```
192 pub fn end_pose(&self) -> piper_driver::state::EndPoseState {
193 self.driver.get_end_pose()
194 }
195
196 /// 检查是否全部使能
197 pub fn is_all_enabled(&self) -> bool {
198 self.joint_enabled_mask() == 0b111111
199 }
200
201 /// 检查是否全部失能
202 pub fn is_all_disabled(&self) -> bool {
203 self.joint_enabled_mask() == 0
204 }
205
206 /// 检查是否部分使能
207 pub fn is_partially_enabled(&self) -> bool {
208 let mask = self.joint_enabled_mask();
209 mask != 0 && mask != 0b111111
210 }
211
212 /// 检查机械臂是否使能(兼容旧 API)
213 ///
214 /// 如果所有关节都使能,返回 `true`。
215 pub fn is_arm_enabled(&self) -> bool {
216 self.is_all_enabled()
217 }
218
219 /// 获取单个关节的状态
220 ///
221 /// 返回 (position, velocity, torque) 元组。
222 /// **注意**:此方法独立读取,可能与其他状态有时间偏斜。
223 /// 如需时间一致性,请使用 `snapshot()` 方法。
224 pub fn joint_state(&self, joint: Joint) -> (Rad, RadPerSecond, NewtonMeter) {
225 let pos = self.driver.get_joint_position();
226 let dyn_state = self.driver.get_joint_dynamic();
227 (
228 Rad(pos.joint_pos[joint.index()]),
229 RadPerSecond(dyn_state.joint_vel[joint.index()]),
230 NewtonMeter(dyn_state.get_torque(joint.index())),
231 )
232 }
233
234 // ============================================================
235 // 连接监控 API
236 // ============================================================
237
238 /// 检查机器人是否仍在响应
239 ///
240 /// 如果在超时窗口内收到反馈,返回 `true`。
241 /// 这可用于检测机器人是否断电、CAN 线缆断开或固件崩溃。
242 ///
243 /// # 示例
244 ///
245 /// ```rust,ignore
246 /// # use piper_client::observer::Observer;
247 /// # fn example(observer: Observer) {
248 /// if observer.is_connected() {
249 /// println!("Robot is still responding");
250 /// } else {
251 /// println!("Robot connection lost!");
252 /// }
253 /// # }
254 /// ```
255 pub fn is_connected(&self) -> bool {
256 self.driver.is_connected()
257 }
258
259 /// 获取自上次反馈以来的时间
260 ///
261 /// 返回自上次成功处理 CAN 帧以来的时间。
262 /// 可用于连接质量监控或诊断。
263 ///
264 /// # 示例
265 ///
266 /// ```rust,ignore
267 /// # use piper_client::observer::Observer;
268 /// # fn example(observer: Observer) {
269 /// let age = observer.connection_age();
270 /// if age.as_millis() > 100 {
271 /// println!("Connection is degrading: {}ms since last feedback", age.as_millis());
272 /// }
273 /// # }
274 /// ```
275 pub fn connection_age(&self) -> std::time::Duration {
276 self.driver.connection_age()
277 }
278
279 /// 获取碰撞保护级别
280 ///
281 /// 返回6个关节的当前碰撞防护等级(0~8,等级0代表不检测碰撞)。
282 ///
283 /// # 返回
284 ///
285 /// 返回 `[J1, J2, J3, J4, J5, J6]` 的碰撞防护等级
286 ///
287 /// # 示例
288 ///
289 /// ```rust,ignore
290 /// # use piper_client::Observer;
291 /// # fn example(observer: Observer) {
292 /// let levels = observer.collision_protection_levels();
293 /// println!("J1-J6 碰撞保护级别: {:?}", levels);
294 ///
295 /// // 检查某个关节的保护等级
296 /// if levels[0] == 0 {
297 /// println!("J1 未启用碰撞保护");
298 /// }
299 /// # }
300 /// ```
301 pub fn collision_protection_levels(&self) -> [u8; 6] {
302 self.driver
303 .get_collision_protection()
304 .map(|state| state.protection_levels)
305 .unwrap_or([0; 6]) // 如果读取失败,返回默认值
306 }
307}
308
309/// 夹爪状态
310#[derive(Debug, Clone, Copy, PartialEq)]
311pub struct GripperState {
312 /// 位置 (0.0-1.0)
313 pub position: f64,
314 /// 力度 (0.0-1.0)
315 pub effort: f64,
316 /// 使能状态
317 pub enabled: bool,
318}
319
320impl Default for GripperState {
321 fn default() -> Self {
322 GripperState {
323 position: 0.0,
324 effort: 0.0,
325 enabled: false,
326 }
327 }
328}
329
330/// 运动快照(逻辑原子性)
331///
332/// **设计说明:**
333/// - 使用 `#[non_exhaustive]` 允许未来非破坏性地添加字段
334/// - 例如:加速度、数据有效性标志等衍生数据
335#[derive(Debug, Clone)]
336#[non_exhaustive] // ✅ 允许未来非破坏性地添加字段
337pub struct MotionSnapshot {
338 /// 关节位置
339 pub position: JointArray<Rad>,
340 /// 关节速度(✅ 使用类型安全的单位)
341 pub velocity: JointArray<RadPerSecond>,
342 /// 关节力矩
343 pub torque: JointArray<NewtonMeter>,
344 /// 读取时间戳(用于调试)
345 pub timestamp: Instant,
346}
347
348// 确保 Send + Sync
349unsafe impl Send for Observer {}
350unsafe impl Sync for Observer {}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 // 注意:单元测试中创建真实的 robot 实例需要真实的 CAN 适配器
357 // 这里只测试类型和基本逻辑,集成测试会测试完整功能
358
359 // 注意:这些测试需要真实的 robot 实例,应该在集成测试中完成
360 // 这里只测试类型系统和基本逻辑
361
362 #[test]
363 fn test_motion_snapshot_structure() {
364 // 测试 MotionSnapshot 结构
365 let snapshot = MotionSnapshot {
366 position: JointArray::splat(Rad(0.0)),
367 velocity: JointArray::splat(RadPerSecond(0.0)),
368 torque: JointArray::splat(NewtonMeter(0.0)),
369 timestamp: Instant::now(),
370 };
371
372 // ✅ 验证速度单位类型正确
373 let _: RadPerSecond = snapshot.velocity[Joint::J1];
374 let _: JointArray<Rad> = snapshot.position;
375 let _: JointArray<NewtonMeter> = snapshot.torque;
376 }
377
378 #[test]
379 fn test_gripper_state_structure() {
380 let gripper = GripperState {
381 position: 0.5,
382 effort: 0.7,
383 enabled: true,
384 };
385
386 // 验证归一化范围
387 assert!(gripper.position >= 0.0 && gripper.position <= 1.0);
388 assert!(gripper.effort >= 0.0 && gripper.effort <= 1.0);
389 }
390
391 #[test]
392 fn test_send_sync() {
393 fn assert_send_sync<T: Send + Sync>() {}
394 assert_send_sync::<Observer>();
395 assert_send_sync::<GripperState>();
396 assert_send_sync::<MotionSnapshot>();
397 }
398}