139 lines
4.5 KiB
Rust
139 lines
4.5 KiB
Rust
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<Vec<u8>> {
|
|
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<UdpEvent>,
|
|
mut cmd_rx: mpsc::UnboundedReceiver<Vec<u8>>,
|
|
addr: String,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
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;
|
|
}
|
|
}
|