lesavka/client/src/bin/lesavka-relayctl.rs

141 lines
4.2 KiB
Rust
Raw Normal View History

use anyhow::{Context, Result, bail};
use lesavka_common::lesavka::{
CapturePowerCommand, Empty, SetCapturePowerRequest, relay_client::RelayClient,
};
use tonic::{Request, transport::Channel};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CommandKind {
Status,
Auto,
On,
Off,
2026-04-20 08:38:26 -03:00
ResetUsb,
}
impl CommandKind {
fn parse(value: &str) -> Option<Self> {
match value {
"status" | "get" => Some(Self::Status),
"auto" => Some(Self::Auto),
"on" | "force-on" => Some(Self::On),
"off" | "force-off" => Some(Self::Off),
2026-04-20 08:38:26 -03:00
"reset-usb" | "recover-usb" => Some(Self::ResetUsb),
_ => None,
}
}
}
struct Config {
server: String,
command: CommandKind,
}
fn usage() -> &'static str {
2026-04-20 08:38:26 -03:00
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|auto|on|off|reset-usb>"
}
fn parse_args() -> Result<Config> {
let mut args = std::env::args().skip(1);
let mut server = "http://127.0.0.1:50051".to_string();
let mut command = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"--server" => {
server = args
.next()
.context("missing value after --server")?
.trim()
.to_string();
}
"--help" | "-h" => {
println!("{}", usage());
std::process::exit(0);
}
_ if command.is_none() => {
command = CommandKind::parse(arg.as_str());
if command.is_none() {
bail!("unknown command `{arg}`\n{}", usage());
}
}
_ => bail!("unexpected argument `{arg}`\n{}", usage()),
}
}
Ok(Config {
server,
command: command.unwrap_or(CommandKind::Status),
})
}
async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
let channel = Channel::from_shared(server_addr.to_string())
.context("invalid relay server address")?
.tcp_nodelay(true)
.connect()
.await
.with_context(|| format!("connecting to relay at {server_addr}"))?;
Ok(RelayClient::new(channel))
}
fn print_state(state: lesavka_common::lesavka::CapturePowerState) {
println!("available={}", state.available);
println!("enabled={}", state.enabled);
println!("mode={}", state.mode);
2026-04-20 01:41:57 -03:00
println!("detected_devices={}", state.detected_devices);
println!("active_leases={}", state.active_leases);
println!("unit={}", state.unit);
println!("detail={}", state.detail);
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let config = parse_args()?;
let mut client = connect(config.server.as_str()).await?;
let reply = match config.command {
CommandKind::Status => client
.get_capture_power(Request::new(Empty {}))
.await
.context("querying capture power state")?
.into_inner(),
CommandKind::Auto => client
.set_capture_power(Request::new(SetCapturePowerRequest {
enabled: false,
command: CapturePowerCommand::Auto as i32,
}))
.await
.context("setting capture power to auto")?
.into_inner(),
CommandKind::On => client
.set_capture_power(Request::new(SetCapturePowerRequest {
enabled: true,
command: CapturePowerCommand::ForceOn as i32,
}))
.await
.context("forcing capture power on")?
.into_inner(),
CommandKind::Off => client
.set_capture_power(Request::new(SetCapturePowerRequest {
enabled: false,
command: CapturePowerCommand::ForceOff as i32,
}))
.await
.context("forcing capture power off")?
.into_inner(),
2026-04-20 08:38:26 -03:00
CommandKind::ResetUsb => {
let reply = client
.reset_usb(Request::new(Empty {}))
.await
.context("forcing USB gadget recovery")?
.into_inner();
println!("ok={}", reply.ok);
return Ok(());
}
};
print_state(reply);
Ok(())
}