实战指南)
arduino-esp32 OpenThread 阻塞式 Thread 网络发现ThreadScan_Discover实战指南【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32本文围绕 arduino-esp32 仓库中的ThreadScan_Discover示例Native API 阻塞式 Thread 网络发现完整讲解如何在 ESP32-H2 / C6 / C5 上仅拉起 IPv6 接口、周期性调用OThreadScan.discoverNetworks()发现周边 Thread 网络并结合OThreadScan源码剖析结果去重、超时与锁机制帮助读者掌握可直接复制运行的阻塞扫描方案及其全部可调参数。1. 示例定位阻塞式blockingThread 发现ThreadScan_Discover是 Thread Network Discovery 演示组 三个示例之一采用与 Wi-FiWiFiScan相同的阻塞式交互模式调用即等待返回后即可直接读取结果。该组三个示例对照如下示例模式ThreadScan_Discover本文阻塞式发现discoverNetworks()同步等待ThreadScan_Async非阻塞用scanComplete()轮询ThreadScan_Callback流式onResult()/onComplete()回调从源码注释看OThreadScan.hOThreadScan封装的是 OpenThread 的otThreadDiscover()对应 ESP-IDF OpenThread CLI 的discover命令即 Thread MLE Discovery 原语——结果同时包含 Thread 身份字段网络名、Extended PAN ID、可加入标志和 IEEE 802.15.4 链路字段扩展地址、PAN ID、信道、RSSI、LQI。这也是 Matter 在配网commissioning阶段列举 Thread 网络所用的同一原语。该示例的核心行为以OThread.begin(false)启动 OpenThread不从 NVS 自动加载数据集通过networkInterfaceUp()拉起 IPv6 接口无需启动 Thread在loop()中每 10 秒调用一次阻塞式discoverNetworks()打印每个OThreadNetworkInfo网络名、Extended PAN ID、PAN ID、扩展地址、信道、RSSI、LQI、joinable 标志每次扫描结束后用scanDelete()释放结果存储。1.1 支持的芯片SoCThread状态ESP32-H2yesSupportedESP32-C6yesSupportedESP32-C5yesSupported1.2 必需的 IDF 配置sdkconfig配置项作用CONFIG_OPENTHREAD_ENABLEDy编译进 OpenThread 协议栈CONFIG_SOC_IEEE802154_SUPPORTEDy确认 SoC 具备 802.15.4 射频这两个配置与示例目录下的 ci.yml 中声明的requires完全一致。源码层面也能印证这一点OThreadScan.h 的整个 API 都被#if SOC_IEEE802154_SUPPORTED CONFIG_OPENTHREAD_ENABLED包裹未启用时该类根本不存在。2. 前置条件先让 Leader 组网阻塞式发现本身不需要本机加入任何网络但要拿到有意义的结果射频范围内必须存在活跃 Thread 网络。推荐做法在第一块 ESP32-H2 / C6 / C5 开发板上烧录 LeaderNode组网节点 示例串口监视器波特率设为115200等待串口输出Role: Leader它构建完整运营数据集——网络名ESP_OpenThread、信道 15、PAN ID0x1234、扩展 PAN ID、网络密钥——并提交并成为新分区 Leader将本示例烧录到第二块板卡。启动顺序Leader 先、扫描板后在原文档和 SimpleThreadNetwork 组总览 中都被反复强调是因为扫描结果网络名、XPAN 等全部来自 Leader 广播的 MLE Discovery Response。3. 完整示例代码解读完整源码见 ThreadScan_Discover.ino可整体复制运行#include Arduino.h #include OThread.h // This sketch stores up to 32 unique networks (library default is 16). // Define OT_DISCOVER_MAX_RESULTS before including OThreadScan.h. #define OT_DISCOVER_MAX_RESULTS 32 #include OThreadScan.h static void printNetwork(const OThreadNetworkInfo net, int index) { Serial.printf(%2d, index 1); Serial.print( | ); Serial.print(net.joinable ? 1 : 0); Serial.print( | ); Serial.printf(%-16.16s, net.networkName); Serial.print( | ); Serial.print(net.extendedPanIdStr()); Serial.print( | ); Serial.printf(%04x, net.panId); Serial.print( | ); Serial.print(net.extAddressStr()); Serial.print( | ); Serial.printf(%2u, net.channel); Serial.print( | ); Serial.printf(%3d, net.rssi); Serial.print( | ); Serial.printf(%3u, net.lqi); Serial.println(); } static void discoverBlocking() { Serial.println(Thread discovery start); int n OThreadScan.discoverNetworks(); // 阻塞式扫描 Serial.println(Thread discovery done); if (n OT_DISCOVER_FAILED) { Serial.println(discovery failed (interface down, lock failure, or timeout)); } else if (n OT_DISCOVER_RUNNING) { Serial.println(discovery already in progress (OT_DISCOVER_RUNNING)); } else { if (n 0) { Serial.println(no Thread networks found); } else { Serial.print(n); Serial.println( network(s) found); Serial.println(Nr | J | Network Name | Extended PAN | PAN | MAC Address | CH | dBm | LQI); for (int i 0; i n; i) { printNetwork(OThreadScan.getResult(i), i); delay(10); } } // Release reserved capacity after every completed scan (including 0 results). OThreadScan.scanDelete(); } Serial.println(-------------------------------------); } void setup() { Serial.begin(115200); OThread.begin(false); // 不加载 NVS 数据集 OThread.networkInterfaceUp(); // 仅拉起 IPv6 接口不启动 Thread Serial.println(Setup done — IPv6 interface up, Thread start not required); } void loop() { discoverBlocking(); delay(10000); // 每 10 秒一轮 }关键要点逐条对应OThread.begin(false)参数OThreadAutoStart为false时不自动启动 Thread 协议、不从 NVS 恢复数据集见 OThread.cpp 中begin()的栈初始化流程配置原生 radio 模式、NVS 存储分区、任务队列后通过握手信号量等待 OpenThread 工作线程完成初始化。发现扫描只需要栈可用 接口 up不需要 attach。networkInterfaceUp()拉起 IPv6 接口是discoverNetworks()的硬性前提OThreadScan.h 的note明确标注 Requires the IPv6 interface to be up。结果读取规则getResult(i)/getResultCount()只能在发现完成后调用阻塞返回值 ≥ 0、scanComplete()≥ 0 或onComplete()之后扫描进行中请使用onResult()流式获取。scanDelete()必须每轮都调示例中即使 0 结果也调用注释写明 Release reserved capacity after every completed scan (including 0 results)。4. 源码深入OThreadScan的返回值约定与内部机制4.1 返回值常量OThreadScan.h 定义了两个状态码与 Wi-Fi 扫描约定一致常量值含义OT_DISCOVER_RUNNING-1发现仍在进行等价WIFI_SCAN_RUNNINGOT_DISCOVER_FAILED-2发现失败或未触发接口未 up、获取锁失败、超时等价WIFI_SCAN_FAILED≥ 00..N成功返回去重后的网络数量4.2 阻塞流程与锁/信号量从 OThreadScan.cpp 的discoverNetworks()实现看完整流程为锁外准备惰性创建完成信号量_doneSem并prepareResultStorage()预分配容量为OT_DISCOVER_MAX_RESULTS的结果 vector——注释特意说明堆分配不能在持有 OT API 锁时执行加锁启动通过esp_openthread_lock_acquire获取 API 锁后OtLockRAII 包装检查实例有效性若_inProgress || otThreadIsDiscoverInProgress(inst)则立即返回OT_DISCOVER_RUNNING将_channel0视为全信道、合法信道11..26映射为位掩码随后调用otThreadDiscover(inst, channelMask, panIdFilter, joinerOnly, eui64Filter, handleDiscoverResult, this)阻塞等待xSemaphoreTake(_doneSem, pdMS_TO_TICKS(_timeoutMs))超时默认 30000 msOT_DISCOVER_DEFAULT_TIMEOUT_MS见 OThreadScan.h超时或_startError非零均返回OT_DISCOVER_FAILED完成信号OpenThread 对每条Discovery Response 回调onDiscoverResult(result)最终回调result nullptr时置_done true并给信号量OThreadScan.cpp。scanComplete()的注释特别指出不要用otThreadIsDiscoverInProgress()推断完成因为 OpenThread 可能在最终回调投递前就已 idle。4.3 结果去重与 RSSI 择优同一网络可能从多个路由器/信标收到多份 Discovery Response。onDiscoverResult通过findResultByExtendedPanId()按Extended PAN ID匹配命中且新 RSSI 更强时替换旧记录即同网多源只保留信号最强的一条OThreadScan.cpp。存储达到OT_DISCOVER_MAX_RESULTS上限后超出部分只流经onResult()回调、不入库。每条 Response 都会触发onResult回调——这正是 Matter/OpenThread 的流式模型。4.4joinable标志的判定值得注意的实现细节MLE Discovery 的可加入性来自Steering Data 布隆过滤器而非 beacon 的 Joining Permitted 位。由于公开的otSteeringData*辅助接口在 Arduino 构建中未启用OThreadScan.cpp 的discoverResultIsJoinable()直接检查结构体mSteeringData.mLength为 0 或超过OT_STEERING_DATA_MAX_LENGTH判为不可加入任一字节非零即过滤器非全零判为可加入。fromActiveScanResult()中据此区分来源out.joinable in.mDiscover ? discoverResultIsJoinable(in) : in.mIsJoinable;mIsJoinable仅对 802.15.4 beacon 主动扫描有效。4.5OThreadNetworkInfo字段一览定义于 OThreadScan.h示例串口表格逐列对应这些字段字段类型含义networkNamechar[OT_NETWORK_NAME_MAX_SIZE1]以\0结尾的 Thread 网络名extendedPanIduint8_t[8]8 字节 Extended PAN IDextendedPanIdStr()输出 16 位小写 hexpanIduint16_tIEEE 802.15.4 PAN ID示例中以%04x打印extAddressuint8_t[8]响应方扩展地址channeluint8_t802.15.4 信道11..26rssiint8_t接收信号强度dBmlqiuint8_t链路质量指示threadVersionuint8_t4-bit MLE Thread 版本joinablebool是否允许加入见 4.4 节nativeCommissionerboolNative Commissioner 标志5. 预期串口输出在 Leader 正常组网时网络名、XPAN 默认值与仓库 Simple Thread Network 演示一致如 Extended PAN IDdead00beef00cafe每 10 秒一轮的典型输出Setup done — IPv6 interface up, Thread start not required Thread discovery start Thread discovery done 1 network(s) found Nr | J | Network Name | Extended PAN | PAN | MAC Address | CH | dBm | LQI 1 | 1 | ESP_OpenThread | dead00beef00cafe | 1234 | aabbccddeeff0011 | 15 | -45 | 255 -------------------------------------四种典型分支Leader 不在附近Thread discovery start Thread discovery done no Thread networks found -------------------------------------失败接口未 up、加锁失败或超时Thread discovery start Thread discovery done discovery failed (interface down, lock failure, or timeout) -------------------------------------上一轮扫描尚未结束阻塞模式下理论上少见但若上一轮超时后 OpenThread 侧仍在跑会命中 OThreadScan.cpp 的OT_DISCOVER_RUNNING分支Thread discovery start Thread discovery done discovery already in progress (OT_DISCOVER_RUNNING) -------------------------------------6. 可定制参数6.1 存储结果上限OT_DISCOVER_MAX_RESULTS库默认每轮最多保存16个唯一按 Extended PAN ID 去重网络。本示例为更密的射频环境将上限提到32#define OT_DISCOVER_MAX_RESULTS 32 #include OThreadScan.h要求#define必须出现在#include OThreadScan.h之前可放在.ino里或更早包含的头文件中也可通过构建系统传-DOT_DISCOVER_MAX_RESULTS32值越大每轮扫描前prepareResultStorage()预分配的两个 vector_results与_rawResults占用的 RAM 越多。6.2 扫描超时setScanTimeout()默认30000 ms阻塞等待与异步完成共用该超时。调用discoverNetworks()之前调整OThreadScan.setScanTimeout(60000); // 单位 ms6.3 信道限制setChannel()OThreadScan.setChannel(15)将扫描限定在单个信道合法范围11..26setChannel(0)默认扫全部支持信道。从discoverChannelMask()实现看OThreadScan.cpp非法信道值会直接导致返回OT_DISCOVER_FAILED。6.4 发现过滤器setDiscoverFilters()OThreadScan.h 中的OThreadDiscoverFilters与 ESP-IDF CLIdiscover的默认行为一致panIdFilter默认OT_PANID_BROADCAST0xffff不过滤joinerOnly、eui64Filter默认关闭。本阻塞示例未使用过滤器需要定向扫描时可自行配置。7. 故障排查启动顺序先启动 LeaderNode组网节点 并等待Role: Leader再烧录本示例。现象可能原因no Thread networks foundLeader 未运行或不在射频范围内——用另一块板启动 Leader。discovery failed接口未 up、加锁失败或超时——确认已调用networkInterfaceUp()尝试调大setScanTimeout()。discovery already in progress上一轮扫描仍在进行OT_DISCOVER_RUNNING——等待其结束或在完成后调用scanDelete()。网络名 / XPAN 对不上附近有多个Thread 网络——与 Leader 的 Extended PAN ID 逐一比对。串口无任何输出串口监视器波特率未设为115200。另外提醒来自 ThreadScan 组总览getResult()/getResultCount()只能在发现完成后使用不要在onResult()/onComplete()回调内部调用scanDelete()等其他OThreadScan方法——回调运行在 OpenThread 任务中且持有 API 锁释放应放在loop()里、scanComplete()结束之后进行。8. 相关示例与延伸阅读Thread Network Discovery — 组总览三种 Native 发现模式对比与统一运行步骤ThreadScan_Async非阻塞scanComplete()轮询模式ThreadScan_Callback逐网络流式回调模式CLI ThreadScan同一discover操作的OThreadCLICLI 等价写法LeaderNode / RouterNode多板测试用的组网节点与入网节点API 定义与实现OThreadScan.h、OThreadScan.cpp。适用前提与限制本指南基于当前仓库的 OpenThread 库与示例仅对支持 802.15.4 射频的 ESP32-H2 / C6 / C5 有效且需要CONFIG_OPENTHREAD_ENABLEDy与CONFIG_SOC_IEEE802154_SUPPORTEDy两项 IDF 配置阻塞式扫描会占用 CPU 最长一个超时周期默认 30 秒实时性要求高的场景请改用 Async 或 Callback 模式。【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考