初步添加了标定支持,需要完善和测试

This commit is contained in:
lennlouisgeek
2026-04-07 01:46:37 +08:00
parent aeb17f194c
commit 770d713d03
19 changed files with 1599 additions and 489 deletions

View File

@@ -1,23 +1,29 @@
use crate::serial_core::calibration_session::*;
use crate::serial_core::codec::Codec;
use crate::serial_core::codecs::tactile_a::TactileACodec;
use crate::serial_core::frame::{FrameHandler, TactileAFrame, TestFrame};
use crate::serial_core::model::{HudChartState, HudPacket};
use crate::serial_core::record::Recording;
use crate::serial_core::record::{FrameTiming, RecordedFrame};
use anyhow::Result;
use log::{debug, info};
use std::fs::File;
use std::future::pending;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tauri::{AppHandle, Emitter};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{self, Duration, MissedTickBehavior};
use tokio_serial::SerialStream;
use tokio_util::sync::CancellationToken;
use std::future::pending;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use log::{info, debug};
use crate::serial_core::record::{FrameTiming, RecordedFrame};
const DEFAULT_TACTILE_COLS: usize = 7;
const DEFAULT_TACTILE_ROWS: usize = 12;
const DEFAULT_TACTILE_POLL_INTERVAL_MS: u64 = 10;
const DEFAULT_TACTILE_REPLY_TIMEOUT_MS: u64 = 140;
use crate::serial_core::codecs::tactile_a::TactileAHandler;
pub enum PollMode<F> {
Disable,
Enabled(Box<dyn PollRequester<F>>)
Enabled(Box<dyn PollRequester<F>>),
}
pub trait SerialFrame: Clone + Send + 'static {
@@ -169,11 +175,19 @@ where
F: SerialFrame,
C: Codec<F> + Send + 'static,
H: FrameHandler<F, T> + Send + 'static,
T: Into<i32>
T: Into<i32>,
{
run_serial_with_poll(
app, port, codec, handler, session_started_at, recording, cancel, PollMode::Disable
).await
app,
port,
codec,
handler,
session_started_at,
recording,
cancel,
PollMode::Disable,
)
.await
}
pub async fn run_serial_with_poll<C, H, T, F>(
@@ -184,7 +198,7 @@ pub async fn run_serial_with_poll<C, H, T, F>(
session_started_at: Instant,
recording: Arc<Mutex<Recording<F>>>,
cancel: CancellationToken,
poll_mode: PollMode<F>
poll_mode: PollMode<F>,
) -> Result<()>
where
F: SerialFrame,
@@ -192,15 +206,13 @@ where
H: FrameHandler<F, T> + Send + 'static,
T: Into<i32>,
{
info!("run_serial_with_poll");
let mut requester = match poll_mode {
PollMode::Disable => None,
PollMode::Enabled(r) => Some(r),
};
let mut poll_interval = requester
.as_ref()
.and_then(|r| r.poll_interval())
.map(|d| {
let mut poll_interval = requester.as_ref().and_then(|r| r.poll_interval()).map(|d| {
let mut it = time::interval(d);
it.set_missed_tick_behavior(MissedTickBehavior::Skip);
it
@@ -211,7 +223,6 @@ where
let mut prune_interval = time::interval(Duration::from_millis(450));
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
loop {
tokio::select! {
_ = cancel.cancelled() => break,
@@ -227,6 +238,7 @@ where
if r.should_request() {
if let Some(req) = r.next_request()? {
let bytes = codec.encode(&req)?;
debug!("send {:02X?}", bytes);
port.write_all(&bytes).await?;
}
}
@@ -281,3 +293,155 @@ where
}
Ok(())
}
// 在 src-tauri/src/serial_core/serial.rs 中添加
pub async fn run_serial_with_calibration(
app: AppHandle,
mut port: SerialStream,
session_started_at: Instant,
cancel: CancellationToken,
mut calibration_session: CalibrationSession,
) -> Result<()> {
let mut codec = TactileACodec::new(DEFAULT_TACTILE_COLS, DEFAULT_TACTILE_ROWS);
let mut handler = TactileAHandler;
let mut requester = TactileAPollRequester::new(
Duration::from_millis(DEFAULT_TACTILE_POLL_INTERVAL_MS),
DEFAULT_TACTILE_COLS,
DEFAULT_TACTILE_ROWS,
Duration::from_millis(DEFAULT_TACTILE_REPLY_TIMEOUT_MS),
);
let mut poll_interval = time::interval(Duration::from_millis(DEFAULT_TACTILE_POLL_INTERVAL_MS));
poll_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
let mut buffer = [0u8; 1024];
let recording = Arc::new(Mutex::new(Recording::new()));
let mut chart_state = HudChartState::new();
let mut prune_interval = time::interval(Duration::from_millis(450));
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
loop {
tokio::select! {
_ = cancel.cancelled() => break,
_ = poll_interval.tick() => {
if requester.should_request() {
if let Some(req) = requester.next_request()? {
let bytes = codec.encode(&req)?;
port.write_all(&bytes).await?;
}
}
}
_ = prune_interval.tick() => {
if let Some(packet) = chart_state.prune_stale() {
app.emit("hud_stream", packet)?;
}
}
read_result = port.read(&mut buffer) => {
let n = read_result?;
if n == 0 {
tokio::task::yield_now().await;
continue;
}
let frames = codec.decode(&buffer[..n], session_started_at)?;
for frame in frames {
requester.on_rx_frame(&frame);
let decode_res = handler
.on_frame(&frame)
.await?
.map(|vals| vals.into_iter().map(Into::into).collect::<Vec<i32>>());
let recorded_frame = RecordedFrame {
timing: FrameTiming { pts_ms: None, dts_ms: frame.dts_ms() },
frame: frame.clone(),
};
{
let mut record = recording
.lock()
.map_err(|_| anyhow::anyhow!("recording state poisoned"))?;
record.push(recorded_frame.clone());
}
let display_values = if let Some(vals) = decode_res.as_ref() {
let summary = vals.iter().copied().sum::<i32>();
chart_state.record_summary(summary as f32);
chart_state.record_pressure_matrix(vals.as_slice());
Some(vec![summary])
} else {
None
};
if let Some(packet) = frame.to_hud_packet(&mut chart_state, display_values.as_deref()) {
app.emit("hud_stream", packet)?;
}
// 检查是否达到目标帧数
let should_export = calibration_session.add_frame(recorded_frame);
if should_export {
// 导出数据
export_calibration_data(&app, &calibration_session, &recording).await?;
// 发送语音提示(这里用事件代替,前端可以播放语音)
app.emit("calibration_voice_prompt", "请添加配重")?;
// 更新状态
calibration_session.export_completed();
if let Ok(mut record) = recording.lock() {
record.frames.clear();
}
}
}
}
}
}
Ok(())
}
use crate::serial_core::codecs::tactile_a::TactileACsvExporter;
use crate::serial_core::record::write_csv;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::Manager;
async fn export_calibration_data(
app: &AppHandle,
calibration_session: &CalibrationSession,
recording: &Arc<Mutex<Recording<TactileAFrame>>>,
) -> Result<()> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.unwrap_or_default();
let filename = format!(
"calibration_round{}_{}.csv",
calibration_session.current_round, timestamp
);
// 创建导出目录
let mut output_dir = match app.path().desktop_dir() {
Ok(path) => path,
Err(_) => std::env::current_dir()?,
};
output_dir.push("calibration_data");
std::fs::create_dir_all(&output_dir)?;
let output_path = output_dir.join(&filename);
let file = File::create(&output_path)?;
// 使用现有的导出逻辑
let recording_lock = recording
.lock()
.map_err(|_| anyhow::anyhow!("Recording poisoned"))?;
let exporter = TactileACsvExporter::with_coarse_calibration(
DEFAULT_TACTILE_COLS * DEFAULT_TACTILE_ROWS,
7 * 12 * 10,
);
write_csv(&recording_lock, &exporter, file)?;
info!("标定数据已导出到: {}", output_path.display());
Ok(())
}