File: loopback.rs

package info (click to toggle)
rust-serialport 4.8.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 844 kB
  • sloc: makefile: 2
file content (272 lines) | stat: -rw-r--r-- 8,315 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
//! This example performs a loopback test using real hardware ports
//!
//! Additionally, some data will be collected and logged during the test to provide some
//! rudimentary benchmarking information. When 'split-port' is specified, the serial port will
//! be split into two channels that read/write "simultaneously" from multiple threads.
//!
//! You can also provide the length (in bytes) of data to test with, and the number of iterations to perform or
//! a list of raw bytes to transmit.
//!
//! To run this example:
//!
//! 1) `cargo run --example loopback /dev/ttyUSB0`
//!
//! 2) `cargo run --example loopback /dev/ttyUSB0 --split-port`
//!
//! 3) `cargo run --example loopback /dev/ttyUSB0 -i 100 -l 32 -b 9600`
//!
//! 4) `cargo run --example loopback /dev/ttyUSB8 --bytes 222,173,190,239`

use std::time::{Duration, Instant};

use clap::Parser;
use serialport::SerialPort;

/// Serialport Example - Loopback
#[derive(Parser)]
struct Args {
    /// The device path to a serialport
    port: String,

    /// The number of read/write iterations to perform
    #[clap(short, long, default_value = "100")]
    iterations: usize,

    /// The number of bytes written per transaction
    ///
    /// Ignored when bytes are passed directly from the command-line
    #[clap(short, long, default_value = "8")]
    length: usize,

    /// The baudrate to open the port with
    #[clap(short, long, default_value = "115200")]
    baudrate: u32,

    /// Bytes to write to the serial port
    ///
    /// When not specified, the bytes transmitted count up
    #[clap(long, use_value_delimiter = true)]
    bytes: Option<Vec<u8>>,

    /// Split the port to read/write from multiple threads
    #[clap(long)]
    split_port: bool,
}

fn main() {
    let args = Args::parse();

    // Open the serial port
    let mut port = match serialport::new(&args.port, args.baudrate)
        .timeout(Duration::MAX)
        .open()
    {
        Err(e) => {
            eprintln!("Failed to open \"{}\". Error: {}", args.port, e);
            ::std::process::exit(1);
        }
        Ok(p) => p,
    };

    // Setup stat-tracking
    let length = args.length;
    let data: Vec<u8> = args
        .bytes
        .unwrap_or_else(|| (0..length).map(|i| i as u8).collect());

    let (mut read_stats, mut write_stats) = Stats::new(args.iterations, &data);

    // Run the tests
    if args.split_port {
        loopback_split(&mut port, &mut read_stats, &mut write_stats);
    } else {
        loopback_standard(&mut port, &mut read_stats, &mut write_stats);
    }

    // Print the results
    println!("Loopback {}:", args.port);
    println!("  data-length: {} bytes", read_stats.data.len());
    println!("  iterations: {}", read_stats.iterations);
    println!("  read:");
    println!("    total: {:.6}s", read_stats.total());
    println!("    average: {:.6}s", read_stats.average());
    println!("    max: {:.6}s", read_stats.max());
    println!("  write:");
    println!("    total: {:.6}s", write_stats.total());
    println!("    average: {:.6}s", write_stats.average());
    println!("    max: {:.6}s", write_stats.max());
    println!("  total: {:.6}s", read_stats.total() + write_stats.total());
    println!(
        "  bytes/s: {:.6}",
        (read_stats.data.len() as f32) / (read_stats.average() + write_stats.average())
    )
}

/// Capture read/write times to calculate average durations
#[derive(Clone)]
struct Stats<'a> {
    pub data: &'a [u8],
    pub times: Vec<Duration>,
    pub iterations: usize,
    now: Instant,
}

