Skip to content
Basic

Features

WireWeaver lets you describe a device's API once, as a Rust trait with your own data types, and generates both ends from it: the server on the device, no_std and without an allocator, and the client on the PC. Calls, property reads and streams go over USB, RTT, UDP or WebSocket in shrink_wrap, a dense bit-packed format that also works on its own. Newer firmware keeps working with older hosts and the other way round.

This page goes through what works today, area by area, in the order you are likely to meet them. Each area starts with what it is for, then lists its features by reading level: Basic is what most projects use, Advanced is for when the defaults are not enough, Nomicon is about internals and code size. Features that work only in part are marked. What is planned, and the details of every item, are in the feature tracker.

Wire format

shrink_wrap turns your Rust structs and enums into compact bytes and back, without an allocator and without copying. Every message between host and device is made of it, and it works on its own as a dense storage format.

Basic

Advanced

  • Owned values without an allocator: ArrayString<N> and ArrayVec<T, N> on fixed arrays, same bytes as String and Vec.
  • Bit-sized fields and payloads: U7, U10, I2 and friends in structs, enum variants and method arguments, e.g. a 7- or 10-bit I2C address in 8 or 11 bits.
  • 64-bit variable-length numbers: UVlq64, 1 to 10 bytes.
  • Columnar series: Series<T>: a list of numbers, timestamps, floats or any struct written field by field, with deltas and bit packing per field, several times smaller than a Vec<T> of samples.
  • Sparse structs: A struct of optional fields stored as an OptionAnyVec, for "only what changed" updates.
  • Checks on read: validate = path rejects out-of-range values as they are read.
  • Display from doc comments: Error enums print their doc lines, no hand-written Display.
  • #[cfg] on enum variants: Compile a variant out; it keeps its number on the wire.
  • Checked discriminants: Variant numbers must fit the chosen ww_repr, implicit ones count like in Rust.
  • Generic field types: Wrapper<'i, T> and types from macro_rules work as fields.
  • Generic types: Structs and enums with type and const parameters (Timed<T>, Buf<const N: usize>) and where clauses derive like any other type.
  • PhantomData fields: A zero-bit PhantomData<U> field carries a unit marker type (Quantity<Volt>) that needs no impls of its own.
  • #[default] fields default in serde too: A RON or JSON file saved before a field was added still reads, the field taking its default.
  • #[wire(W)] fields: Keep a plain i8 or u64 in Rust and send it as I4 or UNib32: range-checked on write, widened on read.
  • transparent wrappers: A one-field struct (Count(u64), Volts<U> with a PhantomData unit) is exactly its field on the wire, size class and Series columns included, so a new type costs no bytes.
  • An enum's tag in a niche: niche lets an enum with a small field stay as small as the field.
  • Narrow tag-only catch-all: #[future_version] Unknown(u16) keeps a small enum small and still reads values from newer versions.
  • Fields written as another type: #[wire(W)] keeps a small handle in Rust and writes the full value it stands for, also in a Series.
  • Unit-typed fields cost nothing: A transparent wrapper field (a Fixed number with its unit) compiles to the same code as its plain field, not only the same bytes.
  • Growing writer: BufWriterOwned for hosts, when the size isn't known up front.
  • Writing in place: Builders that write a value straight into the output buffer, and patching a field after the rest is written.
  • Fixed layouts: Types with a fixed byte layout get fields at constant offsets, read and written without a parser, and a matching C header.
  • Fixed head, sequential tail: head_tail, a format of its own for small firmware, with views that read fields on demand and a writer per enum variant.
  • shrink_wrap in C: A C99 reader and writer of the same format, no malloc.

