1 //! This example performs a loopback test using real hardware ports
2 //!
3 //! Additionally, some data will be collected and logged during the test to provide some
4 //! rudimentary benchmarking information. When 'split-port' is specified, the serial port will
5 //! be split into two channels that read/write "simultaneously" from multiple threads.
6 //!
7 //! You can also provide the length (in bytes) of data to test with, and the number of iterations to perform or
8 //! a list of raw bytes to transmit.
9 //!
10 //! To run this example:
11 //!
12 //! 1) `cargo run --example loopback /dev/ttyUSB0`
13 //!
14 //! 2) `cargo run --example loopback /dev/ttyUSB0 --split-port`
15 //!
16 //! 3) `cargo run --example loopback /dev/ttyUSB0 -i 100 -l 32 -b 9600`
17 //!
18 //! 4) `cargo run --example loopback /dev/ttyUSB8 --bytes 222,173,190,239`
19
20 use std::time::{Duration, Instant};
21
22 use clap::Parser;
23 use serialport::SerialPort;
24
25 /// Serialport Example - Loopback
26 #[derive(Parser)]
27 struct Args {
28 /// The device path to a serialport
29 port: String,
30
31 /// The number of read/write iterations to perform
32 #[clap(short, long, default_value = "100")]
33 iterations: usize,
34
35 /// The number of bytes written per transaction
36 ///
37 /// Ignored when bytes are passed directly from the command-line
38 #[clap(short, long, default_value = "8")]
39 length: usize,
40
41 /// The baudrate to open the port with
42 #[clap(short, long, default_value = "115200")]
43 baudrate: u32,
44
45 /// Bytes to write to the serial port
46 ///
47 /// When not specified, the bytes transmitted count up
48 #[clap(long, use_value_delimiter = true)]
49 bytes: Option<Vec<u8>>,
50
51 /// Split the port to read/write from multiple threads
52 #[clap(long)]
53 split_port: bool,
54 }
55
main()56 fn main() {
57 let args = Args::parse();
58
59 // Open the serial port
60 let mut port = match serialport::new(&args.port, args.baudrate)
61 .timeout(Duration::MAX)
62 .open()
63 {
64 Err(e) => {
65 eprintln!("Failed to open \"{}\". Error: {}", args.port, e);
66 ::std::process::exit(1);
67 }
68 Ok(p) => p,
69 };
70
71 // Setup stat-tracking
72 let length = args.length;
73 let data: Vec<u8> = args
74 .bytes
75 .unwrap_or_else(|| (0..length).map(|i| i as u8).collect());
76
77 let (mut read_stats, mut write_stats) = Stats::new(args.iterations, &data);
78
79 // Run the tests
80 if args.split_port {
81 loopback_split(&mut port, &mut read_stats, &mut write_stats);
82 } else {
83 loopback_standard(&mut port, &mut read_stats, &mut write_stats);
84 }
85
86 // Print the results
87 println!("Loopback {}:", args.port);
88 println!(" data-length: {} bytes", read_stats.data.len());
89 println!(" iterations: {}", read_stats.iterations);
90 println!(" read:");
91 println!(" total: {:.6}s", read_stats.total());
92 println!(" average: {:.6}s", read_stats.average());
93 println!(" max: {:.6}s", read_stats.max());
94 println!(" write:");
95 println!(" total: {:.6}s", write_stats.total());
96 println!(" average: {:.6}s", write_stats.average());
97 println!(" max: {:.6}s", write_stats.max());
98 println!(" total: {:.6}s", read_stats.total() + write_stats.total());
99 println!(
100 " bytes/s: {:.6}",
101 (read_stats.data.len() as f32) / (read_stats.average() + write_stats.average())
102 )
103 }
104
105 /// Capture read/write times to calculate average durations
106 #[derive(Clone)]
107 struct Stats<'a> {
108 pub data: &'a [u8],
109 pub times: Vec<Duration>,
110 pub iterations: usize,
111 now: Instant,
112 }
113
114 impl<'a> Stats<'a> {
115 /// Create new read/write stats
new(iterations: usize, data: &'a [u8]) -> (Self, Self)116 fn new(iterations: usize, data: &'a [u8]) -> (Self, Self) {
117 (
118 Self {
119 data,
120 times: Vec::with_capacity(iterations),
121 iterations,
122 now: Instant::now(),
123 },
124 Self {
125 data,
126 times: Vec::with_capacity(iterations),
127 iterations,
128 now: Instant::now(),
129 },
130 )
131 }
132
133 /// Start a duration timer
start(&mut self)134 fn start(&mut self) {
135 self.now = Instant::now();
136 }
137
138 /// Store a duration
stop(&mut self)139 fn stop(&mut self) {
140 self.times.push(self.now.elapsed());
141 }
142
143 /// Provides the total time elapsed
total(&self) -> f32144 fn total(&self) -> f32 {
145 self.times.iter().map(|d| d.as_secs_f32()).sum()
146 }
147
148 /// Provides average time per transaction
average(&self) -> f32149 fn average(&self) -> f32 {
150 self.total() / (self.times.len() as f32)
151 }
152
153 /// Provides the maximum transaction time
max(&self) -> f32154 fn max(&self) -> f32 {
155 self.times
156 .iter()
157 .max()
158 .map(|d| d.as_secs_f32())
159 .unwrap_or(0.0)
160 }
161 }
162
loopback_standard<'a>( port: &mut Box<dyn SerialPort>, read_stats: &mut Stats<'a>, write_stats: &mut Stats<'a>, )163 fn loopback_standard<'a>(
164 port: &mut Box<dyn SerialPort>,
165 read_stats: &mut Stats<'a>,
166 write_stats: &mut Stats<'a>,
167 ) {
168 let mut buf = vec![0u8; read_stats.data.len()];
169
170 for _ in 0..read_stats.iterations {
171 // Write data to the port
172 write_stats.start();
173 port.write_all(write_stats.data)
174 .expect("failed to write to serialport");
175 write_stats.stop();
176
177 // Read data back from the port
178 read_stats.start();
179 port.read_exact(&mut buf)
180 .expect("failed to read from serialport");
181 read_stats.stop();
182
183 // Crash on error
184 for (i, x) in buf.iter().enumerate() {
185 if read_stats.data[i] != *x {
186 eprintln!(
187 "Expected byte '{:02X}' but got '{:02X}'",
188 read_stats.data[i], x
189 );
190 ::std::process::exit(2);
191 }
192 }
193 }
194 }
195
196 #[rustversion::before(1.63)]
loopback_split<'a>( _port: &mut Box<dyn SerialPort>, _read_stats: &mut Stats<'a>, _write_stats: &mut Stats<'a>, )197 fn loopback_split<'a>(
198 _port: &mut Box<dyn SerialPort>,
199 _read_stats: &mut Stats<'a>,
200 _write_stats: &mut Stats<'a>,
201 ) {
202 unimplemented!("requires Rust 1.63 or later");
203 }
204
205 #[rustversion::since(1.63)]
loopback_split<'a>( port: &mut Box<dyn SerialPort>, read_stats: &mut Stats<'a>, write_stats: &mut Stats<'a>, )206 fn loopback_split<'a>(
207 port: &mut Box<dyn SerialPort>,
208 read_stats: &mut Stats<'a>,
209 write_stats: &mut Stats<'a>,
210 ) {
211 let mut buf = vec![0u8; read_stats.data.len()];
212 let mut rport = match port.try_clone() {
213 Ok(p) => p,
214 Err(e) => {
215 eprintln!("Failed to clone port: {}", e);
216 ::std::process::exit(3);
217 }
218 };
219
220 // Manage threads for read/writing; port usage is not async, so threads can easily deadlock:
221 //
222 // 1. Read Thread: Park -> Read -> Unpark Write ──────┐
223 // └──────────────────────────────────┘
224 // 2. Write Thread: Write -> Unpark Read -> Park ──────┐
225 // └──────────────────────────────────┘
226 std::thread::scope(|scope| {
227 // Get handle for writing thread
228 let wr_thread = std::thread::current();
229
230 // Spawn a thread that reads data for n iterations
231 let handle = scope.spawn(move || {
232 for _ in 0..read_stats.iterations {
233 // Wait for the write to complete
234 std::thread::park();
235
236 read_stats.start();
237 rport
238 .read_exact(&mut buf)
239 .expect("failed to read from serialport");
240 read_stats.stop();
241
242 // Crash on error
243 for (i, x) in buf.iter().enumerate() {
244 if read_stats.data[i] != *x {
245 eprintln!(
246 "Expected byte '{:02X}' but got '{:02X}'",
247 read_stats.data[i], x
248 );
249 ::std::process::exit(2);
250 }
251 }
252
253 // Allow the writing thread to start
254 wr_thread.unpark();
255 }
256 });
257
258 // Write data to the port for n iterations
259 for _ in 0..write_stats.iterations {
260 write_stats.start();
261 port.write_all(write_stats.data)
262 .expect("failed to write to serialport");
263 write_stats.stop();
264
265 // Notify that the write completed
266 handle.thread().unpark();
267
268 // Wait for read to complete
269 std::thread::park();
270 }
271 });
272 }
273