impl<'a> Stats<'a> {
    /// Create new read/write stats
    fn new(iterations: usize, data: &'a [u8]) -> (Self, Self) {
        (
            Self {
                data,
                times: Vec::with_capacity(iterations),
                iterations,
                now: Instant::now(),
            },
            Self {
                data,
                times: Vec::with_capacity(iterations),
                iterations,
                now: Instant::now(),
            },
        )
    }

    /// Start a duration timer
    fn start(&mut self) {
        self.now = Instant::now();
    }

    /// Store a duration
    fn stop(&mut self) {
        self.times.push(self.now.elapsed());
    }

    /// Provides the total time elapsed
    fn total(&self) -> f32 {
        self.times.iter().map(|d| d.as_secs_f32()).sum()
    }

    /// Provides average time per transaction
    fn average(&self) -> f32 {
        self.total() / (self.times.len() as f32)
    }

    /// Provides the maximum transaction time
    fn max(&self) -> f32 {
        self.times
            .iter()
            .max()
            .map(|d| d.as_secs_f32())
            .unwrap_or(0.0)
    }
}

fn loopback_standard<'a>(
    port: &mut Box<dyn SerialPort>,
    read_stats: &mut Stats<'a>,
    write_stats: &mut Stats<'a>,
) {
    let mut buf = vec![0u8; read_stats.data.len()];

    for _ in 0..read_stats.iterations {
        // Write data to the port
        write_stats.start();
        port.write_all(write_stats.data)
            .expect("failed to write to serialport");
        write_stats.stop();

        // Read data back from the port
        read_stats.start();
        port.read_exact(&mut buf)
            .expect("failed to read from serialport");
        read_stats.stop();

        // Crash on error
        for (i, x) in buf.iter().enumerate() {
            if read_stats.data[i] != *x {
                eprintln!(
                    "Expected byte '{:02X}' but got '{:02X}'",
                    read_stats.data[i], x
                );
                ::std::process::exit(2);
            }
        }
    }
}

#[rustversion::before(1.63)]
fn loopback_split<'a>(
    _port: &mut Box<dyn SerialPort>,
    _read_stats: &mut Stats<'a>,
    _write_stats: &mut Stats<'a>,
) {
    unimplemented!("requires Rust 1.63 or later");
}

#[rustversion::since(1.63)]
fn loopback_split<'a>(
    port: &mut Box<dyn SerialPort>,
    read_stats: &mut Stats<'a>,
    write_stats: &mut Stats<'a>,
) {
    let mut buf = vec![0u8; read_stats.data.len()];
    let mut rport = match port.try_clone() {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to clone port: {}", e);
            ::std::process::exit(3);
        }
    };

    // Manage threads for read/writing; port usage is not async, so threads can easily deadlock:
    //
    // 1. Read Thread: Park -> Read -> Unpark Write ──────┐
    //                 └──────────────────────────────────┘
    // 2. Write Thread: Write -> Unpark Read -> Park ──────┐
    //                  └──────────────────────────────────┘
    std::thread::scope(|scope| {
        // Get handle for writing thread
        let wr_thread = std::thread::current();

        // Spawn a thread that reads data for n iterations
        let handle = scope.spawn(move || {
            for _ in 0..read_stats.iterations {
                // Wait for the write to complete
                std::thread::park();

                read_stats.start();
                rport
                    .read_exact(&mut buf)
                    .expect("failed to read from serialport");
                read_stats.stop();

                // Crash on error
                for (i, x) in buf.iter().enumerate() {
                    if read_stats.data[i] != *x {
                        eprintln!(
                            "Expected byte '{:02X}' but got '{:02X}'",
                            read_stats.data[i], x
                        );
                        ::std::process::exit(2);
                    }
                }

                // Allow the writing thread to start
                wr_thread.unpark();
            }
        });

        // Write data to the port for n iterations
        for _ in 0..write_stats.iterations {
            write_stats.start();
            port.write_all(write_stats.data)
                .expect("failed to write to serialport");
            write_stats.stop();

            // Notify that the write completed
            handle.thread().unpark();

            // Wait for read to complete
            std::thread::park();
        }
    });
}