Skip to content

Commit ebbb965

Browse files
authored
Merge pull request #18 from twinleaf/qol
feat(twinleaf-tools, twinleaf): QOL improvements for tio CLI and device workflows
2 parents 3bc136d + 9642a56 commit ebbb965

32 files changed

Lines changed: 4254 additions & 2344 deletions

Cargo.lock

Lines changed: 231 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

twinleaf-tools/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "twinleaf-tools"
3-
version = "2.1.0"
3+
version = "2.2.0"
44
edition = "2021"
55
license = "MIT"
66
description = "Tools for the Twinleaf I/O protocol for reading data from Twinleaf quantum sensors."
@@ -16,13 +16,19 @@ hdf5 = ["twinleaf/hdf5"]
1616
crossbeam = "0.8"
1717
serialport = "4.5"
1818
toml_edit = {version = "0.25", features = ["parse"]}
19-
twinleaf = {version = "1.7", path = "../twinleaf" }
19+
twinleaf = {version = "1.8", path = "../twinleaf" }
2020
time = {version = "0.3", features = ["local-offset", "macros", "formatting"]}
2121
clap = { version = "4.5", features = ["derive"] }
2222
ratatui = "0.30"
2323
tui-prompts = "0.6"
2424
welch-sde = "0.1"
2525
memmap2 = "0.9"
2626
indicatif = "0.18"
27+
console = "0.16"
2728
chrono = "0.4"
2829
clap_complete = "4.5"
30+
eyre = "0.6"
31+
color-eyre = "0.6"
32+
termtree = "1"
33+
nucleo-matcher = "0.3"
34+
humantime = "2.2"

twinleaf-tools/README.md

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,43 @@
22

33
Command-line tools for working with Twinleaf quantum sensors and accessories. Contains a proxy, terminal UIs, and command line utilities.
44

5-
**Note**: In versions <2.0.0, this crate contained binaries named `tio-proxy`, `tio-monitor`, `tio-health`, and `tio-tool`. These commands have been packaged into subcommands under the single binary `tio`. The former three original commands can be simply used without the `-`, while `tio-tool {toolname}` calls have largely been replaced with `tio {toolname}`.
5+
**Note**: In versions <2.0.0, this crate contained binaries named `tio-proxy`, `tio-monitor`, `tio-health`, and `tio-tool`. These commands are now packaged as subcommands under a singular binary `tio`. These commmands are now used without the `-`, with `tio-tool {toolname}` calls having been replaced with `tio {toolname}`.
66

7-
## CLI Usage
8-
All the tools mentioned can have the `--help` argument added to display more information. The general workflow is to connect using `tio proxy` which allows all the CLI tools to work with the single device at the same time.
7+
### Shell completions
8+
9+
`tio` can generate completion scripts for bash, zsh, fish, and PowerShell. Add the matching line to your shell's config file:
10+
11+
# Bash (~/.bashrc)
12+
eval "$(tio completions bash)"
13+
14+
# Zsh (~/.zshrc)
15+
eval "$(tio completions zsh)"
16+
17+
# Fish (~/.config/fish/config.fish)
18+
tio completions fish | source
19+
20+
# PowerShell ($PROFILE)
21+
tio completions powershell | Invoke-Expression
22+
23+
Run `tio completions --help` to see the full list of supported shells. Zsh users may need to prepend `autoload -Uz compinit && compinit`.
924

1025
### Connecting to the device
1126

12-
The proxy makes a device attached via serial port available via Ethernet. The following will automatically scan for a `twinleaf` serial device:
27+
The general workflow is to connect using `tio proxy` which allows all the CLI tools to work with the single device at the same time. The proxy makes a device attached via serial port available via Ethernet. With no arguments it automatically scans for a `twinleaf` serial device:
1328

14-
tio proxy --auto
29+
tio proxy
1530

16-
When there are more than one serial port available, it is necessary to specify the port using:
31+
When more than one serial port is available, specify the URL:
1732

18-
[linux] tio proxy -r /dev/ttyACM0
19-
[macOS] tio proxy -r /dev/cu.usbserialXXXXXX
20-
[wsl1] tio proxy -r COM3
33+
[linux] tio proxy serial:///dev/ttyACM0
34+
[macOS] tio proxy serial:///dev/cu.usbserialXXXXXX
35+
[wsl1] tio proxy serial://COM3
2136

2237
The proxy allows multiple tools to connect to the device over TCP simultaneously.
2338

24-
When a sensor is attached to a hub at port `0`, it is possible to proxy the data directly to that port using the `-s` flag:
39+
When a sensor is attached to a hub at port `0`, restrict the proxy's subtree using the `-s` flag:
2540

26-
tio proxy --auto -s /0
41+
tio proxy -s /0
2742

2843
### Interacting with the device in terminal
2944

@@ -58,7 +73,7 @@ Device commands:
5873

5974
### tio monitor
6075

