CLI wrapper for the OpenThread Border Router REST API.
| Script | Output style |
|---|---|
otbr_restapi_cli.py |
Flattened (JSON:API envelopes unwrapped by default) |
Run from the project root with:
PYTHONPATH=src python3 -m td_cli otbr-restapi [global-options] <command> ...The wrapper forwards the same OTBR REST API global options to otbr_restapi_cli.py.
Compatibility note: otbr-restapi keeps its existing exit-code contract. In this codebase, rc 4 still means an HTTP/action failure, not a missing local input file.
These options apply to every command and must be placed before the subcommand.
| Option | Default | Description |
|---|---|---|
--host HOST |
127.0.0.1 |
OTBR REST API host |
--port PORT |
8081 |
OTBR REST API port |
--base-url URL |
— | Override host/port with a full base URL |
--timeout SECS |
10 |
HTTP request timeout in seconds |
--accept MIME |
application/vnd.api+json |
Default Accept header; choices: application/vnd.api+json, application/json, text/plain |
--raw |
off | Return raw JSON:API envelopes instead of flattened output |
--output FILE |
— | Write JSON result to a file instead of stdout |
--datadir DIR |
auto | Data directory for file reads/writes (takes precedence over $TD_DATA_DIR; if omitted: $TD_DATA_DIR, then /data, then ./data) |
--poll-interval FLOAT |
2.0 |
Seconds between action status polls |
--poll-timeout FLOAT |
derived | Max wall-clock seconds to wait for an action. The default is task_timeout + max(5, 20% of task_timeout, 2 * poll_interval) |
--no-progress |
off | Suppress per-device [N/T] id → status (Xs) progress lines printed to stderr on fetch-all commands |
--no-auto-output |
off | Disable automatic output file naming; send JSON to stdout instead of <datadir>/td-otbr-restapi-<resource>-<command>.json |
--lab |
off | Allow currently experimental mutating commands (node state set, node dataset active set, actions enqueue add-thread-device, actions enqueue reset-network-diag-counter) |
--debug, -d |
off | Enable debug logging |
Experimental command gate: The following commands are currently experimental and require
--lab:node state set,node dataset active set,actions enqueue add-thread-device,actions enqueue reset-network-diag-counter.
| Command | Description |
|---|---|
node get |
Get full OTBR node record from /api/node |
node state get |
Get current Thread radio state |
node state set |
Enable or disable Thread (experimental; requires --lab) |
node dataset active get |
Get active Thread dataset (JSON or TLV hex) |
node dataset active set |
Create or update active Thread dataset (experimental; requires --lab) |
devices list |
List all known devices from /api/devices |
devices get |
Get a single device by extAddress |
devices fetch |
Trigger updateDeviceCollectionTask, wait, return device list |
diagnostics list |
List all cached diagnostic records |
diagnostics get |
Get a single diagnostic record by ID |
diagnostics fetch |
Fetch diagnostics for one device (enqueue + wait + return result) |
diagnostics fetch-all |
Fetch diagnostics for all (or given) devices |
actions list |
List all actions |
actions get |
Get a single action by ID |
actions enqueue add-thread-device |
Enqueue addThreadDeviceTask (experimental; requires --lab) |
actions enqueue get-network-diagnostic |
Enqueue getNetworkDiagnosticTask |
actions enqueue reset-network-diag-counter |
Enqueue resetNetworkDiagCounterTask (experimental; requires --lab) |
actions enqueue get-energy-scan |
Enqueue getEnergyScanTask |
actions enqueue update-device-collection |
Enqueue updateDeviceCollectionTask |
mesh-diagnostics children |
Fetch child table for a device (TLV 29) |
mesh-diagnostics child-ipv6 |
Fetch child IPv6 addresses for a device (TLV 30) |
mesh-diagnostics router-neighbors |
Fetch router neighbor table for a device (TLV 31) |
mesh-diagnostics fetch |
Fetch caller-specified mesh-diagnostic TLVs for one device |
mesh-diagnostics fetch-all |
Fetch mesh diagnostics for all (or given) devices |
topology |
Full sweep: devices fetch → diagnostics fetch-all → mesh-diagnostics fetch-all |
Read or mutate local OTBR node data.
Get the OTBR node record from /api/node.
node get [--fields FIELDS]
| Option | Description |
|---|---|
--fields |
Sparse-field selector (repeatable) |
Examples:
# Print full node record
PYTHONPATH=src python3 -m td_cli otbr-restapi node get
# Select specific fields only
PYTHONPATH=src python3 -m td_cli otbr-restapi node get \
--fields 'threadBorderRouter=extAddress,rloc16,role'
# Save to file
PYTHONPATH=src python3 -m td_cli otbr-restapi --output data/node.json node getGet current Thread radio state.
node state get
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi node state get
PYTHONPATH=src python3 -m td_cli otbr-restapi --host 192.168.1.10 node state getEnable or disable Thread.
Warning: This command is currently experimental and requires
--lab.
node state set --value {enable,disable}
| Option | Required | Description |
|---|---|---|
--value |
yes | enable or disable |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab node state set --value enable
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab node state set --value disableGet the active Thread dataset.
node dataset active get [--text]
| Option | Description |
|---|---|
--text |
Request as text/plain TLV hex string instead of JSON |
When output is written to a file, sensitive JSON fields such as networkKey
and pskc are recursively replaced with [Redacted]. Because operational
dataset TLV hex cannot be filtered field by field, --text file output is
replaced entirely with [Redacted]. Output printed directly to the terminal is
unchanged.
Examples:
# JSON
PYTHONPATH=src python3 -m td_cli otbr-restapi node dataset active get
# Raw TLV hex string
PYTHONPATH=src python3 -m td_cli otbr-restapi node dataset active get --textCreate or update the active Thread dataset. Exactly one input source is required.
Warning: This command is currently experimental and requires
--lab.
node dataset active set (--json JSON | --json-file FILE | --text TEXT | --text-file FILE)
| Option | Description |
|---|---|
--json JSON |
Inline JSON payload |
--json-file FILE |
Path to a JSON file |
--text TEXT |
Inline TLV hex string |
--text-file FILE |
Path to a file containing the TLV hex string |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
node dataset active set --json-file data/dataset.json
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
node dataset active set --text 0e080000000000010000000300000f...Read Thread devices from /api/devices.
List all known devices.
devices list [--fields FIELDS] [--with-meta]
| Option | Description |
|---|---|
--fields |
Repeatable sparse-field selector, e.g. threadDevice=hostName,role |
--with-meta |
Include collection meta alongside the flattened items |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi devices list
PYTHONPATH=src python3 -m td_cli otbr-restapi \
devices list --fields 'threadDevice=hostName,role'
PYTHONPATH=src python3 -m td_cli otbr-restapi devices list --with-metaGet a single device by its extAddress.
devices get --device-id DEVICE_ID [--fields FIELDS]
| Option | Required | Description |
|---|---|---|
--device-id |
yes | Device extAddress (16-char hex) |
--fields |
no | Sparse-field selector |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
devices get --device-id aabbccddeeff0011
PYTHONPATH=src python3 -m td_cli otbr-restapi \
devices get --device-id aabbccddeeff0011 \
--fields 'threadDevice=extAddress,role'Trigger updateDeviceCollectionTask, wait for completion, then return the populated device list. Combines enqueue + poll + list in one call.
devices fetch [--device-count N] [--task-timeout SECS] [--max-age SECS]
[--max-retries N] [--whole-action-attempts N]
[--structured-outcome]
| Option | Default | Description |
|---|---|---|
--device-count |
255 |
Max devices to discover |
--task-timeout |
30 |
Server-side task timeout in seconds; the derived client deadline is 36 seconds with the default poll interval |
--max-age |
60 |
Max age of cached device entries in seconds |
--max-retries |
2 |
Max retries per device |
--whole-action-attempts |
1 |
Whole discovery attempts; repeats only after a known stopped/failed action, never after an ambiguous enqueue or client deadline |
--structured-outcome |
off | Include action, partial-result, freshness, attempt, and timing metadata instead of only the compatibility device array |
deviceCount is a discovery target, not a cap applied to the returned collection. The default 255 lets OTBR exhaust discovery without requiring the caller to know the network size.
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi devices fetch
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/devices.json \
devices fetch --device-count 20 --task-timeout 60Read Thread network diagnostics from /api/diagnostics.
List all known diagnostic records.
diagnostics list [--fields FIELDS] [--with-meta]
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi diagnostics list
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics list --fields 'networkDiagnostics=extAddress,rloc16,macCounters'Get a single diagnostic record by ID.
diagnostics get --diagnostics-id ID
| Option | Required | Description |
|---|---|---|
--diagnostics-id |
yes | Diagnostic record UUID |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics get --diagnostics-id a6a0b433-437e-45bf-9994-58e0d7b32399
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/diag-a6a0.json \
diagnostics get --diagnostics-id a6a0b433-437e-45bf-9994-58e0d7b32399Enqueue getNetworkDiagnosticTask for one device, wait for completion, and return the diagnostic result. MAC counter enrichment is applied by default.
diagnostics fetch --device-id DEVICE_ID
[--types TLV ...]
[--preset {recommended,full,minimal,basic}]
[--task-timeout SECS]
[--child-task-timeout SECS]
[--child-poll-timeout FLOAT]
[--children-fetch-fast | --children-fetch-fast-no]
[--children-fetch-detail | --children-fetch-detail-no]
[--destination-type TYPE]
[--no-fallback]
[--fallback-preset {medium,minimal,basic}]
[--no-enrich-mac-counters]
| Option | Default | Description |
|---|---|---|
--device-id |
required | Device extAddress (16-char hex) |
--types |
recommended set | Space-separated diagnostic TLV names |
--preset |
— | recommended, full, minimal, or basic; overrides --types |
--task-timeout |
15 |
Server-side task timeout in seconds; the default derived client deadline is 20 seconds |
--destination-type |
extended |
Destination addressing mode: extended, mleid, or rloc |
--no-fallback |
off | Disable TLV fallback retry; skip the device immediately on failure |
--fallback-preset |
minimal |
TLV preset to retry with when the primary request fails: medium, minimal, or basic |
--no-enrich-mac-counters |
off | Return raw macCounters values only; skip computed totals and ratios |
Single-device fetch retries with the minimal TLV preset by default after a known terminal action failure. Pass --no-fallback to disable this. Timeouts, ambiguous enqueue outcomes, invalid responses, and transport failures are never retried as replacement actions.
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch --device-id aabbccddeeff0011 --preset recommended
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch --device-id aabbccddeeff0011 \
--types extAddress rloc16 macCounters mleCounters \
--no-enrich-mac-counters
PYTHONPATH=src python3 -m td_cli otbr-restapi --no-auto-output \
diagnostics fetch --device-id aabbccddeeff0011 --preset recommended --no-fallbackFetch diagnostics for all known devices (or a given list), one device at a time. The device list is automatically refreshed via updateDeviceCollectionTask before fetching (pass --no-update-devices to skip). MAC counter enrichment and per-device progress are on by default.
diagnostics fetch-all [--device-ids ID ...]
[--types TLV ...]
[--preset {recommended,full,minimal,basic}]
[--task-timeout SECS]
[--destination-type TYPE]
[--children-expand-by-rloc]
[--no-update-devices]
[--no-fallback]
[--fallback-preset {medium,minimal,basic}]
[--preserve-diagnostics]
[--items-only]
[--no-enrich-mac-counters]
| Option | Default | Description |
|---|---|---|
--device-ids |
all devices | Space-separated extAddress IDs to query |
--types |
recommended set | Diagnostic TLV names |
--preset |
— | recommended, full, minimal, or basic; overrides --types |
--task-timeout |
15 |
Server-side task timeout for routers and non-children |
--child-task-timeout |
30 |
Server-side task timeout for all child passes and legacy child requests |
--child-poll-timeout |
derived | Independent client polling budget for child actions; must satisfy the derived timing minimum |
--children-fetch-fast / --children-fetch-fast-no |
fast enabled | Enable or disable MTD fast TLVs 0, 1, 2, 8 |
--children-fetch-detail / --children-fetch-detail-no |
detail disabled | Enable or disable optional MTD detail TLVs 0, 1, 2, 8, 9 |
--destination-type |
extended |
Destination addressing mode: extended, mleid, or rloc |
--children-expand-by-rloc |
off | After the extended-address sweep, query children listed in selected parents' childTable by RLOC16; requires --destination-type extended |
--no-update-devices |
off | Skip updateDeviceCollectionTask; use the cached device list |
--no-fallback |
off | Disable additional diagnostic actions; known children retain their selected initial sets, with one attempt per enabled pass |
--no-basic-fallback |
off | Suppress only the final basic tier for routers and unknown-mode children; does not suppress MTD fast or its same-set retry |
--fallback-preset |
off | Use the legacy/custom policy with one explicit fallback preset after a known terminal failure |
--preserve-diagnostics |
off | Keep the existing diagnostics collection. By default a full sweep clears it once after checking for active diagnostic work |
--items-only |
off | Return only successful diagnostic items instead of the structured sweep outcome |
--no-enrich-mac-counters |
off | Return raw macCounters without computed totals and ratios |
Note:
--update-deviceshas been replaced by--no-update-devices. Device list refresh now runs by default; pass--no-update-devicesto opt out.
The default result is a structured outcome containing items, deviceResults, partial, clearing state, counts, and timestamps. Results are automatically written to <datadir>/td-otbr-restapi-diagnostics-fetch-all.json unless --no-auto-output or --output is specified.
If unresolved mesh-local IIDs guard a partial sweep, the diagnostic outcome
and its digest-bound .network.json companion are still updated. When no
usable diagnostic items are returned, the previous diagnostic dataset, its
network companion, and its checkpoint are retained unchanged. Outcome scope
uses the normal observed/operator/unknown provenance rules; a guarded outcome
does not make the retained data fresh or complete. A scope-write failure is
reported as an error, and stale bindings remain invalid. Existing stale
bindings are not repaired by cache-only Unified processing.
Every standalone sweep saves its full outcome, not only sweeps affected by
the unresolved-IID guard. --items-only changes the returned primary payload
to the compatibility array but does not discard outcome metadata. The
outcome uses the primary filename with .outcome before its suffix, including
custom output paths; --no-auto-output without an explicit output writes
neither primary nor outcome.
By default, the roster's mode.fullThreadDevice and mode.device fields
classify children. MTD fast requests exactly TLVs 0, 1, 2, 8; optional MTD
detail and FTD medium each request exactly 0, 1, 2, 8, 9. mode.device is
checked against fullThreadDevice when both are present. Missing, malformed,
or conflicting evidence warns and uses the legacy child request progression.
rxOnWhenIdle is not used to infer MTD versus FTD.
The two-pass model deliberately reduces coverage relative to the old 7-TLV
child request: fast does not request macCounters, and neither pass requests
threadStackVersion or mleCounters. Missing evidence stays absent in health
evaluation. With both MTD passes enabled, fast runs first and detail is still
requested after a fast success; up to four actions per MTD child are possible.
Successful fast data is atomically checkpointed before detail begins. If
detail fails, the fast data remains selected and the outcome is partial.
Same-set retry recovery is reported separately from smaller-TLV fallback.
--types, a non-recommended --preset, or --fallback-preset selects the
legacy/custom request policy. Explicit child pass controls cannot be combined
with those options. --no-basic-fallback applies only to the final tier of
router and unknown-mode legacy progression; --no-fallback disables retries
but does not broaden known-child initial TLVs or disable the fast pass.
Off by default. When enabled, the normal extended-address sweep runs first
and requests childTable from every selected non-child device, adding it to
both the primary and fallback TLV lists only when missing (TLV 16 counts as
present). Global presets are not changed. After the sweep, each child entry
from this run's parent results is turned into a six-character RLOC
destination: (parentRloc16 & 0xFC00) | childId, formatted as 00 plus the
four hex digits. For example, parent 0x9400 with child IDs 53 and 14 gives
009435 and 00940e, and parent 0x3800 with child 42 gives 00382a.
- Scope: expansion is one level deep and uses only the selected parents'
current-run tables. It does not read cached data, does not run another
discovery pass, and never expands a child's own
childTable. Parents must report a router RLOC16 (low ten bits zero, router ID 0–62). Child IDs must be integers 1–511. Booleans, floats, and malformed strings are rejected. - Coverage: targets are deduplicated by child RLOC16. A target is skipped
as
coveredwhen the base sweep already returned a valid extended address with that RLOC16. Duplicate entries with different parent identity or child mode are recorded asconflictand are not queried. - Policies: the child's
childTablemode selects the existing mode-aware passes (MTD fast and optional detail, FTD medium) with child timing, retry, and disabled-pass rules. Missing or conflicting mode uses the legacy child progression (legacy-unknown-mode).--types,--preset, and--fallback-presetuse the same legacy/custom request lists as extended-address children. - Identity checks: a response is published or checkpointed only if it has
a usable
extAddress, itsrloc16matches the target, and its extended address is not already in the results. A later pass must return the same extended address as the accepted pass. If a check fails, no further attempt is made for that target, and earlier validated evidence is kept. - Safety: an unresolved or indeterminate action (timeout, transport or
HTTP error, ambiguous enqueue) in the base sweep or during expansion stops
all later expansion actions; the remaining targets are recorded as
not_attempted. Known terminal action failures and invalid responses are recorded for that target and expansion continues with the next one. - Outcome: the structured outcome gains a
childrenExpansionobject withenabled,partial,parentCount,targetCount,attemptedCount,completedCount,coveredCount, per-targetresults(destination,rloc16, parent identity,childId, mode, policy, status, and the validatedextAddresswhen known), and parent/entryissues. BasedeviceResultsand counters keep their meaning. An incomplete expansion makes the overall outcomepartial. Validated child items are appended toitems. Progress and checkpoint labels userloc:<destination>. If expansion is incomplete and no diagnostic items are usable, the previous dataset, network companion, and checkpoint are retained while the outcome is still written. - Compatibility and load: RLOC addressing depends on an OTBR REST build
that resolves non-extended destinations correctly. It has been verified
only against a user-built Docker image; older servers may return no result
or a wrong device, which is reported as
invalid_response. RLOC16 values can become stale if a child reattaches between the parent read and the child request. Each target adds up to one action per enabled pass plus retries, so mesh load and runtime grow with the number of children.
Examples:
# Opt-in child expansion by RLOC after the normal extended-address sweep
PYTHONPATH=src python3 -m td_cli otbr-restapi diagnostics fetch-all \
--children-expand-by-rloc
# Detail-only MTD collection; FTD medium collection remains enabled
PYTHONPATH=src python3 -m td_cli otbr-restapi diagnostics fetch-all \
--children-fetch-fast-no --children-fetch-detail
# Skip known MTD children while still collecting FTD medium diagnostics
PYTHONPATH=src python3 -m td_cli otbr-restapi diagnostics fetch-all \
--children-fetch-fast-no --children-fetch-detail-noExamples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch-all --preset recommended
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch-all --preset recommended --no-update-devices
PYTHONPATH=src python3 -m td_cli otbr-restapi --no-progress \
diagnostics fetch-all \
--device-ids aabbccddeeff0011 aabbccddeeff0022 \
--no-enrich-mac-counters
PYTHONPATH=src python3 -m td_cli otbr-restapi --no-auto-output \
diagnostics fetch-all --preset recommendedRead or enqueue OTBR task actions.
List all actions.
actions list [--fields FIELDS] [--with-meta]
| Option | Description |
|---|---|
--fields |
Sparse-field selector (repeatable) |
--with-meta |
Include collection meta alongside the flattened items |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi actions list
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions list --fields 'action=id,type,status'Get a single action by ID (useful for polling status).
actions get --action-id ACTION_ID [--fields FIELDS]
| Option | Required | Description |
|---|---|---|
--action-id |
yes | Action UUID |
--fields |
no | Sparse-field selector (repeatable) |
Action status values: pending → active → completed / stopped / failed
(addThreadDeviceTask also uses undiscovered and attempted)
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions get --action-id 9ecae480-07a0-4b72-869d-15858196144e
# Lightweight status-only poll
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions get --action-id 9ecae480-07a0-4b72-869d-15858196144e \
--fields 'action=id,status'
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/action-status.json \
actions get --action-id 9ecae480-07a0-4b72-869d-15858196144eEnqueue addThreadDeviceTask to commission a new joiner device.
Warning: This command is currently experimental and requires
--lab.
actions enqueue add-thread-device --pskd PSKD
(--eui EUI | --discerner DISCERNER | --joiner-id JOINER_ID)
[--timeout SECS]
| Option | Required | Description |
|---|---|---|
--pskd |
yes | Pre-Shared Key for the Device |
--eui |
one of three | EUI-64 address |
--discerner |
one of three | Joiner discerner value |
--joiner-id |
one of three | Joiner ID |
--timeout |
no | Task timeout in seconds |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
actions enqueue add-thread-device \
--eui aabbccddeeff0022 \
--pskd J01NME \
--timeout 120
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
actions enqueue add-thread-device \
--discerner 0xabc \
--pskd S3CRETEnqueue getNetworkDiagnosticTask for a destination address.
actions enqueue get-network-diagnostic --destination DEST
[--types TLV ...]
[--preset {recommended,full,minimal,basic}]
[--timeout SECS]
[--destination-type TYPE]
[--wait]
| Option | Default | Description |
|---|---|---|
--destination |
required | Destination address |
--types |
recommended set | TLV names or integers |
--preset |
— | recommended, full, minimal, or basic; overrides --types |
--timeout |
— | Server-side task timeout in seconds |
--destination-type |
— | extended, mleid, or rloc |
--wait |
off | Poll until completion and return the diagnostic result; exit code 4 on stopped/failed |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-network-diagnostic \
--destination aabbccddeeff0011 \
--preset recommended
# Block until complete, return diagnostic result
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-network-diagnostic \
--destination aabbccddeeff0011 \
--types extAddress rloc16 macCounters mleCounters \
--timeout 120 \
--waitEnqueue resetNetworkDiagCounterTask. Only macCounters (TLV 9) and mleCounters (TLV 34) are resettable; passing any other TLV name is rejected client-side before the request is sent.
Warning: This command is currently experimental and requires
--lab.
actions enqueue reset-network-diag-counter --types TYPE ...
[--destination DEST]
[--timeout SECS]
[--destination-type TYPE]
| Option | Required | Description |
|---|---|---|
--types |
yes | Counter TLV names: macCounters and/or mleCounters |
--destination |
no | Target device address |
--timeout |
no | Server-side task timeout in seconds |
--destination-type |
no | extended, mleid, or rloc |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
actions enqueue reset-network-diag-counter \
--destination aabbccddeeff0011 \
--types macCounters mleCounters
PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
actions enqueue reset-network-diag-counter \
--destination aabbccddeeff0011 \
--types mleCounters \
--timeout 60Enqueue getEnergyScanTask. All scan parameters are optional; the server applies its own defaults when they are omitted.
actions enqueue get-energy-scan --destination DEST
--channel-mask CHAN ...
[--count N]
[--period N]
[--scan-duration N]
[--timeout SECS]
[--destination-type TYPE]
| Option | Required | Description |
|---|---|---|
--destination |
yes | Target device address |
--channel-mask |
yes | One or more channel numbers |
--count |
no | Number of scans per channel (server default: 1) |
--period |
no | Time between scans in ms (server default: 32) |
--scan-duration |
no | Duration per channel scan in ms (server default: 0) |
--timeout |
no | Server-side task timeout in seconds |
--destination-type |
no | extended, mleid, or rloc |
Examples:
# Minimal — server uses defaults for count, period, scan-duration
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-energy-scan \
--destination aabbccddeeff0011 \
--channel-mask 11 15
# Explicit parameters
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-energy-scan \
--destination aabbccddeeff0011 \
--channel-mask 11 15 \
--count 3 --period 32 --scan-duration 50 \
--timeout 60
# Full channel scan 11-26
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-energy-scan \
--destination aabbccddeeff0011 \
--channel-mask 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 \
--count 5 --period 32 --scan-duration 100 \
--timeout 120Enqueue updateDeviceCollectionTask and return immediately with the action record. Use actions get to poll status, or use devices fetch for the combined enqueue-wait-list workflow.
actions enqueue update-device-collection
[--device-count N]
[--max-age SECS]
[--max-retries N]
[--timeout SECS]
| Option | Default | Description |
|---|---|---|
--device-count |
255 |
Max devices to discover |
--max-age |
60 |
Max age of cached device entries in seconds |
--max-retries |
2 |
Max retries per device |
--timeout |
5 |
Server-side task timeout in seconds |
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue update-device-collection
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue update-device-collection \
--device-count 20 --max-age 60 --max-retries 3 --timeout 90
# Capture action ID and poll manually
ACTION_ID=$(PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue update-device-collection | jq -r '.[0].id')
echo "Enqueued action: $ACTION_ID"
while true; do
STATUS=$(PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions get --action-id "$ACTION_ID" | jq -r '.status')
echo "status: $STATUS"
[[ "$STATUS" == "completed" || "$STATUS" == "stopped" || "$STATUS" == "failed" ]] && break
sleep 2
done
# Preferred: enqueue + wait + list in one shot
PYTHONPATH=src python3 -m td_cli otbr-restapi \
devices fetch --device-count 20 --task-timeout 90Fetch mesh-diagnostic TLVs via otMeshDiag. These require an additional round-trip on the server and have higher latency than standard diagnostic TLVs.
Router / leader devices only. The server omits
children,childIpv6Addresses, androuterNeighborsfor child devices (RLOC16 lower 10 bits non-zero). Use--routers-onlyonfetch-allor check roles first:PYTHONPATH=src python3 -m td_cli otbr-restapi \ devices list --fields 'threadDevice=extAddress,role,hostName'
Common per-device options for children, child-ipv6, router-neighbors, and fetch:
| Option | Default | Description |
|---|---|---|
--device-id |
required | Device extAddress (16-char hex) |
--task-timeout |
15 |
Server-side task timeout in seconds |
--poll-timeout |
derived | Client deadline derived from task timeout and poll interval; 20 seconds by default |
--destination-type |
extended |
Destination addressing mode: extended, mleid, or rloc |
Fetch the child table for a device (TLV 29).
mesh-diagnostics children --device-id DEVICE_ID
[--task-timeout SECS] [--poll-timeout FLOAT] [--destination-type TYPE]
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics children --device-id aabbccddeeff0011
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/children-aabb.json \
mesh-diagnostics children --device-id aabbccddeeff0011Fetch child IPv6 addresses for a device (TLV 30).
mesh-diagnostics child-ipv6 --device-id DEVICE_ID
[--task-timeout SECS] [--poll-timeout FLOAT] [--destination-type TYPE]
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics child-ipv6 --device-id aabbccddeeff0011
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics child-ipv6 --device-id aabbccddeeff0011 --task-timeout 600Fetch router neighbor table for a device (TLV 31).
mesh-diagnostics router-neighbors --device-id DEVICE_ID
[--task-timeout SECS] [--poll-timeout FLOAT] [--destination-type TYPE]
Examples:
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics router-neighbors --device-id aabbccddeeff0011
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/router-nbrs-aabb.json \
mesh-diagnostics router-neighbors --device-id aabbccddeeff0011Fetch a caller-specified subset of mesh-diagnostic TLVs for one device. Defaults to all three TLVs when --types is omitted.
mesh-diagnostics fetch --device-id DEVICE_ID
[--types {children,childIpv6Addresses,routerNeighbors} ...]
[--task-timeout SECS] [--poll-timeout FLOAT] [--destination-type TYPE]
Examples:
# All three TLVs
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch --device-id aabbccddeeff0011
# Selected subset
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch --device-id aabbccddeeff0011 \
--types children childIpv6Addresses
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch --device-id aabbccddeeff0011 --types childrenFetch mesh diagnostics for all known devices (or a given list), one device at a time. Device list is refreshed automatically before fetching (pass --no-update-devices to skip).
mesh-diagnostics fetch-all [--device-ids ID ...]
[--types {children,childIpv6Addresses,routerNeighbors} ...]
[--task-timeout SECS]
[--poll-timeout FLOAT]
[--destination-type TYPE]
[--no-update-devices]
[--routers-only]
[--preserve-diagnostics]
[--items-only]
| Option | Default | Description |
|---|---|---|
--device-ids |
all devices | Space-separated extAddress IDs to query |
--types |
all three | children, childIpv6Addresses, routerNeighbors |
--task-timeout |
15 |
Server-side task timeout per device in seconds |
--poll-timeout |
derived | Client deadline derived from task timeout and poll interval; 20 seconds by default |
--destination-type |
extended |
Destination addressing mode: extended, mleid, or rloc |
--no-update-devices |
off | Skip updateDeviceCollectionTask; use the cached device list |
--routers-only |
off | Filter to router devices only (RLOC16 lower 10 bits == 0); skips child devices that return empty mesh-diag records |
--preserve-diagnostics |
off | Keep the existing diagnostics collection instead of clearing once for this full sweep |
--items-only |
off | Return only successful diagnostic items; still save the full outcome companion when output is enabled |
Note:
--update-deviceshas been replaced by--no-update-devices. Device list refresh now runs by default.
Results are automatically written to <datadir>/td-otbr-restapi-mesh-diagnostics-fetch-all.json unless --no-auto-output or --output is specified.
Examples:
# Router devices only (recommended)
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch-all --routers-only
# All devices, skip device refresh
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch-all --no-update-devices
# Two specific devices, router-neighbors only
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch-all \
--device-ids aabbccddeeff0011 aabbccddeeff0022 \
--types routerNeighborsRun the full topology sweep in a single command. Executes three steps in sequence and writes all output files automatically under --datadir:
| Step | Action | Output file |
|---|---|---|
| 1 | devices fetch (updateDeviceCollectionTask) |
td-otbr-restapi-devices-fetch.json |
| 2 | diagnostics fetch-all --preset recommended |
td-otbr-restapi-diagnostics-fetch-all.json |
| 3 | mesh-diagnostics fetch-all --routers-only |
td-otbr-restapi-mesh-diagnostics-fetch-all.json |
topology [--preset {recommended,full,minimal,basic}]
[--child-task-timeout SECS]
[--child-poll-timeout FLOAT]
[--children-fetch-fast | --children-fetch-fast-no]
[--children-fetch-detail | --children-fetch-detail-no]
[--skip-devices]
[--skip-diagnostics]
[--skip-mesh-diagnostics]
[--no-update-devices]
[--no-enrich-mac-counters]
[--no-fallback]
[--fallback-preset {medium,minimal,basic}]
[--preserve-diagnostics]
| Option | Default | Description |
|---|---|---|
--preset |
recommended |
TLV preset for the diagnostics step |
--child-task-timeout |
30 |
Server-side timeout for child diagnostic actions |
--child-poll-timeout |
derived | Independent client polling budget for child actions |
--children-fetch-fast / --children-fetch-fast-no |
fast enabled | Enable or disable the MTD fast pass |
--children-fetch-detail / --children-fetch-detail-no |
detail disabled | Enable or disable the optional MTD detail pass |
--skip-devices |
off | Skip Step 1 (device refresh); use the current device list for subsequent steps |
--skip-diagnostics |
off | Skip Step 2 (network diagnostics fetch-all) |
--skip-mesh-diagnostics |
off | Skip Step 3 (mesh diagnostics fetch-all) |
--no-update-devices |
off | When --skip-devices is set, also skip updateDeviceCollectionTask in Steps 2 and 3 |
--no-enrich-mac-counters |
off | Disable MAC counter enrichment on the diagnostics result |
--no-fallback |
off | Disable additional actions while retaining known-child initial sets |
--fallback-preset |
off | Opt in to one terminal-failure retry for Step 2 with medium, minimal, or basic |
--preserve-diagnostics |
off | Keep existing diagnostics instead of the default one-time clear before Step 2 |
Progress for each step and each device is printed to stderr. Pass --no-progress (global flag) to suppress. Compatibility arrays remain in the three primary files. The diagnostic and mesh steps also write .outcome.json sidecars with partial/per-device metadata.
Topology's diagnostics step uses the same mode-aware child policy and pass
controls documented under diagnostics fetch-all;
the mesh step remains independent.
Examples:
# Full sweep with defaults
PYTHONPATH=src python3 -m td_cli otbr-restapi topology
# Remote OTBR, custom data directory
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--host 192.168.1.10 --datadir /tmp/topology \
topology
# Skip device refresh (already done)
PYTHONPATH=src python3 -m td_cli otbr-restapi topology --skip-devices
# Diagnostics step only
PYTHONPATH=src python3 -m td_cli otbr-restapi topology --skip-mesh-diagnostics
# Silent (no progress output)
PYTHONPATH=src python3 -m td_cli otbr-restapi --no-progress topology| Code | Constant | Meaning |
|---|---|---|
0 |
EXIT_SUCCESS |
Success |
1 |
EXIT_UNEXPECTED |
Unexpected / unhandled error |
2 |
EXIT_USAGE |
Usage / argument error (OTBRUsageError) |
3 |
EXIT_CONNECTION |
Connection error (OTBRConnectionError) |
4 |
EXIT_HTTP |
HTTP error, or action stopped/failed (OTBRHTTPError, OTBRActionError) |
5 |
EXIT_INVALID_RESPONSE |
Invalid or unexpected API response (OTBRInvalidResponseError) |
When --no-auto-output is not set and --output is not specified, fetch/list commands automatically write results to <datadir>/<filename>:
| Command | Auto-output filename |
|---|---|
devices list |
td-otbr-restapi-devices-list.json |
devices fetch |
td-otbr-restapi-devices-fetch.json |
diagnostics list |
td-otbr-restapi-diagnostics-list.json |
diagnostics fetch |
td-otbr-restapi-diagnostics-fetch.json |
diagnostics fetch-all |
td-otbr-restapi-diagnostics-fetch-all.json |
actions list |
td-otbr-restapi-actions-list.json |
mesh-diagnostics fetch |
td-otbr-restapi-mesh-diagnostics-fetch.json |
mesh-diagnostics fetch-all |
td-otbr-restapi-mesh-diagnostics-fetch-all.json |
topology (step 1 — devices) |
td-otbr-restapi-devices-fetch.json |
topology (step 2 — diagnostics) |
td-otbr-restapi-diagnostics-fetch-all.json |
topology (step 3 — mesh-diagnostics) |
td-otbr-restapi-mesh-diagnostics-fetch-all.json |
Topology also writes td-otbr-restapi-diagnostics-fetch-all.outcome.json and td-otbr-restapi-mesh-diagnostics-fetch-all.outcome.json. These sidecars distinguish complete and partial sweeps without changing the array shape consumed by existing dashboard code.
Commands with an automatic or explicit output path may atomically update a
sibling .partial.json checkpoint while collection is running. The web server
exposes checkpoint metadata to the dashboard for progressive rendering. A
checkpoint is transient and does not replace the mandatory final output.
Action POST requests are single-attempt because the OTBR action API has no idempotency key. A lost POST response is reported as indeterminate_enqueue; the client does not blindly create a replacement action. Idempotent reads may use bounded transport retries.
Note: The
topologycommand always writes its three files regardless of--no-auto-outputbecause all output is file-based (the command returns nothing to stdout).
The --fields option restricts which attributes are returned. The server filters at the source, so only the requested attributes are transmitted.
Syntax: --fields 'TYPE=field1,field2' or --fields TYPE (all fields of that type).
Repeatable: pass --fields multiple times to select fields from more than one type.
The TYPE matches the JSON:API resource type value for the collection being queried.
Used with devices list, devices get, devices fetch, node get.
| Field | Type | Description |
|---|---|---|
extAddress |
string (hex) | 64-bit IEEE 802.15.4 extended address — device ID |
mlEidIid |
string (hex) | Mesh-Local EID Interface Identifier |
omrIpv6Address |
string (IPv6) | Off-Mesh-Routable IPv6 address |
hostName |
string | mDNS hostname (.local) |
role |
string | Thread role: leader, router, child, sleepy-child |
mode |
object | Device mode flags |
mode.deviceTypeFTD |
bool | true = Full Thread Device |
mode.rxOnWhenIdle |
bool | true = receiver always on |
mode.fullNetworkData |
bool | true = subscribes to full network data |
created |
ISO 8601 | Timestamp when the device entry was created |
updated |
ISO 8601 | Timestamp when the device entry was last updated |
Additional fields present only on threadBorderRouter items:
| Field | Type | Description |
|---|---|---|
rloc16 |
string (hex) | 16-bit RLOC address |
routerId |
number | Router ID (upper 6 bits of RLOC16) |
rlocAddress |
string (IPv6) | Full RLOC IPv6 address |
routerCount |
number | Number of active routers in the network |
networkName |
string | Thread network name |
extPanId |
string (hex) | Extended PAN ID |
leaderData |
object | Leader data |
leaderData.partitionId |
number | Partition ID |
leaderData.weighting |
number | Leader weighting |
leaderData.dataVersion |
number | Network data version |
leaderData.stableDataVersion |
number | Stable network data version |
leaderData.leaderRouterId |
number | Current leader's router ID |
baId |
string (hex) | Border Agent ID |
baState |
string | Border Agent state |
Examples:
devices list --fields 'threadDevice=hostName,role,mode'
devices list --fields threadDevice
node get --fields 'threadBorderRouter=extAddress,rloc16,baId'Used with diagnostics list, diagnostics get, diagnostics fetch, diagnostics fetch-all.
| Field | TLV | Description |
|---|---|---|
extAddress |
0 | 64-bit extended MAC address |
rloc16 |
1 | 16-bit RLOC (hex string, e.g. 0x1400) |
routerId |
1 | Router ID (derived from RLOC16; routers only) |
mode |
2 | Device mode flags (deviceTypeFTD, rxOnWhenIdle, fullNetworkData) |
timeout |
3 | Max polling period for SEDs (seconds) |
connectivity |
4 | Connectivity info (parentPriority, linkQuality1/2/3, leaderCost, idSequence, activeRouters, sedBufferSize, sedDatagramCount) |
route |
5 | Route64 info: idSequence, routeData[] (each with routeId, linkQualityIn, linkQualityOut, routeCost) |
leaderData |
6 | Leader data object |
networkData |
7 | Network data (hex string) |
ipv6Addresses |
8 | List of IPv6 addresses |
macCounters |
9 | MAC packet counters (raw: ifInUnknownProtos, ifInErrors, ifOutErrors, ifInUcastPkts, ifInBroadcastPkts, ifInDiscards, ifOutUcastPkts, ifOutBroadcastPkts, ifOutDiscards; enriched: ifintotalpkts, ifouttotalpkts, iftotalpkts, iftotalerrors, iftotaldiscards, plus per-direction error/discard totals) |
batteryLevel |
14 | Battery level (0–100) |
supplyVoltage |
15 | Supply voltage (mV) |
childTable |
16 | Array of child entries (childId, timeout, linkQuality, mode) |
channelPages |
17 | Supported channel pages (hex string) |
maxChildTimeout |
19 | Max child timeout (seconds) |
eui64 |
23 | EUI-64 address |
version |
24 | Thread version number |
vendorName |
25 | Vendor name string |
vendorModel |
26 | Vendor model string |
vendorSwVersion |
27 | Vendor software version string |
threadStackVersion |
28 | Thread stack version string |
children |
29 | Mesh-diag child table (routers only) — requires otMeshDiag |
childIpv6Addresses |
30 | Mesh-diag child IPv6 addresses (routers only) — requires otMeshDiag |
routerNeighbors |
31 | Mesh-diag router neighbor table (routers only) — requires otMeshDiag |
mleCounters |
34 | MLE counters (radioDisabledCount, detachedRoleCount, childRoleCount, routerRoleCount, leaderRoleCount, attachAttemptsCount, partIdChangesCount, betterPartIdAttachAttemptsCount, newParentCount, plus time counters) |
isBorderRouter |
ext | true if device is a border router |
isLeader |
ext | true if device is the current leader |
isPrimaryBBR |
ext | true if device is the primary Backbone Border Router |
hostsService |
ext | true if device hosts a Thread service |
brCounters |
ext | Border routing packet counters |
Examples:
diagnostics list --fields 'networkDiagnostics=extAddress,rloc16'
diagnostics list --fields 'networkDiagnostics=extAddress,macCounters,mleCounters'
diagnostics list --fields 'networkDiagnostics=extAddress,vendorName,vendorModel,vendorSwVersion,threadStackVersion'Used with actions list, actions get.
| Field | Description |
|---|---|
id |
UUID assigned by the server |
type |
Task type: addThreadDeviceTask, getNetworkDiagnosticTask, resetNetworkDiagCounterTask, getEnergyScanTask, updateDeviceCollectionTask |
status |
pending → active → completed / stopped / failed (addThreadDeviceTask also: undiscovered, attempted) |
created |
ISO 8601 creation timestamp |
destination |
Target device address |
destinationType |
extended, mleid, or rloc |
types |
Requested TLV names (diagnostic and reset tasks) |
timeout |
Countdown timeout in seconds |
eui |
Joiner EUI-64 (addThreadDeviceTask) |
pskd |
Pre-Shared Key for the Device (addThreadDeviceTask) |
maxAge |
Max cache age in seconds (updateDeviceCollectionTask) |
maxRetries |
Max retries per device (updateDeviceCollectionTask) |
deviceCount |
Target device count (updateDeviceCollectionTask) |
channelMask |
Channel list (getEnergyScanTask) |
count |
Scan count (getEnergyScanTask) |
period |
Scan period in ms (getEnergyScanTask) |
scanDuration |
Scan duration in ms (getEnergyScanTask) |
Examples:
actions list --fields 'action=id,status,type'
actions get --action-id <UUID> --fields 'action=id,status,destination'PYTHONPATH=src python3 -m td_cli otbr-restapi devices fetch
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/devices.json \
devices fetch# Enqueue and capture the action ID
ACTION_ID=$(PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue update-device-collection | jq -r '.[0].id')
echo "Enqueued: $ACTION_ID"
# Poll until terminal status
while true; do
STATUS=$(PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions get --action-id "$ACTION_ID" | jq -r '.status')
echo "status: $STATUS"
[[ "$STATUS" == "completed" || "$STATUS" == "stopped" || "$STATUS" == "failed" ]] && break
sleep 2
done
# List the refreshed devices
PYTHONPATH=src python3 -m td_cli otbr-restapi devices listPYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch --device-id aabbccddeeff0011 --preset recommended# Refresh device list and fetch (auto-saved to file)
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch-all --preset recommended
# Skip device refresh when the list was just updated
PYTHONPATH=src python3 -m td_cli otbr-restapi \
diagnostics fetch-all --preset recommended --no-update-devices
# Explicit output path
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--output data/all-diagnostics.json \
diagnostics fetch-all --preset recommendedPYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue get-network-diagnostic \
--destination aabbccddeeff0011 \
--preset recommended \
--waitPYTHONPATH=src python3 -m td_cli otbr-restapi node get
PYTHONPATH=src python3 -m td_cli otbr-restapi node state get
PYTHONPATH=src python3 -m td_cli otbr-restapi node dataset active get
PYTHONPATH=src python3 -m td_cli otbr-restapi node dataset active get --text# All three TLVs for one device
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch --device-id aabbccddeeff0011
# Child table only
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics children --device-id aabbccddeeff0011
# All mesh diagnostics for all router devices (recommended)
PYTHONPATH=src python3 -m td_cli otbr-restapi \
mesh-diagnostics fetch-all --routers-onlyPYTHONPATH=src python3 -m td_cli otbr-restapi \
actions enqueue add-thread-device \
--pskd J01NME \
--eui aabbccddeeff0022 \
--timeout 120PYTHONPATH=src python3 -m td_cli otbr-restapi --lab \
actions enqueue reset-network-diag-counter \
--destination aabbccddeeff0011 \
--types macCounters mleCounters# Device hostname and role
PYTHONPATH=src python3 -m td_cli otbr-restapi \
devices list --fields 'threadDevice=hostName,role'
# Action id and status only (lightweight polling view)
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions list --fields 'action=id,status,type'
# Single action with sparse fields
PYTHONPATH=src python3 -m td_cli otbr-restapi \
actions get --action-id 9ecae480-07a0-4b72-869d-15858196144e \
--fields 'action=id,status'PYTHONPATH=src python3 -m td_cli otbr-restapi \
--host 192.168.1.100 --port 8081 \
devices list
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--base-url http://192.168.1.100:8081 \
devices listPYTHONPATH=src python3 -m td_cli otbr-restapi --raw devices list# Default sweep — all three steps, auto-save all files
PYTHONPATH=src python3 -m td_cli otbr-restapi topology
# Skip mesh diagnostics (faster)
PYTHONPATH=src python3 -m td_cli otbr-restapi topology --skip-mesh-diagnostics
# Remote OTBR, custom data directory
PYTHONPATH=src python3 -m td_cli otbr-restapi \
--host 192.168.1.10 --datadir /tmp/scan \
topology