use tokio::net::UdpSocket; use tokio::sync::mpsc; use std::time::{Duration, Instant}; use crate::protocol::{self, EisMessage}; const DEFAULT_ADDR: &str = "192.168.4.1:5941"; const SETTINGS_FILE: &str = ".eis4_udp_addr"; pub fn load_addr() -> String { let path = dirs_next::home_dir() .map(|h| h.join(SETTINGS_FILE)) .unwrap_or_default(); std::fs::read_to_string(path) .ok() .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .unwrap_or_else(|| DEFAULT_ADDR.to_string()) } pub fn save_addr(addr: &str) { if let Some(path) = dirs_next::home_dir().map(|h| h.join(SETTINGS_FILE)) { let _ = std::fs::write(path, addr); } } const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5); const TIMEOUT: Duration = Duration::from_secs(10); #[derive(Debug, Clone)] pub enum UdpEvent { Status(String), Data(EisMessage), } fn extract_sysex_frames(buf: &[u8]) -> Vec> { let mut frames = Vec::new(); let mut i = 0; while i < buf.len() { if buf[i] == 0xF0 { if let Some(end) = buf[i..].iter().position(|&b| b == 0xF7) { frames.push(buf[i + 1..i + end].to_vec()); i += end + 1; continue; } } i += 1; } frames } pub async fn connect_and_run( tx: mpsc::UnboundedSender, mut cmd_rx: mpsc::UnboundedReceiver>, addr: String, ) -> Result<(), Box> { let esp_addr = if addr.contains(':') { addr.clone() } else { format!("{addr}:5941") }; save_addr(&esp_addr); loop { let _ = tx.send(UdpEvent::Status(format!("Connecting UDP to {esp_addr}..."))); let sock = match UdpSocket::bind("0.0.0.0:0").await { Ok(s) => s, Err(e) => { let _ = tx.send(UdpEvent::Status(format!("Bind failed: {e}"))); tokio::time::sleep(Duration::from_secs(2)).await; continue; } }; if let Err(e) = sock.connect(&esp_addr).await { let _ = tx.send(UdpEvent::Status(format!("Connect failed: {e}"))); tokio::time::sleep(Duration::from_secs(2)).await; continue; } /* Initial keepalive to register with ESP */ let keepalive = protocol::build_sysex_get_temp(); let _ = sock.send(&keepalive).await; let mut last_rx = Instant::now(); let mut last_keepalive = Instant::now(); let mut connected = false; let mut buf = [0u8; 4096]; loop { let deadline = tokio::time::sleep(Duration::from_millis(50)); tokio::pin!(deadline); tokio::select! { result = sock.recv(&mut buf) => { match result { Ok(n) if n > 0 => { last_rx = Instant::now(); if !connected { connected = true; let _ = tx.send(UdpEvent::Status("Connected".into())); } for frame in extract_sysex_frames(&buf[..n]) { if let Some(msg) = protocol::parse_sysex(&frame) { let _ = tx.send(UdpEvent::Data(msg)); } } } Ok(_) => {} Err(e) => { let _ = tx.send(UdpEvent::Status(format!("Recv error: {e}"))); break; } } } cmd = cmd_rx.recv() => { match cmd { Some(pkt) => { if let Err(e) = sock.send(&pkt).await { eprintln!("UDP send: {e}"); } } None => return Ok(()), } } _ = &mut deadline => {} } if last_keepalive.elapsed() >= KEEPALIVE_INTERVAL { let _ = sock.send(&keepalive).await; last_keepalive = Instant::now(); } if connected && last_rx.elapsed() >= TIMEOUT { let _ = tx.send(UdpEvent::Status("Timeout — reconnecting...".into())); break; } } tokio::time::sleep(Duration::from_millis(500)).await; } }