ashv2 9.0.1

Implementation of the Asynchronous Serial Host (ASH) protocol.
Documentation

ashv2

Implementation of Silicon Labs' Asynchronous Serial Host protocol v2 (ASHv2), host side.

Specification

Silicon Labs publishes the documentation online:

Current implementation status

The crate currently provides:

  • Frame parsing/encoding for DATA, ACK, NAK, RST, RST-ACK, and ERROR.
  • CRC-16 validation/generation for all supported frame types.
  • Byte stuffing/unstuffing and ASH payload randomization (masking/unmasking).
  • An async actor runtime started with start(...), with separate transmitter/receiver tasks.
  • Async serial I/O through async-serialport, with receiver-side chunking via ReaderStream.
  • Automatic initial reset handshake (RST -> RST-ACK) before normal traffic.
  • Automatic handling of inbound ACK/NAK and retransmission of queued DATA frames.
  • Automatic reset/recovery on protocol errors (ERROR, RST, and selected I/O failures).

Important behavior details:

  • start(...) splits the native serial port into an async reader, writer, and join handle and starts the actor tasks.
  • Handle::send(payload).await confirms local transmission attempt (I/O success), not the remote ASH response payload.
  • Incoming DATA payloads are delivered through the response channel passed to start(...).
  • Payload type is heapless::Vec<u8, MAX_PAYLOAD_SIZE> (MAX_PAYLOAD_SIZE defaults to 128).

Compile-time tunables (via const_env):

  • ASHV2_MAX_PAYLOAD_SIZE (default: 128)
  • ASHV2_T_RSTACK_MAX_MILLIS (default: 3200)
  • ASHV2_TX_K (default: 5)
  • ASHV2_T_RX_ACK_MAX_MILLIS (default: 3200)
  • ASHV2_REQUEUE_DELAY_MILLIS (default: 100)

Usage

use ashv2::{FlowControl, open, start};
use tokio::sync::mpsc::channel;

#[tokio::main]
async fn main() {
    // Open serial port connected to the NCP.
    // Baud rate is derived from flow control by the crate.
    let serial_port = open("/dev/ttyUSB0", FlowControl::Hardware)
        .expect("Failed to open serial port");

    // Channel for inbound ASH DATA payloads from the NCP.
    let (response_tx, mut response_rx) = channel(64);

    // Start ASH actor tasks.
    let (tasks, handle) = start(serial_port, response_tx);

    // Example EZSP "version" request payload.
    let request_payload = [0x00, 0x00, 0x00, 0x02].into_iter().collect();
    handle
        .send(request_payload)
        .await
        .expect("Failed to transmit request frame");

    // Receive inbound DATA payload from NCP.
    if let Some(response_payload) = response_rx.recv().await {
        println!("Received response payload: {response_payload:?}");
    }

    // Graceful shutdown.
    let _serial_port = tasks
        .terminate()
        .await
        .expect("Failed to terminate actor tasks");
}

Development

The CI workflow currently runs:

  • cargo +nightly fmt --check
  • cargo clippy --all-features -- -A clippy::multiple_crate_versions -D warnings
  • cargo test --all-features
  • cargo build --all-features --release
  • cargo vet check

Legal

This project is free software and is not affiliated with Silicon Labs.

Credits

Special thanks to Simon Farnsworth, Kevin Reid, and the community at https://users.rust-lang.org/.