serialport-rs

repository·main·Indexed 20 days ago

https://github.com/serialport/serialport-rs

A cross-platform low-level serial port library for Rust providing blocking I/O and port enumeration on POSIX and Windows systems. It features a builder pattern for port configuration, support for arbitrary baud rates across various platforms, and a SerialPort trait for cross-platform interaction. Version 4.9.1-alpha0.

Tokens
4.2K
Snippets
13
Records
23
Agent score
73%

What's inside serialport

  1. Close a serial port

    main

    The library uses the RAII (Resource Acquisition Is Initialization) paradigm. A port is automatically closed when the SerialPort object is dropped. You can close it implicitly by letting it go out of scope or explicitly using std::mem::drop(port).

    std::mem::drop(port);
  2. macOS/iOS Baud Rate Support

    main

    On macOS and iOS, the serialport library supports non-standard baud rates by utilizing the iossiospeed ioctl. This is necessary because the standard POSIX termios implementation does not support custom baud rates directly in the termios struct.

    Implementation Detail: To ensure compatibility, the library follows these behaviors:

    1. It uses iossiospeed for all baud rate settings.
    2. Whenever the termios struct is written back (via tcsetattr), a call to iossiospeed follows it to re-apply the desired baud rate.
    3. The termios struct is not cached; it is retrieved from the kernel on every settings adjustment to ensure the kernel's state remains the canonical source.
  3. Understand platform-specific baud rate behavior

    main

    The serialport crate provides a cross-platform API, but underlying platform differences affect how baud rates are handled. When working with non-standard or arbitrary baud rates, be aware of the following platform behaviors:

    • Windows: Uses the DCB struct. It is the most straightforward platform and supports arbitrary baud rates by default.
    • Linux & Android:
      • The Termios API does not support arbitrary baud rates; you must use B* constants and cfsetXspeed() functions.
      • The Termios2 API does support arbitrary baud rates by directly modifying c_ispeed and c_ospeed fields.
    • BSDs (FreeBSD, NetBSD, OpenBSD, DragonFlyBSD): Use a version of the Termios API that supports arbitrary baud rates via the termios2.c_ispeed and termios2.c_ospeed fields.
    • macOS and iOS: While the Termios API theoretically supports arbitrary baud rates, it often fails due to driver limitations. Instead, these platforms use the IOSSIOSPEED ioctl. This requires the port to be in raw mode (via cfmakeraw) and must be reapplied after every tcsetattr call, as tcsetattr resets the baud rate.
  4. How the SerialPort abstraction works

    main

    The library provides a cross-platform interface via the SerialPort trait.

    • Recommended approach: Work with Box<dyn SerialPort>. This ensures your code remains cross-platform by default.
    • Platform-specific approach: If you need features unique to an OS, use the specific structs directly: TTYPort for POSIX systems and COMPort for Windows.
    • Async I/O: This crate provides blocking I/O. For asynchronous functionality, use mio-serial or tokio-serial instead.
  5. Configure arbitrary baud rates on macOS and iOS

    main

    On macOS and iOS, setting arbitrary baud rates is more complex than on other platforms due to driver dependencies. The system uses the IOSSIOSPEED ioctl.

    To ensure the baud rate is applied correctly, you must follow these requirements:

    1. The port must be set into raw mode using cfmakeraw.
    2. The baud rate must be set after every call to tcsetattr, because tcsetattr will reset the baud rate to its previous value.
    3. Note that there is no OS-level mechanism to retrieve the currently active baud rate on these platforms.
  6. How to use the SerialPort trait

    main

    The SerialPort trait is the primary cross-platform interface for interacting with serial ports. It provides methods for configuring port settings (baud rate, parity, etc.), controlling non-data signal pins (RTS, DTR), and reading/writing data. Because it inherits from std::io::Read and std::io::Write, you can use standard Rust I/O utilities with any object implementing SerialPort.

    Key capabilities include:

    • Configuration: Getters and setters for baud_rate, data_bits, flow_control, parity, stop_bits, and timeout.
    • Signal Control: write_request_to_send (RTS), write_data_terminal_ready (DTR), and reading signals like read_clear_to_send (CTS).
    • Buffer Management: bytes_to_read, bytes_to_write, and clear to purge input/output buffers.
    • Cloning: try_clone allows simultaneous reading and writing by creating a new handle to the same connection.
    use serialport::SerialPort;
    use std::io::{Read, Write};
    
    // Assuming 'port' is a Box<dyn SerialPort>
    let mut buffer = [0u8; 32];
    port.read_exact(&mut buffer).unwrap();
    port.write_all(b"hello").unwrap();
  7. Work with USB port locations using the Location type

    main

    When the usbportinfo-location feature is enabled, you can use the Location type to represent the physical or logical hierarchy of a USB port.

    Formatting and Parsing

    • Display: A Location can be formatted as a string using the pattern bus_id-port.chain (e.g., bus-1.2.3). If the bus_id or port_chain is empty, the corresponding part is omitted (e.g., -1 or bus-).
    • Parsing: You can create a Location from a string using Location::from_str().

    Hierarchy Operations

    • parent(): Returns Some(Location) representing the immediate parent in the port chain, or None if there is no parent.
    • is_descendant_of(&other): Returns true if the current location is a descendant of the provided other location (meaning it shares the same bus_id and its port_chain starts with the other's port_chain, but is not identical to it).
    // Example of parsing and hierarchy
    let loc = Location::from_str("bus-1.2.3").unwrap();
    let parent = loc.parent(); // Some(Location { bus_id: "bus", port_chain: [1, 2] })
    
    let child = Location { bus_id: "bus".into(), port_chain: vec![1, 2, 3] };
    let parent_loc = Location { bus_id: "bus".into(), port_chain: vec![1, 2] };
    assert!(child.is_descendant_of(&parent_loc));
  8. Open a serial port using the Builder pattern

    main

    The preferred way to access a serial port is using the serialport::new(path, baud_rate) function, which returns a SerialPortBuilder. You can chain configuration methods to set up the port before calling .open().

    serialport::new("/dev/ttyUSB0", 9600)
        .data_bits(DataBits::Eight)
        .parity(Parity::None)
        .stop_bits(StopBits::One)
        .timeout(Duration::from_millis(10))
        .open()
        .expect("Failed to open port");
    serialport::new("/dev/ttyUSB0", 9600).open().expect("Failed to open port");
  9. Open and configure a serial port

    main

    Use the serialport::new(path, baud_rate) builder pattern to configure and open a port.

    Key configuration methods:

    • .timeout(Duration): Sets the I/O timeout.
    • .dtr_on_open(bool): Automatically sets the DTR (Data Terminal Ready) signal when opening the port.
    • .open(): Opens the port as a Box<dyn SerialPort> (recommended for cross-platform use).
    • .open_native(): Opens the port using platform-specific structs (TTYPort on POSIX, COMPort on Windows) to expose additional platform-specific functionality.
    let port = serialport::new("/dev/ttyUSB0", 115_200)
        .timeout(Duration::from_millis(10))
        .dtr_on_open(true)
        .open()
        .expect("Failed to open port");
  10. Read from and write to a serial port

    main

    The SerialPort trait provides blocking I/O methods. The port operates in blocking mode with a default timeout of 0 ms unless configured otherwise.

    • Writing: Use .write(bytes) to send data.
    • Reading: Use .read(&mut [u8]) to read data into a buffer.
    // Writing
    let output = "This is a test. This is only a test.".as_bytes();
    port.write(output).expect("Write failed!");
    
    // Reading
    let mut serial_buf: Vec<u8> = vec![0; 32];
    port.read(serial_buf.as_mut_slice()).expect("Found no data!");