61-
Displays a live stream of incoming data with in-terminal graphs and command suggestions. Graph a stream by selecting it with the arrow keys and pressing Enter, and enter command mode by typing `:` (colon). Use `tio monitor -s {port}` to specify which port to open or `tio monitor -a` to open all ports.
76+
Displays a live stream of incoming data with in-terminal graphs and command suggestions. Graph a stream by selecting it with the arrow keys and pressing Enter, and enter command mode by typing `:` (colon). Use `tio monitor -s {route}` to scope the view to a single device subtree (e.g. `-s /0/1`), or `--depth N` to limit how deep the device tree is traversed.
6277

6378
Run with:
6479

twinleaf-tools/src/bin/tio.rs

Lines changed: 87 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
11
use clap::{CommandFactory, Parser};
2-
use std::process::ExitCode;
32
use twinleaf_tools::tools::{
4-
tio_health::run_health,
5-
tio_monitor::run_monitor,
6-
tio_proxy::run_proxy,
7-
tio_text_proxy::run_text_proxy,
8-
tio_tool::{
9-
data_dump_all_deprecated, data_dump_deprecated, dump, firmware_upgrade, list_rpcs, log,
10-
log_csv, log_data_dump_deprecated, log_dump, log_hdf, log_metadata, meta_dump_deprecated,
11-
meta_reroute, rpc, rpc_dump,
3+
health::run_health,
4+
list::list_devices,
5+
monitor::run_monitor,
6+
proxy::run_proxy,
7+
proxy_nmea::run_nmea_proxy,
8+
tool::{
9+
dump, firmware_upgrade, list_rpcs, log, log_csv, log_dump, log_hdf, log_inspect,
10+
log_metadata, meta_reroute, rpc, rpc_dump,
1211
},
1312
};
14-
use twinleaf_tools::{Commands, DumpSubcommands, LogSubcommands, RPCSubcommands, TioCli};
13+
use twinleaf_tools::{
14+
Commands, LogSubcommands, MetaSubcommands, ProxySubcommands, RPCSubcommands, TioCli,
15+
};
1516

16-
fn main() -> ExitCode {
17+
fn main() -> eyre::Result<()> {
18+
color_eyre::config::HookBuilder::default()
19+
.display_env_section(false)
20+
.display_location_section(false)
21+
.install()?;
1722
let cli = TioCli::parse();
1823

19-
//TODO: Work on exit code logic
20-
let result = match cli.command {
21-
Commands::Proxy(proxy_cli) => run_proxy(proxy_cli),
24+
match cli.command {
25+
Commands::List { all } => list_devices(all),
26+
Commands::Proxy(mut proxy_cli) => match proxy_cli.subcommands.take() {
27+
Some(ProxySubcommands::Nmea { tio, tcp_port }) => run_nmea_proxy(tio, tcp_port),
28+
None => run_proxy(proxy_cli),
29+
},
2230
Commands::Monitor {
2331
tio,
24-
all,
2532
fps,
2633
colors,
27-
} => run_monitor(tio, all, fps, colors),
34+
depth,
35+
} => run_monitor(tio, fps, colors, depth),
2836
Commands::Health(health_cli) => run_health(health_cli),
29-
Commands::NmeaProxy { tio, tcp_port } => run_text_proxy(tio, tcp_port),
3037
Commands::Rpc {
3138
tio,
3239
subcommands,
@@ -35,90 +42,82 @@ fn main() -> ExitCode {
3542
req_type,
3643
rep_type,
3744
debug,
38-
} => {
39-
let _ = match subcommands {
40-
Some(RPCSubcommands::List { tio }) => list_rpcs(&tio),
41-
Some(RPCSubcommands::Dump {
42-
tio,
43-
rpc_name,
44-
capture,
45-
}) => rpc_dump(&tio, rpc_name, capture),
46-
None => rpc(
47-
&tio,
48-
rpc_name.unwrap_or("".to_string()),
49-
rpc_arg,
50-
req_type,
51-
rep_type,
52-
debug,
53-
),
54-
};
55-
Ok(())
56-
}
45+
} => match subcommands {
46+
Some(RPCSubcommands::List { tio }) => list_rpcs(&tio),
47+
Some(RPCSubcommands::Dump {
48+
tio,
49+
rpc_name,
50+
capture,
51+
}) => rpc_dump(&tio, rpc_name, capture),
52+
None => rpc(
53+
&tio,
54+
rpc_name.unwrap_or("".to_string()),
55+
rpc_arg,
56+
req_type,
57+
rep_type,
58+
debug,
59+
),
60+
},
5761
Commands::Dump {
5862
tio,
59-
subcommands,
6063
data,
6164
meta,
6265
depth,
63-
} => {
64-
let _ = match subcommands {
65-
Some(DumpSubcommands::Data { tio }) => data_dump_deprecated(&tio),
66-
Some(DumpSubcommands::DataAll { tio }) => data_dump_all_deprecated(&tio),
67-
Some(DumpSubcommands::Meta { tio }) => meta_dump_deprecated(&tio),
68-
None => dump(&tio, data, meta, depth),
69-
};
70-
Ok(())
71-
}
66+
} => dump(&tio, data, meta, depth),
7267
Commands::Log {
7368
tio,
7469
subcommands,
7570
file,
7671
unbuffered,
7772
raw,
7873
depth,
79-
} => {
80-
let _ = match subcommands {
81-
Some(LogSubcommands::Metadata { tio, file }) => log_metadata(&tio, file),
82-
Some(LogSubcommands::Dump {
83-
files,
84-
data,
85-
meta,
86-
sensor,
87-
depth,
88-
}) => log_dump(files, data, meta, sensor, depth),
89-
Some(LogSubcommands::DataDump { files }) => log_data_dump_deprecated(files),
90-
Some(LogSubcommands::Csv {
91-
args,
92-
sensor,
93-
output,
94-
}) => log_csv(args, sensor, output),
95-
Some(LogSubcommands::Hdf {
96-
files,
97-
output,
98-
filter,
99-
compress,
100-
debug,
101-
split_level,
102-
split_policy,
103-
}) => log_hdf(
104-
files,
74+
duration,
75+
} => match subcommands {
76+
Some(LogSubcommands::Meta {
77+
tio,
78+
subcommands,
79+
file,
80+
}) => match subcommands {
81+
Some(MetaSubcommands::Reroute {
82+
input,
83+
route,
10584
output,
106-
filter,
107-
compress,
108-
debug,
109-
split_level,
110-
split_policy,
111-
),
112-
None => log(&tio, file, unbuffered, raw, depth),
113-
};
114-
Ok(())
115-
}
116-
Commands::MetaReroute {
117-
input,
118-
route,
119-
output,
120-
} => meta_reroute(input, route, output),
121-
Commands::FirmwareUpgrade {
85+
}) => meta_reroute(input, route, output),
86+
None => log_metadata(&tio, file),
87+
},
88+
Some(LogSubcommands::Dump {
89+
files,
90+
data,
91+
meta,
92+
sensor,
93+
depth,
94+
}) => log_dump(files, data, meta, sensor, depth),
95+
Some(LogSubcommands::Inspect { files }) => log_inspect(files),
96+
Some(LogSubcommands::Csv {
97+
args,
98+
sensor,
99+
output,
100+
}) => log_csv(args, sensor, output),
101+
Some(LogSubcommands::Hdf {
102+
files,
103+
output,
104+
filter,
105+
compress,
106+
debug,
107+
split_level,
108+
split_policy,
109+
}) => log_hdf(
110+
files,
111+
output,
112+
filter,
113+
compress,
114+
debug,
115+
split_level,
116+
split_policy,
117+
),
118+
None => log(&tio, file, unbuffered, raw, depth, duration),
119+
},
120+
Commands::Upgrade {
122121
tio,
123122
firmware_path,
124123
yes,
@@ -127,12 +126,5 @@ fn main() -> ExitCode {
127126
clap_complete::generate(shell, &mut TioCli::command(), "tio", &mut std::io::stdout());
128127
Ok(())
129128
}
130-
};
131-
132-
if result.is_ok() {
133-
ExitCode::SUCCESS
134-
} else {
135-
eprintln!("FAILED");
136-
ExitCode::FAILURE
137129
}
138130
}

twinleaf-tools/src/lib.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
use clap::Parser;
2+
use std::path::PathBuf;
23
use std::time::Duration;
34
use tio::proto::DeviceRoute;
45
use tio::util;
56
use twinleaf::tio;
67
pub mod tools;
8+
pub mod tui;
79
include!("tio_cli.rs");
810

11+
fn parse_device_route(s: &str) -> Result<DeviceRoute, String> {
12+
DeviceRoute::from_str(s).map_err(|_| format!("invalid sensor route: {s:?}"))
13+
}
14+
15+
fn parse_existing_file(s: &str) -> Result<PathBuf, String> {
16+
let p = PathBuf::from(s);
17+
match std::fs::metadata(&p) {
18+
Ok(m) if m.is_file() => Ok(p),
19+
Ok(_) => Err(format!("not a regular file: {s:?}")),
20+
Err(e) => Err(format!("cannot read {s:?}: {e}")),
21+
}
22+
}
23+
924
#[derive(Parser, Debug, Clone)]
1025
pub struct TioOpts {
1126
/// Sensor root address (e.g., tcp://localhost, serial:///dev/ttyUSB0)
1227
#[arg(
1328
short = 'r',
1429
long = "root",
1530
default_value_t = util::default_proxy_url().to_string(),
31+
value_hint = clap::ValueHint::Url,
1632
help = "Sensor root address"
1733
)]
1834
pub root: String,
@@ -22,13 +38,8 @@ pub struct TioOpts {
2238
short = 's',
2339
long = "sensor",
2440
default_value = "/",
41+
value_parser = parse_device_route,
2542
help = "Sensor path in the sensor tree"
2643
)]
27-
pub route_path: String,
28-
}
29-
30-
impl TioOpts {
31-
pub fn parse_route(&self) -> DeviceRoute {
32-
DeviceRoute::from_str(&self.route_path).unwrap_or_else(|_| DeviceRoute::root())
33-
}
44+
pub route: DeviceRoute,
3445
}

0 commit comments

Comments
 (0)