Nomicon

  • Format documented bit by bit: How every type is laid out, and why.
  • Small code on small CPUs: About 20 % less code on Cortex-M0 and RISC-V for the same bytes on the wire.
  • Number access tuned per CPU: Multi-byte numbers through one function on RISC-V, inline on Arm, whichever is smaller.
  • Byte-wise memcpy: Measured savings and downsides of a tiny memcpy for very small targets.
  • Exact sizes of enums: sized enums report the discriminant plus one variant; variants of different sizes are a compile error.
  • Strict bit reads: Malformed data near the end of a buffer is an error, never a misread.
  • Bytes to the end: TailBytes takes the rest of the buffer, written without a length; only allowed last.
  • Size of the rest of a value: TailSize<N>, filled in after writing, so a reader can skip a value or bound it.
  • Lists written in place: RefVecBuilder writes a list into the buffer one element at a time, from a loop or an iterator, with no slice and the same bytes.
  • Series stores and size bounds: A batch of samples kept as one array per field until it is written as a Series, in half the RAM of the samples, and the most bytes the batch can take.
  • Fast, small Series encoding on MCUs: About 340 instructions per sample on a Cortex-M3, and codecs fixed at compile time that link only themselves: 15 KB of flash instead of 91 KB.
  • Series straight from the store: A batch written column by column from the arrays it was collected in, the same bytes in half the instructions.
  • Word-wide bit writer and reader: Bits written and read a word at a time, the same bytes: the observe poll of a 21-value FOC level in three quarters of the cycles on an STM32H725.
  • Sparse slots: OptionAnyVec, a list of optional values of any types where only the present ones take space.
  • Lists of either-or values: EitherAnyVec holds one of two types per element, the flags packed by eight, e.g. a list of results.

Details and what is planned: Wire format in the feature tracker.

API

You describe what a device offers as a Rust trait: methods to call, properties to read and write, streams of data in either direction. WireWeaver generates the device's server and the host's client from it.

Basic

  • Methods, properties, streams and sinks: All in one trait, with traits nested inside to group them.
  • Sync or async: Async and sync servers; async, blocking and promise-style clients, from the same trait.
  • Results and errors: Methods return a value, answer later or say "not implemented"; your own error types for methods and properties.
  • Request context: Every handler knows who asked and can send events or answer later.
  • Arrays of resources: led[] or motor[], one definition for many instances, with the valid indices asked from the device.
  • Shared traits: impl Gpio, impl Uart from the standard library, so common tools work with your device.
  • Constants: const NAME: T; items, read once per connection and cached.
  • Generic types: Timed<Ampere>, Window<4> and your own generic structs and enums anywhere in an API, also in the dynamic client and Python.
  • Type aliases: type Volt = Quantity<VoltUnit> and generic aliases anywhere in an API; tools show what they name.
  • Dependencies from anywhere: An API crate can take traits and types from git and crates.io crates, not only from a path next to it.
  • Types from other crates: An API can use types of a crate that isn't built on WireWeaver, also one with shrink_wrap optional behind a feature, or one the server only reaches through a re-export.
  • Types in modules: API types can live in any pub mod or be re-exported under another name; generated code finds them.
  • Self-referential types: Trees and expressions (RefBox, a list of the type itself) anywhere in an API, also in the dynamic client and Python.

Advanced

  • Optional items: Leave out what a board doesn't have, with no flash spent, and clients know before they call.
  • Optional traits: Leave a whole block of the API out of a build, e.g. behind a Cargo feature, and clients see it is not there.
  • Error lists: Which errors each method can return, shown in docs and tools and checked in tests.
  • Stream functions: Open a stream with typed arguments and a result, like a method call.
  • Observing properties: Ask a device to send changes on change or periodically, within a byte budget.
  • Observing every sample: A fast property's every sample, each at its time, in compressed batches; typed and dynamic clients, the CLI and Python.
  • One clock per observed level: The samples of a level's properties share one time column per update: about a third fewer bytes and half the encoding CPU.
  • Observe cells: Sample a level from an interrupt in place, value by value into a ring of atomics: a quarter of the cycles and a tenth of the stack of sample() at opt-level z, the same wire.
  • A cheaper observe poll: The poll that sends a level's samples spends its time on the codec: half the cycles at opt-level 3, a third at z, the same bytes.
  • Batched streams: A stream opts into sending samples many per event as a compressed Series instead of one at a time, for plain, counted and reliable streams; typed and dynamic clients, the CLI and Python.
  • Compressed streams: A counted stream of text (a UART, a log) goes LZ4-compressed with history across events, 3-5x, when the host decodes it; older hosts get it as it is. hardware check open
  • Size analysis: Worst-case request and reply sizes as constants, checked at compile time; properties and streams are next. in progress
  • API docs in rustdoc: #[ww_trait] also leaves a plain Rust trait, so methods, properties and streams show up on docs.rs. in progress
  • Code generation from build.rs: gen_server / gen_client as an alternative to the macros.
  • Servers on std with owned types: A server running on a PC returns String, Vec and owned types from its handlers, the same bytes on the wire as a firmware's.
  • Generated C: The C runtime is there; generated C for an API's types and server is next. in progress

