Skip to content
Basic

Command line tool

ww is the WireWeaver command line tool. It finds connected devices and lists them with their API, prints a device's API as a resource tree using its introspection data (or an API crate's tree, from source), observes properties, asks any device what it is and what it counted, and tests the link.

Installation

Install from git with cargo (the binary ends up in ~/.cargo/bin):

cargo install --git https://github.com/vhrdtech/wire_weaver wire_weaver_cli

From a local checkout of the repository, just install builds ww with the deploy profile and installs it. It works anywhere: with tpm it goes into the kept-releases stack in ~/.local/bin (the only ww on PATH), otherwise through cargo install --locked --path wire_weaver_cli. just deploy pulls main first and checks ww --version.

Inside the repository it can also be run without installing, with cargo ww <args> (alias from .cargo/config.toml). just install (also just install-cli) installs the current checkout as above, which is handy when testing changes to the tool itself.

Linux permissions

Listing devices works without any setup, but opening one (introspect, info, ...) needs read/write access to /dev/bus/usb/..., usually granted to your user with a udev rule. See USB.

Shell completions

Completions are produced by ww itself, so they always match the installed version. Values of the device selection flags (--serial, --label, --api, --usb-path, ...) are completed from the devices connected right now, with the product and label of each shown as a hint. Register them once in the shell's startup file:

echo 'source <(COMPLETE=bash ww)' >> ~/.bashrc                  # bash
echo 'source <(COMPLETE=zsh ww)' >> ~/.zshrc                    # zsh
echo 'COMPLETE=fish ww | source' >> ~/.config/fish/config.fish  # fish

Elvish and PowerShell are supported as well (COMPLETE=elvish, COMPLETE=powershell). In bash, values containing spaces or quotes are inserted quoted, and values after @ or : complete too (--api blinky_api@<TAB>, --vid-pid c0de:<TAB>).

First steps

Plug in a device and look at what is there:

$ ww list
LOCATION             PRODUCT             SERIAL                    API                 HASH              LABEL
usb 003-1 c0de:cafe  WireWeaver Generic  09000F000251313438333634  all_gpio_api@0.1.0  5ff989a95162d4d7

ww list reads the USB descriptors only, without opening the device. API is the API crate the firmware implements. With one device connected, every other command uses it; with several, pick one with a device selection flag such as --serial 0900.

Next, the device's API as a resource tree, from the introspection data it carries (-d hides doc comments):

$ ww introspect -d
trait AllGpioApi all_gpio_api@0.1.0
└─ 0 impl port[]: ww_gpio::Bank
   ├─ 0 impl pin[]: ww_gpio::Pin
   │  ├─ 0 fn set_output_level(level: Level)
   │  ├─ 1 fn toggle()
   ...

From there:

  • ww info: what the device is (firmware, board, ids, label, last reset), on any device implementing the device contract.
  • ww observe <path>: watch a trait level's observed properties change, no generated code needed. For calls and streams from a script, use Python.
  • ww usb-loopback: check the link itself, round trips and throughput, whatever the API.
  • ww api tree <path> and ww api scaffold <path>: the same tree from an API crate's source, and the server handlers to implement for it.

Commands

Command Needs a device What it does
ww list no (not opened) Lists connected devices matching the device selection
ww introspect yes Prints the device's resource tree from its introspection data
ww api no From an API crate's source: resource tree, AST, server scaffold, snapshots (save, check, diff)
ww observe yes Observes a trait level's +observe properties: values, rate, link bytes/s
ww usb-loopback yes, any API Runs link loopback and throughput tests
ww dfu yes, any API Firmware updates through ww_dfu traits: reboot, status, confirm, revert; flash a chunky device
ww dfu rp a boot ROM RP2040 / RP2350 in BOOTSEL mode: list, flash, reboot, follow back
ww dfu stm32 a boot ROM STM32 system bootloader in DFU mode: list, layout, flash, read, erase, follow back
ww counters yes, any API The device's cnt counters, decoded with its ELF: read, watch, TUI, reset
ww info yes, any API What the device is: firmware, board, ids, label, last reset
ww report yes, any API A bug report in one file: info, counters, stored log, crash, decoded with the ELF
ww pins yes, with PinDiag Pin tests: shorts to GND, the supply and each other, pulls; plan files
ww elf no The ELF directory: ww elf add a release archive, ww elf id
ww settings no Decodes a ww_settings::Store flash dump
ww config no Shows or saves the device selection in a project ww.toml

ww help <command> or ww <command> --help prints all options of a command; the command reference explains each one with examples.

Device selection

Commands working with a device (introspect, observe, info, ...) connect to the only device matching the selection, and ww list shows all the devices it matches. With no selection, the only connected device reporting a WireWeaver API id is used; if several match, they are all printed and nothing is opened, run ww list to see them and narrow the selection.

