use crate::{state::LoopState, LoopFeature};
use tauri::{AppHandle, Emitter, State};
#[derive(serde::Serialize, Clone)]
pub struct SerialLine {
pub line: String,
}
pub fn list_available_ports() -> Result<Vec<String>, String> {
serialport::available_ports()
.map(|ports| ports.into_iter().map(|p| p.port_name).collect())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn loop_serial_ports(state: State<LoopState>) -> Result<Vec<String>, String> {
if !state.has_feature(&LoopFeature::SerialPorts) {
return Err("feature serial_ports not enabled in loop.config.toml".into());
}
list_available_ports()
}
#[tauri::command]
pub async fn loop_serial_start(
port: String,
state: State<'_, LoopState>,
app: AppHandle,
) -> Result<(), String> {
if !state.has_feature(&LoopFeature::SerialRead) {
return Err("feature serial_read not enabled in loop.config.toml".into());
}
if let Some(tx) = state.serial_stop_tx.lock().unwrap().take() {
let _ = tx.send(());
}
let baudrate = state.serial_baudrate;
let (stop_tx, stop_rx) = tokio::sync::oneshot::channel::<()>();
*state.serial_stop_tx.lock().unwrap() = Some(stop_tx);
tokio::spawn(run_serial_loop(port, baudrate, stop_rx, app));
Ok(())
}
async fn run_serial_loop(
port: String,
baudrate: u32,
mut stop_rx: tokio::sync::oneshot::Receiver<()>,
app: AppHandle,
) {
let mut serial = match serialport::new(&port, baudrate)
.timeout(std::time::Duration::from_millis(100))
.open()
{
Ok(s) => s,
Err(e) => {
let _ = app.emit("loop://serial-error", e.to_string());
return;
}
};
let mut buf = vec![0u8; 256];
let mut line_buf = String::new();
loop {
if stop_rx.try_recv().is_ok() {
break;
}
match serial.read(&mut buf) {
Ok(n) => {
let chunk = String::from_utf8_lossy(&buf[..n]);
line_buf.push_str(&chunk);
while let Some(i) = line_buf.find('\n') {
let line = line_buf[..i].trim_end_matches('\r').to_string();
let _ = app.emit("loop://serial", SerialLine { line });
line_buf = line_buf[i + 1..].to_string();
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => {}
Err(e) => {
let _ = app.emit("loop://serial-error", e.to_string());
break;
}
}
}
}
#[tauri::command]
pub fn loop_serial_stop(state: State<LoopState>) -> Result<(), String> {
if !state.has_feature(&LoopFeature::SerialRead) {
return Err("feature serial_read not enabled in loop.config.toml".into());
}
if let Some(tx) = state.serial_stop_tx.lock().unwrap().take() {
let _ = tx.send(());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_ports_returns_ok() {
let ports = list_available_ports();
assert!(ports.is_ok(), "list_available_ports errored: {:?}", ports);
}
}