use std::io::{self, Write as _}; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::thread; use clap::Parser; use crossterm::event::{Event, KeyCode, KeyEventKind}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use crossterm::event; use device_state::{DeviceState, WriteType, apply_wheel_delta}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::sync::mpsc; use tokio::time::{Duration, interval}; const DEVICE_ID: &str = "esp32-1"; const DEFAULT_HOST: &str = "84.238.32.253"; const DEFAULT_PORT: u16 = 7070; const WHEEL_PRECISION: i32 = 32; const WHEEL_STEP: i32 = 1; #[derive(Parser, Debug)] #[command(author, version, about)] struct Args { #[arg(long, default_value = DEFAULT_HOST)] host: String, #[arg(long, default_value_t = DEFAULT_PORT)] port: u16, } enum InputEvent { Button(u8), WheelDelta(i32), Quit, } #[tokio::main] async fn main() -> io::Result<()> { let args = Args::parse(); let addr: SocketAddr = format!("{}:{}", args.host, args.port).parse().unwrap(); let state = Arc::new(Mutex::new(DeviceState::new())); let (input_tx, mut input_rx) = mpsc::channel::(64); spawn_input_thread(input_tx.clone()); tokio::spawn(ui_task(state.clone())); let mut buf = [0u8; 1024]; loop { let stream = match TcpStream::connect(addr).await { Ok(stream) => stream, Err(err) => { eprintln!("connect error: {err}"); tokio::time::sleep(Duration::from_secs(1)).await; continue; } }; let (mut read, mut write) = stream.into_split(); let device_id = device_state::serialize_write(&WriteType::DeviceId(DEVICE_ID)).unwrap(); if write.write_all(device_id.as_bytes()).await.is_err() { tokio::time::sleep(Duration::from_secs(1)).await; continue; } loop { tokio::select! { maybe_event = input_rx.recv() => { match maybe_event { Some(InputEvent::Quit) | None => { let _ = disable_raw_mode(); return Ok(()); } Some(InputEvent::Button(id)) => { let value = state.lock().unwrap().response_value(id); let data = WriteType::QuizResponse(value); let buffer = device_state::serialize_write(&data).unwrap(); if write.write_all(buffer.as_bytes()).await.is_err() { break; } } Some(InputEvent::WheelDelta(diff)) => { let mut state = state.lock().unwrap(); let wheel = state.wheel(); if wheel.max != wheel.min { let mut value = wheel.value; let mut accumulated = wheel.accumulated; apply_wheel_delta( &mut value, wheel.min, wheel.max, &mut accumulated, diff, WHEEL_PRECISION, ); let wheel = state.wheel_mut(); wheel.value = value; wheel.accumulated = accumulated; } } } } read_res = read.read(&mut buf) => { let len = match read_res { Ok(0) => break, Ok(len) => len, Err(_) => break, }; let Ok(text) = core::str::from_utf8(&buf[..len]) else { continue; }; if let Some(last) = text.lines().last() { let Ok(data) = device_state::parse_proxy_output(last) else { continue; }; let mut state = state.lock().unwrap(); state.apply_proxy_output(data); } } } } tokio::time::sleep(Duration::from_millis(500)).await; } } fn spawn_input_thread(tx: mpsc::Sender) { let _ = thread::Builder::new() .name("sim-input".to_string()) .spawn(move || input_thread(tx)); } fn input_thread(tx: mpsc::Sender) { let _ = enable_raw_mode(); let mut stdout = io::stdout(); let _ = crossterm::execute!(stdout, crossterm::cursor::MoveTo(0, 0)); loop { if let Ok(Event::Key(key)) = event::read() { if key.kind == KeyEventKind::Release { continue; } let event = match key.code { KeyCode::Char('1') => Some(InputEvent::Button(1)), KeyCode::Char('2') => Some(InputEvent::Button(2)), KeyCode::Char('3') => Some(InputEvent::Button(3)), KeyCode::Char('4') => Some(InputEvent::Button(4)), KeyCode::Left => Some(InputEvent::WheelDelta(-WHEEL_STEP)), KeyCode::Right => Some(InputEvent::WheelDelta(WHEEL_STEP)), KeyCode::Char('a') => Some(InputEvent::WheelDelta(-WHEEL_STEP)), KeyCode::Char('d') => Some(InputEvent::WheelDelta(WHEEL_STEP)), KeyCode::Char('q') => Some(InputEvent::Quit), _ => None, }; if let Some(event) = event { let quit = matches!(event, InputEvent::Quit); if tx.blocking_send(event).is_err() { break; } if quit { break; } } } } let _ = disable_raw_mode(); } async fn ui_task(state: Arc>) { let mut ticker = interval(Duration::from_millis(50)); loop { ticker.tick().await; let (line1, line2) = { let mut state = state.lock().unwrap(); state.tick(); match state.render_lines() { Some((line1, line2)) => (line1, line2), None => continue, } }; let mut stdout = io::stdout(); let _ = crossterm::execute!(stdout, crossterm::cursor::MoveTo(0, 0)); let _ = writeln!(stdout, "{:<16}", line1.as_str()); let _ = writeln!(stdout, "{:<16}", line2.as_str()); let _ = writeln!(stdout, "[1-4]=buttons <-/->=wheel a/d q=quit"); let _ = stdout.flush(); } }