ESP32 OpenThread CoAP Greenhouse 实战:同一 Mesh 网络上 Plain CoAP 遥测与 CoAPS 安全控制的双传输设计
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
本文基于 arduino-esp32 仓库中的 OpenThread 原生 API 示例CoAP_Greenhouse(示例总览),讲解一个典型的 IoT 双传输架构:开放遥测走明文 CoAP(5683,NON 可靠级),经认证的执行器命令走 CoAPS/DTLS(5684,CON 可靠级),两套协议运行在同一对 Thread Mesh 节点上。读完后你将掌握:Thread Leader/Commissioner 与 Joiner 的双板组网流程、OThreadCoAPServer与OThreadCoAPSecureServer双服务器共存方式、CoAPS 的 sdkconfig/menuconfig 开启方法,以及 PSK 密钥在客户端与服务端之间的匹配细节。
双传输架构:为什么遥测和执行器要分开
该示例模拟一个温室场景:只读环境数据(温度、光照)对网络内任意节点开放,而可写控制命令(灌溉阀门、通风风扇)必须通过 DTLS 认证通道下发。这种"开放遥测 + 认证执行器"的拆分是物联网网关的常见安全模型。仓库用一对 sketch 完整演示了多个资源、CON 与 NON 的可靠性差异,以及同一对 Mesh 节点上的双传输共存:
| Sketch | 角色 |
|---|---|
| greenhouse_server(Leader + 网关) | Leader + Commissioner + 明文 CoAP 服务器 + CoAPS 服务器 |
| greenhouse_client(Joiner + 控制器) | Joiner + 明文 NON 轮询遥测 + CoAPS CON 控制命令 |
原始文档中的架构示意图如下(ASCII 图):
┌─────────────────────────────────────┐ greenhouse_client │ PlainClient (5683) │ │ NON GET greenhouse/temp │ │ NON GET greenhouse/light │ │ SecureClient (5684) │ │ CON PUT valve/water │ │ CON PUT fan/speed │ └─────────────────┬───────────────────┘ │ Thread mesh ┌──────────────────────▼──────────────────────────┐ greenhouse_server │ OThreadCoAPServer — telemetry (read) │ │ OThreadCoAPSecureServer — actuators (write) │ └─────────────────────────────────────────────────┘从源码结构看,客户端通过OThread.getLeaderRloc()直接取 Leader 的 RLOC16 作为服务器地址(greenhouse_client.ino 中serverIp = OThread.getLeaderRloc();),无需额外的 mDNS 解析,因为服务器就是组网时先烧录的那块板子。
资源清单:端口、方法与可靠性
示例共注册 4 个 URI 资源,明文与加密传输各有分工:
| 路径 | 传输 | 方法 | 客户端可靠性 | 用途 |
|---|---|---|---|---|
greenhouse/temp | Plain 5683 | GET | NON | 温度(°C) |
greenhouse/light | Plain 5683 | GET | NON | 光照强度(lux) |
valve/water | CoAPS 5684 | PUT | CON | 灌溉 0–100 % |
fan/speed | CoAPS 5684 | PUT | CON | 通风 0–100 % |
端口约定与 OThreadCoAP.h 中的宏定义一致:OT_COAP_DEFAULT_PORT 5683(明文)、OT_COAP_SECURE_DEFAULT_PORT 5684(CoAPS)。响应码使用头文件中的OT_COAP_RESP_*常量(如 205/204/405),PUT 成功时服务端回204 Changed,方法不匹配回405 Method Not Allowed(见 greenhouse_server.ino 中resp.setCode(OT_COAP_RESP_METHOD_NA)的处理)。
服务端:组网、Commissioner 与双服务器启动
服务端 sketch 位于 greenhouse_server.ino,文件头部声明了全部可调参数:
| 常量 | 含义 |
|---|---|
PSKD | Commissioner 接受的 Joiner 密钥,值J01NME |
JOINER_WINDOW_SEC | addJoiner()窗口有效期,默认 600 秒 |
CHANNEL | 802.15.4 信道,默认 15 |
PAN_ID | 16 位 PAN ID,默认0xBEE5 |
NETKEY | 128 位网络密钥(硬编码的演示值) |
COAP_PSK | 16 字节 CoAPS 预共享密钥,必须与客户端一致 |
COAP_PSK_ID | PSK 身份字符串,默认esp-coap-demo |
setup()的启动链条(源码 greenhouse_server.ino#L158-L227):
- 组建网络:
DataSet ds; ds.initNew();后设置网络名ESP_OT_CoAP_Greenhouse、信道、PAN ID、网络密钥,依次OThread.commitDataSet(ds)→OThread.networkInterfaceUp()→OThread.start(),再轮询OThread.otGetDeviceRole() >= OT_ROLE_CHILD确认附着(30 秒超时,超时则OThread.stop()后 2 秒重试)。 - 启动 Commissioner:
OThread.startCommissioner()成功后调用OThread.addJoiner(PSKD, JOINER_WINDOW_SEC),串口输出Commissioner ready (PSKd "J01NME")。这是客户端能够入网的前提——必须先烧录服务端并等到该提示。 - 注册明文资源:
OThreadCoAPServer.on("greenhouse/temp", OT_COAP_METHOD_GET, onGreenhouseTemp, &s_state)等两条,然后OThreadCoAPServer.begin()。 - 条件注册 CoAPS 资源:只有
OThreadCoAP::secureApiEnabled()为 true(即固件编译时开启了OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE)才执行OThreadCoAPSecureServer.setPSK(...)、注册valve/water与fan/speed并begin();否则打印CoAPS is not enabled in this build,仅保留明文遥测。这保证了同一份代码在未开启 CoAPS 的固件上也能降级运行。
资源处理函数遵循 OThreadCoAP.h 中OThreadCoAPHandler回调约定:检查req.method()是否匹配,不匹配则回405;PUT 处理函数用parsePercent()校验 payload 在 0–100 范围内,越界回400 Bad Request(payload 为"0-100"),合法则更新状态并回204 Changed。每个请求还会由logRequestKind()打印[CON]或[NON]标记与对端 IPv6 地址,便于在串口确认客户端使用的可靠性等级。
loop()中模拟环境漂移:温度每次random(-5,6)/10.0f随机游走并按fanSpeed/200降温(风扇开得越大降温越多),钳制在 18–32 °C;光照在 2000–20000 lux 间随机游走。注意状态是纯内存的(static GreenhouseState s_state),服务端重启后阀门/风扇状态丢失,这也是故障排查表中"服务端重启后需复位客户端重新入网"一行的根源。
客户端:Joiner 入网与 8 秒/24 秒双周期控制
客户端 sketch 位于 greenhouse_client.ino,关键常量与自动化阈值:
| 常量 | 含义 |
|---|---|
PSKD | 必须与服务端 Commissioner 一致的J01NME |
CHANNEL_HINT | 802.15.4 信道提示,默认 15 |
JOIN_TIMEOUT_MS | startJoiner()内部等待 Commissioner 的最长时间(60000 ms) |
TELEMETRY_MS | 明文 NON 轮询周期(默认 8000 ms) |
CONTROL_MS | CoAPS 控制循环周期(默认 24000 ms) |
TEMP_FAN_ON_C | 温度超过 26.0 °C 时风扇设为 75%,否则 15% |
LIGHT_VALVE_LUX | 光照低于 8000 lux 时阀门设为 60%,否则 10% |
COAP_PSK/COAP_PSK_ID | 16 字节 PSK 与身份串,必须与服务端完全一致 |
入网流程joinNetwork()(源码):OThread.setChannel(CHANNEL_HINT)→networkInterfaceUp()→OThread.startJoiner(PSKD, JOIN_TIMEOUT_MS)(返回非OT_ERROR_NONE即Joiner failed: N)→OThread.start()后等待附着。客户端只在setup()里入网,失败则OThread.stop()后 3 秒重试;如果它先于服务端 Commissioner 启动而开机,需要手动复位。
loop()是两个周期叠加:
// 每 8 s:明文 NON 遥测 PlainClient.setConfirmable(false); int code = PlainClient.GET(serverIp, "greenhouse/temp"); // <0 时 OThreadCoAP::errorToString(code) lastTempC = PlainClient.getString().toFloat(); // 每 24 s:自动化 + CoAPS CON PUT(前提:首次遥测成功后 lastTempC > 0) SecureClient.connect(serverIp); // DTLS 握手,失败打印 "DTLS connect failed." SecureClient.setConfirmable(true); sendSecurePut("fan/speed", fanSpeed); // temp > 26 C -> 75% sendSecurePut("valve/water", valveOpen); // light < 8000 -> 60% SecureClient.disconnect();对应源码中pollTelemetry()(greenhouse_client.ino#L97-L116)与runControlLoop()(greenhouse_client.ino#L130-L151)。几个值得注意的实现细节:
- 控制前门控:
loop()中if (lastTempC > 0.0f && OThreadCoAP::secureApiEnabled())才执行控制循环,所以"前 24 秒看不到控制输出"是设计行为——它在等待第一次成功的 NON 遥测(lastTempC初始为 0.0f)。 - DTLS 会话生命周期:每个控制周期
connect()→ 两次PUT→disconnect(),即每 24 秒重新握手一次,而不是保持长连接。这与 OThreadCoAPSecureClient 的约束呼应:每设备仅支持一个活动 CoAPS 客户端会话,且同一设备上不能同时运行OThreadCoAPSecureClient::connect()与OThreadCoAPSecureServer——本示例因此把 CoAPS 服务器放在服务端板、CoAPS 客户端放在客户端板。 - 超时配置:明文客户端
PlainClient.setTimeout(3000);安全客户端SecureClient.setConnectTimeout(10000)(DTLS 握手)与setTimeout(5000)(请求等待,CoAPS 下仅限制 sketch 等待,线上重传走栈默认值,见头文件对setTimeout的注释 OThreadCoAP.h#L681-L687)。
运行步骤与演示凭据
运行前提(来自总览 README):
- 启用 CoAPS 构建选项(见下文"启用 CoAPS"一节;若只跑明文遥测可跳过)。
- 先烧录greenhouse_server,等待串口出现
Commissioner ready以及明文(5683)/CoAPS(5684)两条监听提示。 - 在第二块板烧录 greenhouse_client。
- 两块板均用 115200 波特率打开串口监视器:客户端每 8 秒轮询遥测,并在首次遥测成功后每 24 秒发送 CON 控制命令。
两块 sketch 的 CI 配置也印证了各自需要的配置项:greenhouse_server/ci.yml 声明了CONFIG_OPENTHREAD_ENABLED=y、CONFIG_SOC_IEEE802154_SUPPORTED=y、CONFIG_OPENTHREAD_COMMISSIONER=y。
必需的 IDF 特性(sdkconfig)
| 特性 | 用途 |
|---|---|
CONFIG_OPENTHREAD_ENABLED=y | 编入 OpenThread 协议栈 |
CONFIG_SOC_IEEE802154_SUPPORTED=y | SoC 具备 802.15.4 射频 |
CONFIG_OPENTHREAD_COMMISSIONER=y | greenhouse_server使用 |
CONFIG_OPENTHREAD_JOINER=y | greenhouse_client使用 |
OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1 | 5684 端口 CoAPS 执行器 |
MBEDTLS_KEY_EXCHANGE_PSK_ENABLED | PSK 密码套件 |
共享演示凭据
| 参数 | 值 |
|---|---|
| 网络名 | ESP_OT_CoAP_Greenhouse |
| 信道 | 15 |
| PAN ID | 0xBEE5 |
| Thread Joiner PSKd | J01NME |
| CoAPS PSK id | esp-coap-demo |
明文 CoAP 遥测(5683)在不开任何 CoAPS 构建标志的情况下即可工作;执行器 PUT 命令则必须开启。运行期可用OThreadCoAP::secureApiEnabled()判断当前固件是否包含 CoAPS(OThreadCoAP.h#L162-L166 注释明确其返回OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE的编译状态)。
支持的目标
两个 sketch 均声明支持(见 greenhouse_server/README.md):
| SoC | Thread | 状态 |
|---|---|---|
| ESP32-H2 | 是 | 支持 |
| ESP32-C6 | 是 | 支持 |
| ESP32-C5 | 是 | 支持 |
启用 CoAPS:以 ESP-IDF 组件方式构建 Arduino
标准 Arduino IDE 构建的固件可能不含 CoAPS 安全 API。如需 5684 端口上的 CoAPS 执行器,需要把 sketch 当作ESP-IDF 工程(Arduino 作为 component)构建,再在 menuconfig 中开启特性。可参考仓库文档 docs/esp-idf_component.rst(Arduino as an ESP-IDF component)。操作流程:把 sketch 拷入工程main/目录(.ino改名.cpp),执行idf.py set-target <soc>,然后idf.py menuconfig。
需要开启的选项:
| menuconfig 路径 | 设置 |
|---|---|
| Component config → OpenThread | OpenThread |
| Component config → OpenThread → Thread Core Features | Enable Commissioner(greenhouse_server) |
| Component config → OpenThread → Thread Core Features | Enable Joiner(greenhouse_client) |
| Component config → mbedTLS → TLS Key Exchange Methods | Enable pre-shared-key ciphersuites |
| Component config → mbedTLS → TLS Key Exchange Methods | Enable PSK based ciphersuite modes |
| Component config → OpenThread → Thread Extensioned Features | Use a header file defined by customer |
关键点:CoAPS API 标志OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE不是menuconfig 里的直接开关。使用自定义 OpenThread 头文件时,在其中定义:
#define OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE 1把该头文件路径填到Thread Extensioned Features → OpenThread Custom Header Config,保存后执行idf.py build flash monitor。
预期串口输出
服务端(CoAPS 可用时):
=== CoAP Greenhouse — server === Forming Thread network... Waiting for attach.. Attached as Leader. Starting Commissioner... Commissioner ready (PSKd "J01NME") Starting CoAP servers... Ready. Plain CoAP on port 5683: GET greenhouse/temp, greenhouse/light CoAPS on port 5684: PUT valve/water, fan/speed (PSK id "esp-coap-demo") Mesh-local: fdde:ad00:beef:0:.... [NON] GET greenhouse/temp from fdde:ad00:beef:0:.... [CoAPS CON] PUT fan/speed from fdde:ad00:beef:0:.... Fan -> 75% Valve -> 60%客户端:
=== CoAP Greenhouse — client === Joining Thread network (Joiner)... Commissioning with PSKd "J01NME"... Waiting for attach.. Attached as Child. Greenhouse server: fdde:ad00:beef:0:0:ff:fe00:0 Ready. Polling telemetry and running control loop. [plain NON] temp=24.3 C [plain NON] light=11500 lux Control: temp=24.3 fan=15% light=11500 valve=10% [CoAPS CON] PUT fan/speed=15 -> 2.04 Changed [CoAPS CON] PUT valve/water=10 -> 2.04 Changed若固件未含 CoAPS,两端都会打印CoAPS is not enabled in this build(服务端遥测继续、客户端跳过安全控制);若入网失败,客户端会打印Joiner failed: 7并每 3 秒重试。
故障排查
启动顺序是第一位的排查项:先烧录 greenhouse_server,等Commissioner ready与两条监听提示后再烧录/复位 greenhouse_client;若客户端在 Commissioner 激活前开机,需复位。
| 症状 | 可能原因 |
|---|---|
客户端Join failed | 先烧录 greenhouse_server并等待 Commissioner ready,之后复位客户端 |
CoAPS is not enabled in this build | 固件缺少 CoAPS。按上文"启用 CoAPS"以 ESP-IDF 组件方式重新构建;明文 5683 遥测不受影响 |
| 明文 GET 正常、CoAPS PUT 失败 | CoAPS 构建标志缺失或 PSK 不匹配——5683 遥测可以在无安全构建下工作,两者应分开判断 |
| 客户端跳过控制输出 | 等待首次成功的 NON 遥测(lastTempC必须非零) |
| 客户端入网后服务端复位 | 复位客户端重新入网;服务端状态存于内存,重启即丢失 |
服务端CoAPS server start failed | 构建已启用 CoAPS 但OThreadCoAPSecureServer.begin()失败(PSK、端口或附着问题) |
服务端Plain CoAP server start failed | OpenThread 未附着 |
CON PUT ... failed: Timeout | 服务端 CoAPS 未运行或节点已脱离 Mesh |
相关示例
- CoAP Secure:仅 CoAPS 的最小演示(secure_server / secure_client)。
- Native CoAP 示例总览:所有原生 CoAP 演示与端口约定,还列出了
setPort()、useDefaultCoapRetransmit()、joinMulticastGroup()等本示例未展示的高级选项。 - OThreadCoAP.h:
OThreadCoAPClient/OThreadCoAPServerClass/OThreadCoAPSecureClient/OThreadCoAPSecureServerClass的完整 API 与约束(单例服务器、单 CoAPS 会话、错误码OT_COAP_ERROR_*等)。
以上示例代码与文档均以 Apache License 2.0 发布。
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考