IoT-For-Beginners 数字农业项目结业作业实战:基于 IoT Hub 与 Serverless 从零构建你自己的智能 IoT 设备
2026/9/14 13:49:26 网站建设 项目流程

IoT-For-Beginners 数字农业项目结业作业实战:基于 IoT Hub 与 Serverless 从零构建你自己的智能 IoT 设备

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

本篇文章围绕 2-farm 项目第 6 课作业("Build a new IoT device")展开:你需要综合数字农业课程前 6 节课掌握的全部能力——传感器采集、执行器控制、云端接入、无服务器函数编排——自行设计并搭建一台"传感器 + 执行器"协同工作的新 IoT 设备,将遥测数据发送到 IoT Hub,并用 Serverless 代码根据遥测结果反向控制执行器。读完本文,你将掌握从设备端代码、云端资源、无服务器触发器到安全证书接入的完整落地路径,并了解该作业的评分标准与验收清单。

一、作业目标解读:这是一次端到端能力验收

这份作业位于 6-keep-your-plant-secure,是该课程项目中数字农业(Digital Agriculture)系列的收官任务。作业原文的指令非常简洁:

在过去 6 节课中,你已经学习了数字农业,以及如何使用 IoT 设备收集数据来预测植物生长,并根据土壤湿度读数实现自动浇水。请运用所学,使用一个传感器和一个执行器(可选类型)构建一台新的 IoT 设备:向 IoT Hub 发送遥测数据,并通过无服务器代码(serverless code)依据这些遥测数据控制执行器。你可以沿用本课程或上一个项目中用过的传感器与执行器,如果你有其他硬件,也可以尝试新硬件。

作业原文虽短,但它实质上要求你独立完成一次"设备端 → 云端 → 无服务器处理 → 反向控制设备"的完整闭环,是对以下三个层次的综合验收:

  1. 设备层:编写能同时驱动传感器(采集)与执行器(动作)的 IoT 设备代码;
  2. 连接层:部署 IoT Hub,让设备既能上传遥测(device-to-cloud),又能接收命令(cloud-to-device);
  3. 逻辑层:部署一个由遥测事件触发的 Azure Function,在云端解析数据并调用直接方法(Direct Method)控制执行器。

作业原文最后附带的评估表(Rubric)进一步明确了三个维度的达标线,这是自评和验收的直接依据,详见本文第七节。

二、课程能力回顾:作业中需要用到的六节课技能

在动手之前,先盘点你在这 6 节课中已经掌握的技能,作业正是要求把它们串成一条完整链路:

课程核心能力在作业中的用途
1-predict-plant-growth温度/湿度等传感器数据采集与生长度日(GDD)计算新设备的传感器数据读取与本地处理
2-detect-soil-moisture土壤湿度传感器(ADC 模拟量读取)传感器选型与读数换算的直接模板
3-automated-plant-watering继电器/水泵执行器与直接方法(Direct Method)执行器控制与relay_on/relay_off处理逻辑
4-migrate-your-plant-to-the-cloud设备身份注册、连接字符串、IoT Hub 遥测收发新设备接入 IoT Hub 的配置与代码
5-migrate-application-to-the-cloudAzure Functions、Event Hub 触发器、Registry Manager云端无服务器控制执行器的核心实现
6-keep-your-plant-secure对称密钥 / X.509 证书、连接安全、资源清理可选的安全加固接入方式与结课清理

仓库中 4-migrate-your-plant-to-the-cloud 与 5-migrate-application-to-the-cloud 两份文档记录了云迁移与无服务器化的完整步骤,作业就是要求你脱离课程手把手的指引,独立复现这条链路。

三、第一步:传感器与执行器的选型设计

作业对硬件选型给出了明确自由度:"使用一个传感器和一个执行器(可选类型)……你可以沿用本课程或上一个项目中用过的传感器与执行器,或者尝试新硬件。"这也意味着评估表第一条关注的不是硬件的新奇度,而是"设备代码能否同时驱动传感器与执行器"。

3.1 三条可选硬件路线

