ARTICLE DETAIL

资讯详情

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

IoT-For-Beginners 农场项目实战:将 Wio Terminal 土壤湿度传感器接入 Azure IoT Hub

IoT-For-Beginners 农场项目实战:将 Wio Terminal 土壤湿度传感器接入 Azure IoT Hub IoT-For-Beginners 农场项目实战将 Wio Terminal 土壤湿度传感器接入 Azure IoT Hub【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本文基于 IoT-For-Beginners 课程仓库中“Migrate your plant to the cloud”一课的 Wio Terminal 专题wio-terminal-connect-hub.md完整演示如何把此前运行在公共 MQTT BrokerMosquitto上的土壤湿度采集固件迁移到 Azure IoT Hub包括 PlatformIO 依赖替换、设备连接字符串配置、基于 NTP 的时间同步、遥测上行D2C以及云到设备的 Direct Method 指令处理。读完本文你将掌握一个基于 Azure IoT Hub 设备 SDKC 语言、MQTT 协议的嵌入式设备完整接入流程并能验证遥测数据与继电器控制链路。1. 背景从公共 MQTT Broker 迁移到 IoT Hub在课程的上一课automated-plant-watering中Wio Terminal 固件通过knolleary/PubSubClient库连接公共 MQTT Brokertest.mosquitto.org将土壤湿度发布到ID/telemetry主题并从ID/commands主题接收指令可参考迁移前的配置 config.h 与 platformio.ini。这种方案适合演示原理但存在可靠性、安全性任何人都能监听遥测或下发指令、性能与设备发现能力四方面不足。本课的核心动作就是“迁移”删掉 MQTT 相关代码与依赖改用 Azure IoT Hub 设备 SDK通过设备专属连接字符串认证实现两类通信Device-to-cloudD2C消息设备把土壤湿度遥测发送到 IoT Hub供应用侧代码读取Direct method 请求应用侧通过 IoT Hub 调用设备上的relay_on/relay_off方法来控制继电器且必须收到设备响应才能确认处理成功。最终可运行的完整代码在 code/wio-terminal 目录main.cpp、config.h、ntp.h、platformio.ini。2. 任务一连接设备到 IoT Hub2.1 替换 PlatformIO 依赖在 VS Code 中打开soil-moisture-sensor工程编辑platformio.ini移除knolleary/PubSubClient依赖——它只用于连接公共 MQTT Broker接入 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用于持久保存时间其余五个是 Azure IoT 设备 SDK 的核心库。对比仓库中迁移后的最终 platformio.ini可以看到lib_deps中还保留了工程原有的ArduinoJson 6.17.3、Seeed Arduino rpcWiFi 1.0.5、Seeed Arduino FS、Seeed Arduino SFUD、Seeed Arduino rpcUnified与Seeed_Arduino_mbedtls 3.0.1——mbedtls对 SDK 建立 TLS 安全连接至关重要。在platformio.ini文件底部添加编译标志build_flags -DDONT_USE_UPLOADTOBLOB这是编译 Arduino IoT Hub 代码时必需的编译器宏用于裁剪 SDK 中上传到 Blob 的相关代码减小固件体积。2.2 配置设备连接字符串打开config.h删除全部 MQTT 设置BROKER、CLIENT_NAME、CLIENT_TELEMETRY_TOPIC、SERVER_COMMAND_TOPIC等只保留 WiFi 凭据并新增连接字符串常量// IoT Hub settings const char *CONNECTION_STRING connection string;把connection string替换为前面通过 Azure CLI 注册设备后复制出的连接字符串注册流程见 课程 README 中的“Task - register your IoT device”az iot hub device-identity create与az iot hub device-identity connection-string show。迁移后的 config.h 正是如此组织连接字符串包含 Hub 地址、设备 ID 与设备密钥三部分只认“已注册设备”未知设备无法接入。2.3 用 NTP 同步时间时间型令牌的前提IoT Hub 的连接认证使用基于时间的令牌SAS 令牌设备必须先知道当前时间。与 Windows/macOS/Linux 会自动同步网络时间不同微控制器不会自动联网校时因此需要新增一个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的工作方式是通过NTPClientAzSAMD 架构板子的 NTP 客户端向0.pool.ntp.org反复请求 Unix 纪元时间失败则等待 2 秒重试成功后调用settimeofday把系统时钟设为该时间。由于 Wio Terminal 板载 RTC 电池掉电后仍能继续走时之后即可随时取到正确时间无需每次联网校时。仓库中的 ntp.h 与此完全一致。2.4 改造 main.cpp删除 MQTT建立 IoT Hub 连接打开main.cpp删除全部 MQTT 代码PubSubClient.h头文件、PubSubClient变量声明、reconnectMQTTClient与createMQTTClient方法及所有相关调用。文件此后只保留三件事连接 WiFi、读取土壤湿度、生成 JSON 遥测。在文件顶部添加 IoT Hub SDK 与时间同步的头文件#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是与 IoT Hub 连接的句柄LL 表示 low-level 设备客户端。添加连接状态回调用于在连接/断开时打印状态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); } }添加建立连接的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用config.h中的连接字符串、以MQTT_Protocol创建连接对象 → 失败则打印提示此时应检查连接字符串→ 注册连接状态回调。在setup函数末尾依次调用initTime(); connectIoTHub();对照仓库 main.cpp 的setup实际顺序为初始化串口 → 设置A0为输入、PIN_WIRE_SCL为输出继电器控制脚→connectWiFi()→initTime()→connectIoTHub()→delay(2000)。与 MQTT 客户端一样SDK 是单线程协作式的需要在主循环中给它“工作时间”来处理上行下行消息因此loop顶部要有IoTHubDeviceClient_LL_DoWork(_device_ll_handle);2.5 编译烧录并观察串口输出构建并上传代码后串口监视器应看到类似输出Connecting to WiFi.. Connected! Fetched NTP epoch time is: 1619983687 Sending telemetry {soil_moisture:391} The device client is connected to iothub可以看到先取到 NTP Unix 时间例如1619983687随后设备客户端完成连接。由于建立 TLS 连接需要几秒可能先看到一两条土壤湿度输出、之后才打印连接成功属正常现象。3. 任务二发送遥测数据D2C连接建立后把遥测改发到 IoT Hub。在setup函数上方添加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把字符串包装为 IoT Hub 消息 →IoTHubDeviceClient_LL_SendEventAsync以异步方式发出 D2C 事件 →IoTHubMessage_Destroy释放消息对象。然后在loop中紧跟着串口打印遥测的位置调用见 main.cpp L115-L130Serial.print(Sending telemetry ); Serial.println(telemetry.c_str()); sendTelemetry(telemetry.c_str());此时每 10 秒循环一次analogRead(A0)读取模拟量用DynamicJsonDocument组装成{soil_moisture:值}再发送。可以用 Azure CLI 验证数据已到达 Hubaz iot hub monitor-events --hub-name hub_name输出中payload字段即为设备发出的{soil_moisture: 376}之类内容加--properties anno还能看到时间戳、设备 ID、认证方式等注解annotations。注意免费层限制IoT Hub F1 免费层约 8,000 条消息/天本固件每 10 秒发一条理论上限约 8,640 条/天课程“Challenge”部分正是让读者围绕上报频率与多设备扩展来做取舍。4. 任务三处理 Direct Method 命令设备需要响应服务端下发的继电器控制指令relay_on/relay_off。这类请求是 Direct Method要求设备必须回响应应用侧才能确认指令被处理。4.1 定义方法回调在connectIoTHub函数之前添加对应 main.cpp L27-L47int 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; }参数解析method_name云端请求的方法名relay_on或relay_offpayload/size方法附带的 JSON 负载。文档中也指出其实可以用单一方法实现把目标继电器状态放在payload里传递而非拆成两个方法名response/response_size输出参数用来回填响应正文及其长度返回值IOTHUB_CLIENT_OK表示处理成功。回调先根据方法名对PIN_WIRE_SCL引脚写高/低电平驱动继电器再构造响应。Direct Method 的响应由两部分组成文本响应 返回码。这里文本响应是 JSON 文档{ Result: }代码把该字符串写入堆内存并赋给*response、把长度写入*response_size最后返回IOTHUB_CLIENT_OK。4.2 注册方法回调在connectIoTHub函数末尾把回调挂上IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);仓库最终版 connectIoTHub 中该调用位于连接状态回调注册之后两个回调一并就绪。4.3 优化事件轮询用 work_delay 缩短指令延迟IoTHubDeviceClient_LL_DoWork负责处理 IoT Hub 发来的事件包括 Direct Method。若主循环里只靠一次delay(10000)后再DoWorkDirect Method 最长要等 10 秒才被处理。解决办法是把“10 秒阻塞”改成“100 毫秒 × N 次轮询”在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); }该循环每 100ms 调用一次DoWork直到累计达到delay_time从而把 Direct Method 的处理延迟压缩到最多 100ms。最后修改loop删掉顶部单独的IoTHubDeviceClient_LL_DoWork(_device_ll_handle);调用并把delay(10000)替换为work_delay(10000);此时设备每 10 秒仍上报一次遥测但事件轮询粒度细化为 100ms。5. 验证完整链路固件烧录后可以在终端用 Azure CLI 触发方法并观察设备输出az iot hub invoke-device-method --device-id soil-moisture-sensor \ --method-name relay_on \ --method-payload {} \ --hub-name hub_name继电器吸合同时设备串口打印Direct method received - relay_on把--method-name换成relay_off可让继电器断开。至此土壤湿度遥测上行D2C、Direct Method 下行控制全部走 Azure IoT Hub公共 MQTT Broker 的依赖被完全移除。6. 小结迁移的关键改动清单改动位置迁移前MQTT Broker迁移后IoT Hubplatformio.iniknolleary/PubSubClient 2.8Azure SDK 六件套 build_flags -DDONT_USE_UPLOADTOBLOBconfig.hBroker 地址、客户端 ID、遥测/命令 TopicCONNECTION_STRINGHub 地址 设备 ID 密钥时间无新增ntp.h的initTime()联网取 NTP 时间并写入 RTC上行client.publish(CLIENT_TELEMETRY_TOPIC, ...)sendTelemetry()→IoTHubDeviceClient_LL_SendEventAsync下行订阅命令 Topic 后解析directMethodCallbackIoTHubClient_LL_SetDeviceMethodCallback需回响应与IOTHUB_CLIENT_OK事件轮询单线程 MQTTloop()IoTHubDeviceClient_LL_DoWork配合work_delay(10000)做 100ms 粒度轮询完整可运行工程见 2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal其中 main.cpp 按本文步骤组织setup完成 WiFi、initTime、connectIoTHubloop负责采样、JSON 序列化、sendTelemetry与work_delay。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表