ESP-IDF 中的 OpenThread:基于 802.15.4 的 IPv6 网状网络协议集成实战指南
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
Thread 是一个基于 IP 的网状网络协议,运行在 IEEE 802.15.4 物理层与 MAC 层之上,专为低功耗、低带宽的物联网设备互联而设计。本文以 ESP-IDF 对 OpenThread 的官方支持为主线,介绍其核心概念、examples/openthread下的边界路由器、CLI、RCP、TREL 与睡眠设备等参考实现,并结合components/openthread源码剖析 ESP-IDF 提供的扩展 API 与配置项。读完本文,你将掌握如何在 ESP32 系列 SoC 上构建 Thread 网络、组建 Thread 边界路由器、实现双向 IPv6 连接与跨网络服务发现。
Thread 协议概述
Thread是一个基于 IP 的网状网络协议,它基于 802.15.4 物理层和 MAC 层构建。这意味着 Thread 网络中的每一个节点都天然拥有 IPv6 地址,数据报文可以在网状拓扑中经多跳转发,网络具备自愈能力——当某个节点离线时,路由会自动重新收敛。由于运行在 802.15.4 低速率无线链路上,Thread 特别适合智能家居、楼宇自动化、传感器网络等低功耗场景。
在 ESP-IDF 中,Thread 协议栈由开源的 OpenThread 实现承载,其核心逻辑位于 components/openthread/openthread(上游 OpenThread 源码)中,而 ESP-IDF 通过 components/openthread 组件完成平台移植(时钟、内存、日志、802.15.4 射频驱动等),并提供与 ESP-IDF 网络栈(esp_netif / lwIP)对接的桥梁。
Thread 协议定义了不同的设备角色,例如 Leader(负责网络分片与地址分配的路由节点)、Router、Child(依赖父节点的休眠或非休眠终端设备)以及 Border Router(边界路由器,负责将 Thread 网络与 Wi-Fi / 以太网等外部网络互联)。这些角色与状态变化在 ESP-IDF 的 OpenThread 事件系统中均有对应定义,详见后文“事件系统”小节。
OpenThread 应用示例总览
examples/openthread目录提供了五个官方参考实现,覆盖了 Thread 网络的主要使用形态。以下内容以 examples/openthread 下的各示例 README 与实际源码为准。
ot_br:Thread 边界路由器
ot_br 演示了如何在 ESP 设备上搭建 Thread 边界路由器,实现 Thread 网络与外部网络之间的双向 IPv6 连接,以及基于 SRP/mDNS 的服务发现功能。
基于 Wi-Fi 的边界路由器(默认方案):需要两块 SoC——一块运行本示例的 Wi-Fi SoC(如 ESP32、ESP32-C3、ESP32-S3),一块烧录了 ot_rcp 示例的 IEEE 802.15.4 SoC(如 ESP32-H2)作为射频协处理器。两块开发板通过 UART 连接,典型接法如下表:
ESP32 pin ESP32-H2 pin GND G GPIO4 TX GPIO5 RX 对应的硬件连接示意图如下:
单 SoC 方案:也可以使用同时支持 Wi-Fi 与 Thread 的 SoC(如 ESP32-C6)单芯片运行,但需要启用
CONFIG_ESP_COEX_SW_COEXIST_ENABLE与CONFIG_OPENTHREAD_RADIO_NATIVE两个选项(这两个选项在 ESP32-C6 目标上默认开启)。由于 ESP32-C6 只有一条射频通路,Wi-Fi 与 Thread 无法同时接收,性能会受到显著影响,因此官方推荐双 SoC 方案。基于以太网的边界路由器:将骨干网接口从 Wi-Fi 换成以太网,需要带以太网接口的设备(如 ESP32-Ethernet-Kit、ESP32-P4-Function-EV-Board),并在 menuconfig 中启用
CONFIG_EXAMPLE_CONNECT_ETHERNET。ESP32-P4 使用 Wi-Fi 协处理器:ESP32-P4 本身不支持 Wi-Fi,可通过 ESP-Hosted 组件借助 ESP32-C6 协处理器获得 Wi-Fi 连接能力。
ot_cli:OpenThread 命令行界面
ot_cli 演示了 OpenThread 命令行界面(CLI)的使用,除官方 CLI 的全部命令外,还扩展支持了基于 lwIP 的 TCP、UDP 与 Iperf 测速功能。该示例需要在配备 IEEE 802.15.4 模块的开发板(如 ESP32-H2)上运行,官方支持 ESP32-C5、ESP32-C6、ESP32-H2、ESP32-H21、ESP32-H4、ESP32-S31 等目标。
ot_rcp:射频协处理器
ot_rcp 演示了如何将 802.15.4 SoC 作为 Radio Co-Processor(RCP)与主处理器配合使用,为主处理器扩展 802.15.4 射频能力。它也是构建双芯片 Thread 边界路由器(与 ot_br 配合)的基石。该示例同样需要在配备 IEEE 802.15.4 模块的开发板上运行,并支持 UART、SPI、USB Serial/JTAG 等多种与主机通信的通道(对应sdkconfig.ci.rcp_uart、sdkconfig.ci.rcp_spi、sdkconfig.ci.rcp_usb等 CI 配置)。
ot_trel:Thread Radio Encapsulation Link
ot_trel 演示了 Thread Radio Encapsulation Link(TREL)功能。TREL 将 Thread 的 802.15.4 报文封装后通过 Wi-Fi 传输,使得不带 802.15.4 射频的 Wi-Fi 设备也能参与到 Thread 网络中。该示例需要在配备 Wi-Fi 模块的开发板上运行,对应源码为 components/openthread/src/port/esp_openthread_trel.c,其射频模式在esp_openthread_types.h中定义为RADIO_MODE_TREL。
ot_sleepy_device:低功耗终端设备
ot_sleepy_device 演示了 Thread 低功耗终端(Sleepy End Device)的两种睡眠模式:
deep_sleep:Thread 深度睡眠,设备在无业务时进入深睡,功耗可降至极低水平。官方实测数据可参考其 README 中的功耗曲线图:
light_sleep:Thread 浅睡眠,设备周期性唤醒以保持网络同步,适用于对时延更敏感的场景。该示例在 ESP32-C6 上还有对应的
sdkconfig.defaults.esp32c6配置。
实战:搭建双设备 Thread 网络并测速
以 ot_cli 为例,完整走一遍 Thread 组网流程。
构建、烧录与运行
首先通过 menuconfig 配置工程(示例默认配置即可运行,CLI 默认走 UART;如需改用 USB Serial/JTAG 控制台,可在Component config → ESP System Settings → Channel for console output中选择):
idf.py menuconfig idf.py -p PORT build flash monitor启动后即可获得一个ot前缀的 OpenThread 命令行 shell。输入ot help可以看到全部支持的命令,包括bbr、channel、child、dataset、diag、dns、eui64、factoryreset、ipaddr、state、thread、srp等。
设备一:组建网络(成为 Leader)
在第一块开发板上执行:
esp32h2> ot factoryreset ... # 设备会重启 esp32h2> ot dataset init new Done esp32h2> ot dataset commit active Done esp32h2> ot ifconfig up Done esp32h2> ot thread start Done # 等待数秒后 esp32h2> ot state leader Done此时第一块设备已经作为 Leader 组建了一个 Thread 网络。查看本机地址并导出活跃数据集(Active Dataset,TLV 编码),供第二块设备入网使用:
esp32h2> ot ipaddr fdde:ad00:beef:0:0:ff:fe00:fc00 fdde:ad00:beef:0:0:ff:fe00:8000 fdde:ad00:beef:0:a7c6:6311:9c8c:271b fe80:0:0:0:5c27:a723:7115:c8f8 esp32h2> ot dataset active -x 0e080000000000010000000300001835060004001fffe00208fe7bb701f5f1125d0708fd75cbde7c6647bd0510b3914792d44f45b6c7d76eb9306eec94030f4f70656e5468726561642d35383332010258320410e35c581af5029b054fc904a24c2b27700c0402a0fff8设备二:加入网络
在第二块开发板上,使用 Leader 导出的活跃数据集加入网络:
esp32h2> ot factoryreset ... # 设备会重启 esp32h2> ot dataset set active 0e080000000000010000000300001835060004001fffe00208fe7bb701f5f1125d0708fd75cbde7c6647bd0510b3914792d44f45b6c7d76eb9306eec94030f4f70656e5468726561642d35383332010258320410e35c581af5029b054fc904a24c2b27700c0402a0fff8 esp32h2> ot ifconfig up Done esp32h2> ot thread start Done # 等待数秒后 esp32h2> ot state router # child 也是有效状态 Done第二块设备即以 Router(或 Child)身份加入网络,两个节点之间即可互通。
用 Iperf 测量吞吐量
ot_cli 扩展了 Iperf 命令,用于测量 Thread 网络上的 TCP/UDP 吞吐量。在一台节点上开启服务端:
> iperf -V -s -t 20 -i 3 -p 5001 -f k Done在另一台节点上作为客户端连接。注意应使用节点的 ML-EID(Mesh-Local EID,可通过ot ipaddr mleid查询)作为目标地址:
> ipaddr mleid fdde:ad00:beef:0:a7c6:6311:9c8c:271b Done > iperf -V -c fdde:ad00:beef:0:a7c6:6311:9c8c:271b -t 20 -i 1 -p 5001 -l 85 -f k Done [ ID] Interval Transfer Bandwidth [ 1] 0.0- 1.0 sec 3.15 KBytes 25.16 Kbits/sec ... [ 1] 0.0-10.0 sec 27.80 KBytes 22.24 Kbits/secUDP 测速方式类似,服务端使用iperf -V -u -s ...,客户端使用iperf -V -u -c ...。由于 802.15.4 链路带宽有限,Thread 网络的实测吞吐量通常在几十 Kbit/s 量级,属于协议固有特性。
实战:Thread 边界路由器的双向 IPv6 与服务发现
在 ot_br 中,边界路由器启动后会通过 ICMPv6 路由通告(RA)自动把 Thread 网络的 OMR 前缀与路由表规则发布到 Wi-Fi 网络。在主机上需要开启 IPv6 路由通告接收(将wlan0替换为真实网卡名):
sudo sysctl -w net/ipv6/conf/wlan0/accept_ra=2 sudo sysctl -w net/ipv6/conf/wlan0/accept_ra_rt_info_max_plen=128iOS 14 与 Android 8.1 以上的移动设备会自动配置路由表。
测试双向 IPv6 连通性
在 Thread 终端设备上查看地址,会发现一个带全局前缀的可路由 IPv6 地址:
esp32h2> ot ipaddr fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5 fdde:ad00:beef:0:0:ff:fe00:c402 fdde:ad00:beef:0:ad4a:9a9a:3cd6:e423 fe80:0:0:0:f011:2951:569e:9c4a其中fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5即该节点的可路由全局地址,在主机上可直接 ping 通:
$ ping fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5 PING fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5(fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5) 56 data bytes 64 bytes from fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5: icmp_seq=1 ttl=63 time=459 ms使用 SRP 发布服务(Thread → Wi-Fi)
Thread 网络内设备可以通过 SRP(Service Registration Protocol)注册服务,边界路由器会将其通过 mDNS 转发到 Wi-Fi 网络。在 Thread 终端设备上发布服务my-service._test._udp(主机名test0,端口 12345):
esp32h2> ot srp client host name test0 Done esp32h2> ot srp client host address fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5 Done esp32h2> ot srp client service add my-service _test._udp 12345 Done esp32h2> ot srp client autostart enable Done随后在 Wi-Fi 网络的 Linux 主机上用avahi-browse -r _test._udp -t即可看到该服务,说明服务已成功跨越 Thread 网络边界。
DNS 服务发现(Wi-Fi → Thread)
反过来,先在 Wi-Fi 网络发布服务:
$ avahi-publish-service testhost _test._udp 12345 test=1 dn="aabbbb"然后取边界路由器的 OMR 全局地址(或 ML-EID)配置到 Thread 终端设备的 DNS 服务器上:
esp32h2> ot dns config fd9b:347f:93f7:1:1003:8f00:bcc1:3038 Done之后即可在 Thread 终端设备上解析并浏览该服务:
esp32h2> ot dns browse _test._udp.default.service.arpa. DNS browse response for _test._udp.default.service.arpa. testhost Port:5683, Priority:0, Weight:0, TTL:120 Host:FA001208.default.service.arpa. ... DoneESP-IDF 提供的 OpenThread 扩展 API
OpenThread 网络本身应使用上游 OpenThread API 进行操作,而 ESP-IDF 在 components/openthread/include 中提供了一组额外的 API,用于在 ESP-IDF 环境中启动、管理 OpenThread 实例,并完成网络接口绑定(netif glue)与边界路由功能。这些头文件与中文 API 文档中esp_openthread.inc、esp_openthread_types.inc、esp_openthread_lock.inc、esp_openthread_netif_glue.inc、esp_openthread_border_router.inc一一对应。
核心生命周期 API(esp_openthread.h)
esp_openthread.h 定义了 OpenThread 栈的完整生命周期管理接口:
| 函数 | 作用 | 典型返回值 |
|---|---|---|
esp_openthread_init(init_config) | 初始化完整的 OpenThread 协议栈(同时初始化otInstance),入参为esp_openthread_platform_config_t | ESP_OK/ESP_ERR_NO_MEM/ESP_ERR_INVALID_ARG/ESP_ERR_INVALID_STATE |
esp_openthread_start(config) | 启动完整 OpenThread 栈并创建托管任务(handle task),入参为esp_openthread_config_t | ESP_OK/ESP_ERR_INVALID_STATE |
esp_openthread_auto_start(datasetTlvs) | 启动 Thread 协议并附着到 Thread 网络;datasetTlvs为 TLV 编码的运营数据集,传NULL时依据 Kconfig 配置生成数据集 | ESP_OK/ESP_FAIL |
esp_openthread_launch_mainloop() | 启动 OpenThread 主循环,该函数在栈正常运行期间不会返回 | ESP_OK/ESP_ERR_NO_MEM/ESP_FAIL |
esp_openthread_mainloop_exit() | 通知主循环退出 | ESP_OK/ESP_FAIL |
esp_openthread_get_instance() | 获取底层otInstance指针(可在其他任务中无锁调用) | 实例指针 |
esp_openthread_stop() | 反初始化 OpenThread 栈并删除托管任务 | ESP_OK/ESP_ERR_INVALID_STATE |
esp_openthread_deinit() | 执行 OpenThread 栈与平台驱动的反初始化 | ESP_OK/ESP_ERR_INVALID_STATE |
典型应用流程为:esp_openthread_init()(或esp_openthread_start())→esp_openthread_auto_start()→esp_openthread_launch_mainloop(),退出时调用esp_openthread_stop()/esp_openthread_deinit()。
配置与事件类型(esp_openthread_types.h)
esp_openthread_types.h 是理解整个移植架构的关键头文件,主要包含:
射频模式(Radio Mode),决定 OpenThread 如何获得 802.15.4 射频:
RADIO_MODE_NATIVE:使用 SoC 原生的 802.15.4 射频;RADIO_MODE_UART_RCP/RADIO_MODE_SPI_RCP:通过 UART / SPI 连接外部 15.4 射频协处理器(RCP);RADIO_MODE_TREL:使用 Thread Radio Encapsulation Link(TREL),通过 Wi-Fi 传输 Thread 报文;RADIO_MODE_TRANSPORT:使用自定义传输通道连接 RCP。
主机连接模式(Host Connection Mode),定义设备与外部主机(如 Linux 主机或上位机)的连接方式:
HOST_CONNECTION_MODE_NONE:禁用主机连接;HOST_CONNECTION_MODE_CLI_UART/CLI_USB:以 CLI 方式连接主机;HOST_CONNECTION_MODE_RCP_UART/RCP_SPI/RCP_USB/RCP_TRANSPORT:设备作为 RCP 供主机使用(对应 ot_rcp 示例)。
配置结构体:
esp_openthread_radio_config_t:射频配置,内含radio_mode与对应 RCP 的 UART / SPI / 自定义传输联合体配置;esp_openthread_host_connection_config_t:主机连接配置,内含 UART / USB / SPI Slave / 自定义传输联合体配置;esp_openthread_port_config_t:端口特定配置,包括storage_partition_name(存储 OpenThread 数据集的 NVS 分区名)、netif_queue_size(网络接口报文队列大小)、task_queue_size(任务队列大小);esp_openthread_platform_config_t:平台配置,聚合上述 radio / host / port 三项;esp_openthread_config_t:完整配置,聚合netif_config与platform_config。
事件系统:ESP-IDF 通过OPENTHREAD_EVENT事件基(ESP_EVENT_DECLARE_BASE(OPENTHREAD_EVENT))对外广播 OpenThread 状态变化,事件类型包括OPENTHREAD_EVENT_START、STOP、DETACHED、ATTACHED、ROLE_CHANGED(携带esp_openthread_role_changed_event_t,含前后角色)、IF_UP、IF_DOWN、GOT_IP6、LOST_IP6、多播组加入/离开、TREL 相关事件、SET_DNS_SERVER、PUBLISH_MESHCOP_E/REMOVE_MESHCOP_E以及DATASET_CHANGED(携带esp_openthread_dataset_changed_event_t)。应用程序可以基于这些事件驱动自己的状态机,例如在ATTACHED事件后开始业务逻辑。
线程安全锁(esp_openthread_lock.h)
esp_openthread_lock.h 提供了 OpenThread API 的互斥保护机制。凡是接收otInstance参数的 OpenThread API 都必须在该锁的保护下调用(除非调用点本身位于 OpenThread 回调中):
esp_openthread_lock_init()/esp_openthread_lock_deinit():初始化 / 反初始化锁;esp_openthread_lock_acquire(block_ticks)/esp_openthread_lock_release():获取 / 释放锁,block_ticks为等待的超时 tick 数,返回true表示获取成功;esp_openthread_task_switching_lock_acquire()/esp_openthread_task_switching_lock_release():任务切换锁,用于在 OpenThread API 上下文中需要等待其他任务(如 lwIP)完成、随后再次调用 OpenThread API 的特殊场景。注意该锁的释放接口在非持有者调用时会导致崩溃,其实现位于 esp_openthread_lock.c。
网络接口胶水层(esp_openthread_netif_glue.h)
esp_openthread_netif_glue.h 负责将 OpenThread 接入 ESP-IDF 的esp_netif网络接口体系,实现 Thread 报文与 lwIP 协议栈之间的双向收发,对应实现为 esp_openthread_netif_glue.c 与 esp_openthread_lwip_netif.c:
esp_openthread_netif_glue_init(config):初始化胶水层,返回 glue 指针(失败返回NULL);esp_openthread_netif_glue_deinit():反初始化;esp_openthread_get_netif():获取 OpenThread 对应的esp_netif_t实例;ESP_NETIF_INHERENT_DEFAULT_OPENTHREAD()/ESP_NETIF_DEFAULT_OPENTHREAD():OpenThread 专用的 esp_netif 默认配置宏(接口名OT_DEF,描述openthread,路由优先级 15);esp_openthread_register_meshcop_e_handler(handler, for_publish):注册 meshcop-e 服务发布/移除事件处理器;is_openthread_internal_mesh_local_addr(addr):判断地址是否为 Thread mesh 本地地址。
边界路由器 API(esp_openthread_border_router.h)
esp_openthread_border_router.h 提供了让 ESP 设备作为 OpenThread 边界路由器的接口:
esp_openthread_set_backbone_netif(backbone_netif):设置骨干网接口(Wi-Fi 或以太网),必须在esp_openthread_init之前调用;esp_openthread_border_router_init():初始化边界路由器功能,调用后设备即表现为 OpenThread 边界路由器,需要 Kconfig 选项CONFIG_OPENTHREAD_BORDER_ROUTER的支持(对应实现为 components/openthread/src/port/esp_openthread_transport_rcp.c 同目录下的边界路由相关移植代码);esp_openthread_border_router_deinit():反初始化;esp_openthread_get_backbone_netif():获取骨干网接口;esp_openthread_set_meshcop_instance_name(name)/esp_openthread_get_meshcop_instance_name():设置 / 获取 meshcop(e) 实例名(必须在esp_openthread_border_router_init之前设置,传NULL时使用主机名作为实例名)。
其他扩展 API
除上述五个文档目录中列出的头文件外,components/openthread/include 还包含esp_openthread_cli.h(CLI 注册)、esp_openthread_dns64.h(DNS64/NAT64 相关,详见 examples/openthread/README_nat64.md)、esp_openthread_spinel.h、esp_openthread_transport.h(自定义 RCP 传输通道,对应esp_openthread_transport_config_t中的transport_tx/transport_rx回调)、esp_radio_spinel.h等,供进阶开发使用。
关键 Kconfig 配置项
OpenThread 组件配置集中在 components/openthread/Kconfig 中,通过idf.py menuconfig的Component config → OpenThread菜单访问:
总开关与任务参数
OPENTHREAD_ENABLED:总开关,默认关闭;OPENTHREAD_TASK_NAME:OpenThread 任务名,默认ot_main;OPENTHREAD_TASK_SIZE:任务栈大小(字节),默认 8192;OPENTHREAD_TASK_PRIORITY:任务优先级,默认 5。
控制台与 CLI
OPENTHREAD_CONSOLE_ENABLE:是否启用 OpenThread 专用控制台,默认开启(关闭后仍可使用 ESP-IDF 默认控制台);OPENTHREAD_CONSOLE_TYPE:控制台类型,可选 UART 或 USB Serial/JTAG(取决于ESP_CONSOLE_*配置);OPENTHREAD_CLI:是否启用 OpenThread 命令行接口,默认开启;OPENTHREAD_CONSOLE_COMMAND_PREFIX:注册在 ESP 控制台上的 CLI 命令前缀,默认ot,即通过ot xxx转发到 OpenThread 回调处理。
网络参数(Thread Operational Dataset)
OPENTHREAD_NETWORK_NAME:网络名,默认OpenThread-ESP;OPENTHREAD_MESH_LOCAL_PREFIX:mesh 本地前缀,格式<address>/<plen>,默认fd00:db8:a0:0::/64;OPENTHREAD_NETWORK_CHANNEL:802.15.4 信道,取值范围 11~26,默认 15;OPENTHREAD_NETWORK_PANID:PAN ID(十六进制),范围 0~0xFFFE,默认0x1234;OPENTHREAD_NETWORK_EXTPANID:扩展 PAN ID(十六进制字符串),默认dead00beef00cafe;OPENTHREAD_NETWORK_MASTERKEY:网络主密钥,默认00112233445566778899aabbccddeeff。
当调用esp_openthread_auto_start(NULL)传入空数据集时,上述 Kconfig 配置即用于自动生成运营数据集,因此合理配置这些参数可以直接决定入网时的网络标识与安全密钥。另外,CONFIG_OPENTHREAD_BORDER_ROUTER是启用边界路由器功能(esp_openthread_border_router_init)的前提,CONFIG_OPENTHREAD_RADIO_NATIVE决定是否使用 SoC 原生 802.15.4 射频。
小结
本文围绕 docs/zh_CN/api-reference/network/esp_openthread.rst 展开,系统梳理了 ESP-IDF 对 OpenThread 的完整支持:从 Thread 协议本身的定位(基于 IP、基于 802.15.4 的网状网络),到 examples/openthread 下边界路由器、CLI、RCP、TREL、睡眠设备五大参考示例的实战操作,再到 components/openthread 提供的生命周期管理、配置类型、事件系统、线程安全锁、netif 胶水层与边界路由器 API。无论是快速组建一个双设备 Thread 网络,还是搭建完整的 Thread 边界路由器实现双向 IPv6 与跨网络服务发现,读者都可以在本文与上述示例的基础上直接起步。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考