课程为每种硬件路线都保留了完整示例代码,可以对照复用:

  • 虚拟 IoT 设备(CounterFit):适合没有实体硬件的学习者。示例代码位于 2-farm/lessons/6-keep-your-plant-secure/code/virtual-device/soil-moisture-sensor/app.py,通过counterfit_shims_grove库模拟 Grove ADC 与继电器。
  • 树莓派 + Grove 套件:实体硬件路线,使用grove.adc.ADCgrove.grove_relay.GroveRelay,代码位于 2-farm/lessons/6-keep-your-plant-secure/code/pi/soil-moisture-sensor/app.py。
  • Wio Terminal(Arduino/PlatformIO):C++ 路线。注意一个现实约束:截至课程写作时,Azure Arduino SDK 尚不支持 X.509 证书,详见 wio-terminal-x509.md,因此 Wio Terminal 走对称密钥连接字符串方案最稳妥。

3.2 复用思路:从"土壤湿度 + 继电器"出发

课程中的经典组合是土壤湿度传感器(ADC 输入)+ 继电器(数字输出),其逻辑是:soil_moisture > 450时土壤偏干,打开继电器(水泵浇水);否则关闭。这个组合已经覆盖了"传感器采集 → 云端判断 → 执行器动作"的全部要素。作业中你可以:

  • 直接复用该组合,重点打磨云端的无服务器控制逻辑;
  • 更换传感器(如温湿度、光线、距离传感器),把判定阈值换成新物理量,例如"温度过高则打开风扇(执行器)";
  • 更换执行器(如 LED、蜂鸣器、步进电机),验证你对直接方法处理机制的掌握。

无论怎么组合,设备端代码骨架都是一致的,见下一节。

四、第二步:编写新设备的设备端代码

4.1 最小骨架:遥测 + 直接方法双向通信

以虚拟设备路线为例,仓库示例 app.py 展示了完整的设备端形态,注释中已标注每个区块在作业中的职责:

from counterfit_connection import CounterFitConnection CounterFitConnection.init('127.0.0.1', 5000) # 虚拟硬件连接初始化 import time from counterfit_shims_grove.adc import ADC # 传感器:模拟量 ADC 输入 from counterfit_shims_grove.grove_relay import GroveRelay # 执行器:继电器数字输出 import json from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509 adc = ADC() # 传感器实例 relay = GroveRelay(5) # 执行器实例(GPIO 引脚 5) # --- 连接配置(作业第 2 个评估点:连接 IoT Hub)--- host_name = "<host_name>.azure-devices.net" # 替换为你的 IoT Hub 主机名 device_id = "soil-moisture-sensor-x509" # 替换为你注册的设备 ID x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem") device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id) print('Connecting') device_client.connect() print('Connected') # --- 接收云端命令(作业第 3 个评估点的设备端基础)--- 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 # --- 遥测循环(作业第 1 个评估点:传感器采集与上报)--- while True: soil_moisture = adc.read(0) # 读取传感器 print("Soil moisture:", soil_moisture) message = Message(json.dumps({ 'soil_moisture': soil_moisture })) device_client.send_message(message) # 上报遥测到 IoT Hub time.sleep(10)

树莓派路线的代码结构完全一致,仅将导入语句替换为from grove.adc import ADCfrom grove.grove_relay import GroveRelay,并去掉 CounterFit 初始化,详见 2-farm/lessons/6-keep-your-plant-secure/code/pi/soil-moisture-sensor/app.py。

对比早期课程使用连接字符串的版本(见 4-migrate-your-plant-to-the-cloud/code/virtual-device/soil-moisture-sensor/app.py),两版代码唯一的本质差异在客户端创建方式:IoTHubDeviceClient.create_from_connection_string(connection_string)换成create_from_x509_certificate(x509, host_name, device_id)。如果你的新设备选用对称密钥方案,直接用前者的连接字符串版本即可;选用 X.509 证书方案则用后者的代码,二者都满足作业要求。

4.2 上报哪些遥测、监听哪些方法,由你的方案决定

  • 遥测 JSON:示例中只上报soil_moisture一个字段。作业鼓励你扩展,比如同时上报温度temperature、湿度humidity、光照light等,服务端据此做多条件判断。
  • 直接方法名:示例固定为relay_on/relay_off。你可以按执行器类型设计自己的方法名(如fan_onled_onmotor_start),只要保证设备端处理函数与服务端调用时方法名一一对应。
  • 响应码:处理完方法请求后必须用MethodResponse.create_from_method_request(request, 200)回执 200,让云端确认命令已被执行。

