ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IoT-For-Beginners 第 4 课实战:Wio Terminal 通过 MQTT 订阅云端命令实现夜灯 LED 远程控制

IoT-For-Beginners 第 4 课实战:Wio Terminal 通过 MQTT 订阅云端命令实现夜灯 LED 远程控制 IoT-For-Beginners 第 4 课实战Wio Terminal 通过 MQTT 订阅云端命令实现夜灯 LED 远程控制【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本文基于 IoT-For-Beginners 课程 1-getting-started 的第 4 课Connect your device to the Internet中面向 Seeed Studio Wio Terminal 的官方教程 wio-terminal-commands.md详解如何让夜灯Nightlight设备从“只上报”升级为“可被远程控制”在设备端订阅 MQTT 命令主题、编写消息回调、解析 JSON 命令并驱动 LED。读完并跟着操作后你将完整掌握 PubSubClient 的订阅与回调机制、MQTT 消息 payload 的字节数组处理、ArduinoJson 的命令解析以及“遥测上行、命令下行”这一物联网核心闭环在 Arduino 工程中的完整落地方式。一、背景夜灯项目走到哪一步了在课程 4 的前置小节中你已经完成了两件事按 wio-terminal-mqtt.md 的方法用 PubSubClient 库把 Wio Terminal 连上了公共 MQTT 测试代理 test.mosquitto.org1883 端口明文仅用于学习请勿承载敏感数据按 wio-terminal-telemetry.md 的方法在loop()中每 2 秒读取一次WIO_LIGHT模拟引脚的光照值以 JSON 形式{light: 数值}发布到ID/telemetry主题本地运行的 Python 服务器代码Paho-MQTT订阅该主题并打印收到的遥测。本节要补齐的是闭环的下半部分服务器端根据遥测做出决策再通过 MQTT 把命令发回设备设备收到命令后点亮或熄灭 LED。整个链路即课程 README 中描述的“光照值被读取并检查、LED 被远程受控”的作业流程对应代码位于 code-commands/wio-terminal 目录。二、定义命令主题 SERVER_COMMAND_TOPIC第一步是在配置头文件中声明设备要订阅的命令主题。按照教程在 config.h 文件末尾添加const string SERVER_COMMAND_TOPIC ID /commands;SERVER_COMMAND_TOPIC是设备接收 LED 开/关命令的主题。结合仓库中该文件的完整内容可以看到项目把所有 MQTT 相关配置集中在此文件中统一管理#pragma once #include string using namespace std; // WiFi credentials const char *SSID SSID; const char *PASSWORD PASSWORD; // MQTT settings const string ID ID; const string BROKER test.mosquitto.org; const string CLIENT_NAME ID nightlight_client; const string CLIENT_TELEMETRY_TOPIC ID /telemetry; const string SERVER_COMMAND_TOPIC ID /commands;几个关键约定SSID、PASSWORD、ID需替换为你自己的值ID是设备唯一标识前后缀拼接出主题名与服务器端 server/app.py 中id /commands、id /telemetry的约定必须完全一致否则设备订阅不到服务器发布的命令CLIENT_NAMEIDnightlight_client与服务器端的IDnightlight_server相互区分避免两个客户端复用同一 MQTT 客户端 ID 导致互踢BROKER固定为test.mosquitto.org这是 Mosquitto 官方提供的公共测试代理无需账号即可连接适合验证客户端逻辑但它是公开且无加密的任何人都可能监听你发布的内容。三、在重连函数中订阅命令主题MQTT 连接可能在运行中因网络波动而断开。本项目采用的模式是setup()中先建立一次连接此后loop()每轮都会调用reconnectMQTTClient()检查连接状态断开则以CLIENT_NAME重新client.connect()失败时打印client.state()返回码并等待 5 秒重试。订阅动作必须放在连接成功之后执行因为订阅是对 broker 的请求连接不存在时无法生效。按教程在 main.cpp 的reconnectMQTTClient函数中、client.connect成功后添加client.subscribe(SERVER_COMMAND_TOPIC.c_str());仓库中该函数的完整实现如下订阅语句位于if (client.connect(...))分支内void reconnectMQTTClient() { while (!client.connected()) { Serial.print(Attempting MQTT connection...); if (client.connect(CLIENT_NAME.c_str())) { Serial.println(connected); client.subscribe(SERVER_COMMAND_TOPIC.c_str()); } else { Serial.print(Retying in 5 seconds - failed, rc); Serial.println(client.state()); delay(5000); } } }这里用c_str()是因为 PubSubClient 的subscribe接受 C 风格字符串const char*而SERVER_COMMAND_TOPIC是 Cstd::string。这种“每次重连都重新订阅”的写法值得注意MQTT 的订阅状态保存在 broker 侧会话非持久clean session时重连后需要重新发起订阅放在这里可以确保任何一次重连后订阅关系都被重建。四、实现消息回调 clientCallback从字节数组到 LED 动作这是本节的核心代码。按教程在reconnectMQTTClient函数下方添加void clientCallback(char *topic, uint8_t *payload, unsigned int length) { char buff[length 1]; for (int i 0; i length; i) { buff[i] (char)payload[i]; } buff[length] \0; Serial.print(Message received:); Serial.println(buff); DynamicJsonDocument doc(1024); deserializeJson(doc, buff); JsonObject obj doc.asJsonObject(); bool led_on obj[led_on]; if (led_on) digitalWrite(D0, HIGH); else digitalWrite(D0, LOW); }逐段拆解其技术要点参数含义topic是消息所到达的主题名payload是消息体的原始字节length是字节数。PubSubClient 约定回调签名即为(char*, uint8_t*, unsigned int)。payload 的字节数组转换MQTT 消息体在网络层只是字节序列回调收到的是uint8_t*无符号 8 位整数数组不能直接当 C 字符串使用。代码用 VLAchar buff[length 1]逐字节拷贝并追加\0结束符使其成为可被Serial和 ArduinoJson 解析的文本。这也是教程特别强调的一点。JSON 解析服务器发布的命令体是 JSON 对象{led_on: true/false}。代码使用 ArduinoJson 库DynamicJsonDocument doc(1024)容量 1024 字节对这种小命令体绰绰有余执行deserializeJson再用doc.asJsonObject()取出对象读取led_on布尔属性。驱动执行器Wio Terminal 板载一颗 LED 接在D0引脚setup()中已pinMode(D0, OUTPUT)声明为输出。led_on为 true 时digitalWrite(D0, HIGH)点亮false 时熄灭。从源码结构看clientCallback的定义位置全局作用域、reconnectMQTTClient之前不影响运行因为回调是运行时由setCallback注册的函数指针而非编译期静态注册关键是它必须与PubSubClient::Callback的签名严格匹配。五、在 createMQTTClient 中注册回调定义好回调后还需把它“挂”到客户端上。按教程在 main.cpp 的createMQTTClient函数中添加client.setCallback(clientCallback);仓库中该函数的完整实现void createMQTTClient() { client.setServer(BROKER.c_str(), 1883); client.setCallback(clientCallback); reconnectMQTTClient(); }三步分别是指定 broker 地址与 1883 端口、注册消息回调、执行首次连接连接成功后会触发第二节的订阅。教程中还有一条重要提示clientCallback会被所有已订阅主题的消息触发。本项目当前只订阅了ID/commands所以回调里可以直接按命令格式解析如果日后监听多个主题应利用回调的第一个参数topic区分消息来源再分支处理而不是假设所有进来的消息都是同一种结构。六、上传、验证与预期现象用 PlatformIO 将代码上传到 Wio Terminal。项目的 platformio.ini 声明了开发板与全部依赖版本可据此核对本地环境是否一致[env:seeed_wio_terminal] platform atmelsam board seeed_wio_terminal framework arduino lib_deps knolleary/PubSubClient 2.8 bblanchon/ArduinoJson 6.17.3 seeed-studio/Seeed Arduino rpcWiFi 1.0.5 seeed-studio/Seeed Arduino FS 2.1.1 seeed-studio/Seeed Arduino SFUD 2.0.2 seeed-studio/Seeed Arduino rpcUnified 2.1.3 seeed-studio/Seeed_Arduino_mbedtls 3.0.1其中与命令功能直接相关的是 PubSubClient 2.8subscribe/setCallback/ 回调签名均按此版本 API与 ArduinoJson 6.17.3DynamicJsonDocument、deserializeJson、doc.asJsonObject()是 v6 的写法。打开 Serial Monitor9600 波特率与setup()中Serial.begin(9600)一致先确认设备正常连接 WiFi、连上 broker 并每 2 秒发出遥测。在本地运行服务器代码 server/app.py。该代码订阅ID/telemetry每收到一条遥测即按阈值light 300生成命令并发布到ID/commandsdef handle_telemetry(client, userdata, message): payload json.loads(message.payload.decode()) print(Message received:, payload) command { led_on : payload[light] 300 } print(Sending message:, command) client.publish(server_command_topic, json.dumps(command))改变设备检测到的光照物理传感器遮挡/照射或虚拟设备模拟预期现象是设备串口持续出现Message received:{led_on: true}或{led_on: false}D0 板载 LED 随之亮灭服务器终端则交替打印Message received: {light: ...}与Sending message: {led_on: ...}。教程指出你会“在终端看到收到的消息与发出的命令并看到 LED 随光照水平亮灭”这正是遥测—决策—命令闭环成立的标志。完整的设备端实现可对照 code-commands/wio-terminal 目录下的 src/main.cpp若使用虚拟 IoT 设备替代 Wio Terminal等价的 Python 客户端实现见 code-commands/virtual-device 中的 nightlight/app.py其handle_command回调与上文 C 回调的处理逻辑一一对应解析led_on后led.on()/off()可互为参考。七、源码级纵览命令回调是如何被触发的把 main.cpp 的完整调用链串起来可以更清楚地理解回调触发时机setup() ├─ pinMode(WIO_LIGHT, INPUT) / pinMode(D0, OUTPUT) // 传感器输入、LED 输出 ├─ connectWiFi() // 阻塞直到 WL_CONNECTED └─ createMQTTClient() ├─ client.setServer(BROKER, 1883) ├─ client.setCallback(clientCallback) // 注册回调本节新增 └─ reconnectMQTTClient() └─ client.connect(CLIENT_NAME) 成功后 └─ client.subscribe(SERVER_COMMAND_TOPIC) // 订阅命令主题本节新增 loop() // 每轮循环 ├─ reconnectMQTTClient() // 断线则重连并重新订阅 ├─ client.loop() // 处理 keepalive、收发 MQTT 包 │ └─ 收到订阅主题的消息时触发 clientCallback │ └─ 解析 led_on → digitalWrite(D0, HIGH/LOW) ├─ analogRead(WIO_LIGHT) → JSON 序列化 ├─ client.publish(CLIENT_TELEMETRY_TOPIC, ...) // 遥测上行 └─ delay(2000)两个值得注意的实现细节client.loop()是回调的生命线。PubSubClient 是“非阻塞网络 阻塞业务循环”的混合模型网络包收发、心跳以及已接收消息的回调分发都发生在client.loop()内部。从源码结构看loop()里既发遥测又处理入站命令因此遥测频率delay(2000)间接决定了命令的最大处理延迟——命令到达后必须等下一次client.loop()才会执行回调。订阅在重连路径中重复执行。reconnectMQTTClient内的while (!client.connected())循环意味着连接建立或恢复的每一次都会重新subscribe保证 broker 侧订阅关系始终存在而createMQTTClient只调用一次在setup()中因此不能指望它在断线重连后再次生效。D0 的双重角色D0同时是板载 LED 引脚。setup()中的pinMode(D0, OUTPUT)使该引脚在收到命令前就默认为低电平LED 熄灭命令只是改变其电平逻辑上属于“状态覆盖”而非“动作触发”——这也解释了为何最新一条命令总是直接决定 LED 当前状态天然容忍命令乱序或丢失旧命令被新状态覆盖。八、设计要点小结与延伸主题命名即 API 契约设备端ID /commands、ID /telemetry与服务器端必须严格一致。课程 README 还提示如果要对特定设备单独下发命令可采用/commands/device1这类按设备 ID 细粒度的主题让每台设备只收听发给自己的消息而不是所有设备共享一个命令主题。回调是“多主题共用”的本例只订阅一个主题回调里可硬编码解析命令扩展到多主题时必须用topic参数路由否则结构假设会失效。payload 永远先当字节处理uint8_t* length转成以\0结尾的char数组是所有 Arduino 侧 MQTT 消息处理的标准前置步骤JSON、CSV 等文本格式都依赖这一步。命令的语义决定断线策略本例led_on是幂等状态指令最新一条覆盖之前所有因此 MQTT 无队列、离线期间命令丢失是可接受的若命令需要严格顺序执行如机械臂“抬起—闭合”则需在设计上引入序列号、保留消息retained flag或重放机制——这些取舍课程 4 的 README 在 Loss of connectivity 一节有专门讨论值得延伸阅读。安全边界test.mosquitto.org 为明文公共 broker仅适合学习。生产环境应自建或选用带 TLS 与鉴权的 broker项目依赖中的Seeed_Arduino_mbedtls库即为 TLS 支持预留了基础。参考资料仓库内路径教程原文wio-terminal-commands.md英文原版、阿拉伯语译本 translations/ar/1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md设备端代码src/main.cpp、src/config.h、platformio.ini服务器端代码code-commands/server/app.py虚拟设备等价实现code-commands/virtual-device/nightlight/app.py课程总览MQTT 原理、QoS、断线处理讨论1-getting-started/lessons/4-connect-internet/README.md【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表