ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Arduino ESP32 OpenThread CoAP Secure 实战:基于 DTLS + PSK 的 CoAPS 安全通信指南

2026/9/14 18:25:21 拓冰建站 浏览量
Arduino ESP32 OpenThread CoAP Secure 实战:基于 DTLS + PSK 的 CoAPS 安全通信指南 Arduino ESP32 OpenThread CoAP Secure 实战基于 DTLS PSK 的 CoAPS 安全通信指南【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32本文以 arduino-esp32 仓库中libraries/OpenThread/examples/Native/CoAP/CoAP_Secure/示例群为核心完整讲解如何在 ESP32 系列 SoC 上运行CoAP over DTLSCoAPS服务端如何组网、开启 Commissioner 准入并监听 5684 端口客户端如何以 Joiner 身份入网、建立 DTLS 会话并完成一次 confirmable GET。读完后你可以直接复制仓库中的两份 sketch 跑通安全 CoAP 链路并掌握在 ESP-IDF 构建中启用 CoAPS API 的完整配置方法。1. 示例群总览一主一从两台设备CoAP Secure 演示由两个 sketch 组成使用共享的Pre-Shared Key (PSK)进行 DTLS 认证与 OpenThread CLI 的coaps psk key id语义对齐Sketch角色secure_serverThread Leader Commissioner CoAPS server监听端口5684secure_clientJoiner DTLS 连接 GETstatus客户端通过 CommissionerPSKd 为J01NME入网向 Leader RLOC 发起 DTLS 会话执行一次 confirmable GETstatus打印 payload 后断开连接。整个流程的端到端时序为secure_server板1 secure_client板2 ├─ 组网 ESP_OT_CoAP_Secure ├─ startCommissioner() ├─ addJoiner(J01NME, 600s) ──→ startJoiner(J01NME, 60s) ├─ setPSK() on(status) begin() │ 等待 attachChild │CoAPS listening 5684 ──→ serverIp getLeaderRloc() │ ──→ connect(serverIp) [DTLS 握手] │ onStatus 回调 ◄── GET status ──── │ │ 返回 2.05 secure-ok ──────────→ │ 打印 payload │ ──→ disconnect()两个 sketch 的 README 各自给出了配置项、期望串口输出和故障排查细节本文将其与源码一并整理。适用目标根据两份子 README本演示支持ESP32-H2 / ESP32-C6 / ESP32-C5均带 802.15.4 射频。2. CoAPS 与明文 CoAP 的 API 差异CoAP_Secure 群 README 给出的核心对比如下明文OThreadCoAPClient安全OThreadCoAPSecureClient每次调用都GET(host, path)先connect(host)一次之后GET(path)UDP 端口 5683DTLS 端口 5684无凭据connect()前调用setPSK()从 OThreadCoAP.h 的 API 注释还能补充几点重要的使用约束端口是编译期默认值OT_COAP_DEFAULT_PORT5683、OT_COAP_SECURE_DEFAULT_PORT5684见 OThreadCoAP.h客户端也可通过connect(host, port)覆盖端口。安全客户端是阻塞式的每个请求方法会阻塞到收到响应或setTimeout()超时返回 CoAP 响应码≥0或负的OT_COAP_ERROR_*错误码如-16表示 not connected、-17表示 TLS failed定义见 OThreadCoAP.h。每个设备同一时间只允许一个活跃的 CoAPS 客户端会话同一设备上的安全客户端connect()会阻止OThreadCoAPSecureServer::begin()反之亦然。因此安全服务端应部署在一块板子上、安全客户端在另一块——这正是本演示的双板架构原因见 OThreadCoAP.h 的note。凭据有两种模式setPSK()本演示或setCertificate()X.509 证书 私钥 可选 CA客户端侧还有setVerifyPeer()控制是否校验对端证书。明文服务与安全服务可以在同一设备上同时运行5683 5684 双端口仓库的 CoAP_Greenhouse 示例就是这种组合形态。3. 服务端组网 Commissioner CoAPS Server3.1 secure_server 的工作流程服务端 sketchsecure_server.ino启动后依次完成四件事// 1) 组建 Thread 网络网络名 ESP_OT_CoAP_Secure每次启动 initNew DataSet ds; ds.initNew(); ds.setNetworkName(ESP_OT_CoAP_Secure); ds.setChannel(CHANNEL); // 默认 15 ds.setPanId(PAN_ID); // 默认 0x5EC0 ds.setNetworkKey(NETKEY); // 128 位网络密钥 OThread.commitDataSet(ds); OThread.networkInterfaceUp(); OThread.start(); // 2) 申请 Commissioner 角色打开 joiner 窗口 OThread.startCommissioner(); // 需 CONFIG_OPENTHREAD_COMMISSIONER OThread.addJoiner(PSKD, JOINER_WINDOW_SEC); // PSKd J01NME窗口 600s // 3) 配置 PSK 并启动 CoAPS server OThreadCoAPSecureServer.setPSK(COAP_PSK, sizeof(COAP_PSK), COAP_PSK_ID); OThreadCoAPSecureServer.on(status, OT_COAP_METHOD_GET, onStatus); OThreadCoAPSecureServer.begin(); // onStatus() 对 GET 返回 OT_COAP_RESP_OK secure-ok对应的onStatus回调在 OpenThread worker 任务中执行保持短小static void onStatus(OThreadCoAPRequest req, OThreadCoAPResponse resp, void *ctx) { (void)ctx; if (req.method() ! OT_COAP_REQ_GET) { resp.setCode(OT_COAP_RESP_METHOD_NA); // 405 resp.send(); return; } Serial.printf(CoAPS GET from %s\n, req.remoteIP().toString().c_str()); resp.setCode(OT_COAP_RESP_OK); // 205 resp.setPayload(secure-ok); resp.send(); }3.2 期望串口输出一切正常时服务端应依次打印 CoAP Secure — server Forming Thread network... Waiting for attach.. Attached as Leader. Starting Commissioner... Commissioner ready (PSKd J01NME) Starting CoAPS server... Ready. CoAPS listening on port 5684 (PSK id esp-coap-demo) Mesh-local: fdde:ad00:beef:0:.... CoAPS GET from fdde:ad00:beef:0:....两种失败情形CoAPS is not enabled in this build. This sketch will not run. // 固件未编入 CoAPS CoAPS server start failed. // 安全 API 已启用但 begin() 失败3.3 可配置项所有可调常量都位于.ino文件头部常量用途PSKDCommissioner 接受的 Joiner 密钥J01NMEJOINER_WINDOW_SECaddJoiner()窗口有效期默认 600 sCHANNEL802.15.4 信道默认 15PAN_ID16 位 PAN ID默认0x5EC0NETKEY128 位网络密钥COAP_PSK16 字节 CoAPS 预共享密钥——必须与客户端完全一致COAP_PSK_IDPSK 标识字符串默认esp-coap-demo演示用的 PSK 为{0x10, 0x20, ..., 0xF0, 0x00}共 16 字节。CoAPS 默认监听端口5684OT_COAP_SECURE_DEFAULT_PORT。注意服务端每次上电都会initNew()重建网络新的网钥上下文所以任何一次服务端复位后都要复位客户端重新入网——这是排障表中最常见的一条。4. 客户端Joiner 入网 DTLS GET4.1 secure_client 的工作流程客户端 sketchsecure_client.ino全部动作都在setup()中完成loop()仅休眠// 1) 通过 Commissioner 加入 secure_server 组网 OThread.setChannel(CHANNEL_HINT); // 信道提示默认 15 OThread.networkInterfaceUp(); OThread.startJoiner(J01NME, JOIN_TIMEOUT_MS); // 需 CONFIG_OPENTHREAD_JOINER OThread.start(); serverIp OThread.getLeaderRloc(); // 安全服务器即 Leader RLOC // 2) 打开 DTLS、GET status、断开 SecureClient.setPSK(COAP_PSK, sizeof(COAP_PSK), COAP_PSK_ID); SecureClient.setConnectTimeout(10000); // DTLS 握手超时 SecureClient.setTimeout(5000); // CoAP 请求/响应超时 SecureClient.setConfirmable(true); // CON 请求 SecureClient.onConnectEvent(onSecureEvent); // DTLS 状态事件回调 ... if (!SecureClient.connect(serverIp)) { /* DTLS connect failed */ } int code SecureClient.GET(status); Serial.printf(Payload: %s\n, SecureClient.getString().c_str()); SecureClient.disconnect();入网失败时会打印Join failed, retry in 3s...并循环重试调用OThread.stop()后重新走 JoineronSecureEvent回调则把OT_COAP_SECURE_CONNECTED/OT_COAP_SECURE_DISCONNECTED_PEER/OT_COAP_SECURE_DISCONNECTED_ERROR等事件转成可读日志这些事件常量定义在 OThreadCoAP.h。值得注意的实现细节serverIp OThread.getLeaderRloc()内部调用的是 OpenThread 的otThreadGetLeaderRloc()见 OThread.cpp即把 Leader RLOCFDDE:...:ff:fe00:0 这类地址解析为本地 IPv6 地址作为 DTLS 目的端——Thread 内部一跳寻址无需 DNS。4.2 期望串口输出 CoAP Secure — client Joining Thread network (Joiner)... Commissioning with PSKd J01NME... Waiting for attach.. Attached as Child. Server: fdde:ad00:beef:0:0:ff:fe00:0 Connecting DTLS... DTLS connected GET status - 205 (2.05 Content) Payload: secure-ok DTLS closed by peer失败分支的典型输出Joiner failed: 7 Join failed, retry in 3s... // Commissioner 未就绪 / 窗口已关闭 Attached. Server: fdde:ad00:beef:0:0:ff:fe00:0 DTLS connect failed. // PSK/PSK id 不匹配或 CoAPS 构建缺失4.3 可配置项常量用途PSKDPre-Shared Key for Device必须与服务端 Commissioner 一致CHANNEL_HINT802.15.4 信道提示默认 15必须与网络一致JOIN_TIMEOUT_MSstartJoiner()内部等待 Commissioner 的最长时间默认 60000 msCOAP_PSK16 字节 CoAPS 预共享密钥——必须与服务端一致COAP_PSK_IDPSK 标识字符串默认esp-coap-demo超时参数DTLS 握手setConnectTimeout(10000)CoAP 请求setTimeout(5000)。注意从 API 注释看CoAPS 客户端的setTimeout()只是 sketch 侧等待上限链路层重传走 OpenThread 栈默认参数与明文OThreadCoAPClient的对齐重传行为不同见 OThreadCoAP.h。5. 构建要求与运行时自检5.1 必需的 IDF 特性sdkconfig特性用途CONFIG_OPENTHREAD_ENABLEDyOpenThread 协议栈CONFIG_SOC_IEEE802154_SUPPORTEDy802.15.4 射频CONFIG_OPENTHREAD_COMMISSIONERysecure_server的 Commissioner APICONFIG_OPENTHREAD_JOINERysecure_client的 Joiner APIOPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE1CoAPS / DTLS APIMBEDTLS_KEY_EXCHANGE_PSK_ENABLEDPSK 密码套件演示所需关键陷阱即使缺少 CoAPS 构建标志sketch 也能编译通过但运行时安全操作一律返回OT_COAP_ERROR_NOT_CONNECTED-16。从源码可以确认这一行为OThreadCoAPSecureClient::GET/PUT/POST/DELETE在未定义OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE的编译分支里直接return OT_COAP_ERROR_NOT_CONNECTED;见 OThreadCoAP.cpp而连接前的守卫检查同样返回该错误码。因此两份 sketch 都在setup()开头做了运行时自检if (!OThreadCoAP::secureApiEnabled()) { Serial.println(CoAPS is not enabled in this build. This sketch will not run.); while (1) { delay(1000); } }secureApiEnabled()的实现就是一个编译期开关的映射OThreadCoAP.cppbool OThreadCoAP::secureApiEnabled() { #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE return true; #else return false; #endif }建议在自定义程序中保留这种自检模式避免能编译但连不上的困惑。5.2 以 ESP-IDF 组件方式启用 CoAPS标准 Arduino IDE 构建可能不含 CoAPS 安全 API。需要时把 sketch 作为ESP-IDF 项目 Arduino 组件来构建流程参考仓库内的 Arduino as an ESP-IDF component 文档 与 idf_component_examples 下的工程样板把.ino拷入工程main/目录重命名为.cpp执行idf.py set-target soc后进入idf.py menuconfig启用以下选项menuconfig 路径设置Component config → OpenThreadOpenThreadComponent config → OpenThread → Thread Core FeaturesEnable Commissioner仅secure_server需要Component config → OpenThread → Thread Core FeaturesEnable Joiner仅secure_client需要Component config → mbedTLS → TLS Key Exchange MethodsEnable pre-shared-key ciphersuitesComponent config → mbedTLS → TLS Key Exchange MethodsEnable PSK based ciphersuite modesComponent config → OpenThread → Thread Extensioned FeaturesUse a header file defined by customer其中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构建烧录。5.3 演示共享凭据两份 sketch 必须保持一致的凭据默认值参数值Thread Joiner PSKdJ01NMECoAPS PSK idesp-coap-demoCoAPS PSK 字节16 字节数组0x10..0x00两个.ino中必须一致6. 运行步骤与故障排查6.1 标准运行顺序按上文 5.2 节构建带 CoAPS 的固件。先烧录 secure_serverLeader CoAPS server等待串口出现Commissioner ready (PSKd J01NME)与CoAPS listening on port 5684。在第二块板上烧录 secure_clientJoiner CoAPS client。若客户端在服务端 Commissioner 就绪前就已上电需复位客户端。观察客户端串口输出DTLS connected与GET status - 205及 payloadsecure-ok。6.2 故障排查表现象可能原因CoAPS is not enabled in this build固件缺 CoAPS。按第 5.2 节以 IDF 组件方式重新构建并启用相关选项CoAPS server start failedCoAPS 构建标志未启用或同设备上存在活跃的安全客户端会话客户端Join failed/Joiner failed: 7先启动 secure_server并等待Commissioner ready再复位客户端或 joiner 窗口600 s已关闭DTLS connect failedCoAPS 构建标志缺失或COAP_PSK/ PSK id 两端不匹配客户端已 attached 但 GET 失败服务端未在 5684 上监听安全 server 启动失败检查构建标志服务端在客户端入网后复位客户端可能仍在旧网络上——复位客户端经 Commissioner 重新入网工作过一次、服务端复位后失效服务端每次上电initNew()重建网络必须复位客户端重新入网7. 延伸与仓库内其他示例的关系CoAP Greenhouse同一台网关上明文 CoAPS 双服务组合适合对照学习一设备双传输形态。Native CoAP 示例群总览全部 Native CoAP 演示与端口约定5683/5684。核心封装头文件 OThreadCoAP.hOThreadCoAPSecureClient/OThreadCoAPSecureServerClass/OThreadCoAP::secureApiEnabled()的完整 API 注释实现位于 OThreadCoAP.cpp。线程侧原语startJoiner()/startCommissioner()/getLeaderRloc()实现在 OThread.cpp。本示例遵循 Apache License 2.0与仓库 LICENSE 一致。若要把该链路用于生产需要把演示中的硬编码NETKEY/COAP_PSK替换为安全的密钥注入机制并按需改用setCertificate()证书模式并启用setVerifyPeer(true)校验对端。【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考