twinleaf_tools/cli/
proxy.rs1use clap::{Parser, Subcommand, ValueHint};
2use twinleaf::device::DeviceRoute;
3
4use crate::{parse_device_route, TioOpts};
5
6#[derive(Parser, Debug)]
7#[command(
8 version,
9 about = "Multiplexes access to a sensor, exposing the functionality of tio::proxy via TCP",
10 args_conflicts_with_subcommands = true
11)]
12pub struct ProxyCli {
13 #[command(subcommand)]
14 pub subcommands: Option<ProxySubcommands>,
15
16 #[arg(value_hint = ValueHint::Url, conflicts_with = "mounts")]
18 pub(crate) sensor_url: Option<String>,
19
20 #[arg(long = "mount", value_name = "LOCATOR=/N", value_parser = parse_mount)]
22 pub(crate) mounts: Vec<MountArg>,
23
24 #[arg(short = 'p', long = "port", default_value = "7855")]
26 pub(crate) port: u16,
27
28 #[arg(short = 'k', long)]
30 pub(crate) kick_slow: bool,
31
32 #[arg(
34 short = 's',
35 long = "subtree",
36 default_value = "/",
37 value_parser = parse_device_route,
38 conflicts_with = "mounts",
39 )]
40 pub(crate) subtree: DeviceRoute,
41
42 #[arg(short = 'v', long)]
44 pub(crate) verbose: bool,
45
46 #[arg(short = 'd', long)]
48 pub(crate) debug: bool,
49
50 #[arg(
52 short = 't',
53 long = "timestamp",
54 default_value = "%T%.3f ",
55 hide = true
56 )]
57 pub(crate) timestamp_format: String,
58
59 #[arg(short = 'T', long = "timeout", default_value = "30")]
61 pub(crate) reconnect_timeout: u64,
62
63 #[arg(long)]
65 pub(crate) dump: bool,
66
67 #[arg(long)]
69 pub(crate) dump_data: bool,
70
71 #[arg(long)]
73 pub(crate) dump_meta: bool,
74
75 #[arg(long)]
77 pub(crate) dump_hb: bool,
78
79 #[arg(short = 'a', long = "auto", hide = true)]
81 pub(crate) auto: bool,
82
83 #[arg(short = 'e', long = "enumerate", name = "enum", hide = true)]
85 pub(crate) enumerate: bool,
86}
87
88#[derive(Debug, Clone)]
90pub struct MountArg {
91 pub locator: String,
92 pub prefix: DeviceRoute,
93}
94
95fn parse_mount(s: &str) -> Result<MountArg, String> {
96 let Some((locator, prefix_str)) = s.split_once('=') else {
97 return Err(format!(
98 "expected LOCATOR=/N (e.g. serial:///dev/ttyUSB0=/1), got {s:?}"
99 ));
100 };
101 if locator.is_empty() {
102 return Err(format!("missing sensor locator before '=' in {s:?}"));
103 }
104 let prefix = DeviceRoute::from_str(prefix_str)
105 .map_err(|_| format!("invalid route prefix: {prefix_str:?}"))?;
106 if prefix.len() != 1 {
107 return Err(format!(
108 "mount prefix must be a single segment like /1, got {prefix_str:?}"
109 ));
110 }
111 Ok(MountArg {
112 locator: locator.to_string(),
113 prefix,
114 })
115}
116
117#[derive(Subcommand, Debug)]
118pub enum ProxySubcommands {
119 Nmea {
121 #[command(flatten)]
122 tio: TioOpts,
123
124 #[arg(short = 'p', long = "port", default_value = "7800")]
126 tcp_port: u16,
127 },
128}