update ignore
This commit is contained in:
@@ -1,25 +1,16 @@
|
||||
// src-tauri/src/commands/calibration.rs
|
||||
use crate::commands::serial::SerialConnectionState;
|
||||
use crate::serial_core::calibration_session::{CalibrationProgress, CalibrationSession};
|
||||
use crate::serial_core::codecs::tactile_a::TactileACsvExporter;
|
||||
use crate::commands::serial::{CalibrationRuntime, SerialConnectionState};
|
||||
use crate::serial_core::calibration_session::{
|
||||
CalibrationProgress, CalibrationSession, SharedCalibrationSession,
|
||||
};
|
||||
use crate::serial_core::error::SerialError;
|
||||
use crate::serial_core::record::{write_csv, CsvExporter};
|
||||
use crate::serial_core::serial::{run_serial_with_calibration, PollMode, TactileAPollRequester};
|
||||
use log::info;
|
||||
use crate::serial_core::serial::run_serial_with_calibration;
|
||||
use serde::Serialize;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tauri::{async_runtime::JoinHandle, AppHandle, Manager, State};
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokio_serial::SerialPortBuilderExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
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;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CalibrationResponse {
|
||||
@@ -28,9 +19,11 @@ pub struct CalibrationResponse {
|
||||
pub progress: Option<CalibrationProgress>,
|
||||
}
|
||||
|
||||
struct CalibrationSessionData {
|
||||
cancel: CancellationToken,
|
||||
task: JoinHandle<()>,
|
||||
fn snapshot_progress(
|
||||
session: &SharedCalibrationSession,
|
||||
) -> Result<CalibrationProgress, SerialError> {
|
||||
let session = session.lock().map_err(|_| SerialError::StateError)?;
|
||||
Ok(session.get_progress())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -39,111 +32,170 @@ pub async fn serial_calibrate_with_coarse(
|
||||
port: String,
|
||||
target_frames: usize,
|
||||
max_rounds: usize,
|
||||
round_interval_ms: u64,
|
||||
state: State<'_, SerialConnectionState>,
|
||||
) -> Result<CalibrationResponse, SerialError> {
|
||||
let port_name = port.trim().to_string();
|
||||
if port_name.is_empty() {
|
||||
if port_name.is_empty() || target_frames == 0 || max_rounds == 0 {
|
||||
return Err(SerialError::InvalidConfig);
|
||||
}
|
||||
|
||||
// 检查是否有活跃的标定会话
|
||||
{
|
||||
let calibration_session = state
|
||||
.calibration_session
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
if calibration_session.is_some() {
|
||||
return Err(SerialError::AlreadyConnected);
|
||||
}
|
||||
if state.has_active_serial_session()? || state.has_active_calibration()? {
|
||||
return Err(SerialError::AlreadyConnected);
|
||||
}
|
||||
|
||||
// 创建新的标定会话
|
||||
let mut session = CalibrationSession::new(target_frames, max_rounds);
|
||||
let mut session = CalibrationSession::new(target_frames, max_rounds, round_interval_ms);
|
||||
session.start();
|
||||
let session = Arc::new(Mutex::new(session));
|
||||
let progress = snapshot_progress(&session)?;
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
let session_started_at = Instant::now();
|
||||
|
||||
let task_cancel = cancel.clone();
|
||||
let task_app = app.clone();
|
||||
// let task_port_name = port_name.clone();
|
||||
let progress = session.get_progress();
|
||||
let session_for_state = session.clone();
|
||||
let task_session = session.clone();
|
||||
let session_started_at = Instant::now();
|
||||
|
||||
let port = tokio_serial::new(&port_name, 921600)
|
||||
.open_native_async()
|
||||
.map_err(|_| SerialError::OpenError)?;
|
||||
|
||||
let _ = tauri::async_runtime::spawn(async move {
|
||||
// 这里调用新的标定处理函数
|
||||
let task = tauri::async_runtime::spawn(async move {
|
||||
if let Err(error) = run_serial_with_calibration(
|
||||
task_app.clone(),
|
||||
port,
|
||||
session_started_at,
|
||||
task_cancel,
|
||||
session,
|
||||
task_session.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("标定任务异常退出: {error}");
|
||||
eprintln!("calibration task exited with error: {error}");
|
||||
}
|
||||
|
||||
{
|
||||
let manager = task_app.state::<SerialConnectionState>();
|
||||
let Ok(mut runtime) = manager.calibration_runtime.lock() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let should_clear = runtime
|
||||
.as_ref()
|
||||
.map(|current: &CalibrationRuntime| Arc::ptr_eq(¤t.session, &task_session))
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_clear {
|
||||
runtime.take();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 保存标定会话状态
|
||||
let mut calibration_session = state
|
||||
.calibration_session
|
||||
let mut runtime = state
|
||||
.calibration_runtime
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
*calibration_session = Some(session_for_state);
|
||||
if runtime.is_some() {
|
||||
cancel.cancel();
|
||||
task.abort();
|
||||
return Err(SerialError::AlreadyConnected);
|
||||
}
|
||||
|
||||
*runtime = Some(CalibrationRuntime {
|
||||
session,
|
||||
cancel,
|
||||
task,
|
||||
});
|
||||
|
||||
Ok(CalibrationResponse {
|
||||
success: true,
|
||||
message: "标定已开始".to_string(),
|
||||
message: "calibration started".to_string(),
|
||||
progress: Some(progress),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn serial_calibrate_stop(
|
||||
state: State<'_, SerialConnectionState>,
|
||||
) -> Result<CalibrationResponse, SerialError> {
|
||||
let runtime = {
|
||||
let mut runtime = state
|
||||
.calibration_runtime
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
runtime.take()
|
||||
};
|
||||
|
||||
let Some(CalibrationRuntime {
|
||||
session,
|
||||
cancel,
|
||||
task,
|
||||
}) = runtime
|
||||
else {
|
||||
return Ok(CalibrationResponse {
|
||||
success: false,
|
||||
message: "no active calibration".to_string(),
|
||||
progress: None,
|
||||
});
|
||||
};
|
||||
|
||||
cancel.cancel();
|
||||
let _ = task.await;
|
||||
let progress = snapshot_progress(&session).ok();
|
||||
|
||||
Ok(CalibrationResponse {
|
||||
success: true,
|
||||
message: "calibration stopped".to_string(),
|
||||
progress,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn serial_calibrate_add_weight(
|
||||
state: State<'_, SerialConnectionState>,
|
||||
) -> Result<CalibrationResponse, SerialError> {
|
||||
let mut calibration_session = state
|
||||
.calibration_session
|
||||
let runtime = state
|
||||
.calibration_runtime
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
|
||||
if let Some(session) = calibration_session.as_mut() {
|
||||
match session.weight_added() {
|
||||
Ok(_) => Ok(CalibrationResponse {
|
||||
success: true,
|
||||
message: "配重已添加,继续标定".to_string(),
|
||||
progress: Some(session.get_progress()),
|
||||
}),
|
||||
Err(e) => Err(SerialError::StateError),
|
||||
}
|
||||
} else {
|
||||
Err(SerialError::StateError)
|
||||
}
|
||||
let Some(runtime) = runtime.as_ref() else {
|
||||
return Err(SerialError::StateError);
|
||||
};
|
||||
|
||||
let mut session = runtime
|
||||
.session
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
session
|
||||
.weight_added()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
|
||||
Ok(CalibrationResponse {
|
||||
success: true,
|
||||
message: "calibration advanced".to_string(),
|
||||
progress: Some(session.get_progress()),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn serial_calibrate_status(
|
||||
state: State<'_, SerialConnectionState>,
|
||||
) -> Result<CalibrationResponse, SerialError> {
|
||||
let calibration_session = state
|
||||
.calibration_session
|
||||
let runtime = state
|
||||
.calibration_runtime
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
|
||||
if let Some(session) = calibration_session.as_ref() {
|
||||
if let Some(runtime) = runtime.as_ref() {
|
||||
let progress = snapshot_progress(&runtime.session)?;
|
||||
Ok(CalibrationResponse {
|
||||
success: true,
|
||||
message: "标定状态".to_string(),
|
||||
progress: Some(session.get_progress()),
|
||||
message: "calibration active".to_string(),
|
||||
progress: Some(progress),
|
||||
})
|
||||
} else {
|
||||
Ok(CalibrationResponse {
|
||||
success: false,
|
||||
message: "没有活跃的标定会话".to_string(),
|
||||
message: "no active calibration".to_string(),
|
||||
progress: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,7 +94,9 @@ pub fn file_explorer_list(
|
||||
|
||||
Ok(FileExplorerListResponse {
|
||||
current_path: current_path.display().to_string(),
|
||||
parent_path: current_path.parent().map(|parent| parent.display().to_string()),
|
||||
parent_path: current_path
|
||||
.parent()
|
||||
.map(|parent| parent.display().to_string()),
|
||||
roots: collect_roots(&app),
|
||||
entries,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::serial_core::calibration_session::CalibrationSession;
|
||||
use crate::serial_core::calibration_session::SharedCalibrationSession;
|
||||
use crate::serial_core::codecs::tactile_a::{
|
||||
export_recording_csv, TactileACodec, TactileACsvImporter, TactileAHandler,
|
||||
};
|
||||
@@ -71,11 +71,32 @@ struct SerialSession {
|
||||
current_record: SharedTactileRecording,
|
||||
}
|
||||
|
||||
pub struct CalibrationRuntime {
|
||||
pub session: SharedCalibrationSession,
|
||||
pub cancel: CancellationToken,
|
||||
pub task: JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SerialConnectionState {
|
||||
session: Mutex<Option<SerialSession>>,
|
||||
last_record: Mutex<Option<SharedTactileRecording>>,
|
||||
pub calibration_session: Mutex<Option<CalibrationSession>>,
|
||||
pub calibration_runtime: Mutex<Option<CalibrationRuntime>>,
|
||||
}
|
||||
|
||||
impl SerialConnectionState {
|
||||
pub(crate) fn has_active_serial_session(&self) -> Result<bool, SerialError> {
|
||||
let session = self.session.lock().map_err(|_| SerialError::StateError)?;
|
||||
Ok(session.is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn has_active_calibration(&self) -> Result<bool, SerialError> {
|
||||
let runtime = self
|
||||
.calibration_runtime
|
||||
.lock()
|
||||
.map_err(|_| SerialError::StateError)?;
|
||||
Ok(runtime.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -100,11 +121,8 @@ pub async fn serial_connect(
|
||||
return Err(SerialError::InvalidConfig);
|
||||
}
|
||||
|
||||
{
|
||||
let session = state.session.lock().map_err(|_| SerialError::StateError)?;
|
||||
if session.is_some() {
|
||||
return Err(SerialError::AlreadyConnected);
|
||||
}
|
||||
if state.has_active_serial_session()? || state.has_active_calibration()? {
|
||||
return Err(SerialError::AlreadyConnected);
|
||||
}
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
@@ -26,7 +26,10 @@ pub fn win_toggle_maximize(app: AppHandle) -> Result<(), String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn win_close(app: AppHandle, state: State<'_, SerialConnectionState>) -> Result<(), String> {
|
||||
pub async fn win_close(
|
||||
app: AppHandle,
|
||||
state: State<'_, SerialConnectionState>,
|
||||
) -> Result<(), String> {
|
||||
disconnect_active_session(state.inner())
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod commands;
|
||||
pub mod serial_core;
|
||||
pub mod log;
|
||||
pub mod serial_core;
|
||||
use commands::serial::SerialConnectionState;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
@@ -19,6 +19,7 @@ pub fn run() {
|
||||
commands::serial::serial_import_csv,
|
||||
commands::serial::serial_import_csv_from_path,
|
||||
commands::calibration::serial_calibrate_with_coarse,
|
||||
commands::calibration::serial_calibrate_stop,
|
||||
commands::calibration::serial_calibrate_add_weight,
|
||||
commands::calibration::serial_calibrate_status,
|
||||
commands::window::win_minimize,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use fern::{Dispatch, colors::{Color, ColoredLevelConfig}};
|
||||
use log::{debug};
|
||||
use fern::{
|
||||
colors::{Color, ColoredLevelConfig},
|
||||
Dispatch,
|
||||
};
|
||||
use log::debug;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
@@ -46,40 +49,36 @@ pub fn setup_logger() {
|
||||
|
||||
let console_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(
|
||||
format_args!(
|
||||
"{colors_line}[{data} {level} {target} {colors_line}] {message}\x1B[0m",
|
||||
colors_line = format_args!(
|
||||
"\x1B[{}m",
|
||||
colors_line.get_color(&record.level()).to_fg_str()
|
||||
),
|
||||
data = humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||
target = record.target(),
|
||||
level = colors_level.color(record.level()),
|
||||
message = message,
|
||||
)
|
||||
);
|
||||
out.finish(format_args!(
|
||||
"{colors_line}[{data} {level} {target} {colors_line}] {message}\x1B[0m",
|
||||
colors_line = format_args!(
|
||||
"\x1B[{}m",
|
||||
colors_line.get_color(&record.level()).to_fg_str()
|
||||
),
|
||||
data = humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||
target = record.target(),
|
||||
level = colors_level.color(record.level()),
|
||||
message = message,
|
||||
));
|
||||
})
|
||||
.level(level)
|
||||
.chain(std::io::stdout());
|
||||
// .chain(fern::DateBased::new("program.log", "%Y-%m-%d"))
|
||||
// .apply()
|
||||
// .unwrap();
|
||||
// .chain(fern::DateBased::new("program.log", "%Y-%m-%d"))
|
||||
// .apply()
|
||||
// .unwrap();
|
||||
|
||||
let log_dir = ensure_log_dir();
|
||||
let log_file_base = log_dir.join("program.log");
|
||||
|
||||
let file_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(
|
||||
format_args!(
|
||||
"[{data} {level} {target}] {message}",
|
||||
data = humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||
target = record.target(),
|
||||
level = colors_level.color(record.level()),
|
||||
message = message,
|
||||
)
|
||||
);
|
||||
out.finish(format_args!(
|
||||
"[{data} {level} {target}] {message}",
|
||||
data = humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||
target = record.target(),
|
||||
level = colors_level.color(record.level()),
|
||||
message = message,
|
||||
));
|
||||
})
|
||||
.level(level)
|
||||
.chain(fern::DateBased::new(log_file_base, "%Y-%m-%d"));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use log::debug;
|
||||
use tauri_demo_lib::log::setup_logger;
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::serial_core::frame::TactileAFrame;
|
||||
use crate::serial_core::record::{RecordedFrame, Recording};
|
||||
use crate::serial_core::record::RecordedFrame;
|
||||
use serde::Serialize;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -8,8 +8,7 @@ use std::sync::{Arc, Mutex};
|
||||
pub enum CalibrationState {
|
||||
Idle,
|
||||
CollectingData,
|
||||
ExportingData,
|
||||
WaitingForWeight,
|
||||
WaitingForNextRound,
|
||||
Completed,
|
||||
}
|
||||
|
||||
@@ -17,21 +16,23 @@ pub enum CalibrationState {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CalibrationSession {
|
||||
pub state: CalibrationState,
|
||||
pub target_frame: usize,
|
||||
pub target_frames: usize,
|
||||
pub collected_frames: usize,
|
||||
pub current_round: usize,
|
||||
pub max_rounds: usize,
|
||||
pub round_interval_ms: u64,
|
||||
pub data: Vec<RecordedFrame<TactileAFrame>>,
|
||||
}
|
||||
|
||||
impl CalibrationSession {
|
||||
pub fn new(targt_frame: usize, max_round: usize) -> Self {
|
||||
pub fn new(target_frames: usize, max_rounds: usize, round_interval_ms: u64) -> Self {
|
||||
Self {
|
||||
state: CalibrationState::Idle,
|
||||
target_frame: targt_frame,
|
||||
target_frames,
|
||||
collected_frames: 0,
|
||||
current_round: 1,
|
||||
max_rounds: max_round,
|
||||
max_rounds,
|
||||
round_interval_ms,
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -40,10 +41,6 @@ impl CalibrationSession {
|
||||
self.state = CalibrationState::CollectingData;
|
||||
self.collected_frames = 0;
|
||||
self.data.clear();
|
||||
println!(
|
||||
"标定第 {} 轮开始,目标收集 {} 个有效帧",
|
||||
self.current_round, self.target_frame
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_frame(&mut self, frame: RecordedFrame<TactileAFrame>) -> bool {
|
||||
@@ -53,24 +50,24 @@ impl CalibrationSession {
|
||||
|
||||
self.data.push(frame);
|
||||
self.collected_frames += 1;
|
||||
|
||||
if self.collected_frames >= self.target_frame {
|
||||
self.state = CalibrationState::ExportingData;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
self.collected_frames >= self.target_frames
|
||||
}
|
||||
|
||||
pub fn export_completed(&mut self) {
|
||||
self.state = CalibrationState::WaitingForWeight;
|
||||
println!("请修改配重,继续标定");
|
||||
if self.current_round >= self.max_rounds {
|
||||
self.state = CalibrationState::Completed;
|
||||
} else {
|
||||
self.state = CalibrationState::WaitingForNextRound;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weight_added(&mut self) -> Result<(), String> {
|
||||
self.begin_next_round()
|
||||
}
|
||||
|
||||
pub fn begin_next_round(&mut self) -> Result<(), String> {
|
||||
if self.current_round >= self.max_rounds {
|
||||
self.state = CalibrationState::Completed;
|
||||
println!("标定完成,共 {} 轮", self.current_round);
|
||||
} else {
|
||||
self.current_round += 1;
|
||||
self.start();
|
||||
@@ -79,15 +76,30 @@ impl CalibrationSession {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.state = CalibrationState::Idle;
|
||||
self.collected_frames = 0;
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
matches!(
|
||||
self.state,
|
||||
CalibrationState::CollectingData | CalibrationState::WaitingForNextRound
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_progress(&self) -> CalibrationProgress {
|
||||
CalibrationProgress {
|
||||
state: self.state.clone(),
|
||||
current_round: self.current_round,
|
||||
max_rounds: self.max_rounds,
|
||||
collected_frames: self.collected_frames,
|
||||
target_frames: self.target_frame,
|
||||
progress_percentage: if self.target_frame > 0 {
|
||||
(self.collected_frames as f32 / self.target_frame as f32) * 100.0
|
||||
target_frames: self.target_frames,
|
||||
round_interval_ms: self.round_interval_ms,
|
||||
is_active: self.is_active(),
|
||||
progress_percentage: if self.target_frames > 0 {
|
||||
(self.collected_frames as f32 / self.target_frames as f32) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
@@ -103,7 +115,9 @@ pub struct CalibrationProgress {
|
||||
pub max_rounds: usize,
|
||||
pub collected_frames: usize,
|
||||
pub target_frames: usize,
|
||||
pub round_interval_ms: u64,
|
||||
pub is_active: bool,
|
||||
pub progress_percentage: f32,
|
||||
}
|
||||
|
||||
pub type SharedCalibrationSession = Arc<Mutex<Option<CalibrationSession>>>;
|
||||
pub type SharedCalibrationSession = Arc<Mutex<CalibrationSession>>;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::serial_core::{frame::TestFrame, record::Recording};
|
||||
|
||||
|
||||
pub mod tactile_a;
|
||||
pub mod test;
|
||||
pub type TestRecording = Recording<TestFrame>;
|
||||
|
||||
@@ -13,14 +13,12 @@ use async_trait::async_trait;
|
||||
use csv::StringRecord;
|
||||
use log::debug;
|
||||
use std::io::Read;
|
||||
use std::os::raw;
|
||||
|
||||
const FRAME_BUFFER_MIN_LENGTH: usize = 15;
|
||||
const IGNOR_RAW_DATA_VAL: i32 = 10;
|
||||
|
||||
pub struct TactileACodec {
|
||||
buffer: Vec<u8>,
|
||||
frame_nb: u64,
|
||||
expected_data_len: usize,
|
||||
}
|
||||
|
||||
@@ -68,7 +66,6 @@ impl TactileACodec {
|
||||
pub fn new(cols: usize, rows: usize) -> TactileACodec {
|
||||
Self {
|
||||
buffer: Vec::new(),
|
||||
frame_nb: 0,
|
||||
expected_data_len: cols * rows * 2,
|
||||
}
|
||||
}
|
||||
@@ -82,7 +79,6 @@ impl TactileACodec {
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| {
|
||||
let mut raw_val = u16::from_le_bytes([chunk[0], chunk[1]]) as i32;
|
||||
println!("raw_val: {}", raw_val);
|
||||
if raw_val < IGNOR_RAW_DATA_VAL {
|
||||
raw_val = 0;
|
||||
}
|
||||
@@ -245,7 +241,7 @@ impl FrameHandler<TactileAFrame, i32> for TactileAHandler {
|
||||
match frame {
|
||||
TactileAFrame::Rep(rep) => {
|
||||
let vals = TactileACodec::parse_data_frame(&rep.payload)?;
|
||||
debug!("vals is {:?}", vals);
|
||||
|
||||
Ok(Some(vals))
|
||||
}
|
||||
_ => Ok(None),
|
||||
@@ -253,6 +249,15 @@ impl FrameHandler<TactileAFrame, i32> for TactileAHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn trans_data_2_n(val: i32) -> i32 {
|
||||
if val <= 74602 {
|
||||
val / 466
|
||||
} else if val > 74602 && val <= 105503 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl TactileACsvExporter {
|
||||
pub fn new(channels: usize) -> Self {
|
||||
TactileACsvExporter {
|
||||
@@ -271,7 +276,7 @@ impl TactileACsvExporter {
|
||||
|
||||
impl CsvExporter<TactileARepFrame> for TactileACsvExporter {
|
||||
type Error = CodecError;
|
||||
fn csv_header(&self, recording: &Recording<TactileARepFrame>) -> Vec<String> {
|
||||
fn csv_header(&self, _recording: &Recording<TactileARepFrame>) -> Vec<String> {
|
||||
let mut header: Vec<String> = Vec::new();
|
||||
for i in 0..self.channels {
|
||||
header.push(format!("channel{}", i + 1));
|
||||
|
||||
@@ -244,9 +244,7 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use csv::Reader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_read_csv_basic() -> anyhow::Result<()> {
|
||||
|
||||
@@ -37,7 +37,7 @@ impl<F> Recording<F> {
|
||||
pub fn push(&mut self, ite: RecordedFrame<F>) {
|
||||
self.frames.push(ite);
|
||||
}
|
||||
pub fn check_frame_need_record(ite: RecordedFrame<F>) {}
|
||||
pub fn check_frame_need_record(_ite: RecordedFrame<F>) {}
|
||||
}
|
||||
|
||||
pub trait CsvExporter<F> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::serial_core::calibration_session::*;
|
||||
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};
|
||||
@@ -164,9 +164,9 @@ impl PollRequester<TactileAFrame> for TactileAPollRequester {
|
||||
|
||||
pub async fn run_serial<C, H, T, F>(
|
||||
app: AppHandle,
|
||||
mut port: SerialStream,
|
||||
mut codec: C,
|
||||
mut handler: H,
|
||||
port: SerialStream,
|
||||
codec: C,
|
||||
handler: H,
|
||||
session_started_at: Instant,
|
||||
recording: Arc<Mutex<Recording<F>>>,
|
||||
cancel: CancellationToken,
|
||||
@@ -294,111 +294,218 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 在 src-tauri/src/serial_core/serial.rs 中添加
|
||||
// 鍦?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,
|
||||
calibration_session: SharedCalibrationSession,
|
||||
) -> 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),
|
||||
);
|
||||
info!("run_serial_with_calibration begin");
|
||||
emit_calibration_status(&app, &calibration_session)?;
|
||||
|
||||
let mut poll_interval = time::interval(Duration::from_millis(DEFAULT_TACTILE_POLL_INTERVAL_MS));
|
||||
poll_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||
let run_result = async {
|
||||
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 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);
|
||||
let mut poll_interval =
|
||||
time::interval(Duration::from_millis(DEFAULT_TACTILE_POLL_INTERVAL_MS));
|
||||
poll_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||
|
||||
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?;
|
||||
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);
|
||||
let mut next_round_at: Option<Instant> = None;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => break,
|
||||
_ = poll_interval.tick() => {
|
||||
if let Some(deadline) = next_round_at {
|
||||
if Instant::now() >= deadline {
|
||||
next_round_at = None;
|
||||
begin_next_calibration_round(&app, &calibration_session)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = 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(),
|
||||
};
|
||||
|
||||
if calibration_is_collecting(&calibration_session)?
|
||||
&& requester.should_request()
|
||||
{
|
||||
let mut record = recording
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("recording state poisoned"))?;
|
||||
record.push(recorded_frame.clone());
|
||||
if let Some(req) = requester.next_request()? {
|
||||
let bytes = codec.encode(&req)?;
|
||||
port.write_all(&bytes).await?;
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
}
|
||||
_ = 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 should_export = calibration_session.add_frame(recorded_frame);
|
||||
let frames = codec.decode(&buffer[..n], session_started_at)?;
|
||||
for frame in frames {
|
||||
requester.on_rx_frame(&frame);
|
||||
|
||||
if should_export {
|
||||
// 导出数据
|
||||
export_calibration_data(&app, &calibration_session, &recording).await?;
|
||||
let decode_res = handler
|
||||
.on_frame(&frame)
|
||||
.await?
|
||||
.map(|vals| vals.into_iter().map(Into::into).collect::<Vec<i32>>());
|
||||
|
||||
// 发送语音提示(这里用事件代替,前端可以播放语音)
|
||||
app.emit("calibration_voice_prompt", "请添加配重")?;
|
||||
let display_values = if let Some(vals) = decode_res.as_ref() {
|
||||
let summary = vals.iter().copied().sum::<i32>();
|
||||
let val_summary = summary - vals[vals.len() - 1];
|
||||
if val_summary < 8400 {
|
||||
continue;
|
||||
}
|
||||
chart_state.record_summary(summary as f32);
|
||||
chart_state.record_pressure_matrix(vals.as_slice());
|
||||
Some(vec![summary])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 更新状态
|
||||
calibration_session.export_completed();
|
||||
let recorded_frame = RecordedFrame {
|
||||
timing: FrameTiming { pts_ms: None, dts_ms: frame.dts_ms() },
|
||||
frame: frame.clone(),
|
||||
};
|
||||
|
||||
if let Ok(mut record) = recording.lock() {
|
||||
record.frames.clear();
|
||||
{
|
||||
let mut record = recording
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("recording state poisoned"))?;
|
||||
record.push(recorded_frame.clone());
|
||||
}
|
||||
|
||||
if let Some(packet) = frame.to_hud_packet(&mut chart_state, display_values.as_deref()) {
|
||||
app.emit("hud_stream", packet)?;
|
||||
}
|
||||
|
||||
let should_export = {
|
||||
let mut session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
session.add_frame(recorded_frame)
|
||||
};
|
||||
|
||||
if should_export {
|
||||
let current_round = {
|
||||
let session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
session.current_round
|
||||
};
|
||||
|
||||
export_calibration_data(&app, current_round, &recording).await?;
|
||||
|
||||
let (progress, round_interval_ms) = {
|
||||
let mut session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
session.export_completed();
|
||||
(session.get_progress(), session.round_interval_ms)
|
||||
};
|
||||
app.emit("calibration_status", progress.clone())?;
|
||||
|
||||
if let Ok(mut record) = recording.lock() {
|
||||
record.frames.clear();
|
||||
}
|
||||
|
||||
if progress.state == CalibrationState::Completed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if round_interval_ms == 0 {
|
||||
begin_next_calibration_round(&app, &calibration_session)?;
|
||||
} else {
|
||||
next_round_at =
|
||||
Some(Instant::now() + Duration::from_millis(round_interval_ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
if cancel.is_cancelled() {
|
||||
stop_calibration_session(&app, &calibration_session)?;
|
||||
}
|
||||
|
||||
run_result
|
||||
}
|
||||
|
||||
fn calibration_is_collecting(calibration_session: &SharedCalibrationSession) -> Result<bool> {
|
||||
let session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
Ok(session.state == CalibrationState::CollectingData)
|
||||
}
|
||||
|
||||
fn begin_next_calibration_round(
|
||||
app: &AppHandle,
|
||||
calibration_session: &SharedCalibrationSession,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
if session.state == CalibrationState::WaitingForNextRound {
|
||||
session
|
||||
.begin_next_round()
|
||||
.map_err(|error| anyhow::anyhow!(error))?;
|
||||
}
|
||||
}
|
||||
|
||||
emit_calibration_status(app, calibration_session)
|
||||
}
|
||||
|
||||
fn stop_calibration_session(
|
||||
app: &AppHandle,
|
||||
calibration_session: &SharedCalibrationSession,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
if session.state != CalibrationState::Completed {
|
||||
session.stop();
|
||||
}
|
||||
}
|
||||
|
||||
emit_calibration_status(app, calibration_session)
|
||||
}
|
||||
|
||||
fn emit_calibration_status(
|
||||
app: &AppHandle,
|
||||
calibration_session: &SharedCalibrationSession,
|
||||
) -> Result<()> {
|
||||
let progress = {
|
||||
let session = calibration_session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("calibration session poisoned"))?;
|
||||
session.get_progress()
|
||||
};
|
||||
|
||||
app.emit("calibration_status", progress)?;
|
||||
Ok(())
|
||||
}
|
||||
use crate::serial_core::codecs::tactile_a::TactileACsvExporter;
|
||||
@@ -407,7 +514,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::Manager;
|
||||
async fn export_calibration_data(
|
||||
app: &AppHandle,
|
||||
calibration_session: &CalibrationSession,
|
||||
current_round: usize,
|
||||
recording: &Arc<Mutex<Recording<TactileAFrame>>>,
|
||||
) -> Result<()> {
|
||||
let timestamp = SystemTime::now()
|
||||
@@ -415,12 +522,8 @@ async fn export_calibration_data(
|
||||
.map(|duration| duration.as_millis())
|
||||
.unwrap_or_default();
|
||||
|
||||
let filename = format!(
|
||||
"calibration_round{}_{}.csv",
|
||||
calibration_session.current_round, timestamp
|
||||
);
|
||||
let filename = format!("calibration_round{}_{}.csv", current_round, timestamp);
|
||||
|
||||
// 创建导出目录
|
||||
let mut output_dir = match app.path().desktop_dir() {
|
||||
Ok(path) => path,
|
||||
Err(_) => std::env::current_dir()?,
|
||||
@@ -431,7 +534,6 @@ async fn export_calibration_data(
|
||||
let output_path = output_dir.join(&filename);
|
||||
let file = File::create(&output_path)?;
|
||||
|
||||
// 使用现有的导出逻辑
|
||||
let recording_lock = recording
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("Recording poisoned"))?;
|
||||
@@ -442,6 +544,5 @@ async fn export_calibration_data(
|
||||
|
||||
write_csv(&recording_lock, &exporter, file)?;
|
||||
|
||||
info!("标定数据已导出到: {}", output_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
pub fn usize_to_u16_be_bytes(n: usize) -> [u8; 2] {
|
||||
@@ -41,7 +40,9 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_crc8_itu() -> anyhow::Result<()> {
|
||||
let req_vec = vec![0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00];
|
||||
let req_vec = vec![
|
||||
0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00,
|
||||
];
|
||||
let checksum = calc_crc8_itu(req_vec.as_slice());
|
||||
assert_eq!(checksum, 0x7A);
|
||||
|
||||
@@ -50,10 +51,12 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_crc8_smbus() -> anyhow::Result<()> {
|
||||
let req_vec = vec![0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00];
|
||||
let req_vec = vec![
|
||||
0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00,
|
||||
];
|
||||
let checksum = calc_crc8_smbus(req_vec.as_slice());
|
||||
assert_eq!(checksum, 0x2F);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user