Nomicon

  • Builders for events and errors: Written straight into the output buffer.
  • Dispatch on index slices: Requests are routed by match on their path, the base of multi requests.
  • Names and paths that don't collide: Generated names carry the whole trait chain; type paths come from Cargo.
  • Counted and reliable streams: Exact counts of lost items; acknowledgements, resends and resume after a reconnect. in progress
  • Credit sinks: Flow control for data sent to a device, so its buffer never overflows, and lossy links resume where they stopped.
  • Resend and resume inside a batch: A resend or a resume from the middle of a multi-unit item gets only the units you don't have.
  • Credit sinks without a size limit: Sessions of any length, positions at most 4 bytes on the wire.
  • Credit sinks that resume: A sink (UART tx, a DFU plan) continues after a reconnect or a host restart, on the same acknowledgements as reliable streams. hardware check open
  • Reconnect: Commander::reconnect() connects again after disconnect().keep_streams() and resumes streams, sinks and observers. in progress
  • Resume checked on hardware: A UART sink on a real board put 1 MB on the line exactly once through link drops and a host restart.
  • Trait paths: Call a shared trait wherever it sits in a device's API. in progress

Details and what is planned: Wire format, Server / device side, Documentation, Examples, Testing, CI and performance in the feature tracker.

Device side

The server is the code on the device that answers requests. It is generated from the trait, needs neither std nor an allocator and does no IO itself, so it runs on any MCU and RTOS over the transport you choose.

Basic

  • no_std, no allocator: The generated server works on the smallest MCUs and does no IO itself.
  • Your event loop: An async Server with wait() / handle() inside your own loop, or a blocking one.
  • Media: USB (embassy), RTT, WebSocket and UDP, the last two also on embassy-net.
  • Device descriptors: Serial number, API name, version and a user label, visible before connecting.

Advanced

  • Deferred replies everywhere: Answer a call later, also on nested trait levels.
  • Replies by priority: A reply to an urgent request leaves at once, also a deferred one sent later from your main loop; Sink::flush sends what waits now. hardware check open
  • Errors that say where: A device's error names the site that raised it, by a number that is the same in every firmware.
  • Sync handlers in an async server: Handlers that never wait are plain fns, only the ones that await are async: less flash.
  • Observed properties: The device sends property changes within the budget each host asked for.
  • Event-driven observe: The device sleeps until an observed property is due, and wakes at once when one changes.
  • ECC-safe STM32 flash: Reads a flash word broken by a power cut as zeros (stores) or as an ECC error (bootloaders) instead of crashing. hardware check open
  • I2C and SPI slave engine: Register map, answer queue, clock stretching and events of a slave, wired to your peripheral's interrupts.
  • Streaming LZ4: ww_lz4 compresses a byte stream chunk by chunk on the device, with history across chunks: shell text 3-5x smaller in 512 B chunks, standard LZ4 blocks any LZ4 library decodes.
  • Claims, pins and labels: Mark a bus or port #[claim(..)], the pins it #[shares] and a #[label] a person recognises: in introspection for the hub, ww and GUIs, so one program can have a resource to itself through the hub while others keep reading.

Nomicon

  • Split send and receive: Independent TX and RX paths for every transport; handlers called straight from the receive loop.
  • Stream stores: Data a host hasn't acknowledged yet, kept in RAM or in a power-cut safe log on SD card or flash.
  • Logs that survive a crash: A store in RAM that outlives a reset, for what happened just before a fault.
  • Smaller observe code: A firmware carries the observe logic once, not once per trait level: about 15 KiB less flash on a big API.

Details and what is planned: Server / device side in the feature tracker.

Host side

On the PC, a generated Rust client calls a device like a local library. A dynamic client does the same for any device from the description the device sends of itself; the ww tool and Python are built on it.

Basic

  • Rust client: Async, blocking or promise-style calls on a typed client.
  • One ? per call: Device errors and link errors in one Result.
  • Transports: USB, RTT through a debug probe, WebSocket, UDP, and in-process for tests.
  • Picking a device: By serial number, label or path, with clear errors when none is found.
  • Compatibility check on connect: The client compares its API with the device's and tells what doesn't match.

