refactor: 重构架构并整理仓库
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "imu"
|
||||
description = "IMU data contract"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
|
||||
|
||||
//! IMU 抽象:姿态数据与读取接口(ARCHITECTURE.md §5.1)。
|
||||
//!
|
||||
//! `driver crates::delta10` 为 DETA10 实现 [`Imu`];测试可用
|
||||
//! `motor::testing` 风格的替身。
|
||||
//!
|
||||
//! # 单位与坐标系
|
||||
//!
|
||||
//! 与 ARCHITECTURE.md §5.1 的 [`ImuData`] 一致:
|
||||
//!
|
||||
//! | 字段 | 单位 | 坐标系 |
|
||||
//! | --- | --- | --- |
|
||||
//! | `accel` | m/s² | 机体系(x 前、y 左、z 上) |
|
||||
//! | `gyro` | rad/s | 机体系 |
|
||||
//! | `mag` | µT | 机体系 |
|
||||
//! | `roll` / `pitch` / `yaw` | 度 | 机体 → 世界(yaw 逆时针为正) |
|
||||
//!
|
||||
//! 时间戳为单调时钟纳秒(见 [`crate::time`]),用于和电机反馈对齐。
|
||||
|
||||
/// 一帧 IMU 数据。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct ImuData {
|
||||
/// 加速度(m/s²,机体系 x/y/z)。
|
||||
pub accel: [f32; 3],
|
||||
/// 角速度(rad/s,机体系 x/y/z)。
|
||||
pub gyro: [f32; 3],
|
||||
/// 磁力计(µT,机体系 x/y/z);无磁力计时为 `[0.0; 3]`。
|
||||
pub mag: [f32; 3],
|
||||
/// 横滚角(度)。
|
||||
pub roll: f32,
|
||||
/// 俯仰角(度)。
|
||||
pub pitch: f32,
|
||||
/// 偏航角(度)。
|
||||
pub yaw: f32,
|
||||
/// 单调时钟时间戳(纳秒)。
|
||||
pub timestamp_ns: u64,
|
||||
}
|
||||
|
||||
impl ImuData {
|
||||
/// 所有字段是否有限(NaN / inf 不得进入控制律)。
|
||||
#[must_use]
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.accel.iter().chain(&self.gyro).chain(&self.mag).all(|v| v.is_finite())
|
||||
&& self.roll.is_finite()
|
||||
&& self.pitch.is_finite()
|
||||
&& self.yaw.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
/// IMU 读取错误。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ImuError {
|
||||
/// 链路关闭。
|
||||
Closed,
|
||||
/// 帧 ID / 长度不符(多半是总线接错或协议模式不对)。
|
||||
BadFrame,
|
||||
/// **帧载荷布局尚未确认**(ARCHITECTURE.md 决策点 D1)。
|
||||
///
|
||||
/// 见 `delta10-protocol`:DETA10 手册链接已失效,载荷偏移在
|
||||
/// 拿到数据手册前不得臆造,因此解码器会显式返回本错误。
|
||||
LayoutUnconfirmed,
|
||||
/// 校验和 / 校验失败。
|
||||
Checksum,
|
||||
/// 底层 IO 失败。
|
||||
Io,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ImuError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Closed => write!(f, "IMU 链路已关闭"),
|
||||
Self::BadFrame => write!(f, "IMU 帧 ID 或长度不符"),
|
||||
Self::LayoutUnconfirmed => {
|
||||
write!(f, "IMU 载荷布局未确认(ARCHITECTURE.md 决策点 D1:需 DETA10 数据手册)")
|
||||
}
|
||||
Self::Checksum => write!(f, "IMU 校验失败"),
|
||||
Self::Io => write!(f, "IMU 底层 IO 失败"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for ImuError {}
|
||||
|
||||
/// 硬件无关的 IMU 边界。
|
||||
pub trait Imu {
|
||||
/// 底层错误类型。
|
||||
type Error: core::error::Error + Send + Sync + 'static;
|
||||
|
||||
/// 读取一帧姿态数据(阻塞直到有帧或链路关闭)。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 链路关闭、帧不合法、载荷布局未确认时返回错误。
|
||||
fn read(&mut self) -> impl core::future::Future<Output = Result<ImuData, Self::Error>>;
|
||||
|
||||
/// 非阻塞读取:本轮没有新帧时返回 `Ok(None)`。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 仅在链路确定损坏时返回错误。
|
||||
fn try_read(&mut self) -> Result<Option<ImuData>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_data_is_zero_and_finite() {
|
||||
let data = ImuData::default();
|
||||
assert!((data.yaw).abs() < 1e-6);
|
||||
assert!(data.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_finite_attitude_is_detected() {
|
||||
let data = ImuData { pitch: f32::NAN, ..ImuData::default() };
|
||||
assert!(!data.is_finite());
|
||||
let data = ImuData { gyro: [0.0, f32::INFINITY, 0.0], ..ImuData::default() };
|
||||
assert!(!data.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layout_unconfirmed_error_names_the_decision_point() {
|
||||
let text = ImuError::LayoutUnconfirmed.to_string();
|
||||
assert!(text.contains("D1"), "{text}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user