use easycom::{Command, Response, Session, Transport};
use std::io::{self, BufRead, Read, Write};
use std::net::TcpStream;
use std::time::Duration;
struct TcpTransport(TcpStream);
impl Transport for TcpTransport {
type Error = io::Error;
fn write(&mut self, frame: &[u8]) -> Result<(), Self::Error> {
self.0.write_all(frame)
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.0.read(buf)
}
}
#[cfg(feature = "std")]
struct SerialTransport(Box<dyn serialport::SerialPort>);
#[cfg(feature = "std")]
impl Transport for SerialTransport {
type Error = io::Error;
fn write(&mut self, frame: &[u8]) -> Result<(), Self::Error> {
self.0.write_all(frame)
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.0.read(buf)
}
}
fn run_repl<T: Transport>(mut session: Session<T>)
where
T::Error: std::fmt::Display,
{
let stdin = io::stdin();
print_help();
print!("> ");
io::stdout().flush().ok();
for line in stdin.lock().lines() {
let line = match line {
Ok(l) => l,
Err(e) => {
eprintln!("Read error: {e}");
break;
}
};
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
print!("> ");
io::stdout().flush().ok();
continue;
}
let cmd = match parts[0] {
"az" => {
let n: u16 = parse_arg(&parts, 1, "az <0-360>");
Command::Azimuth(n)
}
"el" => {
let n: u16 = parse_arg(&parts, 1, "el <0-180>");
Command::Elevation(n)
}
"pos" => {
let az: u16 = parse_arg(&parts, 1, "pos <az> <el>");
let el: u16 = parse_arg(&parts, 2, "pos <az> <el>");
Command::AzimuthElevation { az, el }
}
"query" => Command::QueryPosition,
"status" => Command::QueryStatus,
"stop" => Command::Stop,
"keepalive" => Command::KeepAlive,
"help" => {
print_help();
print!("> ");
io::stdout().flush().ok();
continue;
}
"quit" | "exit" | "q" => break,
other => {
eprintln!("Unknown command: {other}. Type 'help'.");
print!("> ");
io::stdout().flush().ok();
continue;
}
};
match session.send(cmd) {
Ok(Response::Ack) => println!("OK"),
Ok(Response::Position { az, el }) => println!("AZ={az:03} EL={el:03}"),
Ok(Response::AzimuthPosition(az)) => println!("AZ={az:03}"),
Ok(Response::ElevationPosition(el)) => println!("EL={el:03}"),
Ok(Response::Status(status)) => println!("Status: {status:?}"),
Ok(Response::StatusRegister(val)) => println!("Status register: {val}"),
Ok(Response::ErrorRegister(val)) => println!("Error register: {val}"),
Ok(Response::ConfigValue { register, value }) => {
println!("Config[{register}] = {value}")
}
Ok(Response::Error) => println!("Device error (?)"),
Err(e) => eprintln!("Error: {e}"),
}
print!("> ");
io::stdout().flush().ok();
}
}
fn parse_arg<T: std::str::FromStr>(parts: &[&str], idx: usize, usage: &str) -> T {
parts
.get(idx)
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| {
eprintln!("Usage: {usage}");
std::process::exit(1);
})
}
fn print_help() {
println!(
"Commands: az <n> el <n> pos <az> <el> query status stop keepalive help quit"
);
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let mode = args.get(1).map(String::as_str).unwrap_or("tcp");
match mode {
"tcp" => {
let addr = args.get(2).map(String::as_str).unwrap_or("127.0.0.1:4533");
let stream = TcpStream::connect(addr).unwrap_or_else(|e| {
eprintln!("Failed to connect to {addr}: {e}");
std::process::exit(1);
});
stream
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
println!("Connected to {addr}");
run_repl(Session::new(TcpTransport(stream)));
}
"serial" => {
let port_name = args.get(2).map(String::as_str).unwrap_or("/dev/ttyUSB0");
let baud: u32 = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(9600);
let port = serialport::new(port_name, baud)
.timeout(Duration::from_secs(2))
.open()
.unwrap_or_else(|e| {
eprintln!("Failed to open {port_name}: {e}");
std::process::exit(1);
});
println!("Opened {port_name} at {baud} baud");
run_repl(Session::new(SerialTransport(port)));
}
other => {
eprintln!("Unknown mode: {other}. Use 'tcp' or 'serial'.");
std::process::exit(1);
}
}
}