Advanced

  • Dynamic client: Talk to an API known only at runtime.
  • Cached API descriptions: A device's description is downloaded once, then found by its hash.
  • Multi requests: Read many properties or call a method on many array elements in one request.
  • Fallible methods in multi requests: Batch methods returning Result (an I2C write and read) into one round trip, each answering what its own call would.
  • Same method twice in a multi call: Two writes over the same I2C bus (or any repeated call) go into one batch, answers in order.
  • Observing properties: Typed subscriptions and a cache of the latest values, shared by everyone watching.
  • Series without generated code: ww, Python and any dynamic client read and write a Series<T> from introspection alone, Python reads it as numpy columns.
  • Quantities without generated code: ww and Python show a ww_si value with its unit (3300 mV), Python as a quantity object with to_si().
  • Exact units without generated code: ww and Python show a Fixed value as the device stores it (1730000000000000 µs, 4 KiB) and write 5 ms into it exactly, or refuse with the type named instead of rounding.
  • Link setup timeout: Connecting over a slow link (a debug probe) waits seconds, a fast one fails quickly, and you can set the time.
  • Local trait: The API as an async Rust trait that the client and your own struct both implement, so one call site works in process (a direct call) or remote. in progress
  • Calls that work on any device: Shared traits called whatever a device's own API version. in progress
  • Calls fail fast while disconnected: A call made while the link is down fails at once with Disconnected instead of waiting for a reconnect.
  • Urgent requests: .urgent() (or .priority(p)) on a call, read or write sends it and its answer at once instead of waiting for the link's accumulation window; everything else shares frames. hardware check open
  • Windows kept to the microsecond: A link that batches messages for 1 ms waits 1 ms, not up to 2 for the PC's timer tick. hardware check open
  • Who holds a device: A busy error names the program already holding a USB interface or RTT probe, pid and since when; a crash releases it right away.
  • Hub routing in connect: A device the ww hub holds, or one opened again and again from a shell, is reached through the hub, which a program starts by itself when needed; WW_HUB says when. in progress
  • Back after a hub restart: A program reaching its device through the ww hub reconnects by itself when the hub crashes, is killed or hands over, and its counted and reliable streams go on; the app hears about the gap. hardware check open
  • ww plugins: ww NAME runs ww-NAME from PATH like cargo does, with your device selection; ww dfu flash is the ww-chunky plugin.
  • Clocks steered by every program: A program connected to a board directly keeps the board's clock on the PC's UTC by itself, and leaves it to the ww hub when it goes through one. hardware check open
  • Polite handover to the hub: A command-line tool or notebook holding a device lets the hub take it when another program asks, and goes on through the hub after a few milliseconds; a GUI says no and is named in the other's error. hardware check open
  • One lock file per device: A USB device with a crafted serial number can't make another device look held: every device key gets a lock file of its own.
  • Live link handover on Linux: A program holding a USB device hands its open link to the hub without a reconnect: its calls in flight are answered, its streams go on, the device notices nothing. hardware check open
  • Who connects: Every client tells the device its program, user, host, pid and kind in link setup; a device can log it, a hub or a lock file shows it.
  • STM32 ROM DFU: Flash an STM32 over USB through its built-in bootloader (ww dfu stm32 flash), also straight after the app rebooted into it.
  • Following a reboot: Stay with a device while it reboots into its updater (RP2040 / RP2350 boot ROM so far). in progress
  • Updating a chunky device: ww dfu flash app.elf: only what changed goes over the wire, then a trial boot that the new firmware confirms or the device reverts. in progress

Nomicon

Details and what is planned: Client / host side in the feature tracker.

Transports

How the bytes get from one end to the other. Small messages are packed into one packet and big ones split, and a link layer checks on connect that both ends speak the same protocol.

Basic

  • USB, RTT, WebSocket, UDP: And in-process, also as simulated USB for tests.
  • USB network adapter: The device shows up as a USB network card with its own DHCP server, for WebSocket or UDP without drivers.
  • USB VID:PID: Set your own assigned VID:PID on the device side instead of the shared test ID.

