feat: 初始化

This commit is contained in:
Horldsence
2026-09-22 20:05:16 +08:00
commit 7d87987e41
54 changed files with 6446 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
//! vcan 集成测试:在真实 Linux 内核 CAN 栈上验证桥接。
//!
//! 仅 Linux 下编译;默认 `#[ignore]`,需要 vcan0(见 justfile `vcan-test`)。
#![cfg(target_os = "linux")]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::time::Duration;
use dji_esc::{CanFrame, CanLink};
use motor_core::Motor;
use socketcan::tokio::CanSocket;
use socketcan::{CanDataFrame, CanFrame as ScFrame, EmbeddedFrame as _, Frame as _, StandardId};
use tokio::time::timeout;
const IFACE: &str = "vcan0";
fn sc_data_frame(id: u16, data: [u8; 8]) -> ScFrame {
ScFrame::Data(CanDataFrame::new(StandardId::new(id).unwrap(), &data).unwrap())
}
#[tokio::test]
#[ignore = "requires vcan0 (see `just vcan-test`)"]
async fn bridge_relays_command_to_bus() {
let link = dji_esc_socketcan::open_link(IFACE).unwrap();
let observer = CanSocket::open(IFACE).unwrap();
let command = CanFrame::new(0x200, [0x04, 0xD2, 0, 0, 0, 0, 0, 0]);
link.outgoing_tx.send(command).await.unwrap();
let got = timeout(Duration::from_secs(2), observer.read_frame())
.await
.expect("timeout waiting for command frame")
.unwrap();
match got {
ScFrame::Data(df) => {
assert_eq!(df.raw_id(), 0x200);
assert_eq!(df.data(), &[0x04, 0xD2, 0, 0, 0, 0, 0, 0]);
}
other => panic!("expected data frame, got {other:?}"),
}
}
#[tokio::test]
#[ignore = "requires vcan0 (see `just vcan-test`)"]
async fn bridge_relays_feedback_from_bus() {
let CanLink { outgoing_tx: _, mut incoming_rx } = dji_esc_socketcan::open_link(IFACE).unwrap();
let simulator = CanSocket::open(IFACE).unwrap();
// 电调 1 反馈:角度 8191、-364rpm、电流 123、45℃。
let feedback = sc_data_frame(0x201, [0x1F, 0xFF, 0xFE, 0x94, 0x00, 0x7B, 45, 0]);
simulator.write_frame(feedback).await.unwrap();
let got = timeout(Duration::from_secs(2), incoming_rx.recv())
.await
.expect("timeout waiting for feedback frame")
.unwrap();
assert_eq!(got.id, 0x201);
assert_eq!(got.data, [0x1F, 0xFF, 0xFE, 0x94, 0x00, 0x7B, 45, 0]);
}
#[tokio::test]
#[ignore = "requires vcan0 (see `just vcan-test`)"]
async fn esc_bus_end_to_end_over_vcan() {
// EscBus ↔ 真实内核 CAN 栈 ↔ 模拟电调(第二个 socket)。
let link = dji_esc_socketcan::open_link(IFACE).unwrap();
let mut esc = EscSim::new(IFACE, &[1]);
let bus = dji_esc::EscBus::spawn(
link,
vec![dji_esc::EscConfig::gm3508("m1", 1, 1.0)],
dji_esc::BusOptions::default(),
)
.unwrap();
bus.motor(1).unwrap().set_velocity(2000.0);
// 仿真 2s:模拟电调按命令回反馈,速度环应收敛。
let motor = bus.motor(1).unwrap().clone();
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
loop {
esc.step_once().await;
if motor.state().rotor_speed_rpm > 1900.0 {
break;
}
assert!(
tokio::time::Instant::now() < deadline,
"not converged: {}",
motor.state().rotor_speed_rpm
);
tokio::time::sleep(Duration::from_millis(5)).await;
}
bus.shutdown().await;
}
/// 最小电调模拟:收命令帧 → 更新转速 → 回反馈帧。
struct EscSim {
socket: CanSocket,
speed_rpm: f32,
angle: u16,
}
impl EscSim {
fn new(iface: &str, _esc_ids: &[u8]) -> Self {
Self { socket: CanSocket::open(iface).unwrap(), speed_rpm: 0.0, angle: 0 }
}
/// 排空当前命令帧并推进模型一步(dt = 5ms)。
async fn step_once(&mut self) {
const DT: f32 = 0.005;
let mut current = 0i16;
while let Ok(frame) = self.socket.try_read_frame() {
if let ScFrame::Data(df) = frame {
if df.raw_id() == 0x200 {
current = i16::from_be_bytes([df.data()[0], df.data()[1]]);
}
}
}
let target = f32::from(current) * 9254.0 / 16384.0;
self.speed_rpm += (target - self.speed_rpm) * (1.0 - (-DT / 0.12).exp());
self.angle =
(u32::from(self.angle) + (self.speed_rpm / 60.0 * 8192.0 * DT) as u32) as u16 % 8192;
let data = {
let mut d = [0u8; 8];
d[0..2].copy_from_slice(&self.angle.to_be_bytes());
d[2..4].copy_from_slice(&(self.speed_rpm as i16).to_be_bytes());
d[4..6].copy_from_slice(&current.to_be_bytes());
d[6] = 30;
d
};
self.socket.write_frame(sc_data_frame(0x201, data)).await.unwrap();
}
}