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

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

@@ -0,0 +1,150 @@
// 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::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 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 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 {
pub success: bool,
pub message: String,
pub progress: Option<CalibrationProgress>,
}
struct CalibrationSessionData {
cancel: CancellationToken,
task: JoinHandle<()>,
}
#[tauri::command]
pub async fn serial_calibrate_with_coarse(
app: AppHandle,
port: String,
target_frames: usize,
max_rounds: usize,
state: State<'_, SerialConnectionState>,
) -> Result<CalibrationResponse, SerialError> {
let port_name = port.trim().to_string();
if port_name.is_empty() {
return Err(SerialError::InvalidConfig);
}
// 检查是否有活跃的标定会话
{
let calibration_session = state
.calibration_session
.lock()
.map_err(|_| SerialError::StateError)?;
if calibration_session.is_some() {
return Err(SerialError::AlreadyConnected);
}
}
// 创建新的标定会话
let mut session = CalibrationSession::new(target_frames, max_rounds);
session.start();
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 port = tokio_serial::new(&port_name, 921600)
.open_native_async()
.map_err(|_| SerialError::OpenError)?;
let _ = tauri::async_runtime::spawn(async move {
// 这里调用新的标定处理函数
if let Err(error) = run_serial_with_calibration(
task_app.clone(),
port,
session_started_at,
task_cancel,
session,
)
.await
{
eprintln!("标定任务异常退出: {error}");
}
});
// 保存标定会话状态
let mut calibration_session = state
.calibration_session
.lock()
.map_err(|_| SerialError::StateError)?;
*calibration_session = Some(session_for_state);
Ok(CalibrationResponse {
success: true,
message: "标定已开始".to_string(),
progress: Some(progress),
})
}
#[tauri::command]
pub async fn serial_calibrate_add_weight(
state: State<'_, SerialConnectionState>,
) -> Result<CalibrationResponse, SerialError> {
let mut calibration_session = state
.calibration_session
.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)
}
}
#[tauri::command]
pub fn serial_calibrate_status(
state: State<'_, SerialConnectionState>,
) -> Result<CalibrationResponse, SerialError> {
let calibration_session = state
.calibration_session
.lock()
.map_err(|_| SerialError::StateError)?;
if let Some(session) = calibration_session.as_ref() {
Ok(CalibrationResponse {
success: true,
message: "标定状态".to_string(),
progress: Some(session.get_progress()),
})
} else {
Ok(CalibrationResponse {
success: false,
message: "没有活跃的标定会话".to_string(),
progress: None,
})
}
}

View File

@@ -1,3 +1,4 @@
pub mod calibration;
pub mod file_explorer;
pub mod serial;
pub mod window;

View File

@@ -1,3 +1,4 @@
use crate::serial_core::calibration_session::CalibrationSession;
use crate::serial_core::codecs::tactile_a::{
export_recording_csv, TactileACodec, TactileACsvImporter, TactileAHandler,
};
@@ -23,7 +24,6 @@ const DEFAULT_TACTILE_REPLY_TIMEOUT_MS: u64 = 140;
type SharedTactileRecording = Arc<Mutex<TactileARecording>>;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SerialConnectResponse {
@@ -74,7 +74,8 @@ struct SerialSession {
#[derive(Default)]
pub struct SerialConnectionState {
session: Mutex<Option<SerialSession>>,
last_record: Mutex<Option<SharedTactileRecording>>
last_record: Mutex<Option<SharedTactileRecording>>,
pub calibration_session: Mutex<Option<CalibrationSession>>,
}
#[tauri::command]
@@ -176,7 +177,7 @@ pub async fn serial_connect(
port: port_name.clone(),
cancel,
task,
current_record
current_record,
});
Ok(SerialConnectResponse {
@@ -190,6 +191,24 @@ pub async fn serial_connect(
pub async fn serial_disconnect(
state: State<'_, SerialConnectionState>,
) -> Result<SerialConnectResponse, SerialError> {
let Some(port) = disconnect_active_session(state.inner()).await? else {
return Ok(SerialConnectResponse {
port: String::new(),
connected: false,
message: "already disconnected".to_string(),
});
};
Ok(SerialConnectResponse {
port,
connected: false,
message: "disconnected".to_string(),
})
}
pub(crate) async fn disconnect_active_session(
state: &SerialConnectionState,
) -> Result<Option<String>, SerialError> {
let session = {
let mut guard = state.session.lock().map_err(|_| SerialError::StateError)?;
guard.take()
@@ -202,18 +221,15 @@ pub async fn serial_disconnect(
current_record,
}) = session
else {
return Ok(SerialConnectResponse {
port: String::new(),
connected: false,
message: "already disconnected".to_string(),
});
return Ok(None);
};
cancel.cancel();
let _ = task.await;
let frame_count = current_record.lock().map(|record| {
record.frames.len()
}).unwrap_or(0);
let frame_count = current_record
.lock()
.map(|record| record.frames.len())
.unwrap_or(0);
info!("last_record has {} frames", frame_count);
@@ -221,12 +237,7 @@ pub async fn serial_disconnect(
*last_record = Some(current_record);
}
Ok(SerialConnectResponse {
port,
connected: false,
message: "disconnected".to_string(),
})
Ok(Some(port))
}
#[tauri::command]
@@ -290,7 +301,10 @@ pub fn serial_export_csv_to_path(
}
#[tauri::command]
pub fn serial_import_csv(file_name: String, csv_content: String) -> Result<SerialImportResponse, SerialError> {
pub fn serial_import_csv(
file_name: String,
csv_content: String,
) -> Result<SerialImportResponse, SerialError> {
let mut importer = TactileACsvImporter::new(file_name.as_str());
let packets = importer
.load(Cursor::new(csv_content.into_bytes()))
@@ -347,7 +361,10 @@ fn resolve_record_for_export(
return Ok(recording);
}
let last_record = state.last_record.lock().map_err(|_| SerialError::StateError)?;
let last_record = state
.last_record
.lock()
.map_err(|_| SerialError::StateError)?;
last_record.clone().ok_or(SerialError::NoRecordedData)
}
@@ -368,7 +385,10 @@ fn snapshot_record_frame_count(
.map_err(|_| SerialError::StateError);
}
let last_record = state.last_record.lock().map_err(|_| SerialError::StateError)?;
let last_record = state
.last_record
.lock()
.map_err(|_| SerialError::StateError)?;
let Some(record) = last_record.as_ref() else {
return Ok(0);
};

View File

@@ -1,4 +1,5 @@
use tauri::{AppHandle, Manager, WebviewWindow};
use crate::commands::serial::{disconnect_active_session, SerialConnectionState};
use tauri::{AppHandle, Manager, State, WebviewWindow};
fn main_window(app: &AppHandle) -> Result<WebviewWindow, String> {
app.get_webview_window("main")
@@ -25,8 +26,11 @@ pub fn win_toggle_maximize(app: AppHandle) -> Result<(), String> {
}
#[tauri::command]
pub fn win_close(app: AppHandle) -> Result<(), String> {
main_window(&app)?
.close()
.map_err(|error| error.to_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())?;
app.exit(0);
Ok(())
}