Advanced

  • Framer: Packs messages into packets with 1 byte of overhead, CRC and recovery from lost frames.
  • Link layer: Link setup, device info and protocol version exchange.
  • Frame counter and setup token: Nothing of a previous session is taken for the new one after a reconnect; UDP datagrams carry a frame counter and a CRC-32 on every message, USB packets nothing they don't need.
  • The ww hub: One small process per user that knows the devices, who holds them and which programs are connected, with its own API. hardware check open
  • ww hub: See and steer the hub from the shell: its status and who started it, who holds each of your devices (the hub, another ww program, or any program on Linux), keep it up for a bench, stop it; ww list shows what it holds. in progress
  • The hub from Python: python -m wire_weaver.hub starts it from the wheel, connect() reaches a device through it, and wire_weaver.hub() talks to the hub itself; a notebook kernel restart reconnects in no time with the device still open.
  • Clocks steered by the hub: Boards with a shared clock stay on the PC's UTC between short commands: the hub steers them, ww sync shows how, and a board the PC knows is steered right after it opens.
  • Many programs, one device: Several programs use one device at once through the hub, each with its own calls, streams and observed properties. hardware check open
  • Hub version skew: Programs built with different WireWeaver releases share one hub; a newer program replaces an older hub without losing anyone, and a hub that can't serve a program says which versions and what to do. hardware check open
  • The whole device, or lent out: A firmware update has the device to itself while other programs keep reading, and ww hub lend hands it to probe-rs or dfu-util for one command and takes it back. hardware check open
  • Local sockets: A device served by another process on the same PC (a simulator, later the ww hub), reachable by your own user only. hardware check open
  • Link tap: Every frame, message and link event of a link recorded and decoded: which packet a request went out in, what a lost packet took with it.
  • USB throughput on small MCUs: Measured and tuned on an STM32G0 so far. in progress

Details and what is planned: Transports and protocols, Server / device side, Evolution, versioning and compatibility in the feature tracker.

Introspection

A device can send a description of its own API, so tools and scripts work with it without its source code.

Basic

Advanced

  • Bit-sized numbers in the model: U7, I12, nibbles and the like, read and written by the dynamic client and Python.
  • Units in the model: A device's API carries the definition of every unit, scale and origin it uses, also of your own crate's, so ww, the dynamic client and Python convert and show them without a built-in table.
  • Moved flag bits: Relocated #[flag] fields in the model and in dynamic values.

Nomicon

  • Bundles: The model with the crate versions and signatures of what it leaves out.

Details and what is planned: Introspection in the feature tracker.

Evolution

Devices and hosts are updated at different times. These keep old hosts working with new firmware and the other way round, and tell you before a release when a change would break that.

Basic

Advanced

  • API identity: Every API and type has a signature; shared traits are referred to by hash.
  • API snapshots: Descriptions of the standard library are left out of what a device sends and filled in by the host.
  • Definitions from another revision: A device built from an unpublished revision still connects, with a warning.
  • Units compared by meaning: Milli → Pow10<-3> passes as the same value; lbf·s → N·s is caught as breaking with the factor, as are another zero of a point or a wrapping number on one side.

Details and what is planned: Evolution, versioning and compatibility in the feature tracker.

Tools

A command line tool, a Python module and a GUI to work with devices and APIs without writing a client.

Basic

  • ww command line tool: List devices, show a device's API as a tree, with shell completions.
  • Python: Any device from Python, its API taken from the device itself.
  • Server scaffold: ww api scaffold prints a stub for every handler a server needs.
  • Device info and reports: ww info for any device; ww report adds counters, logs and crash data in one file.
  • Safe to look at: A device or another program can't write terminal escape sequences into what ww prints: control characters and bidi tricks in a product string, name or label always show up as plain text.

Advanced

  • API bundles: ww api save, check and diff.
  • Watching properties: ww observe and Python print updates as they arrive, with rates.
  • Explaining bytes: ww explain shows every field of a buffer with its bits, from hex or a value written in Rust. in progress
  • Link test: ww usb-loopback measures round trips, losses and speed both ways.
  • Watching the link: ww monitor, ww stats, ww capture and ww explain FILE: the link decoded live, counted, saved and read back; --monitor and --capture do it for any command's own connection.
  • ww through a debug probe: ww --rtt CHIP runs every command against a board with no USB of its own, over RTT through its ST-LINK or other probe, with probe serials and chip names completed in the shell.
  • Devices on one clock: ww sync keeps several devices on the host's UTC and shows how closely each follows and how far apart in phase they are. hardware check open
  • Plain-unit hints: ww api check and ww api scaffold point out a plain number whose name ends in _ms, _hz, _bytes and the like, and what fixed unit type it could become instead.
  • Pins live per connector: ww pins tui shows every pin of a board on its connector, read live, with keys to drive, pull and release them. hardware check open
  • Pin tests from the command line: ww pins test finds shorts and pulls on any board serving PinDiag; plan files make it a pass/fail test. hardware check open
  • Settings from a flash dump: ww settings dump decodes a settings store without the device.
  • Values from text: Typed values written as text and turned into bytes at runtime.
  • Dev GUI: Shows the parsed API and the generated code side by side; a UI for any device is prototyped. in progress

