ARTICLE DETAIL

建站实战干货

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

arduino-esp32 OpenThread notes_client 实战:用 OThread Joiner 入网并驱动 CoAP REST 笔记服务

2026/9/14 10:34:40 拓冰建站 浏览量
arduino-esp32 OpenThread notes_client 实战:用 OThread Joiner 入网并驱动 CoAP REST 笔记服务 arduino-esp32 OpenThread notes_client 实战用 OThread Joiner 入网并驱动 CoAP REST 笔记服务【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32notes_client是 arduino-esp32 仓库 OpenThread 原生 API 示例集中 CoAP CRUD 双板演示的客户端它通过 Thread Joiner 状态机以 PSKdJ01NME加入由notes_server组建的 Thread 网络将 Thread Leader RLOC 解析为 CoAP 目的地并提供一个串行命令菜单对资源路径notes执行 list / read / add / update / del 五种 REST 操作。读完本文你将掌握OThread.startJoiner()的完整入网调用链、OThreadCoAPClient阻塞式请求的配置要点超时、confirmable、JSON Content-Format以及该示例在 ESP32-H2 / ESP32-C6 / ESP32-C5 上的完整部署、串口交互与故障排查方法。1. 示例定位CoAP CRUD 双板演示的客户端该示例位于 CoAP CRUD 目录之下与notes_server组成一对服务器端是 ThreadLeader Commissioner并挂接一个基于OThreadCoAPResourceStore的 REST 资源存储路径notes标准 CoAP 端口5683回调模式处理请求客户端即本文主角——ThreadJoiner 交互式串口 CLI发送 confirmable 的 GET / POST / PUT / DELETE 请求。它需要另一块板上运行 notes_server 且处于无线电覆盖范围内两块板各烧录一个 sketch。notes_server暴露的 REST API 如下客户端正是围绕这张表来构造请求的MethodPath动作响应GETnotes列出全部笔记JSON 数组205 ContentGETnotes/2读取 id 为 2 的笔记205或404POSTnotes创建笔记JSON body201 CreatedLocation-Path: notes/NPUTnotes/2更新笔记 2204 Changed或404DELETEnotes/2删除笔记 2202 Deleted或404POST / PUT 的 body 为 JSON{text:...}。客户端通过CoapClient.setContentFormat(OT_COAP_FORMAT_JSON)声明请求体格式资源存储以 JSON 应答并在每个响应上设置 Content-Format 选项。GET / DELETE 无请求负载因此 Content-Format 只作用于这些方法的响应侧。支持的目标 SoCSoCThread状态ESP32-H2yesSupportedESP32-C6yesSupportedESP32-C5yesSupported2. 前置条件与启动顺序客户端对网络的加入发生在第一次入网或擦除 NVS / 恢复出厂之后且只在setup()中执行。因此必须满足同一无线电范围内已有一个notes_server实例在运行网络名ESP_OT_CoAP_CRUD、信道 15、PAN ID0xC0DE服务器已进入Commissioner ready (PSKd J01NME)状态且仍处于其加入者窗口内——该窗口由服务器端的JOINER_WINDOW_SEC控制默认为600 秒如果客户端开机时服务器尚未就绪需要在服务器 Commissioner 激活后按下客户端的 reset 键重新入网服务器每次复位都会调用initNew()组建新网络——此时必须复位客户端重新加入服务器每次启动都生成新的网络标识这是该演示有意为之的行为。从服务器源码 notes_server.ino 可以看到这些约定的来源startNetwork()中DataSet ds; ds.initNew()创建全新数据集并写入网络名、信道、PAN ID 和 16 字节 Network Key随后OThread.startCommissioner()申请 Commissioner 角色、OThread.addJoiner(PSKD, JOINER_WINDOW_SEC)打开加入者窗口之后OThreadCoAPServer.begin()启动 CoAP 服务Notes.attach(OThreadCoAPServer, notes, 8)把内存资源存储挂在notes路径上最多 8 条。3. 客户端源码剖析从 setup() 到 loop()完整源码见 notes_client.ino核心骨架如下// 1) Bring OT stack up and configure the CoAP client. OThread.begin(false); CoapClient.setTimeout(4000); CoapClient.setConfirmable(true); // 2) Join the notes_server network (retries until attached). OThread.setChannel(CHANNEL_HINT); OThread.networkInterfaceUp(); OThread.startJoiner(J01NME, JOIN_TIMEOUT_MS); OThread.start(); serverIp OThread.getLeaderRloc(); // 3) In loop(): parse Serial commands and issue CoAP CRUD calls. printResult(CoapClient.GET(serverIp, notes)); // list printResult(CoapClient.POST(serverIp, notes, body)); // add printResult(CoapClient.PUT(serverIp, notes/1, body)); // update printResult(CoapClient.DELETE(serverIp, notes/1)); // del3.1 栈初始化与 CoAP 客户端配置setup()首先以Serial.begin(115200)打开串口然后依次调用OThread.begin(false)— 初始化 OpenThread 栈参数为是否作为 sleepy end device 运行示例传falseCoapClient.setTimeout(4000)— 阻塞请求最长等待 4000 ms。OThreadCoAPClient是阻塞式客户端每次GET/POST/PUT/DELETE会一直阻塞到收到响应或超时随后返回 CoAP 响应码≥ 0或负的错误码CoapClient.setConfirmable(true)— 请求使用confirmable (CON)消息具备重传可靠性CoapClient.setContentFormat(OT_COAP_FORMAT_JSON)— 将 POST / PUT 请求体声明为 IANA Content-Format 50application/json该常量定义于 OThreadCoAP.h。GET / DELETE 无负载此项被忽略。值得注意的是从 OThreadCoAP.h 的接口注释看对于 confirmable 明文 CoAP 请求栈层的 CON 重传定时会默认对齐到setTimeout()的值使“协议栈”与“sketch 等待”同时放弃低于 1500 ms一次 CON 尝试的 ACK 下限 1000 ms × 1.5 随机因子的超时会被自动抬升除非先调用useDefaultCoapRetransmit()。示例选择 4000 ms恰好避开了这个下限。3.2 Thread Joiner 入网流程入网逻辑封装在joinNetwork()中setup()以while (!joinNetwork())包裹它失败则OThread.stop()后每 3 秒重试一次直到 attach 成功。流程为OThread.setChannel(CHANNEL_HINT)— 把 802.15.4 信道提示设为 15跳过全信道扫描必须与服务器网络信道一致OThread.networkInterfaceUp()— 拉起 IPv6 接口。这一点在 OThread.h 的startJoiner()文档中被明确列为前提IPv6 栈必须已 up且 Thread 协议尚未startOThread.startJoiner(PSKD, JOIN_TIMEOUT_MS)— 同步运行 Joiner 状态机向 Commissioner 出示 PSKd 换取 active dataset。从接口签名看pskd为 632 个 base32 线程字符timeoutMs为阻塞等待上限示例传 60000 ms此外还可选传入 provisioning URL、vendor name/model 等返回OT_ERROR_NONE即成功否则打印Joiner failed: codeOThread.start()— 加入器完成后启用 Thread 协议dataset 已由 Commissioner 配置好waitForAttach(ATTACH_TIMEOUT_MS, ATTACH_DOT_MS)— 每 100 ms 轮询OThread.otGetDeviceRole() OT_ROLE_CHILD最长等 30 s期间每 2 s 打一个点作为进度提示serverIp OThread.getLeaderRloc()— 取得 ThreadLeader RLOCMesh-Local 前缀下的 RLOC 单播地址作为 CoAP 目的地。由于服务器就是网络 Leader客户端无需任何 DNS/UDP 发现手段即可定位 REST 服务——这是本示例的地址解析策略。3.3 串口命令菜单与 CRUD 调用attach 成功后打印命令摘要并进入loop()每次Serial.available()时按\n读一行交给handleCommand()。命令与底层 CoAP 请求的对应关系命令CoAP 操作listGETnotesread idGETnotes/idadd textPOSTnotesbody{text:...}update id textPUTnotes/iddel idDELETEnotes/idhelp打印命令摘要每个请求结果由printResult()统一呈现响应码非负时打印- code status string状态串来自OThreadCoAP::responseCodeToString()若payloadLength() 0再打印 payload负值时打印- error string如 timeout、not attached。update命令在源码中用line.indexOf( , 7)定位第二个空格来拆分 id 与 text缺参数会提示Usage: update id text。JSON 转义注意add/update用简单字符串拼接构造 JSON{text\:\ text \}文本中包含或\时不做转义、会生成非法 JSON——只使用纯文本或在自己的应用代码中自行构造/转义 JSON。4. 预期串口输出一切正常时的完整输出115200波特率 CoAP CRUD — notes client Joining Thread network (Joiner)... Commissioning with PSKd J01NME... Waiting for attach.. Attached as Child. Notes server: fdde:ad00:beef:0:0:ff:fe00:0 Commands: list | read id | add text | update id text | del id Ready. Enter a command. list - 205 2.05 Content [] add buy milk - 201 2.01 Created {id:1,text:buy milk}服务器尚未就绪Joiner 阶段失败、进入 3 秒重试时 CoAP CRUD — notes client Joining Thread network (Joiner)... Commissioning with PSKd J01NME... Joiner failed: 7 Join failed, retry in 3s...若已 attach 但 CRUD 命令失败printResult()打印的- error name对应 OThreadCoAP.h 中定义的一组负错误码例如OT_COAP_ERROR_TIMEOUT-11、OT_COAP_ERROR_NOT_ATTACHED-13可据此区分“网络层超时”与“栈已掉线”两类故障。5. 可定制项与 sdkconfig 要求所有可调常量集中在 notes_client.ino 文件头部常量作用示例取值PSKDPre-Shared Key for Device必须与服务器 Commissioner 一致J01NMECHANNEL_HINT802.15.4 信道提示跳过扫描必须与服务器网络一致15JOIN_TIMEOUT_MSstartJoiner()内等待 Commissioner 的上限60000ATTACH_TIMEOUT_MSattach 轮询上限源码中另有定义30000CoAP 客户端超时在setup()中经CoapClient.setTimeout(4000)毫秒设置。该 sketch 构建所依赖的 IDF 特性sdkconfig在 ci.yml 中声明与 README 的说明一致Feature为什么需要CONFIG_OPENTHREAD_ENABLEDy构建 OpenThread 协议栈CONFIG_SOC_IEEE802154_SUPPORTEDy确保 SoC 带有 802.15.4 射频CONFIG_OPENTHREAD_JOINERy启用 sketch 使用的otJoiner*API对照 OThread.h 的实现可以看到这些宏并非摆设startJoiner()/getJoinerState()等 Joiner 接口被#if CONFIG_OPENTHREAD_JOINER包裹未开启时连声明都不存在[OThreadCoAP.h](https://link.gitcode.com/i/ac4c571366c499c66eb64097628a798c#L20)整体也受SOC_IEEE802154_SUPPORTED CONFIG_OPENTHREAD_ENABLED条件编译保护。而服务器端额外需要CONFIG_OPENTHREAD_COMMISSIONERy用于startCommissioner()/addJoiner()。6. 故障排查启动顺序是第一要务先烧录并启动 notes_server等待Commissioner ready客户端只在setup()中入网——如果它在服务器就绪前就启动了复位该板即可。症状可能原因Joiner failed: code后跟Join failed, retry in 3s...首次 attach 失败见具体数字码sketch 每 3 s 自动重试只有Join failed, retry in 3s...服务器 Commissioner 未就绪——先启动服务器再复位客户端- error timeout服务器不可达、Leader RLOC 错误或服务器已复位客户端还留在旧网络上- error not attachedattach 后栈掉线——复位客户端read 时404 Not Found笔记 id 无效服务器复位后命令全部失败服务器每次启动都initNew()组新网络——复位客户端重新加入客户端早于服务器就绪启动入网只在 setup 执行——在服务器 Commissioner 激活后按reset一个值得留意的边界行为来自组级 README如果客户端在没有任何服务器运行时仍能“加入成功”多半是 NVS 中残留了旧的 active dataset——擦除 NVS或在服务器组建目标网络后复位客户端。7. 延伸阅读CoAP CRUD 组级概览REST API 总表、双板运行步骤与通用故障排查notes_server服务器端Leader Commissioner OThreadCoAPResourceStore的完整实现Thread Commissioning — JoinerNode不带 CoAP 流量的同款 Joiner 流程适合单独验证入网环节OThreadCoAP.hOThreadCoAPClient/OThreadCoAPServer/OThreadCoAPResourceStore的完整接口与语义注释端口 5683/5684、超时对齐、CoAPS 限制等OThread.hJoiner / Commissioner 原生 APIstartJoiner、stopJoiner、startCommissioner、addJoiner声明及前提条件说明。该示例代码采用 Apache License 2.0 许可。【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考