Flag Env variable ww.toml key Matches
-s, --serial WW_SERIAL serial Serial number containing the value
-l, --label WW_LABEL label User label, whole
-p, --product WW_PRODUCT product Product description containing the value
--manufacturer WW_MANUFACTURER manufacturer Manufacturer containing the value
--api WW_API api Implemented API: name[@req], e.g. blinky_api@^0.1
--vid-pid WW_VID_PID vid_pid USB VID:PID in hex, e.g. c0de:cafe
--usb-path WW_USB_PATH usb_path USB bus and port chain as shown by ww list, e.g. 003-1.2
--rtt WW_RTT rtt Connect over RTT through a debug probe: the probe-rs chip name, e.g. STM32G0B1RETx, see below
--probe WW_PROBE probe With --rtt, the probe: serial containing the value, VID:PID in hex or VID:PID:SERIAL
--elf WW_ELF elf The firmware ELF file: with --rtt the RTT control block address is taken from it instead of scanning RAM; counters and report decode with it when given on the command line
--probe-speed-khz WW_PROBE_SPEED_KHZ probe_speed_khz With --rtt, the SWD clock in kHz, not a filter (the probe's default without it)
--timeout-ms WW_TIMEOUT_MS timeout_ms Request timeout in milliseconds, not a filter
--link-setup-timeout-ms WW_LINK_SETUP_TIMEOUT_MS link_setup_timeout_ms How long to wait for the device to answer when connecting, in milliseconds, not a filter. Default: USB 1000, RTT 10000
  • All text matching ignores case.
  • The version requirement in --api uses Cargo's SemVer syntax; without it, any version matches.
  • --usb-path pins a physical port, so it keeps selecting the same device across re-enumeration, and tells apart identical devices without serial numbers.
  • The flags are global: they can be given before or after the subcommand (ww --serial 2100 list and ww list --serial 2100 are the same).

Settings of different kinds are combined, so for example api from ww.toml and --serial on the command line narrow the selection together. Each setting is taken from the first place that has it: command line flag > environment variable > ww.toml.

Through a debug probe (RTT)

A board with no USB of its own (a Nucleo-64 has only its ST-LINK) is reached over RTT through the debug probe, with probe-rs: --rtt CHIP connects every command (info, counters, sync, pins, dfu reboot, observe, report, ...) through it instead of USB. The firmware serves WireWeaver on the RTT channels ww_up and ww_down (see RTT); a running firmware is attached to as it is, without a reset.

ww --rtt STM32G0B1RETx info                                     # the only probe
ww --rtt STM32G0B1RETx --probe 066D counters read                # one of several, by a part of its serial
ww --rtt STM32G0B1RETx --elf target/thumbv6m-none-eabi/debug/fw sync   # control block from the ELF, no RAM scan
ww list --probes                                                 # the probes, and who holds each
  • Without --elf the RAM is scanned for the RTT control block (about 1.5 s for 144 KiB through an ST-LINK); with it the address is read from the _SEGGER_RTT symbol. A firmware that doesn't match the file is still found by a scan, with a warning.
  • The USB filters (--serial, --label, --product, --manufacturer, --vid-pid, --usb-path) describe a USB device and are refused next to --rtt from the same or a stronger source; from ww.toml under a --rtt flag they are left out (ww names them in its messages). --api is checked on the device once connected.
  • --probe and --probe-speed-khz without --rtt are an error.
  • A probe is held by one program at a time. When another WireWeaver program has it, the error names it (held by g0b1re, pid 4242, ... from its lock file); a program that writes no lock file (probe-rs run, OpenOCD) is named on Linux from /proc. With a ww hub holding it (ww hub take rtt:<probe serial>:<chip>), several programs share the probe.
  • Shell completion offers the chip names (from what is typed so far) and the serials of the connected probes.

A board project keeps it in ww.toml; a relative elf path there is relative to the file:

[device]
rtt = "STM32G0B1RETx"
elf = "target/thumbv6m-none-eabi/debug/nucleo_g0b1re_template"

Project config: ww.toml

Device selection that stays the same for a project goes into the [device] table of a ww.toml, using the keys from the table above:

# ww.toml
[device]
api = "blinky_api@^0.1"
serial = "4B333720"
timeout_ms = 2000

ww.toml is looked up in the current directory and then its parents, so one file at the project root covers the whole project. --config <path> (or WW_CONFIG) uses another file, --no-config ignores it. Unknown keys are reported as errors rather than ignored. ww config show prints the config file in use, then every setting with its resolved value and where it came from (flag, env variable or file), so it answers "why is this device (not) selected".

See also

  • Command reference: every command and option
  • USB: how devices report their API id and user label, and how the host finds them
  • ww_self: introspection data format