Details and what is planned: Evolution, versioning and compatibility, Client / host side in the feature tracker.

Standard library

Ready-made traits and types for what many devices have: units, time, GPIO and buses, logs, firmware info, updates. They are shared, so a tool that knows one of them works with every device that implements it.

Basic

  • Device contract: What every device answers: firmware, board, ids, user label and last reset.
  • Common errors: CommonError (Busy, Timeout, InvalidArgument, ...) shared by all APIs.
  • Units and numbers: ww_si quantities like volts, hertz and seconds, ww_numeric values.
  • Units checked by the compiler: One generic quantity type with the unit in the type: Volt * Ampere is a Watt, Volt + Ampere doesn't build, 3300 mV prints itself.
  • Units known at run time: A value whose unit is only known on the device, a configurable sensor channel, carries its unit and is shown like a typed one.
  • Quantities as exact as Fixed: A run-time Quantity takes binary prefixes (KiB), powers of ten and two without a name and full-scale fractions (1/65535), and carries bounds checked on decode.
  • Numbers with a fixed unit: Fixed<Micro, Second, u32>: the unit, scale and bounds are in the type, the wire carries exactly the u32, and it prints as 1500 µs.
  • Minutes, hours, days and Unix timestamps: Secs, Minutes, Hours and Days next to the existing µs/ms/ns, and a moment in POSIX time since the Unix epoch as UnixSecs, UnixMillis, UnixMicros or UnixNanos.
  • Counts, percent, binary byte sizes and whole hertz: Count<Of, T> for a count of things, Percent/Permille for a share of a Ratio, Kibibytes/Mebibytes/Gibibytes beside Bytes, Hz, and counted Line/Row/Record/Entry beside Item/Sample/Frame/Packet.
  • Units without WireWeaver: exact_units, a no_std crate with no dependencies, gives dimensions, unit markers, exact scales and Fixed to any project; the wire format is a feature.
  • Angles, bytes and samples are dimensions: Bytes never convert into % or rad, rad/s isn't Hz, a torque isn't a joule, and samples per second aren't a frequency: the compiler and every client tell them apart.
  • Date and time: ww_date_time.
  • IO family: GPIO, UART, I2C, SPI, ADC, PWM, 1-Wire and more, all in one style. in progress
  • GPIO: Pins and banks: levels, modes, pulls and edge events.
  • CAN bus: From a simple bridge to a bus debugger.
  • Logs: defmt logs over a reliable stream, including what was logged before the host connected. hardware check open
  • Indicators: LEDs off, essential or normal, and a blink to find a unit on the bench.
  • Indicators in phase: Devices with a shared clock breathe and blink together, aligned to UTC, with nothing to configure. hardware check open
  • Firmware updates: ww_dfu for A/B, in-place and bootloader updates, tested with reboots and power cuts. in progress
  • 1-Wire: Transactions and ROM search.
  • Stock I2C drivers on the host: An embedded-hal I2C driver crate runs unchanged against the I2C bus of a remote device, one transaction per round trip. hardware check open
  • More buses: ww_i2c, ww_uart, ww_memory and the ww_gpio_hl helpers, usable with gaps. in progress

