• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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