The wireless-programmer binary is both the daemon and a one-shot client of
it. Run it with no subcommand (or daemon) to start the daemon; run any of
the subcommands below to talk to a running daemon over its Unix socket.
wireless-programmer [OPTIONS] [COMMAND]
Commands:
daemon Run the IPC daemon (default when no subcommand is given)
scan Enumerate candidate devices on the radio
probe Read a single candidate's device info
program Start a programming job and stream its progress
identify Blink a device's LED so an operator can find it
link-status Report radio/link state
hello Exchange version + driver capabilities
job Inspect or control a running job
Options:
--socket <SOCKET> Override the daemon socket path (every subcommand)
-i, --interface <IFACE> Wireless interface for the daemon (e.g. wlan0)
-v, --verbose Verbose logging (daemon only)
-h, --help Print help
-V, --version Print version
Client subcommands connect to the daemon socket, resolved in this order:
--socket PATHon the command line;$BIGFRED_DATA_DIR/run/wireless-programmer/wireless-programmer.sock;$DATA_DIR/run/wireless-programmer/wireless-programmer.sock;/data/run/wireless-programmer/wireless-programmer.sock.
The daemon creates the parent directory and binds the socket with mode
0660. Peers are checked via SO_PEERCRED against an allowlist (default
bigfred, bigfred-wizard); override it with
WIRELESS_PROGRAMMER_ALLOW_USERS=alice,bob (comma-separated login names).
Because the mode is 0660, the socket also needs a group owner, or a
non-root client is refused by the filesystem before SO_PEERCRED is ever
consulted. On startup the daemon chowns the socket to the primary group of
the first allowlist entry (so bigfred by default); set
WIRELESS_PROGRAMMER_SOCKET_GROUP_USER to choose a different login name
whose primary group should own it. If that user does not exist, or the
daemon is not privileged enough to chown, it logs a warning and leaves the
socket owner-only — useful on a development machine, fatal for peers.
By default the daemon picks the first interface under /sys/class/net that
has a wireless subdirectory. On a hub with more than one radio, pin it:
wireless-programmer --interface wlan1
wireless-programmer daemon -i wlp2s0 --verbose--interface / -i is accepted both at the top level (when starting the
daemon with no subcommand) and on daemon. A missing or non-wireless name
fails at start-up with a non-zero exit. The same choice can be set with
WIRELESS_PROGRAMMER_INTERFACE; the CLI flag overrides the environment.
link-status reports the configured (or auto-selected) interface name.
Every client subcommand accepts:
--json— emit machine-readable JSON instead of human-readable text;--timeout 30s— per-operation timeout (parsed byhumantime, default 10s);--socket PATH— override the daemon socket path.
# 1. What drivers does this daemon know?
wireless-programmer hello
# 2. Bring the radio up and scan for config APs.
wireless-programmer scan
# DRIVER KEY RSSI LABEL
# wifred AA:BB:CC:DD:EE:01 -54 wiFred-config-AABBCCDDEE01
# wifred AA:BB:CC:DD:EE:02 -61 wiFred-config-AABBCCDDEE02
# 3. Read one device's current config over the radio.
wireless-programmer probe --driver wifred --key AA:BB:CC:DD:EE:01
# 4. Blink the LED so an operator can find the physical throttle.
wireless-programmer identify --driver wifred --key AA:BB:CC:DD:EE:01 --count 5scan and probe return noCandidates / candidateNotFound errors when
nothing matches; pipe --json for scripting:
wireless-programmer scan --json | jq '.[] | select(.rssi != null) | .key'program starts a job, then opens a job.watch stream and prints progress
until the job reaches a terminal state (done, failed, cancelled). The
exit code reflects the outcome: 0 on done, non-zero otherwise.
wireless-programmer program \
--driver wifred \
--key AA:BB:CC:DD:EE:01 \
--identity 122145 \
--wifi-ssid bigfred2 \
--wifi-psk-file /run/secrets/bigfred-psk \
--server-host bigfred.local \
--server-port 12090 \
--roster-file roster.json--wifi-psk takes the passphrase inline, which leaves it visible in
/proc/<pid>/cmdline to every local user and in shell history. Prefer
--wifi-psk-file (trailing newline stripped), or --wifi-psk-file - to read
it from stdin:
printf '%s' "$PSK" | wireless-programmer program ... --wifi-psk-file -Omit both for an open network. --server-automatic makes --server-host and
--server-port optional, since the device discovers the server over mDNS; the
port then defaults to the wiThrottle port 12090.
--request-file loads a complete ProgramRequest JSON document and ignores
the individual --identity / --wifi-* / --server-* / --roster-file
flags. This is the form bigfred / bigfred-wizard use when they already
have the full request assembled.
wireless-programmer program --driver wifred --key AA:BB:CC:DD:EE:01 \
--request-file request.jsonrequest.json:
When using the individual flags, pass the roster as a JSON array with
--roster-file. Each entry is a RosterEntry; address: null disables the
slot, and the driver caps the number of slots (WiFred: 4) and the highest
function index (WiFred: 16).
roster.json:
[
{ "address": 3, "longAddress": false, "mode": "128", "direction": 0,
"functions": [ { "index": 0, "value": 0 } ] },
{ "address": 4209, "longAddress": true, "mode": "28", "direction": 0,
"functions": [ { "index": 0, "value": 0 }, { "index": 1, "value": 4 } ] }
]| Flag | Field | Notes |
|---|---|---|
--identity |
identity |
Opaque; 6-digit BigFred pairing code for WiFred |
--wifi-ssid |
wifi.ssid |
Required |
--wifi-psk / --wifi-psk-file |
wifi.psk |
Mutually exclusive; omit both for an open network |
--server-host / --server-port |
server.host / server.port |
Required unless --server-automatic |
--server-automatic |
server.automatic |
mDNS discovery instead of a fixed host; port defaults to 12090 |
--roster-file |
roster |
JSON array of RosterEntry |
By default program follows the job's job.watch stream and prints every
frame as it arrives — human-readable lines on stderr, or one compact JSON
object per line on stdout with --json, so a consumer can read progress
incrementally rather than waiting for the outcome. Pass --no-watch to
return the job id immediately and exit:
JOB=$(wireless-programmer program --driver wifred --key AA:BB:CC:DD:EE:01 \
--request-file request.json --no-watch)
wireless-programmer job watch --id "$JOB"wireless-programmer job get --id <id> # snapshot
wireless-programmer job watch --id <id> # stream until terminal
wireless-programmer job cancel --id <id> # request cancellationjob watch prints JobFrames as they arrive until the job reaches done /
failed / cancelled; with --json each frame is one compact JSON object on
its own line.
If no frame arrives within the timeout, the client reports no job progress frame within <timeout> rather than a bare I/O error. Note that the daemon's
worker loop is hardware-gated: until it drives a live radio, job.watch
answers with a single snapshot frame and then goes quiet, so watching a job on
a device-less host reaches that idle deadline by design.
wireless-programmer link-status
# busy: false
# interface: -
# rfkill blocked: falsebusy is true while a programming job holds the radio. rfkillBlocked
reflects the kernel rfkill state (the hub's udev rule unblocks it at boot).
| Code | Meaning |
|---|---|
0 |
Success (job reached done, or a query returned) |
1 |
Failure — daemon error, failed/cancelled job, or a client/CLI error |
Errors are printed to stderr as error: <message>. Local problems (a missing
flag, an unreadable --request-file) are reported as such rather than as
daemon failures, so error: --wifi-ssid is required (or use --request-file)
means the invocation was wrong, not that the daemon misbehaved. With --json
progress frames still go to stdout, one per line, so a failed job's frames
remain machine-parseable.
| Variable | Purpose |
|---|---|
BIGFRED_DATA_DIR |
Data root (default /data); socket is <dir>/run/wireless-programmer/wireless-programmer.sock |
DATA_DIR |
Fallback data root |
WIRELESS_PROGRAMMER_ALLOW_USERS |
Comma-separated peer allowlist (daemon only) |
WIRELESS_PROGRAMMER_SOCKET_GROUP_USER |
Login name whose primary group owns the socket (daemon only; defaults to the first allowlist entry) |
WIRELESS_PROGRAMMER_INTERFACE |
Wireless interface name for the daemon (e.g. wlan0); overridden by --interface |
WIRELESS_PROGRAMMER_GIT_COMMIT |
Git commit baked into the hello response (build-time) |
WIRELESS_PROGRAMMER_BUILD_TIME |
UTC build timestamp baked into version metadata (build-time, optional) |
Release binaries also carry an ELF section .wireless-programmer.version
({"version":"v…","commit":"…"}) injected by the release workflow; hello
prefers that tag over CARGO_PKG_VERSION when present.
{ "identity": "122145", "wifi": { "ssid": "bigfred2", "psk": "correct-horse-battery-staple" }, "server": { "host": "bigfred.local", "port": 12090, "automatic": false }, "roster": [ { "address": 3, "longAddress": false, "mode": "128", "direction": 0, "functions": [ { "index": 0, "value": 0 }, { "index": 1, "value": 4 } ] } ] }