Skip to content

Commit 1770143

Browse files
authored
Merge pull request #128 from Rahix/add-cli-parser
Add commandline argument parser
2 parents 4e6d2ac + 4f31e21 commit 1770143

File tree

6 files changed

+182
-15
lines changed

6 files changed

+182
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ All notable changes to the `Serial Monitor` crate will be documented in this fil
1111
* allow to plot only every n-th point (max points is 5000, if dataset is larger, it will reduce it by showing only every
1212
2nd point. if it is larger than 10000 only every 3rd point etc...)
1313
* Fixed the theme preference setting not being properly restored after restarting the application (
14-
thanks [@Rahinx](https://github.com/Rahix))
15-
* Fixed bug where you couldn't change the color and label of columns (thanks [@Rahinx](https://github.com/Rahix))
16-
* Fixed bug where for a single column of data the graph would never show (thanks [@Rahinx](https://github.com/Rahix))
14+
thanks [@Rahix](https://github.com/Rahix))
15+
* Fixed bug where you couldn't change the color and label of columns (thanks [@Rahix](https://github.com/Rahix))
16+
* Fixed bug where for a single column of data the graph would never show (thanks [@Rahix](https://github.com/Rahix))
17+
* Added a commandline interface for selecting the serial port and its settings.
1718

1819
## 0.3.4
1920

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ tempfile = { version = "3.15", optional = true }
2626
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "http2"], optional = true }
2727
semver = { version = "1.0.24", optional = true }
2828
crossbeam-channel = "0.5.14"
29+
gumdrop = "0.8.1"
2930

3031
[target.'cfg(not(target_os = "ios"))'.dependencies]
3132
eframe = { version = "0.32", features = ["persistence", "wayland", "x11"] }

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib
2626

2727
### Compile from source
2828

29-
The source code can be run using ```cargo run``` or bundled to a platform-executable using cargo bundle.
29+
The source code can be run using `cargo run` or bundled to a platform-executable using cargo bundle.
3030
Currently [cargo bundle](https://github.com/burtonageo/cargo-bundle) only supports linux and macOS
3131
bundles [see github issue](https://github.com/burtonageo/cargo-bundle/issues/77).
3232
As a work-around we can use [cargo wix](https://github.com/volks73/cargo-wix) to create a windows installer.
@@ -70,6 +70,39 @@ cargo install cargo-wix
7070
cargo wix
7171
```
7272

73+
## Commandline Arguments
74+
You can start the app with commandline arguments to automatically select a serial port and its settings:
75+
76+
```text
77+
Usage: serial-monitor-rust [OPTIONS]
78+
79+
Positional arguments:
80+
device Serial port device to open on startup
81+
82+
Optional arguments:
83+
-b, --baudrate BAUDRATE Baudrate (default=9600)
84+
-d, --databits DATABITS Data bits (5, 6, 7, default=8)
85+
-f, --flow FLOW Flow conrol (hard, soft, default=none)
86+
-s, --stopbits STOPBITS Stop bits (default=1, 2)
87+
-p, --parity PARITY Parity (odd, even, default=none)
88+
-F, --file FILE Load data from a file instead of a serial port
89+
--column COLUMN-LABELS Column labels, can be specified multiple times for more columns
90+
--color COLUMN-COLORS Column colors (hex color without #), can be specified multiple times for more columns
91+
-h, --help
92+
```
93+
94+
Example usage:
95+
96+
```sh
97+
serial-monitor-rust /dev/ttyACM0 --baudrate 115200
98+
```
99+
100+
You can also preconfigure the column settings. The following example configures the name and color for two columns in the incoming data:
101+
102+
```sh
103+
serial-monitor-rust --column Raw --color '808080' --column Temperature --color 'ff8000' /dev/ttyACM0
104+
```
105+
73106
## Features:
74107

75108
- [X] Plotting and printing of data simultaneously
@@ -110,4 +143,4 @@ Tested on:
110143
- Windows 10 x86
111144
- ...
112145

113-
One might have to delete the ```Cargo.lock``` file before compiling.
146+
One might have to delete the `Cargo.lock` file before compiling.

src/gui.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub struct MyApp {
157157
show_warning_window: WindowFeedback,
158158
do_not_show_clear_warning: bool,
159159
init: bool,
160+
cli_column_colors: Vec<egui::Color32>,
160161
#[cfg(feature = "self_update")]
161162
new_release: Option<Release>,
162163
}
@@ -176,6 +177,7 @@ impl MyApp {
176177
load_names_rx: Receiver<Vec<String>>,
177178
send_tx: Sender<String>,
178179
gui_cmd_tx: Sender<GuiCommand>,
180+
cli_column_colors: Vec<egui::Color32>,
179181
) -> Self {
180182
let mut file_dialog = FileDialog::default()
181183
//.initial_directory(PathBuf::from("/path/to/app"))
@@ -256,6 +258,7 @@ impl MyApp {
256258
init: false,
257259
show_color_window: ColorWindow::NoShow,
258260
file_opened: false,
261+
cli_column_colors,
259262
#[cfg(feature = "self_update")]
260263
new_release: None,
261264
settings_window_open: false,
@@ -325,9 +328,15 @@ impl MyApp {
325328
}
326329
if self.colors.len() != self.labels.len() {
327330
self.colors = (0..max(self.labels.len(), 1))
328-
.map(|i| COLORS[i % COLORS.len()])
331+
.map(|i| {
332+
self.cli_column_colors
333+
.get(i)
334+
.copied()
335+
.unwrap_or(COLORS[i % COLORS.len()])
336+
})
329337
.collect();
330-
self.color_vals = (0..max(self.data.plots.len(), 1)).map(|_| 0.0).collect();
338+
self.color_vals =
339+
(0..max(self.data.plots.len(), 1)).map(|_| 0.0).collect();
331340
}
332341
}
333342

src/main.rs

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ use crossbeam_channel::{select, Receiver, Sender};
1313
use eframe::egui::{vec2, ViewportBuilder};
1414
use eframe::{egui, icon_data};
1515
use egui_plot::PlotPoint;
16+
pub use gumdrop::Options;
1617
use preferences::AppInfo;
1718
use std::cmp::max;
1819
use std::path::PathBuf;
1920
use std::sync::{Arc, RwLock};
21+
use std::thread;
2022
use std::time::Duration;
21-
use std::{env, thread};
2223

2324
mod color_picker;
2425
mod custom_highlighter;
@@ -76,6 +77,7 @@ fn main_thread(
7677
load_rx: Receiver<PathBuf>,
7778
load_names_tx: Sender<Vec<String>>,
7879
gui_cmd_rx: Receiver<GuiCommand>,
80+
cli_column_labels: Vec<String>,
7981
) {
8082
// reads data from mutex, samples and saves if needed
8183
let mut data = DataContainer::default();
@@ -110,7 +112,7 @@ fn main_thread(
110112
data.dataset = vec![vec![]; max(split_data.len(), 1)];
111113
if let Ok(mut gui_data) = data_lock.write() {
112114
gui_data.plots = (0..max(split_data.len(), 1))
113-
.map(|i| (format!("Column {i}"), vec![]))
115+
.map(|i| (cli_column_labels.get(i).cloned().unwrap_or_else(|| format!("Column {i}")), vec![]))
114116
.collect();
115117
}
116118
failed_format_counter = 0;
@@ -262,13 +264,114 @@ fn main_thread(
262264
}
263265
}
264266

267+
fn parse_databits(s: &str) -> Result<serialport::DataBits, String> {
268+
let d: u8 = s
269+
.parse()
270+
.map_err(|_e| format!("databits not a number: {s}"))?;
271+
Ok(serialport::DataBits::try_from(d).map_err(|_e| format!("invalid databits: {s}"))?)
272+
}
273+
274+
fn parse_flow(s: &str) -> Result<serialport::FlowControl, String> {
275+
match s {
276+
"none" => Ok(serialport::FlowControl::None),
277+
"soft" => Ok(serialport::FlowControl::Software),
278+
"hard" => Ok(serialport::FlowControl::Hardware),
279+
_ => Err(format!("invalid flow-control: {s}")),
280+
}
281+
}
282+
283+
fn parse_stopbits(s: &str) -> Result<serialport::StopBits, String> {
284+
let d: u8 = s
285+
.parse()
286+
.map_err(|_e| format!("stopbits not a number: {s}"))?;
287+
Ok(serialport::StopBits::try_from(d).map_err(|_e| format!("invalid stopbits: {s}"))?)
288+
}
289+
290+
fn parse_parity(s: &str) -> Result<serialport::Parity, String> {
291+
match s {
292+
"none" => Ok(serialport::Parity::None),
293+
"odd" => Ok(serialport::Parity::Odd),
294+
"even" => Ok(serialport::Parity::Even),
295+
_ => Err(format!("invalid parity setting: {s}")),
296+
}
297+
}
298+
299+
fn parse_color(s: &str) -> Result<egui::Color32, String> {
300+
Ok(egui::ecolor::HexColor::from_str_without_hash(s)
301+
.map_err(|e| format!("invalid color {s:?}: {e:?}"))?
302+
.color())
303+
}
304+
305+
#[derive(Debug, Options)]
306+
struct CliOptions {
307+
/// Serial port device to open on startup
308+
#[options(free)]
309+
device: Option<String>,
310+
311+
/// Baudrate (default=9600)
312+
#[options(short = "b")]
313+
baudrate: Option<u32>,
314+
315+
/// Data bits (5, 6, 7, default=8)
316+
#[options(short = "d", parse(try_from_str = "parse_databits"))]
317+
databits: Option<serialport::DataBits>,
318+
319+
/// Flow conrol (hard, soft, default=none)
320+
#[options(short = "f", parse(try_from_str = "parse_flow"))]
321+
flow: Option<serialport::FlowControl>,
322+
323+
/// Stop bits (default=1, 2)
324+
#[options(short = "s", parse(try_from_str = "parse_stopbits"))]
325+
stopbits: Option<serialport::StopBits>,
326+
327+
/// Parity (odd, even, default=none)
328+
#[options(short = "p", parse(try_from_str = "parse_parity"))]
329+
parity: Option<serialport::Parity>,
330+
331+
/// Load data from a file instead of a serial port
332+
#[options(short = "F")]
333+
file: Option<std::path::PathBuf>,
334+
335+
/// Column labels, can be specified multiple times for more columns
336+
#[options(no_short, long = "column")]
337+
column_labels: Vec<String>,
338+
339+
/// Column colors (hex color without #), can be specified multiple times for more columns
340+
#[options(no_short, long = "color", parse(try_from_str = "parse_color"))]
341+
column_colors: Vec<egui::Color32>,
342+
343+
help: bool,
344+
}
345+
265346
fn main() {
266347
egui_logger::builder().init().unwrap();
267348

349+
let args = CliOptions::parse_args_default_or_exit();
350+
268351
let gui_settings = load_gui_settings();
269352
let saved_serial_device_configs = load_serial_settings();
270353

271-
let device_lock = Arc::new(RwLock::new(Device::default()));
354+
let mut device = Device::default();
355+
if let Some(name) = args.device {
356+
device.name = name;
357+
}
358+
if let Some(baudrate) = args.baudrate {
359+
device.baud_rate = baudrate;
360+
}
361+
if let Some(databits) = args.databits {
362+
device.data_bits = databits;
363+
}
364+
if let Some(flow) = args.flow {
365+
device.flow_control = flow;
366+
}
367+
if let Some(stopbits) = args.stopbits {
368+
device.stop_bits = stopbits;
369+
}
370+
if let Some(parity) = args.parity {
371+
device.parity = parity;
372+
}
373+
374+
let device_lock = Arc::new(RwLock::new(device));
272375
let devices_lock = Arc::new(RwLock::new(vec![gui_settings.device.clone()]));
273376
let data_lock = Arc::new(RwLock::new(GuiOutputDataContainer::default()));
274377
let connected_lock = Arc::new(RwLock::new(false));
@@ -316,14 +419,12 @@ fn main() {
316419
load_rx,
317420
loaded_names_tx,
318421
gui_cmd_rx,
422+
args.column_labels,
319423
);
320424
});
321425

322-
let args: Vec<String> = env::args().collect();
323-
if args.len() > 1 {
324-
load_tx
325-
.send(PathBuf::from(&args[1]))
326-
.expect("failed to send file");
426+
if let Some(file) = args.file {
427+
load_tx.send(file).expect("failed to send file");
327428
}
328429

329430
let options = eframe::NativeOptions {
@@ -372,6 +473,7 @@ fn main() {
372473
loaded_names_rx,
373474
send_tx,
374475
gui_cmd_tx,
476+
args.column_colors,
375477
)))
376478
}),
377479
) {

0 commit comments

Comments
 (0)