Skip to main content

piper_client/control/
mit_controller.rs

1//! MIT 模式高层控制器
2//!
3//! 简化 MIT 模式的使用,提供:
4//! - 阻塞式位置控制
5//! - 自动状态管理
6//! - 循环锚点机制(消除累积漂移,保证精确 200Hz)
7//! - 容错性增强(允许偶发丢帧)
8//!
9//! # 设计理念
10//!
11//! - **Option 模式**:使用 `Option<Piper<Active<MitMode>>>` 允许安全提取
12//! - **状态流转**:`park()` 返还 `Piper<Standby>`,支持继续使用
13//! - **循环锚点**:使用绝对时间锚点,消除累积漂移
14//! - **容错性**:允许最多连续 5 帧丢帧(25ms @ 200Hz)
15//!
16//! # 使用示例
17//!
18//! ```rust,no_run
19//! use piper_client::control::MitController;
20//! use piper_client::control::MitControllerConfig;
21//! use piper_client::state::*;
22//! use piper_client::types::*;
23//! use std::time::Duration;
24//!
25//! # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
26//! # // 注意:此示例展示了API用法,实际运行需要硬件连接
27//! # // 以下代码为伪代码,仅展示API调用方式
28//! #
29//! # // 假设已经连接并使能
30//! # // let piper = ...;
31//! #
32//! // 创建控制器配置
33//! let config = MitControllerConfig {
34//!     kp_gains: [5.0; 6],  // Nm/rad
35//!     kd_gains: [0.8; 6],  // Nm/(rad/s)
36//!     rest_position: None,
37//!     control_rate: 200.0,
38//! };
39//! # // let mut controller = MitController::new(piper, config);
40//!
41//! // 运动到目标位置(使用锚点机制,保证精确 200Hz)
42//! let target = [
43//!     Rad(0.5), Rad(0.7), Rad(-0.4),
44//!     Rad(0.2), Rad(0.3), Rad(0.5),
45//! ];
46//! # // let reached = controller.move_to_position(
47//! # //     target,
48//! # //     Rad(0.01),
49//! # //     Duration::from_secs(5),
50//! # // )?;
51//!
52//! // 显式停车(返还 Piper<Standby>)
53//! # // let piper_standby = controller.park(DisableConfig::default())?;
54//! # Ok(())
55//! # }
56//! ```
57
58use std::time::{Duration, Instant};
59use tracing::{error, warn};
60
61use crate::observer::Observer;
62use crate::state::machine::{Active, DisableConfig, MitMode, Piper, Standby};
63use crate::types::*;
64
65/// MIT 控制器配置
66#[derive(Debug, Clone)]
67pub struct MitControllerConfig {
68    /// PD 控制器 Kp 增益(每个关节独立)
69    ///
70    /// 单位:Nm/rad
71    /// 典型值:3.0 - 10.0
72    pub kp_gains: [f64; 6],
73
74    /// PD 控制器 Kd 增益(每个关节独立)
75    ///
76    /// 单位:Nm/(rad/s)
77    /// 典型值:0.3 - 1.5
78    pub kd_gains: [f64; 6],
79
80    /// 休息位置(Drop 时自动移动到此位置)
81    ///
82    /// `None` 表示不自动移动(仅失能)
83    pub rest_position: Option<[Rad; 6]>,
84
85    /// 控制循环频率(Hz)
86    ///
87    /// 使用绝对时间锚点机制,实际频率将精确锁定在此值。
88    /// 推荐值:200.0 Hz(与固件更新频率一致)
89    pub control_rate: f64,
90}
91
92impl Default for MitControllerConfig {
93    fn default() -> Self {
94        Self {
95            kp_gains: [5.0; 6],
96            kd_gains: [0.8; 6],
97            rest_position: None,
98            control_rate: 200.0,
99        }
100    }
101}
102
103/// 控制错误
104#[derive(Debug, thiserror::Error)]
105pub enum ControlError {
106    /// Controller was already parked
107    #[error("Controller was already parked, cannot execute commands")]
108    AlreadyParked,
109
110    /// 连续错误超过阈值
111    #[error("Consecutive CAN failures: {count}, last error: {last_error}")]
112    ConsecutiveFailures {
113        count: u32,
114        #[source]
115        last_error: Box<RobotError>,
116    },
117
118    /// 其他机器人错误
119    #[error("Robot error: {0}")]
120    RobotError(#[from] RobotError),
121
122    /// 超时
123    #[error("Operation timeout: {timeout_ms}ms")]
124    Timeout { timeout_ms: u64 },
125}
126
127/// MIT 模式高层控制器
128///
129/// **核心特性**:
130/// - ✅ Option 模式:允许安全提取 Piper
131/// - ✅ 循环锚点:消除累积漂移,保证精确 200Hz
132/// - ✅ 容错性:允许最多连续 5 帧丢帧
133/// - ✅ 状态流转:`park()` 返还 `Piper<Standby>`
134pub struct MitController {
135    /// ⚠️ Option 包装,允许 park() 时安全提取
136    piper: Option<Piper<Active<MitMode>>>,
137
138    /// 状态观察器
139    observer: Observer,
140
141    /// 控制器配置
142    config: MitControllerConfig,
143}
144
145impl MitController {
146    /// 创建新的 MIT 控制器
147    ///
148    /// # 参数
149    ///
150    /// - `piper`: 已使能 MIT 模式的 Piper
151    /// - `config`: 控制器配置
152    ///
153    /// # 示例
154    ///
155    /// ```rust,no_run
156    /// # use piper_client::control::MitController;
157    /// # use piper_client::control::MitControllerConfig;
158    /// # use piper_client::state::*;
159    /// #
160    /// # // 假设已经使能 MIT 模式
161    /// # // let piper = ...;
162    /// #
163    /// let config = MitControllerConfig::default();
164    /// # // let controller = MitController::new(piper, config);
165    /// # // 使用 controller 进行控制...
166    /// ```
167    pub fn new(piper: Piper<Active<MitMode>>, config: MitControllerConfig) -> Self {
168        // 提取 observer(Clone 是轻量的,Arc 指针)
169        let observer = piper.observer().clone();
170
171        Self {
172            piper: Some(piper),
173            observer,
174            config,
175        }
176    }
177
178    /// 阻塞式运动到目标位置
179    ///
180    /// **循环锚点机制**:
181    /// - ✅ 使用绝对时间锚点,消除累积漂移
182    /// - ✅ 无论 CAN 通信耗时多少,频率都锁定在 200Hz
183    /// - ✅ 自动处理任务超时(Overrun)
184    ///
185    /// # 容错性
186    ///
187    /// 控制循环具有**容错性**:允许偶尔的 CAN 通信错误(最多连续 5 帧)。
188    ///
189    /// # 参数
190    ///
191    /// - `target`: 目标关节位置(弧度)
192    /// - `threshold`: 到达阈值(弧度)
193    /// - `timeout`: 超时时间
194    ///
195    /// # 返回
196    ///
197    /// - `Ok(true)`: 到达目标
198    /// - `Ok(false)`: 超时未到达
199    /// - `Err(ControlError::ConsecutiveFailures)`: 连续错误超过阈值
200    ///
201    /// # 示例
202    ///
203    /// ```rust,no_run
204    /// # use piper_client::control::MitController;
205    /// # use piper_client::types::*;
206    /// # use std::time::Duration;
207    /// #
208    /// # let mut controller: MitController = unsafe { std::mem::zeroed() };
209    /// // 运动到目标位置,5秒超时
210    /// # // let reached = controller.move_to_position(
211    /// # //     [Rad(0.5), Rad(0.7), Rad(-0.4), Rad(0.2), Rad(0.3), Rad(0.5)],
212    /// # //     Rad(0.01),  // 1cm 阈值
213    /// # //     Duration::from_secs(5),
214    /// # // )?;
215    /// ```
216    pub fn move_to_position(
217        &mut self,
218        target: [Rad; 6],
219        threshold: Rad,
220        timeout: Duration,
221    ) -> core::result::Result<bool, ControlError> {
222        const MAX_TOLERANCE: u32 = 5; // 允许连续丢 5 帧(25ms @ 200Hz)
223        let mut error_count = 0;
224
225        let start = Instant::now();
226
227        // 使用绝对时间锚点机制消除累积漂移,保证精确的 200Hz 控制频率
228        let period = Duration::from_secs_f64(1.0 / self.config.control_rate); // 5ms @ 200Hz
229        let mut next_tick = Instant::now();
230
231        // 提取 piper 引用(避免每次都解 Option)
232        let _piper = self.piper.as_ref().ok_or(ControlError::AlreadyParked)?;
233
234        while start.elapsed() < timeout {
235            // 1. 设定下一个锚点(绝对时间)
236            next_tick += period;
237
238            // 2. 发送命令 & 检查状态(耗时操作)
239            match self.command_joints(JointArray::from(target), None) {
240                Ok(_) => {
241                    error_count = 0; // 重置计数器
242                },
243                Err(e) => {
244                    error_count += 1;
245                    if error_count > MAX_TOLERANCE {
246                        error!(
247                            "Consecutive CAN failures ({}): {:?}. Aborting motion.",
248                            error_count, e
249                        );
250                        return Err(ControlError::ConsecutiveFailures {
251                            count: error_count,
252                            last_error: Box::new(e),
253                        });
254                    }
255                    warn!(
256                        "Transient CAN error ({}): {:?}, skipping frame. \
257                         This is acceptable as long as errors don't occur consecutively.",
258                        error_count, e
259                    );
260                    // 跳过本帧,继续循环
261                    continue;
262                },
263            }
264
265            // 3. 检查是否到达
266            let current = self.observer.joint_positions();
267            let reached =
268                current.iter().zip(target.iter()).all(|(c, t)| (*c - *t).abs() < threshold);
269
270            if reached {
271                return Ok(true);
272            }
273
274            // 4. 睡眠到下一个锚点(自动扣除耗时操作的时间)
275            let now = Instant::now();
276            if next_tick > now {
277                // 还有剩余时间,睡眠到下一个锚点
278                spin_sleep::sleep(next_tick - now);
279            } else {
280                // ⚠️ 任务超时(Overrun):耗时操作超过了预期周期
281                warn!(
282                    "Control loop overrun: operation took {:?}, \
283                     but next tick was {:?} from now (expected {:?}). \
284                     Skipping sleep to catch up.",
285                    now.duration_since(next_tick - period),
286                    next_tick.duration_since(now),
287                    period
288                );
289                // 追赶锚点:不睡眠,直接进入下一帧
290                // 重置锚点到当前时间,避免后续累积延迟
291                next_tick = now;
292            }
293        }
294
295        Ok(false)
296    }
297
298    /// 发送关节命令(MIT 模式 PD 控制)
299    ///
300    /// 直接传递每个关节的 kp/kd 增益到固件,让固件进行 PD 计算,
301    /// 而不是在软件中计算 PD 输出。这样可以充分利用硬件的实时性能。
302    ///
303    /// # 参数
304    ///
305    /// - `target`: 目标位置
306    /// - `feedforward`: 前馈力矩(可选)
307    fn command_joints(
308        &self,
309        target: JointArray<Rad>,
310        feedforward: Option<JointArray<NewtonMeter>>,
311    ) -> crate::types::Result<()> {
312        // 使用配置中的每个关节独立的 kp/kd 增益
313        let kp = JointArray::from(self.config.kp_gains);
314        let kd = JointArray::from(self.config.kd_gains);
315
316        // 速度参考(目标速度,通常为 0)
317        let velocities = JointArray::from([0.0; 6]);
318
319        // 前馈力矩(可选)
320        let torques = match feedforward {
321            Some(ff) => ff,
322            None => JointArray::from([NewtonMeter(0.0); 6]),
323        };
324
325        // 直接传递 kp/kd 增益到底层,让固件进行 PD 计算
326        let piper =
327            self.piper.as_ref().ok_or(ControlError::AlreadyParked).map_err(|e| match e {
328                ControlError::AlreadyParked => crate::RobotError::InvalidTransition {
329                    from: "Active<MitMode>".to_string(),
330                    to: "Standby".to_string(),
331                },
332                _ => crate::RobotError::StatePoisoned {
333                    reason: format!("Controller error: {:?}", e),
334                },
335            })?;
336        piper.command_torques(&target, &velocities, &kp, &kd, &torques)
337    }
338
339    /// 放松关节(发送零力矩命令)
340    ///
341    /// **注意**:此方法只发送一次零力矩命令,不会阻塞。
342    /// 如果需要让关节自然下垂,应该多次调用或在循环中调用。
343    ///
344    /// # 软降级
345    ///
346    /// 如果发送失败,只记录警告,不返回错误(软降级策略)。
347    ///
348    /// # 示例
349    ///
350    /// ```rust,no_run
351    /// # use piper_client::control::MitController;
352    /// # let mut controller: MitController = unsafe { std::mem::zeroed() };
353    /// controller.relax_joints();
354    /// ```
355    pub fn relax_joints(&mut self) {
356        let zero_pos = JointArray::from([Rad(0.0); 6]);
357        let zero_vel = JointArray::from([0.0; 6]);
358        let zero_kp = JointArray::from([0.0; 6]);
359        let zero_kd = JointArray::from([0.0; 6]);
360        let zero_torques = JointArray::from([NewtonMeter(0.0); 6]);
361
362        let piper = match self.piper.as_ref() {
363            Some(p) => p,
364            None => {
365                warn!("Cannot relax joints: Piper already consumed");
366                return;
367            },
368        };
369
370        if let Err(e) =
371            piper.command_torques(&zero_pos, &zero_vel, &zero_kp, &zero_kd, &zero_torques)
372        {
373            warn!("Failed to relax joints: {:?}. Continuing anyway.", e);
374        }
375    }
376
377    /// 停车(失能并返还 `Piper<Standby>`)
378    ///
379    /// **资源管理**:
380    /// - ✅ 返还 `Piper<Standby>`,支持继续使用
381    /// - ✅ 使用 Option 模式,安全提取 Piper
382    ///
383    /// # 安全保证
384    ///
385    /// **显式调用 park()**(推荐):
386    /// - 提取 Piper,调用 disable(),等待完成
387    /// - 返回 `Piper<Standby>` 可继续使用
388    /// - 不会触发 Piper 的 Drop
389    ///
390    /// **忘记调用 park()**(安全网):
391    /// - MitController 被 drop 时,`Piper<Active>::drop()` 自动触发
392    /// - 发送 disable 命令(不等待确认)
393    /// - 电机被安全失能
394    ///
395    /// # 示例
396    ///
397    /// ```rust,no_run
398    /// # use piper_client::control::MitController;
399    /// # use piper_client::state::*;
400    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
401    /// # let mut controller: MitController = unsafe { std::mem::zeroed() };
402    ///
403    /// // 方式 1:显式停车(推荐)
404    /// let piper_standby = controller.park(DisableConfig::default())?;
405    /// // 现在 piper_standby 可以重新使能或做其他操作
406    ///
407    /// // 方式 2:直接丢弃(触发自动安全失能)
408    /// // drop(controller);  // 自动调用 Piper::drop(),发送 disable 命令
409    /// # Ok(())
410    /// # }
411    /// ```
412    pub fn park(mut self, config: DisableConfig) -> crate::types::Result<Piper<Standby>> {
413        // 安全提取 piper(Option 变为 None)
414        let piper = self.piper.take().ok_or(ControlError::AlreadyParked).map_err(|e| match e {
415            ControlError::AlreadyParked => crate::RobotError::InvalidTransition {
416                from: "Active<MitMode>".to_string(),
417                to: "Standby".to_string(),
418            },
419            _ => crate::RobotError::StatePoisoned {
420                reason: format!("Controller error: {:?}", e),
421            },
422        })?;
423
424        // 失能并返回到 Standby 状态
425        piper.disable(config)
426    }
427
428    /// 获取 Observer(只读)
429    pub fn observer(&self) -> &Observer {
430        &self.observer
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_config_default() {
440        let config = MitControllerConfig::default();
441        assert_eq!(config.kp_gains[0], 5.0);
442        assert_eq!(config.kd_gains[0], 0.8);
443        assert!(config.rest_position.is_none());
444        assert_eq!(config.control_rate, 200.0);
445    }
446}