| //! This example performs a test using real hardware ports. |
| //! |
| //! This tool serves as a debugging aid when encountering errors using `serialport-rs`. It should |
| //! expose any kernel or driver bugs that your system may have by running physical ports through |
| //! many configurations. |
| //! |
| //! There are 3 ways to run this example: |
| //! |
| //! 1) With a single port not connected to an external device: |
| //! `cargo run --example hardware_check /dev/ttyUSB0` |
| //! |
| //! 2) With a single port physically connected in loopback mode (RX<->TX) |
| //! `cargo run --example hardware_check /dev/ttyUSB0 --loopback` |
| //! |
| //! 3) With two ports physically connected to each other |
| //! `cargo run --example hardware_check /dev/ttyUSB0 /dev/ttyUSB1` |
| |
| use std::io::Write; |
| use std::str; |
| use std::time::Duration; |
| |
| use assert_hex::assert_eq_hex; |
| use clap::{Arg, Command}; |
| |
| use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPort, StopBits}; |
| |
| const TEST_MESSAGE: &[u8] = "Test Message".as_bytes(); |
| |
| fn main() { |
| let matches = Command::new("Serialport Example - Hardware Check") |
| .about("Test hardware capabilities of serial ports") |
| .disable_version_flag(true) |
| .arg(Arg::new("port") |
| .help("The device path to a serial port") |
| .use_value_delimiter(false) |
| .required(true)) |
| .arg(Arg::new("loopback") |
| .help("Run extra tests if the port is configured for hardware loopback. Mutually exclusive with the --loopback-port option") |
| .use_value_delimiter(false) |
| .conflicts_with("loopback-port") |
| .long("loopback")) |
| .arg(Arg::new("loopback-port") |
| .help("The device path of a second serial port that is connected to the first serial port. Mutually exclusive with the --loopback option.") |
| .use_value_delimiter(false) |
| .takes_value(true) |
| .long("loopback-port")) |
| .get_matches(); |
| |
| let port1_name = matches.value_of("port").unwrap(); |
| let port2_name = matches.value_of("loopback-port").unwrap_or(""); |
| let port1_loopback = matches.is_present("loopback"); |
| |
| // Loopback mode is only available when a single port is specified |
| if port1_loopback && !port2_name.is_empty() { |
| eprintln!("ERROR: loopback mode can only be enabled when a single port is specified."); |
| ::std::process::exit(1); |
| } |
| |
| // Run single-port tests on port1 |
| let mut port1 = match serialport::new(port1_name, 9600).open() { |
| Err(e) => { |
| eprintln!("Failed to open \"{}\". Error: {}", port1_name, e); |
| ::std::process::exit(1); |
| } |
| Ok(p) => p, |
| }; |
| test_single_port(&mut *port1, port1_loopback); |
| |
| if !port2_name.is_empty() { |
| // Run single-port tests on port2 |
| let mut port2 = match serialport::new(port2_name, 9600).open() { |
| Err(e) => { |
| eprintln!("Failed to open \"{}\". Error: {}", port2_name, e); |
| ::std::process::exit(1); |
| } |
| Ok(p) => p, |
| }; |
| test_single_port(&mut *port2, false); |
| |
| // Test loopback pair |
| test_dual_ports(&mut *port1, &mut *port2); |
| } |
| } |
| |
| macro_rules! baud_rate_check { |
| ($port:ident, $baud:expr) => { |
| let baud_rate = $baud; |
| if let Err(e) = $port.set_baud_rate(baud_rate) { |
| println!(" {:?}: FAILED ({})", baud_rate, e); |
| } |
| match $port.baud_rate() { |
| Err(_) => println!(" {:?}: FAILED (error retrieving baud rate)", baud_rate), |
| Ok(r) if r != baud_rate => println!( |
| " {:?}: FAILED (baud rate {:?} does not match set baud rate {:?})", |
| baud_rate, r, baud_rate |
| ), |
| Ok(_) => println!(" {:?}: success", baud_rate), |
| } |
| }; |
| } |
| |
| macro_rules! data_bits_check { |
| ($port:ident, $data_bits:path) => { |
| let data_bits = $data_bits; |
| if let Err(e) = $port.set_data_bits(data_bits) { |
| println!(" {:?}: FAILED ({})", data_bits, e); |
| } else { |
| match $port.data_bits() { |
| Err(_) => println!("FAILED to retrieve data bits"), |
| Ok(r) if r != data_bits => println!( |
| " {:?}: FAILED (data bits {:?} does not match set data bits {:?})", |
| data_bits, r, data_bits |
| ), |
| Ok(_) => println!(" {:?}: success", data_bits), |
| } |
| } |
| }; |
| } |
| |
| macro_rules! flow_control_check { |
| ($port:ident, $flow_control:path) => { |
| let flow_control = $flow_control; |
| if let Err(e) = $port.set_flow_control(flow_control) { |
| println!(" {:?}: FAILED ({})", flow_control, e); |
| } else { |
| match $port.flow_control() { |
| Err(_) => println!("FAILED to retrieve flow control"), |
| Ok(r) if r != flow_control => println!( |
| " {:?}: FAILED (flow control {:?} does not match set flow control {:?})", |
| flow_control, r, flow_control |
| ), |
| Ok(_) => println!(" {:?}: success", flow_control), |
| } |
| } |
| }; |
| } |
| |
| macro_rules! parity_check { |
| ($port:ident, $parity:path) => { |
| let parity = $parity; |
| if let Err(e) = $port.set_parity(parity) { |
| println!(" {:?}: FAILED ({})", parity, e); |
| } else { |
| match $port.parity() { |
| Err(_) => println!("FAILED to retrieve parity"), |
| Ok(r) if r != parity => println!( |
| " {:?}: FAILED (parity {:?} does not match set parity {:?})", |
| parity, r, parity |
| ), |
| Ok(_) => println!(" {:?}: success", parity), |
| } |
| } |
| }; |
| } |
| |
| macro_rules! stop_bits_check { |
| ($port:ident, $stop_bits:path) => { |
| let stop_bits = $stop_bits; |
| if let Err(e) = $port.set_stop_bits(stop_bits) { |
| println!(" {:?}: FAILED ({})", stop_bits, e); |
| } else { |
| match $port.stop_bits() { |
| Err(_) => println!("FAILED to retrieve stop bits"), |
| Ok(r) if r != stop_bits => println!( |
| "FAILED, stop bits {:?} does not match set stop bits {:?}", |
| r, stop_bits |
| ), |
| Ok(_) => println!(" {:?}: success", stop_bits), |
| } |
| } |
| }; |
| } |
| |
| macro_rules! clear_check { |
| ($port:ident, $buffer_direction:path) => { |
| let buffer_direction = $buffer_direction; |
| match $port.clear(buffer_direction) { |
| Ok(_) => println!(" {:?}: success", buffer_direction), |
| Err(ref e) => println!(" {:?}: FAILED ({})", buffer_direction, e), |
| } |
| }; |
| } |
| |
| macro_rules! call_query_method_check { |
| ($port:ident, $func:path) => { |
| match $func($port) { |
| Ok(_) => println!(" {}: success", stringify!($func)), |
| Err(ref e) => println!(" {}: FAILED ({})", stringify!($func), e), |
| } |
| }; |
| } |
| |
| fn test_single_port(port: &mut dyn serialport::SerialPort, loopback: bool) { |
| println!("Testing '{}':", port.name().unwrap()); |
| |
| // Test setting standard baud rates |
| println!("Testing baud rates..."); |
| baud_rate_check!(port, 9600); |
| baud_rate_check!(port, 38_400); |
| baud_rate_check!(port, 115_200); |
| |
| // Test setting non-standard baud rates |
| println!("Testing non-standard baud rates..."); |
| baud_rate_check!(port, 10_000); |
| baud_rate_check!(port, 600_000); |
| baud_rate_check!(port, 1_800_000); |
| |
| // Test setting the data bits |
| println!("Testing data bits..."); |
| data_bits_check!(port, DataBits::Five); |
| data_bits_check!(port, DataBits::Six); |
| data_bits_check!(port, DataBits::Seven); |
| data_bits_check!(port, DataBits::Eight); |
| |
| // Test setting flow control |
| println!("Testing flow control..."); |
| flow_control_check!(port, FlowControl::Software); |
| flow_control_check!(port, FlowControl::Hardware); |
| flow_control_check!(port, FlowControl::None); |
| |
| // Test setting parity |
| println!("Testing parity..."); |
| parity_check!(port, Parity::Odd); |
| parity_check!(port, Parity::Even); |
| parity_check!(port, Parity::None); |
| |
| // Test setting stop bits |
| println!("Testing stop bits..."); |
| stop_bits_check!(port, StopBits::Two); |
| stop_bits_check!(port, StopBits::One); |
| |
| // Test bytes to read and write |
| println!("Testing bytes to read and write..."); |
| call_query_method_check!(port, SerialPort::bytes_to_write); |
| call_query_method_check!(port, SerialPort::bytes_to_read); |
| |
| // Test clearing a buffer |
| println!("Test clearing software buffers..."); |
| clear_check!(port, ClearBuffer::Input); |
| clear_check!(port, ClearBuffer::Output); |
| clear_check!(port, ClearBuffer::All); |
| |
| // Test transmitting data |
| print!("Testing data transmission..."); |
| std::io::stdout().flush().unwrap(); |
| // Make sure the port has sane defaults |
| set_defaults(port); |
| let msg = "Test Message"; |
| port.write_all(msg.as_bytes()) |
| .expect("Unable to write bytes."); |
| println!("success"); |
| |
| if loopback { |
| print!("Testing data reception..."); |
| port.set_timeout(Duration::from_millis(250)).ok(); |
| |
| let mut buf = [0u8; 12]; |
| if let Err(e) = port.read_exact(&mut buf) { |
| println!("FAILED ({})", e); |
| } else { |
| assert_eq!( |
| str::from_utf8(&buf).unwrap(), |
| msg, |
| "Received message does not match sent" |
| ); |
| println!("success"); |
| } |
| } |
| } |
| |
| fn check_test_message(sender: &mut dyn SerialPort, receiver: &mut dyn SerialPort) { |
| // Ignore any "residue" from previous tests. |
| sender.clear(ClearBuffer::All).unwrap(); |
| receiver.clear(ClearBuffer::All).unwrap(); |
| |
| let send_buf = TEST_MESSAGE; |
| let mut recv_buf = [0u8; TEST_MESSAGE.len()]; |
| |
| sender.write_all(send_buf).unwrap(); |
| sender.flush().unwrap(); |
| |
| match receiver.read_exact(&mut recv_buf) { |
| Ok(()) => { |
| assert_eq_hex!(recv_buf, send_buf, "Received message does not match sent",); |
| println!(" success"); |
| } |
| Err(e) => println!("FAILED: {:?}", e), |
| } |
| } |
| |
| fn test_dual_ports(port1: &mut dyn serialport::SerialPort, port2: &mut dyn serialport::SerialPort) { |
| println!( |
| "Testing paired ports '{}' and '{}':", |
| port1.name().unwrap(), |
| port2.name().unwrap() |
| ); |
| |
| // Make sure both ports are set to sane defaults |
| set_defaults(port1); |
| set_defaults(port2); |
| |
| // Test sending strings from port1 to port2 |
| println!( |
| " Transmitting from {} to {}...", |
| port1.name().unwrap(), |
| port2.name().unwrap() |
| ); |
| |
| let baud_rate = 2_000_000; |
| println!(" At {},8,n,1,noflow...", baud_rate); |
| std::io::stdout().flush().unwrap(); |
| if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } else { |
| println!("FAILED (does this platform & port support arbitrary baud rates?)"); |
| } |
| |
| let baud_rate = 115_200; |
| println!(" At {},8,n,1,noflow...", baud_rate); |
| std::io::stdout().flush().unwrap(); |
| if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } else { |
| println!("FAILED"); |
| } |
| |
| let baud_rate = 57_600; |
| println!(" At {},8,n,1,noflow...", baud_rate); |
| std::io::stdout().flush().unwrap(); |
| if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } else { |
| println!("FAILED"); |
| } |
| |
| let baud_rate = 10_000; |
| println!(" At {},8,n,1,noflow...", baud_rate); |
| std::io::stdout().flush().unwrap(); |
| if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } else { |
| println!("FAILED (does this platform & port support arbitrary baud rates?)"); |
| } |
| let baud_rate = 9600; |
| println!(" At {},8,n,1,noflow...", baud_rate); |
| std::io::stdout().flush().unwrap(); |
| if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } else { |
| println!("FAILED"); |
| } |
| |
| // Test flow control |
| port1.set_flow_control(FlowControl::Software).unwrap(); |
| port2.set_flow_control(FlowControl::Software).unwrap(); |
| println!(" At 9600,8,n,1,softflow..."); |
| std::io::stdout().flush().unwrap(); |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| |
| port1.set_flow_control(FlowControl::Hardware).unwrap(); |
| port2.set_flow_control(FlowControl::Hardware).unwrap(); |
| println!(" At 9600,8,n,1,hardflow..."); |
| std::io::stdout().flush().unwrap(); |
| check_test_message(port1, port2); |
| check_test_message(port2, port1); |
| } |
| |
| fn set_defaults(port: &mut dyn serialport::SerialPort) { |
| port.set_baud_rate(9600).unwrap(); |
| port.set_data_bits(DataBits::Eight).unwrap(); |
| port.set_flow_control(FlowControl::Software).unwrap(); |
| port.set_parity(Parity::None).unwrap(); |
| port.set_stop_bits(StopBits::One).unwrap(); |
| // TODO: Clean up timeouts and use a less-arbitrary value here. The previous timeout of 0 made |
| // test_dual_ports fail due to a timeout where having at least some some made them pass. |
| port.set_timeout(Duration::from_millis(1000)).unwrap(); |
| } |