五、第三步:连接 IoT Hub——对称密钥与 X.509 证书两条路线

5.1 对称密钥:连接字符串的三个组成部分

在 6-keep-your-plant-secure 课程正文 中,连接字符串被拆解成三部分,这是你为新设备注册身份时的核心知识:

HostName=soil-moisture-sensor.azure-devices.net;DeviceId=soil-moisture-sensor;SharedAccessKey=Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=
Key值示例说明
HostNamesoil-moisture-sensor.azure-devices.netIoT Hub 的 URL
DeviceIdsoil-moisture-sensor设备的唯一 ID
SharedAccessKeyBhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=设备与 IoT Hub 共享的对称密钥

关于对称密钥的底层工作机制,课程文档给出了关键细节,值得在作业方案中体现:

  • 设备首次连接时发送共享访问签名(SAS token),包含 IoT Hub URL、过期时间戳(通常为当前时间起 1 天)以及用 SharedAccessKey 加密的签名;IoT Hub 用同一密钥解密校验,并验证当前时间早于过期时间,从而阻止他人截获并重放 SAS token。
  • 因为依赖过期时间,设备需要从 NTP 服务器获取准确时间,时间不准会导致连接失败。
  • 每个设备拥有 2 把密钥与 2 条对应连接字符串,用于密钥轮换(rotation):一把泄露时可切换另一把并重新生成。
  • 将密钥硬编码进源码属于糟糕实践,但学习阶段可以这样做,前提是绝不能把密钥提交进公开源码仓库

5.2 X.509 证书:用 Azure CLI 一步生成设备身份

若你的新设备选择非对称加密(公钥/私钥对)路线,课程文档指出最常用的算法是 RSA,并推荐直接使用 Azure CLI 自动完成"生成密钥对 + 自签名证书 + 注册设备身份":

az iot hub device-identity create --device-id soil-moisture-sensor-x509 \ --am x509_thumbprint \ --output-dir . \ --hub-name <hub_name>

<hub_name>替换为你的 IoT Hub 名称。命令执行后会在当前目录生成两个文件,必须妥善保管

  • soil-moisture-sensor-x509-key.pem:设备私钥,绝不能提交进公开源码控制;
  • soil-moisture-sensor-x509-cert.pem:设备的 X.509 证书文件。

随后在设备代码中做四处改动(详见 single-board-computer-x509.md):

# 1) 导入 X509 from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509 # 2) 声明主机名与设备 ID host_name = "<host_name>" # 从 connection_string 的 HostName 段获取,以 .azure-devices.net 结尾 device_id = "soil-moisture-sensor-x509" # 3) 用两个 .pem 文件构造 X509 实例 x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem") # 4) 用证书创建客户端,替代 create_from_connection_string device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id)

X.509 证书的机制要点(摘自课程正文):

  • X.509 证书是包含公钥的数字文档,通常由受信任的第三方——证书颁发机构(CA)——签发并数字签名,用于证明"公钥确实来自证书声明的那个人";
  • 自签名证书仅供测试,生产环境严禁使用
  • 证书含多个字段:公钥归属、签发 CA 信息、有效期、公钥本身;使用前应校验其确由原 CA 签发;
  • X.509 的一大优势是可多设备共享:创建一张证书上传到 IoT Hub,所有设备共用,每个设备只需持有自己的私钥;
  • 设备向 IoT Hub 加密消息所用的 Azure 公钥是公开的,仅用于加密而非解密,因此可以安全地出现在源码中。

需要再次提醒:Wio Terminal(Arduino 路线)的 Azure SDK 当前不支持 X.509 证书(见 wio-terminal-x509.md),实验 X.509 建议使用虚拟设备或树莓派。

六、第四步:用 Serverless 代码依据遥测控制执行器

这是作业评估表中"Exemplary"级别的关键项——"部署由遥测事件触发的 Azure Function 来控制设备"。完整的服务端参考实现位于 5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/init.py:

import logging import azure.functions as func import json import os from azure.iot.hub import IoTHubRegistryManager from azure.iot.hub.models import CloudToDeviceMethod def main(event: func.EventHubEvent): body = json.loads(event.get_body().decode('utf-8')) device_id = event.iothub_metadata['connection-device-id'] logging.info(f'Received message: {body} from {device_id}') soil_moisture = body['soil_moisture'] if soil_moisture > 450: direct_method = CloudToDeviceMethod(method_name='relay_on', payload='{}') else: direct_method = CloudToDeviceMethod(method_name='relay_off', payload='{}') logging.info(f'Sending direct method request for {direct_method.method_name} for device {device_id}') registry_manager_connection_string = os.environ['REGISTRY_MANAGER_CONNECTION_STRING'] registry_manager = IoTHubRegistryManager(registry_manager_connection_string) registry_manager.invoke_device_method(device_id, direct_method) logging.info('Direct method request sent!')

6.1 把这段代码改造成"你的新设备"服务端

  • 解析遥测json.loads(event.get_body().decode('utf-8'))解析设备上报的 JSON,字段名必须与设备端Message(json.dumps({...}))中定义的字段一致。
  • 识别设备event.iothub_metadata['connection-device-id']从 IoT Hub 注入的元数据中拿到发送者设备 ID——这正是该实现优于早期 MQTT 广播方案之处:命令只发给对应设备,多设备部署时互不干扰。
  • 改写判定逻辑:把soil_moisture > 450换成你新物理量的阈值,把relay_on/relay_off换成你设备端注册的方法名。
  • 发送命令:通过IoTHubRegistryManager调用invoke_device_method(device_id, direct_method)。Registry Manager 是 IoT Hub 提供的设备管理工具,支持云到设备消息、直接方法、设备孪生读写以及设备注册管理。

6.2 触发器与绑定的关键配置

服务端触发器基于 IoT Hub 的 Event Hub 兼容端点(IoT Hub 底层由 Azure Event Hubs 扩展而来),其绑定配置见仓库中的 function.json:

{ "scriptFile": "__init__.py", "bindings": [ { "type": "eventHubTrigger", "name": "event", "direction": "in", "eventHubName": "", "connection": "IOT_HUB_CONNECTION_STRING", "cardinality": "one", "consumerGroup": "$Default", "dataType": "binary" } ] }

课程文档强调的配置要点:

  • "connection": "IOT_HUB_CONNECTION_STRING"指向设置项名称而非连接字符串本身——连接字符串存放在local.settings.json(本地)或 Application Settings(云端),防止泄露;
  • "cardinality": "one":模板默认值是many,需改为one,否则事件会以列表传入,代码调用event.get_body()时会出现AttributeError: 'list' object has no attribute 'get_body'
  • "eventHubName": "":连接字符串已包含eventHubName信息,此处必须置空,否则会报 "The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both" 错误;
  • "consumerGroup": "$Default"使用默认消费组,多个应用同时消费时需使用不同消费组。

6.3 本地运行与云端部署的关键命令

本地联调(需要安装 Azure Functions Core Tools、VS Code 扩展与 Azurite 本地存储模拟器):

func init --worker-runtime python soil-moisture-trigger # 初始化函数应用 func new --name iot-hub-trigger --template "Azure Event Hub trigger" # 新建触发器 func start # 本地启动

本地运行前需在local.settings.json中设置三项(AzureWebJobsStorage指向本地模拟器,另两项连接字符串通过 Azure CLI 获取):

{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "IOT_HUB_CONNECTION_STRING": "<Event Hub 兼容端点连接字符串>", "REGISTRY_MANAGER_CONNECTION_STRING": "<ServiceConnect 策略连接字符串>" } }

对应的两条获取命令:

# 事件中心兼容端点(供触发器读取遥测) az iot hub connection-string show --default-eventhub --output table --hub-name <hub_name> # ServiceConnect 策略(供 Registry Manager 下发命令,权限最小化) az iot hub connection-string show --policy-name service --output table --hub-name <hub_name>

部署到云端

# 创建存储账户(函数运行时依赖,名称全局唯一,限小写字母与数字,最长 24 字符) az storage account create --resource-group soil-moisture-sensor --sku Standard_LRS --name <storage_name> # 创建 Linux 消费计划的 Python 函数应用 az functionapp create --resource-group soil-moisture-sensor \ --runtime python --functions-version 3 --os-type Linux \ --consumption-plan-location <location> \ --storage-account <storage_name> --name <functions_app_name> # 写入应用设置(连接字符串以环境变量形式注入代码) az functionapp config appsettings set --resource-group soil-moisture-sensor \ --name <functions_app_name> \ --settings "IOT_HUB_CONNECTION_STRING=<connection string>" # 发布代码 func azure functionapp publish <functions_app_name>

发布成功的标志性输出:

Deployment successful. Remote build succeeded! Syncing triggers... Functions in soil-moisture-sensor: iot-hub-trigger - [eventHubTrigger]

发布后让设备持续上报,改变传感器状态(如把湿度传感器移出土壤),即可观察到继电器随之开关——这就是"遥测驱动的 Serverless 控制闭环"在真实云环境中运行的效果。

七、评分标准(Rubric):对照自检的三个维度

作业原文的评估表就是最终的验收标准,逐条对照可以避免"代码能跑但漏了云端环节"的典型失分:

标准优秀(Exemplary)合格(Adequate)需改进(Needs Improvement)
编写使用传感器和执行器的 IoT 设备代码设备代码同时驱动传感器与执行器设备代码仅驱动传感器仅驱动执行器无法编写出使用传感器或执行器的设备代码
将 IoT 设备连接到 IoT Hub成功部署 IoT Hub,既发送遥测又接收命令成功部署 IoT Hub,但只能发送遥测只能接收命令无法部署 IoT Hub 或无法从设备与其通信
使用无服务器代码控制执行器成功部署由遥测事件触发的 Azure Function 来控制设备成功部署了由遥测事件触发的 Azure Function,但无法控制执行器无法部署 Azure Function

对照这份表格可以提炼出三条"保优秀"的硬指标:

  1. 设备代码必须同时包含传感器读取与执行器驱动,且两者都要真正工作;
  2. IoT Hub 链路必须是双向的:遥测上行(device-to-cloud)与直接方法下行(cloud-to-device)缺一不可;
  3. Azure Function 必须真的把遥测转化为执行器动作,即"触发成功"与"成功控制执行器"是两码事,后者才算达标。

八、结课提醒:完成后清理云资源

6-keep-your-plant-secure 课程正文 特别强调:这是本项目的最后一课,完成作业后务必清理云服务,以降低持续成本(例如免费层 IoT Hub 只有一个名额,不清理就无法再建)。

仓库根目录的 clean-up.md 给出了标准清理方式:本项目所有云服务都创建在资源组内,删除资源组即可连带删除其全部服务:

az group delete --name <resource-group-name>

确认提示Are you sure you want to perform this operation? (y/n):后输入y,删除过程需要一些时间。涉及清理的资源可能包括:资源组、IoT Hub、设备注册、存储账户、函数应用、Azure Maps 账户、自定义视觉项目、容器注册表、认知服务资源等(取决于你在各课程中创建的内容)。

九、作业之外的延伸思考

  • 安全是作业的隐藏加分点:第 6 课主题是"保护你的植物"(Keep your plant secure),作业中如果你在连接环节选用 X.509 证书、避免在源码中硬编码密钥、为 Registry Manager 使用权限最小的service策略连接字符串,都是对课程安全主题的直接呼应。
  • 多设备场景:服务端通过connection-device-id精确下发命令,天然支持多套"传感器 + 执行器"同时在线,这也是课程早期 MQTT 广播方案不具备的能力。
  • 状态保持的挑战:第 5 课挑战题指出,Serverless 函数无法像 MQTT 订阅那样"暂时退订"来实现浇水延时,如需延时控制,应考虑数据库或设备孪生等外部状态方案——这可以作为新设备设计的加分设计。

至此,你已具备独立完成该作业所需的全部要素:设备端代码骨架、双向云连接、Serverless 控制闭环、安全接入选项、评分对照表与收尾清理流程。照着本文从设备端一路打通到云端,即可达成评估表中"优秀"级别的全部三条标准。

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询