use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; use std::time::Duration; use crossbeam_channel::{self, Receiver, Sender, TryRecvError}; use crate::recording::Recorder; use crate::serial_core::serial::{ SerialIoStats, SerialPortReadWrite, SerialProtocol, run_serial_loop, }; /// Connection state visible to the UI. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConnectionState { Disconnected, Connecting, Connected, Streaming, Error, } /// A pressure sample forwarded to the render layer. pub struct PressureSample { pub matrix: Vec, pub rows: u32, pub cols: u32, } struct Session { cancel_tx: Sender<()>, handle: JoinHandle<()>, sample_rx: Receiver>, stats_rx: Receiver, rows: u32, cols: u32, } /// Thread-safe connection manager that the UI and renderer can share. pub struct ConnectionManager { state: Arc>, session: Arc>>, latest_sample: Arc>>, stats: Arc>, } impl ConnectionManager { pub fn new() -> Self { Self { state: Arc::new(Mutex::new(ConnectionState::Disconnected)), session: Arc::new(Mutex::new(None)), latest_sample: Arc::new(Mutex::new(None)), stats: Arc::new(Mutex::new(SerialIoStats::default())), } } pub fn state(&self) -> ConnectionState { *self.state.lock().unwrap() } pub fn set_state(&self, new_state: ConnectionState) { *self.state.lock().unwrap() = new_state; } pub fn stats(&self) -> SerialIoStats { let session = self.session.lock().unwrap(); if let Some(ref session) = *session { loop { match session.stats_rx.try_recv() { Ok(stats) => *self.stats.lock().unwrap() = stats, Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => break, } } } *self.stats.lock().unwrap() } /// Connect to the given serial port and start streaming in a background thread. pub fn connect( &self, port_name: &str, rows: u32, cols: u32, baud_rate: u32, protocol: SerialProtocol, recorder: Recorder, ) { self.disconnect(); self.set_state(ConnectionState::Connecting); let port = port_name.to_owned(); let state = Arc::clone(&self.state); let session_guard = Arc::clone(&self.session); let latest_sample = Arc::clone(&self.latest_sample); let (cancel_tx, cancel_rx) = crossbeam_channel::bounded::<()>(1); let (sample_tx, sample_rx) = crossbeam_channel::bounded::>(16); let (stats_tx, stats_rx) = crossbeam_channel::bounded::(16); *self.stats.lock().unwrap() = SerialIoStats::default(); let handle = thread::spawn(move || { let result = run_device_loop( &port, rows, cols, baud_rate, protocol, &state, &cancel_rx, &sample_tx, &stats_tx, &latest_sample, recorder, ); if let Err(e) = result { eprintln!("[connection] device loop error: {e}"); *state.lock().unwrap() = ConnectionState::Error; } }); *session_guard.lock().unwrap() = Some(Session { cancel_tx, handle, sample_rx, stats_rx, rows, cols, }); } /// Disconnect from the device, stopping the background thread. pub fn disconnect(&self) { let session = { let mut guard = self.session.lock().unwrap(); guard.take() }; if let Some(session) = session { let _ = session.cancel_tx.send(()); let _ = session.handle.join(); } self.set_state(ConnectionState::Disconnected); *self.latest_sample.lock().unwrap() = None; *self.stats.lock().unwrap() = SerialIoStats::default(); } /// Drain pending samples (non-blocking) and return the last one. pub fn take_latest_sample(&self) -> Option { let session = self.session.lock().unwrap(); if let Some(ref session) = *session { let mut last = None; loop { match session.sample_rx.try_recv() { Ok(vals) => { let matrix = vals.iter().map(|v| (*v).max(0) as u32).collect(); last = Some(PressureSample { matrix, rows: session.rows, cols: session.cols, }); } Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => break, } } if last.is_some() { return last; } } None } } impl Default for ConnectionManager { fn default() -> Self { Self::new() } } /// The blocking device loop that runs on a background thread. #[allow(clippy::too_many_arguments)] fn run_device_loop( port_name: &str, rows: u32, cols: u32, baud_rate: u32, protocol: SerialProtocol, state: &Arc>, cancel_rx: &Receiver<()>, sample_tx: &Sender>, stats_tx: &Sender, latest_sample: &Arc>>, recorder: Recorder, ) -> Result<(), Box> { let port = serialport::new(port_name, baud_rate) .timeout(Duration::from_millis(1)) .open()?; *state.lock().unwrap() = ConnectionState::Connected; let mut rw = SerialPortReadWrite::new(port); *state.lock().unwrap() = ConnectionState::Streaming; run_serial_loop( &mut rw, rows as usize, cols as usize, protocol, cancel_rx, sample_tx, Some(stats_tx), Some(&recorder), ); if let Ok(mut latest) = latest_sample.lock() { *latest = None; } Ok(()) }