simulator progress
This commit is contained in:
parent
18cdc54781
commit
852fa32c93
4 changed files with 126 additions and 22 deletions
9
.envrc
Normal file
9
.envrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# the shebang is ignored, but nice for editors
|
||||
|
||||
if type -P lorri &>/dev/null; then
|
||||
eval "$(lorri direnv)"
|
||||
else
|
||||
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
|
||||
use flake
|
||||
fi
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2956,6 +2956,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ default-target = "x86_64-unknown-linux-gnu"
|
|||
clap = { version = "4.5.45", features = ["derive"] }
|
||||
crossterm = "0.28.1"
|
||||
device-state = { path = "../device-state" }
|
||||
tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread", "net", "sync", "time", "io-util"] }
|
||||
tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread", "net", "sync", "time", "io-util", "signal"] }
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ use std::sync::{Arc, Mutex};
|
|||
use std::thread;
|
||||
|
||||
use clap::Parser;
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use crossterm::cursor::Show;
|
||||
use crossterm::queue;
|
||||
use crossterm::event;
|
||||
use device_state::{DeviceState, WriteType, apply_wheel_delta};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
|
@ -34,11 +36,30 @@ enum InputEvent {
|
|||
Quit,
|
||||
}
|
||||
|
||||
struct TerminalGuard;
|
||||
|
||||
impl TerminalGuard {
|
||||
fn new() -> Self {
|
||||
let _ = enable_raw_mode();
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminalGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = disable_raw_mode();
|
||||
let mut stdout = io::stdout();
|
||||
let _ = queue!(stdout, Show);
|
||||
let _ = stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
let args = Args::parse();
|
||||
let addr: SocketAddr = format!("{}:{}", args.host, args.port).parse().unwrap();
|
||||
|
||||
let _terminal = TerminalGuard::new();
|
||||
let state = Arc::new(Mutex::new(DeviceState::new()));
|
||||
let (input_tx, mut input_rx) = mpsc::channel::<InputEvent>(64);
|
||||
|
||||
|
|
@ -46,30 +67,64 @@ async fn main() -> io::Result<()> {
|
|||
tokio::spawn(ui_task(state.clone()));
|
||||
|
||||
let mut buf = [0u8; 1024];
|
||||
let sigint = tokio::signal::ctrl_c();
|
||||
tokio::pin!(sigint);
|
||||
|
||||
loop {
|
||||
let stream = match TcpStream::connect(addr).await {
|
||||
Ok(stream) => stream,
|
||||
let stream = loop {
|
||||
tokio::select! {
|
||||
_ = &mut sigint => {
|
||||
log_error("received SIGINT");
|
||||
return Ok(());
|
||||
}
|
||||
maybe_event = input_rx.recv() => {
|
||||
if matches!(maybe_event, Some(InputEvent::Quit) | None) {
|
||||
log_error("quitting");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
connect = TcpStream::connect(addr) => {
|
||||
match connect {
|
||||
Ok(stream) => break stream,
|
||||
Err(err) => {
|
||||
eprintln!("connect error: {err}");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
log_error(&format!("connect error: {err}"));
|
||||
tokio::select! {
|
||||
_ = &mut sigint => {
|
||||
log_error("received SIGINT");
|
||||
return Ok(());
|
||||
}
|
||||
maybe_event = input_rx.recv() => {
|
||||
if matches!(maybe_event, Some(InputEvent::Quit) | None) {
|
||||
log_error("quitting");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_secs(1)) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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() {
|
||||
log_error("failed to send device id");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut sigint => {
|
||||
log_error("received SIGINT");
|
||||
return Ok(());
|
||||
}
|
||||
maybe_event = input_rx.recv() => {
|
||||
match maybe_event {
|
||||
Some(InputEvent::Quit) | None => {
|
||||
let _ = disable_raw_mode();
|
||||
log_error("quitting");
|
||||
return Ok(());
|
||||
}
|
||||
Some(InputEvent::Button(id)) => {
|
||||
|
|
@ -77,6 +132,7 @@ async fn main() -> io::Result<()> {
|
|||
let data = WriteType::QuizResponse(value);
|
||||
let buffer = device_state::serialize_write(&data).unwrap();
|
||||
if write.write_all(buffer.as_bytes()).await.is_err() {
|
||||
log_error("write error while sending response");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -103,9 +159,15 @@ async fn main() -> io::Result<()> {
|
|||
}
|
||||
read_res = read.read(&mut buf) => {
|
||||
let len = match read_res {
|
||||
Ok(0) => break,
|
||||
Ok(0) => {
|
||||
log_error("server closed connection");
|
||||
break;
|
||||
}
|
||||
Ok(len) => len,
|
||||
Err(_) => break,
|
||||
Err(err) => {
|
||||
log_error(&format!("read error: {err}"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
let Ok(text) = core::str::from_utf8(&buf[..len]) else {
|
||||
continue;
|
||||
|
|
@ -121,7 +183,20 @@ async fn main() -> io::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
log_error("reconnecting in 500ms");
|
||||
tokio::select! {
|
||||
_ = &mut sigint => {
|
||||
log_error("received SIGINT");
|
||||
return Ok(());
|
||||
}
|
||||
maybe_event = input_rx.recv() => {
|
||||
if matches!(maybe_event, Some(InputEvent::Quit) | None) {
|
||||
log_error("quitting");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_millis(500)) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,16 +207,15 @@ fn spawn_input_thread(tx: mpsc::Sender<InputEvent>) {
|
|||
}
|
||||
|
||||
fn input_thread(tx: mpsc::Sender<InputEvent>) {
|
||||
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('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
Some(InputEvent::Quit)
|
||||
}
|
||||
KeyCode::Char('1') => Some(InputEvent::Button(1)),
|
||||
KeyCode::Char('2') => Some(InputEvent::Button(2)),
|
||||
KeyCode::Char('3') => Some(InputEvent::Button(3)),
|
||||
|
|
@ -165,11 +239,10 @@ fn input_thread(tx: mpsc::Sender<InputEvent>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = disable_raw_mode();
|
||||
}
|
||||
|
||||
async fn ui_task(state: Arc<Mutex<DeviceState>>) {
|
||||
let mut last = (String::new(), String::new());
|
||||
let mut ticker = interval(Duration::from_millis(50));
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
|
|
@ -182,11 +255,32 @@ async fn ui_task(state: Arc<Mutex<DeviceState>>) {
|
|||
}
|
||||
};
|
||||
|
||||
let rendered = (pad_16(line1.as_str()), pad_16(line2.as_str()));
|
||||
if rendered == last {
|
||||
continue;
|
||||
}
|
||||
last = rendered.clone();
|
||||
|
||||
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 _ = write!(stdout, "[lcd] {}\r\n", rendered.0);
|
||||
let _ = write!(stdout, "[lcd] {}\r\n", rendered.1);
|
||||
let _ = stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn pad_16(s: &str) -> String {
|
||||
let mut out = String::with_capacity(16);
|
||||
for ch in s.chars().take(16) {
|
||||
out.push(ch);
|
||||
}
|
||||
while out.len() < 16 {
|
||||
out.push(' ');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn log_error(msg: &str) {
|
||||
let mut stderr = io::stderr();
|
||||
let _ = write!(stderr, "[simulator] {msg}\r\n");
|
||||
let _ = stderr.flush();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue