ARTICLE DETAIL

资讯详情

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

IoT-For-Beginners 实战:将 Wio Terminal 土壤湿度传感器从公共 MQTT 迁移到 Azure IoT Hub

IoT-For-Beginners 实战:将 Wio Terminal 土壤湿度传感器从公共 MQTT 迁移到 Azure IoT Hub IoT-For-Beginners 实战将 Wio Terminal 土壤湿度传感器从公共 MQTT 迁移到 Azure IoT Hub【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本篇技术指南以 Azure IoT for Beginners 课程《Migrate your plant to the cloud》为核心讲解如何把上一课中通过公共 MQTT 代理收发数据的 Wio Terminal 土壤湿度传感器程序完整迁移为基于 Azure IoT Hub 的云连接方案包括 PlatformIO 依赖与编译标志改造、设备连接字符串配置、NTP 时间同步、遥测上报D2C 消息以及直接方法Direct Method命令处理。读完本篇你将掌握用 Azure IoT C SDKArduino 端口在 Wio Terminal 上建立 IoT Hub 连接的完整代码骨架并能用 Azure CLI 验证遥测与远程控制继电器。背景为什么要把植物从公共 MQTT 代理迁到云端在上一课中你的 IoT 设备通过公共 MQTT 代理broker发送土壤湿度遥测、接收继电器控制命令这非常适合演示原理但在真实商业场景中有明显短板免费公共代理不保证可用性、可能随时下线消息是公开的任何人都能监听遥测或向你的硬件发送命令吞吐量只面向少量测试消息并且无法获知有哪些设备已连接。课程 README 将这些问题归纳为可靠性、安全性、性能与可发现性四个维度详见 README。云厂商提供的 IoT 服务本文使用微软 Azure IoT Hub专门解决这些问题有大规模可靠性投入、内置设备级安全机制未注册设备无法连接、支持每天数百万条消息的吞吐并且提供免费层F1 SKU每天 8000 条消息供学习和开发使用。IoT Hub 与设备之间的通信不像 MQTT 那样依赖多个自定义 topic而是提供了几种定义好的消息模型本文会用到其中两种设备到云D2C消息设备发往 IoT Hub 的遥测数据应用代码可以从中读取直接方法请求Direct Method应用代码通过 IoT Hub 请求设备执行某个动作如打开/关闭继电器且必须携带响应让调用方知道是否执行成功。底层实现上IoT Hub 与设备之间的通信仍可基于 MQTT、HTTPS 或 AMQP。本文的 Arduino 示例显式指定使用 MQTT 协议。前置条件与硬件基础本部分教程面向Wio TerminalSeeed Studio开发环境为 VS Code PlatformIO。在开始之前需要先完成课程 README 中描述的前置工作已注册 Azure 订阅学生可用 Azure for Students其他人用 Azure 免费订阅并安装 Azure CLI、添加azure-iot扩展、执行az login登录已创建资源组soil-moisture-sensor与 IoT Hub 实例az iot hub create--sku F1免费层、--partition-count 2Hub 名称全局唯一已在 Hub 中注册设备soil-moisture-sensor并通过az iot hub device-identity connection-string show取得该设备专属的连接字符串含 Hub URL、设备 ID 与密钥。关键命令摘要如下详见 README# 创建资源组 az group create --name soil-moisture-sensor --location location # 创建 IoT Hub免费层每天 8000 条消息 az iot hub create --resource-group soil-moisture-sensor \ --sku F1 \ --partition-count 2 \ --name hub_name # 注册设备 az iot hub device-identity create --device-id soil-moisture-sensor --hub-name hub_name # 获取设备连接字符串务必妥善保存连接字符串含密钥不能泄露 az iot hub device-identity connection-string show \ --device-id soil-moisture-sensor --output table --hub-name hub_name⚠️ 连接字符串相当于设备身份的凭据应像密码一样妥善保管切勿提交到公开仓库。安全细节会在后续课程中进一步讲解。第一步改造 platformio.ini —— 移除 MQTT、引入 Azure IoT 依赖在 VS Code 中打开soil-moisture-sensor项目编辑 platformio.ini。移除knolleary/PubSubClient库依赖——它用于连接公共 MQTT 代理连接 IoT Hub 不再需要。新增以下库依赖seeed-studio/Seeed Arduino RTC 2.0.0 arduino-libraries/AzureIoTHub 1.6.0 azure/AzureIoTUtility 1.6.1 azure/AzureIoTProtocol_MQTT 1.6.0 azure/AzureIoTProtocol_HTTP 1.6.0 azure/AzureIoTSocket_WiFi 1.0.2各依赖的作用Seeed Arduino RTC提供与 Wio Terminal 板上实时时钟RTC交互的代码用于在本地跟踪时间。从源码看它配合samd/NTPClientAz.h一起使用见下文ntp.hAzureIoTHubarduino-libraries 维护Arduino 平台上的 Azure IoT Hub 客户端提供IoTHub_Init、IoTHubDeviceClient_LL_CreateFromConnectionString等核心 APIAzureIoTUtilitySDK 底层工具库AzureIoTProtocol_MQTT/AzureIoTProtocol_HTTP协议适配层本文实际使用 MQTTAzureIoTSocket_WiFi基于 WiFi 的 socket 实现承载网络传输。在文件末尾追加编译标志build_flags -DDONT_USE_UPLOADTOBLOB该标志会在编译 Arduino 版 IoT Hub 代码时被预处理器使用用于禁用上传到 Blob 存储的相关功能是编译通过的必要条件。仓库中的完整platformio.ini还保留了bblanchon/ArduinoJson 6.17.3构造遥测 JSON、seeed-studio/Seeed Arduino rpcWiFi 1.0.5WiFi 连接以及板级支持库这些在迁移后继续沿用不要误删。第二步配置 config.h —— 用连接字符串替换 MQTT 设置打开 config.h删除所有 MQTT 设置如 broker 地址、端口、topic、用户名密码等仅保留 WiFi 凭据与新的 IoT Hub 设置#pragma once #include string using namespace std; // WiFi credentials const char *SSID SSID; const char *PASSWORD PASSWORD; // IoT Hub settings const char *CONNECTION_STRING CONNECTION STRING;将CONNECTION STRING替换为你在前置步骤中用 Azure CLI 获取并保存的设备连接字符串。该字符串会作为设备身份凭据在connectIoTHub()中传给 SDK。第三步NTP 时间同步 —— 新建 ntp.hIoT Hub 的 SAS 认证使用基于时间的令牌因此设备必须知道当前时间。与 Windows/macOS/Linux 等操作系统不同微控制器不会自动通过网络同步时间所以需要显式地从 NTP 服务器获取当前时间并写入 Wio Terminal 的实时时钟RTC只要设备不断电之后就能随时取到正确时间。新建ntp.h内容与仓库中的 ntp.h 一致#pragma once #include DateTime.h #include time.h #include samd/NTPClientAz.h #include sys/time.h static void initTime() { WiFiUDP _udp; time_t epochTime (time_t)-1; NTPClientAz ntpClient; ntpClient.begin(); while (true) { epochTime ntpClient.getEpochTime(0.pool.ntp.org); if (epochTime (time_t)-1) { Serial.println(Fetching NTP epoch time failed! Waiting 2 seconds to retry.); delay(2000); } else { Serial.print(Fetched NTP epoch time is: ); char buff[32]; sprintf(buff, %.f, difftime(epochTime, (time_t)0)); Serial.println(buff); break; } } ntpClient.end(); struct timeval tv; tv.tv_sec epochTime; tv.tv_usec 0; settimeofday(tv, NULL); }这段代码定义initTime()函数通过NTPClientAz客户端向公共 NTP 服务器0.pool.ntp.org请求 epoch 时间失败则打印错误并每 2 秒重试成功后把返回的 UNIX 时间戳自 1970-01-01 00:00:00 UTC 起的秒数打印到串口并通过settimeofday写入系统时钟底层对应 Wio Terminal 的 RTC。串口输出的Fetched NTP epoch time is: 1619983687就是这类 epoch 时间戳可借助在线工具转换为可读日期。第四步重构 main.cpp —— 移除 MQTT接入 IoT Hub打开 main.cpp。4.1 清理旧 MQTT 代码删除所有与 MQTT 相关的代码PubSubClient.h头文件、PubSubClient变量声明、reconnectMQTTClient与createMQTTClient方法以及所有相关调用。清理后该文件只保留三类职责连接 WiFi、读取土壤湿度、构造 JSON 文档。4.2 添加 IoT Hub 头文件与全局句柄在文件顶部#include区域加入 IoT Hub 库头文件与时间设置头文件#include AzureIoTHub.h #include AzureIoTProtocol_MQTT.h #include iothubtransportmqtt.h #include ntp.h紧接在 include 指令下方声明连接句柄IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle;IOTHUB_DEVICE_CLIENT_LL_HANDLE是 SDK 提供的一个不透明句柄类型代表一条到 IoT Hub 的设备端连接后续的发送遥测、处理直接方法、驱动消息循环都依赖它。4.3 连接状态回调在句柄声明下方添加连接状态回调函数static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context) { if (result IOTHUB_CLIENT_CONNECTION_AUTHENTICATED) { Serial.println(The device client is connected to iothub); } else { Serial.println(The device client has been disconnected); } }当设备与 IoT Hub 的连接状态发生变化建立连接、断开连接、认证成功/失败时SDK 会调用此回调状态信息输出到串口方便调试。4.4 连接 IoT Hub添加connectIoTHub()函数void connectIoTHub() { IoTHub_Init(); _device_ll_handle IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol); if (_device_ll_handle NULL) { Serial.println(Failure creating Iothub device. Hint: Check your connection string.); return; } IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL); }执行流程IoTHub_Init()初始化 SDK 的全局状态IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol)使用config.h中的连接字符串创建一个基于 MQTT 协议的设备客户端连接。注意这里传入的是MQTT_Protocol由AzureIoTProtocol_MQTT.h提供若创建失败返回NULL向串口打印提示Failure creating Iothub device. Hint: Check your connection string.——如果你在串口看到这句话最可能的原因是连接字符串复制错误或已失效请检查config.h成功后将连接状态回调注册到客户端此后 SDK 会通过它通知连接状态变化。4.5 在 setup 中编排初始化顺序在setup()中按顺序完成 WiFi 连接、NTP 时间同步、IoT Hub 连接void setup() { Serial.begin(9600); while (!Serial) ; // Wait for Serial to be ready delay(1000); pinMode(A0, INPUT); pinMode(PIN_WIRE_SCL, OUTPUT); connectWiFi(); initTime(); // 先取得当前时间SAS 令牌依赖它 connectIoTHub(); delay(2000); }其中initTime()调用应放在setup函数末尾源码中紧随connectWiFi()之后、connectIoTHub()之前用于设置当前时间随后调用connectIoTHub()。connectWiFi()沿用上一课的循环重试实现见源码 main.cpp在WiFi.status() ! WL_CONNECTED期间不断打印Connecting to WiFi..并重试。4.6 编译上传并观察串口输出编译并上传到 Wio Terminal 后串口监视器应输出类似以下内容Connecting to WiFi.. Connected! Fetched NTP epoch time is: 1619983687 Sending telemetry {soil_moisture:391} The device client is connected to iothub从输出可以看到先连接 WiFi再通过 NTP 获取时间随后设备客户端成功连接 IoT Hub。设备连接可能需要数秒因此在连接完成前你可能会先看到土壤湿度数据行。第五步发送遥测 —— 用 SendEventAsync 代替 MQTT publish设备连接成功后就可以把土壤湿度遥测发给 IoT Hub替代原来发给 MQTT 代理的 publish 操作。在setup函数上方添加sendTelemetry()void sendTelemetry(const char *telemetry) { IOTHUB_MESSAGE_HANDLE message_handle IoTHubMessage_CreateFromString(telemetry); IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL); IoTHubMessage_Destroy(message_handle); }执行流程IoTHubMessage_CreateFromString(telemetry)把字符串形式的遥测负载包装成 SDK 消息对象IOTHUB_MESSAGE_HANDLEIoTHubDeviceClient_LL_SendEventAsync异步地把消息作为D2C 事件发送到 IoT HubIoTHubMessage_Destroy释放消息对象避免内存泄漏。在loop()中紧接向串口打印遥测的代码之后调用它void loop() { int soil_moisture analogRead(A0); DynamicJsonDocument doc(1024); doc[soil_moisture] soil_moisture; string telemetry; serializeJson(doc, telemetry); Serial.print(Sending telemetry ); Serial.println(telemetry.c_str()); sendTelemetry(telemetry.c_str()); work_delay(10000); }遥测 JSON 由ArduinoJson构造字段为soil_moisture值来自analogRead(A0)Wio Terminal 的 A0 引脚接土壤湿度传感器例如{soil_moisture:391}。底层说明IoTHubDeviceClient_LL_SendEventAsync是 SDK 的 LLLow LevelAPI采用事件驱动模型消息并不是立即发出而是由IoTHubDeviceClient_LL_DoWork驱动底层协程/线程真正推送。这也解释了下一步为什么需要一个持续调用DoWork的循环。第六步处理命令 —— 直接方法回调控制继电器服务端代码要远程控制继电器需要以**直接方法请求Direct Method**的形式下发命令设备侧必须注册对应的回调。6.1 直接方法回调在connectIoTHub函数之前添加回调函数int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback) { Serial.printf(Direct method received %s\r\n, method_name); if (strcmp(method_name, relay_on) 0) { digitalWrite(PIN_WIRE_SCL, HIGH); } else if (strcmp(method_name, relay_off) 0) { digitalWrite(PIN_WIRE_SCL, LOW); } char resultBuff[16]; sprintf(resultBuff, {\Result\:\\}); *response_size strlen(resultBuff); *response (unsigned char *)malloc(*response_size); memcpy(*response, resultBuff, *response_size); return IOTHUB_CLIENT_OK; }要点说明SDK 收到直接方法请求时调用此回调被请求的方法名放在method_name参数中函数先向串口打印方法名然后根据方法名控制继电器relay_on把PIN_WIRE_SCL拉高digitalWrite(..., HIGH)relay_off拉低——这也是硬件接线中继电器信号线所接的引脚直接方法请求要求应答应答由两部分组成文本响应与返回码。代码构造{Result:}形式的 JSON 响应字符串把长度写入response_size用malloc分配内存并把内容复制到response指针最后返回IOTHUB_CLIENT_OK表示方法已被正确处理提示也可以只实现一个直接方法把继电器期望状态放在请求的payload中即回调的payload参数进行解析从而把两个方法合并为一个。6.2 注册回调在connectIoTHub()函数末尾设置连接状态回调之后注册方法回调IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);6.3 优化消息循环work_delayloop()必须周期性调用IoTHubDeviceClient_LL_DoWork来驱动消息收发否则从 Hub 发来的直接方法请求无法被及时处理。原始代码中 10 秒的delay(10000)会阻塞整条循环导致直接方法最长 10 秒才被处理一次。解决方式是把 10 秒延迟拆成多次 100ms 的短延迟并在每次短延迟前调用DoWork。在loop上方添加void work_delay(int delay_time) { int current 0; do { IoTHubDeviceClient_LL_DoWork(_device_ll_handle); delay(100); current 100; } while (current delay_time); }然后在loop()中删除原本在函数顶部的IoTHubDeviceClient_LL_DoWork(_device_ll_handle);调用并把delay(10000)替换为work_delay(10000);这样设备在等待 10 秒的过程中最多 100ms 就会调用一次DoWork直接方法请求的响应延迟被压缩到 100ms 级别。第七步用 Azure CLI 验证遥测与远程控制完成代码修改、编译上传后可以回到命令行验证整套链路是否打通以下命令均来自 README。监控遥测事件保持设备运行并发送遥测在终端执行az iot hub monitor-events --hub-name hub_namehub_name替换为你的 IoT Hub 名称。控制台会实时打印设备发送的 D2C 消息payload与设备端发送的 JSON 一致Starting event monitor, use ctrl-c to stop... { event: { origin: soil-moisture-sensor, module: , interface: , component: , payload: {\soil_moisture\: 376} } }消息还附带自动生成的属性annotations如设备 ID、SAS 认证方式、入队时间UNIX 时间戳等可用--properties anno查看。监控完毕后按CtrlC退出。远程调用直接方法执行以下命令触发relay_onaz iot hub invoke-device-method --device-id soil-moisture-sensor \ --method-name relay_on \ --method-payload {} \ --hub-name hub_name--method-name指定要调用的方法名对应设备端strcmp(method_name, relay_on)的分支--method-payload以 JSON 形式携带方法参数。调用后继电器应打开设备串口输出Direct method received - relay_on重复执行并把--method-name改为relay_off继电器关闭。对于本项目使用的免费层 IoT Hub每天 8000 条消息当前代码每 10 秒发送一条遥测。课程挑战环节留了一个思考题每 10 秒一条消息对应每天多少条消息是否超出了免费层配额如何调整采样频率以兼顾及时性与配额这些问题可作为后续优化的练习。源码对照与进一步探索本文所有代码均可在仓库中找到可运行的完整版本完整工程目录code/wio-terminal其中 platformio.ini 为构建配置src/main.cpp 为主程序src/config.h 为 WiFi 与连接字符串配置src/ntp.h 为 NTP 时间同步实现同一课程的 Raspberry Pi / 虚拟设备方案见 single-board-computer-connect-hub.md其 Python 实现 app.py 使用azure.iot.device库通过IoTHubDeviceClient.create_from_connection_string、send_message与on_method_request_received实现与本文 Arduino 代码完全对等的功能遥测上报 relay_on/relay_off直接方法是理解两种语言实现差异的绝佳对照课程整体脉络、云概念讲解、Hub 创建与 CLI 命令见 README。完成本文的迁移后你的土壤湿度传感器程序已经稳定地接入 Azure IoT Hub遥测上报与继电器远程控制全部经由云完成——这是从“能跑通”的本地 MQTT 演示迈向可扩展、安全、可靠的云原生 IoT 应用的关键一步。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表