OThreadScan Class
About
The OThreadScan class discovers nearby Thread networks using MLE discover
(otThreadDiscover() / OpenThread CLI discover). Each result is an
OThreadNetworkInfo structure with Thread identity fields (network name,
Extended PAN ID, joinable flag) and IEEE 802.15.4 link fields (extended
address, PAN ID, channel, RSSI, LQI).
This is the same primitive Matter uses to list Thread networks during
commissioning. The global instance is OThreadScan (same pattern as
OThreadUDP and OThreadCoAPServer).
Key Features:
Single entry point:
discoverNetworks()— no separate 802.15.4 or energy scan modes in the public API.Blocking, async poll (
scanComplete()), and streaming callbacks (onResult()/onComplete()).Optional discover filters (PAN ID, joiner-only, EUI-64) matching
otThreadDiscover()parameters.Wi-Fi scan style return codes:
OT_DISCOVER_RUNNING(-1),OT_DISCOVER_FAILED(-2).Result storage is pre-reserved (
OT_DISCOVER_MAX_RESULTS, default 16) before each scan so the OpenThread callback path does not heap-allocate while the OT API lock is held.
Use Cases:
List Thread networks before joining or commissioning.
Build a network-picker UI on a joiner device.
Debug RF environment when a Leader is expected nearby.
Prerequisites:
OThread.begin()andOThread.networkInterfaceUp()— Thread does not need to be started (matches Matter pre-commission discovery).For meaningful results, at least one Leader or Router should be active nearby.
Include the header:
#include <OThreadScan.h>
OThreadNetworkInfo
Each discovery response is stored as OThreadNetworkInfo:
Field |
Description |
|---|---|
|
Thread network name (null-terminated) |
|
Extended PAN ID (8 bytes) |
|
IEEE 802.15.4 PAN ID |
|
Responder extended address (8 bytes) |
|
IEEE 802.15.4 channel (11..26) |
|
Received signal strength (dBm) |
|
Link Quality Indicator |
|
Thread version (4-bit MLE value) |
|
Joining permitted (non-empty Steering Data for MLE discover) |
|
Native Commissioner flag when present |
Helper methods: networkNameStr(), extendedPanIdStr(),
extAddressStr().
OThreadDiscoverFilters
Optional filters for setDiscoverFilters() (defaults match CLI
discover with no extra arguments):
OThreadDiscoverFilters filters;
filters.panIdFilter = OT_PANID_BROADCAST; // 0xffff = no PAN filter
filters.joinerOnly = false;
filters.eui64Filter = false;
OThreadScan.setDiscoverFilters(filters);
API Reference
Configuration
setScanTimeout
void setScanTimeout(uint32_t ms);
Maximum time to wait for blocking discovery or async completion. Default:
OT_DISCOVER_DEFAULT_TIMEOUT_MS (30000 ms).
OT_DISCOVER_MAX_RESULTS
Maximum unique networks stored per scan (default 16). Vectors are
pre-reserved to this size before each scan. Define the macro before including
OThreadScan.h to override:
#define OT_DISCOVER_MAX_RESULTS 32
#include <OThreadScan.h>
Or pass -DOT_DISCOVER_MAX_RESULTS=32 at compile time. See
ThreadScan_Discover
for a sketch example.
setChannel
void setChannel(uint8_t channel);
Limit discovery to one IEEE 802.15.4 channel (11..26). Pass 0 to scan all
supported channels (channel mask 0 in otThreadDiscover()).
setDiscoverFilters
void setDiscoverFilters(const OThreadDiscoverFilters &filters);
Apply PAN / joiner / EUI-64 filters before the next discoverNetworks()
call.
Callbacks
onResult
void onResult(OThreadDiscoverResultCallback callback, void *context = nullptr);
Called for each network as OpenThread receives a Discovery Response. Matches the OpenThread / Matter streaming model.
onComplete
void onComplete(OThreadDiscoverCompleteCallback callback, void *context = nullptr);
Called when discovery finishes. resultCount is the number of networks
collected, or OT_DISCOVER_FAILED on failure. error is the OpenThread
error from the start request (OT_ERROR_NONE on success).
Discovery
discoverNetworks
int16_t discoverNetworks(bool async = false);
Start MLE Thread network discovery.
async == false— blocks until complete orsetScanTimeout()expires; returns network count on success.async == true— returnsOT_DISCOVER_RUNNINGimmediately on success.
Return values:
>= 0— number of networks found (blocking mode, orscanComplete()when done).OT_DISCOVER_RUNNING(-1) — discovery in progress.OT_DISCOVER_FAILED(-2) — failed, timed out, or not started.
At most OT_DISCOVER_MAX_RESULTS unique networks (default 16) are stored. Vectors are
pre-reserved before the scan starts so the OpenThread callback does not allocate
while the API lock is held. Multiple discovery responses with the same Extended
PAN ID are merged in storage (strongest RSSI kept). onResult() is still
invoked for every Discovery Response. Additional unique networks beyond the
cap are still delivered through onResult() but are omitted from
getResult() / getResultCount().
scanComplete
int16_t scanComplete();
Poll async discovery state from loop(). Same return semantics as
discoverNetworks() in blocking/async modes. Completion is reported only
after the final discovery callback sets internal state; do not treat
otThreadIsDiscoverInProgress() as a substitute for scanComplete().
scanDelete
void scanDelete();
Release stored results and free associated memory (internal vector capacity
is released, not just cleared). Call after processing results. This is a no-op
until the final discovery callback has completed, even if OpenThread already
reports discover idle; call again after the scan finishes. After a timeout it
likewise stays a no-op until OpenThread delivers that final callback — retry
once isDiscoverInProgress() returns false.
isDiscoverInProgress
bool isDiscoverInProgress() const;
Returns true while an MLE discovery scan is in progress.
Results
Result access (WiFiScan-style)
Indexed result APIs (getResult(), getResultCount(), field getters, and
getActiveScanResult()) are available only after discovery completes:
Blocking:
discoverNetworks()return value>= 0Async:
scanComplete()return value>= 0Callbacks:
onComplete()has fired
While a scan is still running, these methods return empty values (count 0,
false, nullptr). Use onResult() to handle networks as they are
discovered. Do not call other OThreadScan methods from inside
onResult() or onComplete() (they run on the OpenThread task with the
API lock held).
getResultCount
uint16_t getResultCount() const;
Number of entries from the last completed discovery (saturated at
UINT16_MAX if the internal list is larger). Returns 0 while discovery
is still in progress.
getResult
const OThreadNetworkInfo &getResult(uint16_t index) const;
bool getResult(uint16_t index, OThreadNetworkInfo &info) const;
Access one result by index (0 .. count-1). Internal storage is valid until
scanDelete(). Returns a static empty entry while discovery is still in
progress or if the index is out of range.
Index-based convenience getters (networkName(), panId(),
extendedPanIdStr(), extAddressStr(), channel(), rssi(),
lqi(), isJoinable(), threadVersion(), isNativeCommissioner())
delegate to getResult(index).
getActiveScanResult
const otActiveScanResult *getActiveScanResult(uint16_t index) const;
Raw OpenThread result for advanced use. Valid until scanDelete(). Returns
nullptr while discovery is still in progress or if the index is out of range.
Usage Patterns
Blocking (WiFiScan-style)
#include <OThread.h>
#include <OThreadScan.h>
void setup() {
Serial.begin(115200);
OThread.begin(false);
OThread.networkInterfaceUp();
int n = OThreadScan.discoverNetworks();
if (n >= 0) {
for (int i = 0; i < n; ++i) {
const OThreadNetworkInfo &net = OThreadScan.getResult(i);
Serial.println(net.networkNameStr());
}
OThreadScan.scanDelete();
}
}
Async poll (WiFiScanAsync-style)
OThreadScan.discoverNetworks(true);
void loop() {
int16_t status = OThreadScan.scanComplete();
if (status >= 0) {
// process OThreadScan.getResult(i) ...
OThreadScan.scanDelete();
}
}
Streaming callbacks
static void onNetwork(const OThreadNetworkInfo &info, void *) {
Serial.println(info.networkNameStr());
}
static void onDone(int16_t resultCount, otError error, void *) {
// Summary only — do not call scanDelete() here (OT API lock held).
Serial.printf("discovery done: %d\n", resultCount);
}
void setup() {
OThreadScan.onResult(onNetwork);
OThreadScan.onComplete(onDone);
}
void loop() {
OThreadScan.discoverNetworks(true);
while (OThreadScan.scanComplete() == OT_DISCOVER_RUNNING) {
delay(50);
}
OThreadScan.scanDelete();
}
Examples
Native API examples (libraries/OpenThread/examples/Native/ThreadScan/):
ThreadScan_Discover — blocking discovery.
ThreadScan_Async —
scanComplete()polling.ThreadScan_Callback —
onResult()/onComplete().
CLI equivalent: CLI ThreadScan (discover command via OThreadCLI).
Troubleshooting
- No networks found
Start a Leader or Router on another board first (for example SimpleThreadNetwork LeaderNode).
Confirm
OThread.networkInterfaceUp()was called beforediscoverNetworks().
- ``OT_DISCOVER_FAILED``
IPv6 interface down, lock/allocation failure, or timeout — confirm
networkInterfaceUp()and increasesetScanTimeout()if needed.
- ``OT_DISCOVER_RUNNING``
Discovery is already in progress — wait for
scanComplete()to report completion before starting another scan or callingscanDelete().
- Wrong network listed
Multiple partitions nearby — compare
extendedPanIdwith the target Leader.
See also
OpenThread — overview and Native vs CLI comparison.
OpenThread Class —
OThread.begin(),networkInterfaceUp().OpenThread CLI — CLI
discovercommand.