Skip to content
Basic

Transport protocols

Generated server and client code does no IO: the same API runs over any medium, and the medium is picked when connecting. On the host, each transport is a feature of wire_weaver_client; on the device, of ww_device (or the wire_weaver_usb_embassy crate for USB).

Which one to pick

Transport Use it for Host feature Device side
USB MCU boards: fast, no drivers on any OS, hot-plug, many devices told apart by serial usb (default) wire_weaver_usb_embassy (embassy-usb)
RTT a board with a debug probe attached and no USB to spare, early bring-up rtt ww_device, rtt feature
WebSocket devices on a network that can run TCP, Linux boards, simulators in another process ws ww_device, ws feature
UDP devices on a network, telemetry where a lost event beats a stall udp ww_device, udp feature
in-process tests and simulators: the device runs in the same process in_process ww_device::Server on channels

CAN bus, I2C and UART as transports are planned (the can, i2c and uart features of wire_weaver_client are placeholders without code yet).

Connecting

The medium is chosen in the client config, the rest of the code doesn't change:

let blinky = Blinky::new().connect().await?;                                    // USB, driver's default filter
let blinky = Blinky::config(|c| c.usb().serial_eq(serial)).connect().await?;   // USB, one device of several
let blinky = Blinky::config(|c| c.rtt("RP2040".into(), None)).connect().await?; // RTT through a debug probe
let blinky = Blinky::config(|c| c.websocket_url("ws://192.168.1.10:8080/ww")).connect().await?;
let blinky = Blinky::config(|c| c.udp_addr("192.168.1.10:9000")).connect().await?;
let blinky = Blinky::config(|c| c.in_process_path("sim/blinky".into())).connect().await?;

connect_blocking() does the same without an async runtime of your own. After connecting, every transport behaves the same: link setup with a version check, introspection, pings and timeouts, streams.

How the layers fit

Messages from the generated code are packed into frames by the framer: many small messages share one USB packet or UDP datagram, a big one is split across several. The link layer on top of it sets up the session (versions, buffer sizes), keeps it alive with pings and ends it with a reason; it also has the device's event loop. WebSocket and in-process connections carry whole messages and skip the framer (the simulated USB device of in_process::usb_device keeps it, to test exactly what goes over USB).