- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
本指南以 tests/net/emcute 测试应用为切入点,系统讲解 RIOT 操作系统中 emCute(OASIS MQTT-SN 协议实现)模块的完整使用与验证流程。你将掌握该测试应用的编译运行方式、全部 shell 命令的语义与参数、测试脚本的自动化验证逻辑,以及 emCute 底层 API 与关键编译配置,并能在自己的 RIOT 节点上复现 MQTT-SN 发布/订阅全流程。
测试应用概览(Overview)
tests/net/emcute/README.md 开门见山:这是一个用于验证 emcute 模块的测试应用(test application),必须与tests/目录下的测试脚本配合运行。它的核心价值在于——不只提供一个可交互的 MQTT-SN 客户端 demo,还配套了一整套可自动执行的协议一致性测试,用于验证 RIOT 节点与 MQTT-SN 网关之间的报文交互是否符合规范。
启动测试的命令只有一条:
BOARD="<your choice> make flash test-as-root"test-as-root是 RIOT 测试框架中需要 root 权限才能执行的目标(本测试需要创建/操作 TAP 设备与 bridge,见下文测试脚本分析),因此它被显式排除在 CI 自动运行之外(见 Makefile 中的TEST_ON_CI_BLACKLIST += all)。
应用结构:一个 emcute 线程 + 一个 shell
测试应用的核心源码位于 tests/net/emcute/main.c。其架构遵循 emCute 的双线程模型:emCute 需要一个独立线程负责收包与发送 PING 报文,所有"用户空间函数"(连接、订阅、发布等)则在另一个(用户)线程中运行,两者通过 thread flags 同步(见 sys/include/net/emcute.h 的 Design Decisions 一节)。
main()的启动流程非常清晰(main.c):
- 打印
success: starting test application; - 以
EMCUTE_PRIO = THREAD_PRIORITY_MAIN - 1优先级创建名为emcute的线程,线程入口_emcute_thread直接调用emcute_run(CONFIG_EMCUTE_DEFAULT_PORT, EMCUTE_ID)——该函数会阻塞所在线程,持续运行 emCute 的报文接收器; - 主线程随后调用
shell_run()启动 RIOT shell,注册 8 个 MQTT-SN 操作命令。
其中EMCUTE_ID被定义为"emcute test app .......",注释戏称其"达到了 client ID 的最大长度",用于测试超长 client ID 的场景(MQTT-SN 1.2 规范中 client ID 上限为 23 字节)。
shell 缓冲区被放大到 512 字节(SHELL_BUFSIZE),注释明确说明是为了"支持超长主题的 sub 命令";同时 Makefile 通过-DSTDIO_UART_RX_BUFSIZE="512"将 UART 接收缓冲同步放大,保证在真实硬件上通过串口输入长命令不丢字节。
编译配置与模块依赖
Makefile 揭示了测试应用所需的模块栈:
- 链路层:native 板卡使用 TAP 设备,其余板卡通过 ethos(
ETHOS_BAUDRATE默认 115200)实现串口隧道,相关逻辑在 Makefile.board.dep 中按板卡分派(native 用netdev_tap,其他板用stdio_ethos); - 网络栈:
auto_init_gnrc_netif+gnrc_ipv6_default+gnrc_netif_single(单网络接口,简化 shell 操作)+sock_udp; - 应用层:
emcute(被测模块)+od(hexdump 输出)+shell/shell_cmds_default+sock_util。
关键编译期配置CONFIG_EMCUTE_TOPIC_MAXLEN在未通过 Kconfig 设置时被定义为249(注释为256 - 7,见 Makefile),用于测试接近上限的主题名(测试脚本中会用到 248 字节的超长主题)。
Shell 命令全解:MQTT-SN 客户端操作手册
测试应用把 emCute API 逐一映射为 shell 命令(命令表见 main.c)。以下是每个命令的完整语义:
con <addr> [<will topic> <will msg>]— 连接网关
解析网关 IPv6 地址(支持sock_udp_name2ep可识别的地址形式),端口缺省时回落到CONFIG_EMCUTE_DEFAULT_PORT(默认 1883)。若提供第 2、3 个参数,则在连接握手阶段同步注册 Last Will 主题与消息——这正是 MQTT-SN CONNECT 流程中WILLTOPICREQ → WILLTOPIC → WILLMSGREQ → WILLMSG → CONNACK的完整序列(见 emcute.c 的实现)。成功输出success: connected to gateway at <addr>。
注意:con使用clean = true建立全新会话(main.c),且若当前已连接,必须先discon才能重新连接(否则 emCute 返回EMCUTE_NOGW)。
discon— 断开连接
调用emcute_discon()发送 DISCONNECT 报文。未连接时返回错误error: not connected to any broker。
reg <topic name>— 向网关注册主题名
调用emcute_reg()从网关换取数字 topic ID。应用内部用_topic_name_find()在最多NUMOFTOPS = 4个槽位中查找或分配空闲槽(main.c),注册成功打印success: registered to topic '<name> [<id>]'。此命令是pub的前置条件——发布前主题必须先注册并获得 ID。
pub <topic name> <data_len> [QoS level]— 发布数据
在已注册主题上发布指定字节数的数据。数据内容是填充字节(memset(_pub_buf, 92, len),即 ASCII\)。可选第三参数为 QoS 级别:0/1/2分别映射到EMCUTE_QOS_0/1/2(_get_qos()实现于 main.c,其他值一律回落 QoS 0)。data_len超出CONFIG_EMCUTE_BUFSIZE(默认 512)时拒绝发送,防止缓冲区溢出。测试脚本用它对 QoS 0/1 进行了 1~503 字节的分级发布验证。
sub <topic name> [QoS level]— 订阅主题
以给定 QoS 订阅主题,并注册回调_on_pub:收到发布时打印
### got publication of <len> bytes for topic '<name>' [<id>] ###主题名超长(>CONFIG_EMCUTE_TOPIC_MAXLEN)时直接报错。底层emcute_sub()要求sub->topic.name与sub->cb必须预先设置(见 emcute.h),测试应用正是按此契约填充emcute_sub_t。
unsub <topic name>— 取消订阅
按主题名查找订阅并调用emcute_unsub(),成功清空槽位并打印success: unsubscribed from '<name>'。
will <will topic name> <will message content>— 更新遗嘱
连接建立后动态更新 Last Will:先emcute_willupd_topic()更新遗嘱主题,再emcute_willupd_msg()更新遗嘱消息。这让测试者无需重新连接即可修改遗嘱内容,对应 MQTT-SN 的 WILLTOPICUPD/WILLMSGUPD 报文。
info— 打印客户端状态
显示当前网关Broker: '[<ipv6>]:<port>'、已注册主题列表(- Topics:,含 topic ID)与当前订阅列表(- Subscriptions:)。测试脚本在收尾阶段正是通过解析该命令的输出来断言注册/订阅状态是否与网关侧一致。
测试脚本:scapy 驱动的 MQTT-SN 协议自动化验证
tests/net/emcute/tests-as-root/01-run.py 是本测试应用的灵魂——它用 Python + Scapy 在主机侧实现了一个完整的 MQTT-SN 网关状态机(MQTTSNServer(Automaton)),与板载节点进行真实报文交互。这也是命令必须以 root 运行的原因:脚本需要创建原始 socket、读取 TAP 接口的主机链路本地地址,并查询 bridge 信息(get_bridge()/get_host_lladdr())。
测试流程(testfunc())要点:
- 共执行 8 组测试用例,覆盖 QoS 0/1 × 发布/订阅、retain 标志、超长主题(248 字节)、以及"订阅前先注册"(
sub_w_reg)等组合; - 数据长度按步长 50 从 0 递增到
DATA_MAX_LEN = 512 - 9(对应 512 字节接收缓冲减去 PUBLISH 头与长度字段的开销),并在data_len == 0时专门注入 3 种故意损坏的报文(length 字段过小/过大/载荷超长),验证节点能正确丢弃而非崩溃; - 网关对每个请求都先发送 2~3 个畸形报文(broken length、garbage payload,见
send_CONNACK、send_REGACK、send_PUBACK_if_required),再发送合法应答——这一设计系统性验证了 emcute.c 中get_len()/set_len()对 length 字段边界(单字节 vs 3 字节扩展长度)的解析健壮性,以及on_publish()中len < pos + 6的畸形包丢弃逻辑; - 网关通过
WAITING(exp_type, tid, mid)状态与receive_wrong_message/receive_unexpected_parameters条件严格比对报文类型、QoS 标志与 message ID,任何不符都会进入UNEXPECTED_MESSAGE_TYPE错误状态并输出诊断; - 订阅场景下网关还会反过来向节点连续推送 PUBLISH(含 QoS 1 时的 PUBACK 握手),并断言节点回调打印的字节数完全一致。
整个测试以info命令输出的逐行断言(Broker:、- Topics、- Subscriptions)收尾,最终reboot重启节点、打印SUCCESS。
底层支撑:emCute API 与可调配置
测试应用调用的每个 shell 命令背后都是 emcute.h 中声明的公开 API,其返回值枚举贯穿全应用的错误处理:
| 返回值 | 含义 |
|---|---|
EMCUTE_OK | 操作成功 |
EMCUTE_NOGW | 未连接到网关 |
EMCUTE_REJECT | 操作被 broker 拒绝 |
EMCUTE_OVERFLOW | 缓冲区空间不足 |
EMCUTE_TIMEOUT | 等待应答超时 |
EMCUTE_NOTSUP | 不支持的特性 |
编译期配置(均可通过 Kconfig 或 CFLAGS 覆盖):
CONFIG_EMCUTE_DEFAULT_PORT(默认 1883):UDP 监听/源端口;CONFIG_EMCUTE_BUFSIZE(默认 512):收发缓冲区各用一份(总量为两倍),16/8 位平台须小于 32768;pub的data_len上限即由此决定;CONFIG_EMCUTE_TOPIC_MAXLEN(默认 196,本测试改为 249):主题名最大长度,必须小于 256−6 且小于 BUFSIZE−6;CONFIG_EMCUTE_KEEPALIVE(默认 360 秒):CONNECT 报文告知网关的保活间隔;CONFIG_EMCUTE_T_RETRY(默认 15 秒)与CONFIG_EMCUTE_N_RETRY(默认 3 次):同步请求的重传定时器与最大重传次数,对应 MQTT-SN 规范 6.13 节的 Tretry/Nretry。
syncsend()(emcute.c)展示了其同步语义的实现:发送请求 → 启动 xtimer →thread_flags_wait_any()阻塞等待响应或超时标志 → 超时则重发,直至达到N_RETRY上限。这也是con/reg/sub等命令在无应答时返回EMCUTE_TIMEOUT的来源。
此外,从 emcute.h 的实现状态声明可以明确当前版本的能力边界:支持连接/断开、Last Will 注册与更新、主题注册、订阅/退订、周期 PINGREQ 与重传处理;而网关发现(ADVERTISE/GWINFO/SEARCHGW)、QoS 2 与 QoS −1、预定义/短主题 ID、睡眠模式(带 duration 的 DISCONNECT)等仍列为 TODO。写作实际应用时需避开这些未实现特性。
从测试到实战:以 emcute_mqttsn 示例为参照
若想把这套测试知识迁移到真实应用,可对照 examples/networking/mqtt/emcute_mqttsn 示例(测试应用EMCUTE_ID的注释也指向它)。两者命令集几乎一致,但示例版提供了一整套主机侧环境搭建指引:
- 搭建网关:编译 Eclipse Mosquitto RSMB(
mosquitto.rsmb),配置文件同时监听 MQTT-SN(UDP 1885)与 MQTT(TCP 1886)并开启 IPv6:trace_output protocol listener 1885 INADDR_ANY mqtts ipv6 true listener 1886 INADDR_ANY ipv6 true随后
./broker_mqtts config.conf启动; - 配置 native 网络:由于 RSMB 无法处理链路本地地址,需用
tapsetup脚本创建tap/tapbr,给tapbr0配置站点全局前缀(如sudo ip a a fec0:affe::1/64 dev tapbr0),再在 RIOT 实例内用ifconfig 5 add fec0:affe::99配置同前缀地址; - 收发消息:
con fec0:affe::1 1885连接,sub hello/world订阅,pub hello/world "One more beer, please."发布。
示例 FAQ 还记录了 RSMB 的两个已知坑(IPv6 链路本地地址应答异常、重复主题注册后重新分配 topic ID),在实际联调多节点时值得参考。
小结
tests/net/emcute是一个"麻雀虽小、五脏俱全"的协议测试样本:以 8 个 shell 命令覆盖 emCute 全部已实现能力,以 scapy 状态机网关对 CONNECT/REGISTER/PUBLISH/SUBSCRIBE 等核心报文做严格一致性校验,并通过畸形报文注入检验实现的健壮性。理解它,等于同时掌握了 RIOT 上 MQTT-SN 客户端的用户视角操作、开发者视角 API 契约,以及测试者视角的协议验证方法论。
- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
相关推荐
RIOT 外设 ADC 测试应用解析:从编译运行到源码级原理
RIOT 外设 ADC 测试应用解析:从编译运行到源码级原理 导读 本文以 RIOT OS 中的 tests/periph/adc 测试应用为线索,完整讲解如何
物联网嵌入式操作系统实时系统RIOT 平台 emCute(MQTT-SN)实战指南:从 Mosquitto RSMB 网关到 native 节点发布订阅
RIOT 平台 emCute(MQTT SN)实战指南:从 Mosquitto RSMB 网关到 native 节点发布订阅 本篇技术指南以 RIOT 官方示例
物联网嵌入式操作系统实时系统RIOT 中 MAX313xx RTC 驱动测试应用全解析:从 shell 命令到源码级验证
RIOT 中 MAX313xx RTC 驱动测试应用全解析:从 shell 命令到源码级验证 本指南围绕 RIOT 仓库中的 tests/drivers/max
物联网嵌入式操作系统实时系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考