Advanced

  • Rounding by name: A conversion that loses part of a step says how it rounds and what happens past the end of the storage (saturate, wrap or no value), and gives the same number on the device, in Rust and in a dynamic client; Q formats saturate and binary angles wrap by their type.
  • Exact scales: Every scale, from µ and Ki to 1/65535, a 12-bit ADC step, Q15 or a degree in radians, is one exact factor: equal however written, and a conversion between two is worked out at compile time and never rounds.
  • Imperial and US customary units: Every inch, pound, gallon, psi, Btu and horsepower as the exact factor its legal definition gives, ambiguous names like gallon or ton never bare, and pounds of force never mistaken for pounds of mass: lbf·s converts to N·s exactly or is refused; a unit that is only a value a standard prints (inHg at 32 °F) says so to clients. Opt-in, so an API without them pays nothing.
  • Points and origins: A temperature in °C or °F, a register formula like raw/256 + 25 °C, gauge pressure or a heading that wraps is a point against an origin: point minus point is an amount, adding two points doesn't compile, and conversions between origins are exact.
  • Kinds that share a dimension: A voltage gain V/V, a current gain A/A, a power gain W/W and a duty cycle never convert into each other, nor a transimpedance V/A into Ω: the compiler refuses, a named step crosses, and clients and the evolution check tell them apart. in progress
  • Gains in dB: A gain's dB is 20·lg for V/V and A/A, 10·lg for W/W, by its kind; a duty cycle has no dB. in progress
  • Typed products and quotients: Multiplying or dividing two quantities gives back a type that carries the exact result unit and scale, never silently truncated, with checked, saturating and wrapping forms for what doesn't fit.
  • Indices and sequence numbers: A sample's place in a capture, channel 1 of 4 or a frame counter that wraps is a type of its own with the same bytes as the number: positions subtract to an amount, adding two doesn't compile, 1-based ones refuse 0, and sequence numbers compare correctly across the wrap.
  • Quantities without wide integers: On a small MCU, comparing and converting quantities with 32-bit numbers takes only 32-bit code: no 64- or 128-bit arithmetic is linked.
  • ADC: Single reads, sample streams and triggered captures.
  • PWM: Outputs and input capture.
  • Device time: A clock with sync to a shared time source.
  • Host clock sync: Devices without a sync line follow the host's clock: the host fits offset and rate from round trips and steers each device.
  • I2C and SPI slave: The device stands in for a chip on another master's bus: a register map the host fills, or answers the host sends, with every transfer reported back. hardware check open
  • I2C master errors and limits: Which byte was not acknowledged, a clock stretch timeout, a stuck bus reported and freed on request, and the adapter's limits. hardware check open
  • Logic analyzer: Triggered captures, transitions compressed. in progress
  • Pin tests: Shorts, pulls and floating pins on any board through one trait, the board's safety rules kept in its firmware. in progress
  • Interned strings: Names and reasons sent as a small id, the text read from the firmware's ELF on the host. in progress
  • Signal generator: Replays captures, drives DAC channels. in progress
  • Counters: A device's cnt counters, named on the host from its firmware file.
  • Recorder control: Status, format, recordings, markers and settings of a logger that writes to an SD card. in progress
  • Block device: Read, check and write an SD card or flash by block over the link, writes under a lease. in progress
  • Raw SD card: Registers, init report and raw commands with timing, for bring-up and card checks. in progress
  • Age of a crash record: How many boots ago the saved crash happened.
  • Settings store: A device's settings in its own flash, power-cut safe, readable by older and newer firmware. hardware check open
  • Versions: ww_version.

Nomicon

  • Request and event model: ww_client_server, the messages every call is made of.
  • Tape blocks: ww_tape: the generic blocks of track records that live feeds and log files (link captures) are made of.

Details and what is planned: Evolution, versioning and compatibility in the feature tracker.

Examples

Working projects to start from: host programs, firmware for a few boards, and the crate layout to copy.

Basic

  • Project layout: An <name>_api crate with the trait and types, next to the code that serves and calls it.
  • Host examples: Blinky, all GPIOs, UART, and blinky from Python.
  • Firmware examples: STM32 Nucleo boards, RP2040 / RP2350 and QEMU, over USB, RTT, WebSocket and UDP.
  • Serialization only: shrink_wrap without an API, and a comparison with other formats.

Advanced

Details and what is planned: Examples in the feature tracker.

Testing

How WireWeaver is kept working: every change runs through CI, against the real client and server code.

Advanced

  • Continuous integration: Formatting, lints, docs, tests, semver checks and firmware builds on every change.
  • Integration tests: Every API feature tested through the real host event loop and device server.
  • Fuzzing: The framer and shrink_wrap's readers fed random input; more targets to come. in progress
  • Test coverage: A few remaining cases. in progress

Nomicon

  • Conformance matrix: One corpus of golden bytes every wire-format implementation must match: derive, dynamic client, Python, C; guarded against a sealed case changing and against a wire kind going untested.
  • Code size bench: Firmware images per case for RISC-V and Cortex-M, measured on every change.
  • Binary size tracking: Flash and RAM per feature in CI. in progress
  • Units on a microcontroller: What typed units (Fixed) cost in instructions, flash and stack against plain integer fields, from a harness anyone can re-run.
  • Power cuts on STM32: A flash store reset thousands of times mid-write on real hardware. in progress

Details and what is planned: Testing, CI and performance, Wire format in the feature tracker.