serialport-stream 0.4.0

Async runtime-agnostic AsyncRead/AsyncWrite for serial ports; optional Stream support
Documentation

serialport-stream-rs

Async serial port I/O as futures::AsyncRead and AsyncWrite, with optional Stream support. Uses POSIX termios on Unix and Win32 COMM APIs on Windows.

Async runtime agnostic — implements futures traits only; no Tokio/async-std dependency. Works with any executor that polls those futures (Tokio, async-std, futures_lite::future::block_on, etc.).

Installation

[dependencies]
serialport-stream = "0.4"

Optional features:

# Stream / try_next — background receive FIFO pump (Unix and Windows)
serialport-stream = { version = "0.4", features = ["stream"] }

# Diagnostic logs (EAGAIN retries, receive-buffer diagnostics)
serialport-stream = { version = "0.4", features = ["tracing"] }

Examples below also use futures-lite (blocking) or tokio.

Read behavior

Platform AsyncRead Stream / try_next (stream feature)
Unix Direct async-io poll on the port Poll-based background read thread → FIFO
Windows Overlapped ReadFile + windows thread-pool WaitCommEvent background read thread → FIFO

With the stream feature, AsyncRead also reads from the FIFO. Do not mix Stream / try_next and AsyncRead on the same port — that can split messages across calls.

API Each call returns
Stream / try_next All bytes in the FIFO (buffer drained)
AsyncRead Up to your buffer length; remainder stays in the FIFO

There is no backpressure on FIFO paths; the buffer can grow without bound.

Write behavior

Platform AsyncWrite
Unix Direct async-io poll on the port
Windows Overlapped WriteFile + windows thread-pool

Usage

AsyncRead (default)

Works without extra features. Preferred path on both platforms.

use serialport_stream::{new, AsyncReadExt};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut port = new("/dev/ttyUSB0", 115200).open()?;
    let mut buf = [0u8; 256];
    let n = port.read(&mut buf).await?;
    println!("read {n} bytes");
    Ok(())
}

Example: cargo run --example tokio_async_read -- /dev/ttyUSB0 115200

Add --features tracing and pass --trace in examples that support it for diagnostic logs.

For tokio::io::AsyncRead, bridge with tokio_util::compat (tokio-util feature compat).

Stream (stream feature)

Requires features = ["stream"].

Blocking (futures_lite::stream::block_on):

use serialport_stream::new;
use futures_lite::stream;

fn main() -> std::io::Result<()> {
    let stream = new("COM3", 115200).dtr_on_open(true).open()?;

    for chunk in stream::block_on(stream) {
        println!("{:?}", chunk?);
    }

    Ok(())
}

Example: cargo run --example read_stream --features stream -- COM3 115200

Tokio:

use serialport_stream::{new, TryStreamExt};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut stream = new("/dev/ttyUSB0", 9600).open()?;

    while let Some(bytes) = stream.try_next().await? {
        println!("Received: {bytes:?}");
    }

    Ok(())
}

Example: cargo run --example tokio_read_stream --features stream -- /dev/ttyUSB0 115200

Writing

AsyncWriteExt is re-exported. On Unix, writes use async-io; on Windows, overlapped WriteFile with a windows thread-pool completion.

use serialport_stream::{new, AsyncWriteExt};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut port = new("/dev/ttyUSB0", 115200).open()?;
    port.write_all(b"PING\r\n").await?;
    port.flush().await?;
    Ok(())
}

Read + write example (AsyncRead + AsyncWrite, no extra features): cargo run --example tokio_async_rw -- /dev/ttyUSB0 115200

For tokio::io::AsyncWrite, use tokio_util::compat as above.

Builder

Open with new(path, baud_rate), then chain options and call .open():

  • .data_bits, .parity, .stop_bits, .flow_control — default is 8N1, no flow control
  • .dtr_on_open(bool) — drive DTR on open
  • .clear(ClearBuffer::Input | Output | All) — purge driver buffers at open

Types DataBits, Parity, StopBits, FlowControl, and ClearBuffer are exported from serialport_stream.

Acknowledgements

Some of the platform I/O code is inspired by serialport-rs.

License

This project is licensed under either of:

at your option.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.