从 MQTT 迁移到 Azure IoT Hub:物联网花园设备上云实战指南

从 MQTT 迁移到 Azure IoT Hub:物联网花园设备上云实战指南 从 MQTT 迁移到 Azure IoT Hub物联网花园设备上云实战指南【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本文基于 IoT-For-Beginners 项目「农场」单元第 4 课2-farm/lessons/4-migrate-your-plant-to-the-cloud整理而成完整教程以英文原版 README 为权威来源德语翻译版位于 translations/de/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md。导读在上一课中你使用本地运行的服务器代码通过公共 MQTT Broker 控制继电器构成了一个联网自动灌溉系统的最小闭环。但公共 Broker 在可靠性、安全性、性能与设备识别方面均不适用于生产环境。本课将带你理解云计算与云 IoT 服务的基本概念并手把手把土壤湿度传感器设备从公共 MQTT Broker迁移到 Microsoft Azure IoT Hub从申请免费订阅、用 Azure CLI 创建资源组与 IoT Hub、注册设备身份到分别用 PythonRaspberry Pi / 虚拟设备与 CWio Terminal两种 SDK 完成连接、遥测上报与直接方法控制。学完本课你将具备把一个真实 IoT 设备接入企业级云服务的最小完整链路能力。为什么上云从公共 MQTT Broker 的局限说起上一课的设备与公共 MQTT Broker 通信是为了演示发布/订阅机制。文档明确指出公共测试 Broker 在商用环境中有四个明显短板可靠性免费服务没有任何可用性保障随时可能被关闭安全性Broker 是公开的任何人可能窃听你的遥测数据甚至发送命令操控硬件性能只面向少量测试消息设计无法承受大规模消息量识别Discovery没有任何手段获知当前有哪些设备已连接。云 IoT 服务如 Azure IoT Hub由大型云厂商运营在可靠性上有重金投入内置安全机制防止黑客读取数据或发送伪造指令同时具备弹性伸缩能力可每日处理数百万条消息。作为回报用户需按月付费但多数云厂商提供免费层每天限消息条数与连接设备数足以支撑开发学习——本课使用的正是免费层。云与 Azure把别人的计算机变成可弹性租赁的资源从自建机房到按需租赁云计算的本质被通俗地称为别人的计算机someone elses computer。传统模式下企业为员工或公众提供数据库、文件存储、网站等服务必须自建并运维机房涵盖采购计算机、硬件维护、供电与制冷、网络、楼宇与软件安全、软件安装更新等全部环节。这不仅昂贵、需要大量专业人才而且响应缓慢——例如电商为圣诞旺季需提前数月采购部署硬件旺季过后设备又长期闲置。云模型将上述职责转移给云厂商。客户按需租用计算资源需求上升时扩容、下降时缩减。云厂商凭借大规模采购降低成本、投资自动化运维工具甚至自研硬件并通过规模效应让整体拥有成本更低。云数据中心遍布全球规模可达数平方公里部分数据中心自带发电设施且通常在能效、可再生能源、节水与森林复育等可持续性方面优于众多小型机房。Microsoft Azure 与本课程的角色Azure 是微软的开发者云也是本课程后续所有云端操作的载体。本章所有实操环节订阅、资源组、IoT Hub、设备注册、事件监控、直接方法调用都基于 Azure 云平台完成。第一步创建 Azure 免费订阅使用云服务必须先注册订阅。本课提供两种免费订阅路径文档撰写时点信息实际以官方页面为准订阅类型适用对象凭证要求赠送额度有效期Azure for Students年满 18 周岁的学生无需信用卡用学校邮箱验证学生身份100 美元云资源额度 含免费版 IoT 服务的免费服务12 个月在校期间可每年续期Azure 免费订阅Azure Free Account非学生人群需信用卡仅用于人机验证不会扣款首 30 天 200 美元额度 Azure 免费层服务额度用尽后不会自动扣款除非升级到即用即付 微软另提供面向 18 岁以下学生的 Azure for Students Starter但截至文档撰写时该档不支持 IoT 服务。 若你通过学校参加本系列课程可能已获学校下发的 Azure 订阅可先咨询老师跳过此步。学生注册两种途径通过 GitHub Student Developer Packeducation.github.com/pack激活 Azure for Students 优惠或直接在 azure.microsoft.com/free/students 注册。非学生则在 azure.microsoft.com/free 注册 Azure 免费订阅。认识云 IoT 服务的连接与安全模型云 IoT 服务本质上是管理设备连接的托管消息服务。设备通常通过两种方式接入设备 SDKDevice SDK官方库封装了主题、安全、重连等细节是推荐的接入方式直接使用通信协议如 MQTT 或 HTTP 直连。设备接入服务后应用的其他部分再通过服务 SDKService SDK读取设备遥测、下发命令——这与你此前通过 MQTT 收发消息的模式类似只是把 Broker 换成了托管服务。设备到服务的连接模型如下图所示安全方面云 IoT 服务通过只允许已注册设备接入实现认证设备要么预先注册要么持有服务签发的密钥或证书完成首次自助注册。未知设备连接会被拒绝其发送的消息也会被忽略此外IoT 服务通常还提供注册设备清单查询、单播/组播下发、以及与云数据库等组件的低代码集成如遥测直通存储等附加能力。用 Azure CLI 创建 IoT Hub 资源准备安装 Azure CLI 与 azure-iot 扩展Azure 资源可通过 Web 门户或命令行CLI配置本课统一使用 CLI。步骤按 Azure CLI 官方文档安装 CLI安装 IoT 扩展用于管理 IoT 服务的命令集合az extension add --name azure-iot登录 Azure 账号会打开浏览器完成认证az login若账号关联多个订阅如学校订阅 个人学生订阅先列出全部订阅az account list --output table输出示例其中IsDefault标记当前默认订阅➜ ~ az account list --output table Name CloudName SubscriptionId State IsDefault ---------------------- ----------- ------------------------------------ ------- ----------- School-subscription AzureCloud cb30cde9-814a-42f0-a111-754cb788e4e1 Enabled True Azure for Students AzureCloud fa51c31b-162c-4599-add6-781def2e1fbf Enabled False切换到目标订阅az account set --subscription SubscriptionId再次执行az account list可确认IsDefault已变为True。创建资源组Azure 中的每项服务IoT Hub、虚拟机、数据库、AI 服务等都被视为资源Resource且必须归属于一个资源组Resource Group——即若干资源的逻辑分组。资源组的价值在于统一管理完成项目后删除资源组即可连带删除全部资源。查询可部署的 Azure 区域文档撰写时约 65 个az account list-locations --output table➜ ~ az account list-locations --output table DisplayName Name RegionalDisplayName ----------------------- ------------------- ------------------------------------- East US eastus (US) East US East US 2 eastus2 (US) East US 2 South Central US southcentralus (US) South Central US ...记下距离你最近的区域的Name列值如eastus。创建名为soil-moisture-sensor的资源组资源组名需在订阅内唯一az group create --name soil-moisture-sensor \ --location location创建 IoT Hub 实例在资源组中创建 IoT Hubaz iot hub create --resource-group soil-moisture-sensor \ --sku F1 \ --partition-count 2 \ --name hub_name参数说明--name hub_nameHub 名称必须全球唯一它会被拼进访问 Hub 的 URL建议采用soil-moisture-sensor-前缀加随机词或姓名--sku F1指定免费层Free tier免费层每日支持8000 条消息且覆盖付费层的大部分功能注意每个订阅仅能创建一个免费 IoT Hub--partition-count 2定义 Hub 支持的数据流分区数。更多分区可减少多客户端并发读写时的数据阻塞这是创建免费 Hub 必须显式设置的参数。分区细节超出本课范围。创建过程通常需要一分钟或更久。Azure 的不同价格档位称为 tier层级各档成本、功能与数据量配额不同可参考 Azure IoT Hub 定价文档。与 IoT Hub 的四种通信方式与 MQTT 按主题自由收发不同IoT Hub 定义了若干固定的设备—云端通信模式底层实际可走 MQTT、HTTPS 或 AMQP设备到云D2CDevice-to-Cloud消息设备发往 Hub 的消息如遥测应用代码可从 Hub 读取。底层依赖 Azure Event Hubs读取这类消息时常被称为读取事件events云到设备C2DCloud-to-Device消息应用代码经 Hub 向设备发送的消息直接方法请求Direct Method应用代码请求设备执行某动作如控制执行器要求设备返回响应调用方以此判断处理是否成功设备孪生Device Twin设备与 Hub 间同步的 JSON 文档用于存放设备上报的属性或云端希望设备采用desired的设置。存储与可靠性消息与直接方法请求可在 Hub 中保留可配置时长默认一天设备或应用断线重连后可补拉离线期间的消息设备孪生则持久化存储在 Hub设备随时重连都能获取最新孪生。注册设备身份并获取连接字符串Hub 创建后设备必须先注册才能连接。注册时会下发一条设备专属连接字符串connection string包含 Hub 信息、设备 ID 与用于认证的密钥。连接字符串是通用概念连接数据库、各类服务均会用到必须妥善保管安全性会在后续课程详述。注册设备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保存输出中的连接字符串后续两种设备接入方案都会用到。设备接入上云两种硬件两条 SDK 路径设备端需改用官方设备 SDK 连接 IoT Hub替换原有的 MQTT 客户端代码。仓库提供了完整可运行的参考实现CWio Terminalcode/wio-terminal/soil-moisture-sensor详细步骤见 wio-terminal-connect-hub.mdPythonRaspberry Pi / 虚拟设备code/pi/soil-moisture-sensor/app.py 与 code/virtual-device/soil-moisture-sensor/app.py详细步骤见 single-board-computer-connect-hub.md。Python 路径Raspberry Pi / 虚拟设备安装 Azure IoT 设备 SDKpip3 install azure-iot-device在app.py顶部导入 SDK 类型并移除paho.mqtt相关代码from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse connection_string connection string⚠️ 文档明确提示连接字符串不应硬编码在源码中可能被提交进版本库而泄露此处仅为教学简化理想做法是用环境变量配合python-dotenv之类的工具后续课程会展开。创建并连接设备客户端参考 code/pi/soil-moisture-sensor/app.pydevice_client IoTHubDeviceClient.create_from_connection_string(connection_string) print(Connecting) device_client.connect() print(Connected)运行后预期输出piraspberrypi:~/soil-moisture-sensor $ python3 app.py Connecting Connected Soil moisture: 379上报遥测在主循环内、sleep 之前构造Message并发送message Message(json.dumps({ soil_moisture: soil_moisture })) device_client.send_message(message)处理直接方法定义回调处理relay_on/relay_off并通过MethodResponse.create_from_method_request(request, 200)返回 HTTP 200 状态码作为成功响应def handle_method_request(request): print(Direct method received - , request.name) if request.name relay_on: relay.on() elif request.name relay_off: relay.off() method_response MethodResponse.create_from_method_request(request, 200) device_client.send_method_response(method_response) device_client.on_method_request_received handle_method_request 也可以把继电器目标状态放进直接方法的 payload用单个方法而非relay_on/relay_off两个方法实现状态可从request对象中读取。C 路径Wio Terminal / PlatformIO打开soil-moisture-sensor工程在platformio.ini中移除knolleary/PubSubClient原用于公共 MQTT Broker新增如下依赖与 platformio.ini 一致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 实时时钟交互的代码用于计时其余库负责连接 IoT Hub。并在文件末尾添加编译标志编译 Arduino IoT Hub 代码所需build_flags -DDONT_USE_UPLOADTOBLOB在 config.h 中移除 MQTT 设置加入连接字符串// IoT Hub settings const char *CONNECTION_STRING connection string;NTP 校时IoT Hub 连接令牌基于时间而微控制器不像 Windows/macOS/Linux 会自动同步网络时间需从 NTP 服务器获取当前时间并写入 Wio Terminal 的实时时钟。新增 ntp.h其核心initTime()循环调用ntpClient.getEpochTime(0.pool.ntp.org)获取 UNIX 时间戳失败则等待 2 秒重试最后通过settimeofday写入系统时钟。在 main.cpp 中移除全部 MQTT 代码替换为 IoT Hub 头文件#include AzureIoTHub.h #include AzureIoTProtocol_MQTT.h #include iothubtransportmqtt.h #include ntp.h连接 Hubsetup中先initTime()再调用connectIoTHub()IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle; 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); IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL); }连接状态变化由connectionStatusCallback回调输出到串口。连接失败时串口会提示检查连接字符串。上报遥测sendTelemetry用IoTHubMessage_CreateFromString创建消息、IoTHubDeviceClient_LL_SendEventAsync异步发送、随后IoTHubMessage_Destroy释放对象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); }处理直接方法directMethodCallback根据method_name写高低电平控制继电器本课程用PIN_WIRE_SCL引脚驱动并必须构造 JSON 响应{Result:}写入response/response_size返回IOTHUB_CLIENT_OKint 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 代码运行在单线程上loop中必须周期性调用IoTHubDeviceClient_LL_DoWork(_device_ll_handle)处理收发的消息。为避免 10 秒delay阻塞直接方法响应参考实现使用work_delay把大延时拆成 100ms 的小步每步都调用DoWork从而让设备至多等待 100ms 就能处理直接方法见 main.cppvoid work_delay(int delay_time) { int current 0; do { IoTHubDeviceClient_LL_DoWork(_device_ll_handle); delay(100); current 100; } while (current delay_time); }上传固件后串口监视器应看到类似输出Connecting to WiFi.. Connected! Fetched NTP epoch time is: 1619983687 Sending telemetry {soil_moisture:391} The device client is connected to iothub用 Azure CLI 监控设备遥测暂不修改服务器代码先用 CLI 直接观察设备上报的事件az iot hub monitor-events --hub-name hub_name设备发送遥测后控制台会实时打印事件其中payload即为设备上报的原始消息Starting event monitor, use ctrl-c to stop... { event: { origin: soil-moisture-sensor, module: , interface: , component: , payload: {\soil_moisture\: 376} } }, { event: { origin: soil-moisture-sensor, module: , interface: , component: , payload: {\soil_moisture\: 381} } }每条消息还会自动附带时间戳等属性这些附加信息称为注解annotations。使用--properties anno查看全部注解az iot hub monitor-events --properties anno --hub-name hub_nameStarting event monitor, use ctrl-c to stop... { event: { origin: soil-moisture-sensor, module: , interface: , component: , properties: {}, annotations: { iothub-connection-device-id: soil-moisture-sensor, iothub-connection-auth-method: {\scope\:\device\,\type\:\sas\,\issuer\:\iothub\,\acceptingIpFilterRule\:null}, iothub-connection-auth-generation-id: 637553997165220462, iothub-enqueuedtime: 1619976150288, iothub-message-source: Telemetry, x-opt-sequence-number: 1379, x-opt-offset: 550576, x-opt-enqueued-time: 1619976150277 }, payload: {\soil_moisture\: 381} } }注解中的时间值采用UNIX 时间自 1970 年 1 月 1 日零时起的秒数。观察完毕用ctrl-c停止监控。 截至文档撰写时az iot扩展在 Apple SiliconM 系列芯片上尚未完整可用此类用户可改用 Azure IoT Tools for Visual Studio Code 等途径监控消息。用直接方法远程控制继电器无需写任何服务器代码CLI 即可向设备发起直接方法调用az iot hub invoke-device-method --device-id soil-moisture-sensor \ --method-name relay_on \ --method-payload {} \ --hub-name hub_name其中--method-payload以 JSON 形式携带方法入参此处为空对象。执行后可见继电器吸合设备串口输出Direct method received - relay_on把--method-name改为relay_off重复执行即可关闭继电器。这验证了设备端直接方法回调Python 的handle_method_request或 C 的directMethodCallback的完整闭环。 挑战免费层配额与采样频率设计免费层 IoT Hub 每日限额8000 条消息。当前代码每 10 秒发送一条遥测请计算每 10 秒一条的发送频率下一天会产生多少条消息是否会超出免费层配额土壤湿度测量应以多高的频率发送才必要但不过度如何修改代码在免费层额度内完成监控如果加入第二台设备整体消息预算该如何重新分配延伸学习与作业SDK 开源示例IoT Hub SDK 对 Arduino 与 Python 均开源。Wio Terminal 用户可参考 Arduino 侧azure-iot-pal-arduino仓库pal/samples下的示例Raspberry Pi / 虚拟设备用户可参考azure-iot-sdk-python仓库azure-iot-hub/samples下的 Python 示例。课后作业完成 assignment.md 中关于云服务的学习任务加深对 IaaS/PaaS/SaaS 等云服务模型的理解。课前准备本课假设已学完上一课MQTT 与本地服务器控制继电器相关内容可回看农场单元第 3 课。至此你的土壤湿度传感器程序已成功接入 Azure IoT Hub——设备既能安全上报遥测也能被云端以直接方法远程控制为下一课将应用迁移到云端打下了基础。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考