IoT-For-Beginners 智慧农业实战:用 6 堂课从土壤传感器到云端物联网农场
2026/9/15 1:18:55 网站建设 项目流程

IoT-For-Beginners 智慧农业实战:用 6 堂课从土壤传感器到云端物联网农场

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

数字农业正在重塑农业生产的每一个环节——从预测作物生长、感知土壤墒情,到自动灌溉、上云迁移与设备安全。本篇文章基于开源课程项目 IoT-For-Beginners 的第二个学习项目"Zemеделие с IoT(Farming with IoT)"系列(见 translations/bg/2-farm/README.md,英文原版见 2-farm/README.md),完整梳理其 6 节课的知识脉络与可运行代码,帮助你掌握一套从零搭建"传感器 → MQTT/云 → 执行器"闭环智慧灌溉系统的完整技术方案。

为什么农业需要物联网

随着全球人口持续增长,农业承受的压力与日俱增。可供耕作的土地总量几乎不变,而气候变化却在不断加剧挑战——尤其是对于依赖种植成果养家糊口的约 20 亿"自给自足型农民"(subsistence farmers)而言。物联网恰恰能帮助农民做出更聪明的决策:种什么、何时收,从而提升产量、减少人工、及时发现并处理虫害。

在这个项目中,你将学会如何把物联网应用到农业场景中,最终实现"数字化农业(Digital Agriculture)"的核心能力:

  • 温度测量——通过温度预测植物生长与成熟周期;
  • 自动化灌溉——测量土壤湿度,仅在土壤缺水时启动灌溉系统,取代定时浇水(定时浇水在炎热干旱期会浇水不足,在雨季又会过度浇水);
  • 虫害防治——利用自动化机器人或无人机上的摄像头巡检虫害,只在需要处喷洒农药,减少农药用量及其对水源的污染。

课程总览:6 节课的递进路线

本系列共 6 节课,从单点传感器测量一路演进到云端安全部署,形成一条完整的工程主线:

序号主题核心能力
1用物联网预测植物生长测量环境温度、计算生长度日(GDD)、MQTT 发布遥测、服务端落盘
2检测土壤湿度湿度传感器原理、传感器与 IoT 设备通信协议、传感器标定
3自动化植物浇水继电器控制大功率水泵、MQTT 下发命令、传感器/执行器时序控制
4将植物迁移到云端云与 IoT Hub 概念、Azure CLI 建资源组与 IoT Hub、设备注册与直连
5将应用逻辑迁移到云端Serverless、Azure Functions、IoT Hub 事件触发器、Registry Manager 下发直连方法
6保障植物安全加密原理、对称密钥与连接字符串、X.509 证书认证

课程中会使用部分云资源;若未完整完成本项目的所有课时,建议参考 clean-up.md 清理云资源,避免产生不必要的费用。


第 1 课:用物联网预测植物生长

温度为什么对种植至关重要

植物生长需要水、二氧化碳、养分、光照与热量。大多数人对前几项很熟悉,却常常忽略热量——这正是植物在春季气温回升时开花、温室能促进植物生长、暖冬时雪莲花或水仙提前发芽的原因。针对日均温度,每种植物都存在三个关键温度:

  • 基础温度(Base temperature):植物能够生长的最低日均温度;
  • 最适温度(Optimum temperature):植物生长最快的日均温度;
  • 最高温度(Maximum temperature):植物能承受的温度上限,超过后植物会停止生长以保水存活。

💁 以上均为昼夜平均温度。植物昼夜需要不同温度,白天更高效地进行光合作用、夜间节省能量。

不同植物物种这三个温度值各不相同,这就是为什么有些植物适合热带、有些适合寒带。下图展示了"生长速率—温度"关系曲线:低于基础温度无生长,速率随温度升高至最适温度达峰,之后回落,至最高温度时生长停止:

如果农民能控制温度(例如商业温室),就可以为植物优化环境——比如商业番茄温室白天约 25°C、夜间约 20°C,以获得最快生长。

生长度日(Growing Degree Days,GDD)

生长度日(又称生长度单位)是以温度衡量植物生长的指标:在水分、养分、CO₂ 充足的前提下,温度决定生长速率。GDD 按天计算,即一天的平均温度(°C)减去植物基础温度。每种植物成熟、开花、结果都需要一定数量的 GDD,每天累积的 GDD 越多,植物长得越快。

GDD 的完整公式较复杂,课程使用一个常用的简化近似公式:

  • GDD:生长度日数量
  • T_max:当日最高温度(°C)
  • T_min:当日最低温度(°C)
  • T_base:植物基础温度(°C)

玉米示例:不同品种的玉米成熟需要 800~2,700 个 GDD,基础温度 10°C。若某日测得最高温 16°C、最低温 12°C,则当日 GDD = (16 + 12) / 2 − 10 = 4。若该品种玉米需 800 GDD 成熟,则还需累积 796 个 GDD:

💁 还有处理 T_max 高于 30°C 或 T_min 低于 T_base 的变体公式,本课先忽略这些边界情况。

从传感器数据计算 GDD 的完整架构

植物不会按固定日期生长,例如你无法确定种子播下后 100 天必然结果。农民只能凭经验粗略判断成熟时间,再每日巡检——这对大型农场是巨大的人力负担,还容易漏掉意外早熟的作物。通过测量温度计算 GDD,农民只需在接近预期成熟期时检查。

物联网方案的标准架构是:IoT 设备测量温度 → 通过 MQTT 等协议将遥测数据发布到互联网 → 服务端代码订阅数据并存储(如数据库或 CSV)→ 后续分析(如夜间任务计算当日 GDD、累计各作物 GDD,并在接近成熟时告警):

服务端还可以为数据补充额外信息:IoT 设备发布自身标识,服务端据此查表得到设备位置与监测的作物类型;还可以补充当前时间——因为部分 IoT 设备缺少精确时钟硬件,或需要额外代码通过网络读取时间。

实战:发布温度遥测并落盘为 CSV

课程提供了三套硬件路径的引导文档(Wio Terminal / Raspberry Pi / 虚拟设备),温度发布与 MQTT 连接说明见 2-farm/lessons/1-predict-plant-growth。服务端完整实现见 code-server/temperature-sensor-server/app.py,其核心逻辑:

client_name = id + 'temperature_sensor_server' # 客户端标识反映项目名 temperature_file_name = 'temperature.csv' # CSV 文件名 fieldnames = ['date', 'temperature'] # 两列:日期与温度 if not path.exists(temperature_file_name): with open(temperature_file_name, mode='w') as csv_file: writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer.writeheader() # 首行写入列头 def handle_telemetry(client, userdata, message): payload = json.loads(message.payload.decode()) print("Message received:", payload) with open(temperature_file_name, mode='a') as temperature_file: temperature_writer = csv.DictWriter(temperature_file, fieldnames=fieldnames) temperature_writer.writerow({'date': datetime.now().astimezone() .replace(microsecond=0).isoformat(), 'temperature': payload['temperature']})

关键要点:

  • client_name使用id + 'temperature_sensor_server'形式,保证 MQTT 客户端标识唯一;
  • CSV 采用追加模式(mode='a')写入,日期列使用ISO 8601 格式(含时区、不含微秒),温度列来自遥测消息;
  • 运行一段时间后的temperature.csv形如:
date,temperature 2021-04-19T17:21:36-07:00,25 2021-04-19T17:31:36-07:00,24

理想情况下应连续采集一整天数据再做 GDD 计算(若用虚拟设备,可勾选 random 并设置范围,避免每次返回相同温度值)。

实战:手工计算 GDD

例如草莓基础温度为 10°C。某日temperature.csv中最高温 25°C、最低温 12°C,则:(25 + 12) / 2 − 10 = 8.5,当日草莓获得8.5 个 GDD。草莓结果约需 250 个 GDD,因此还需继续累积:

课程的课后作业要求使用仓库提供的 gdd.ipynb Jupyter Notebook 可视化温度数据并计算 GDD(安装pandasmatplotlibjupyter后执行jupyter notebook gdd.ipynb),评分标准是至少捕获 1~2 天的完整数据。


第 2 课:检测土壤湿度

土壤湿度的意义与测量方式

植物吸收水分用于三件事:光合作用(水+CO₂+光生成碳水化合物与氧气)、蒸腾作用(通过叶片气孔交换 CO₂,同时输送养分、类似出汗般为植物降温)、以及维持结构(植物约 90% 是水,比人类的 60% 还高,水让细胞保持刚性,缺水会萎蔫甚至死亡)。

土壤过干植物无法吸收足够水分,过湿则根部无法获得足够氧气导致根系死亡。因此理想的土壤应"不干不涝",物联网通过测量土壤湿度让农民按需浇水。常用的传感器有两种:

  • 电阻式(Resistive):两根探针插入土壤,一端通电、另一端接收,通过测量土壤电阻判断湿度。水是良好导体,土壤含水越高电阻越低;
  • 电容式(Capacitive):测量正负电极板间可存储电荷量(电容)。土壤电容随湿度变化并转换为电压,土壤越湿输出电压越低。

两者都是模拟传感器,通过电压表示湿度——那么电压如何变成代码里的数值?这引出了传感器与 IoT 设备的通信机制。

传感器如何与 IoT 设备通信

GPIO 引脚

GPIO 是一组可连接硬件的引脚(Raspberry Pi、Wio Terminal 等开发板常见),部分引脚提供 3.3V/5V 电压,部分为接地,其余可通过程序设为输出(发送电压)或输入(接收电压)。仅需开关量(高/低)的数字传感器与执行器可直接使用 GPIO:例如按钮连接在 5V 与输入引脚之间,按下时回路接通、读到高电平;LED 连接在输出引脚与地之间(需串联电阻防止烧毁),输出高电平即点亮。

模拟引脚与 ADC

Arduino 等设备提供带 ADC(模数转换器)的模拟引脚,将电压范围转换为数值。通常 ADC 为 10 位分辨率,即把电压映射为 0~1,023 的整数。例如 3.3V 板子上传感器返回 3.3V 则读到 1,023,返回 1.65V 则读到 511。土壤湿度传感器依赖电压,因此走模拟引脚,返回 0~1,023。回顾第一项目"小夜灯"课程:光传感器返回 0~1,023——Wio Terminal 直接接模拟引脚;Raspberry Pi 则经 Grove Base Hat 上集成 ADC 的模拟引脚通信;虚拟设备模拟模拟引脚发送 0~1,023 的值。

通信协议:I²C、UART、SPI 与无线
  • I²C:多控制器多外设协议,数据以带地址的数据包传输,设备地址通常出厂硬编码。总线由 SDA(串行数据)、SCL(串行时钟)两根主线和 VCC、GND 两根电源线组成。数据发送时,某设备发出起始条件成为控制器,发送目标设备地址及读/写标志,传输完成后发停止条件。I²C 有三种速度模式:标准模式 100Kbps、快速模式(Raspberry Pi 上限 400Kbps)、高速模式 3.4Mbps;
  • UART:两设备间点对点串行通信,各有一个发送(Tx)与接收(Rx)引脚,交叉相连实现双向传输。通过波特率(常用 9,600bps)确定传输速度,用起始位与停止位界定一个字节(8 位)数据;
  • SPI:面向短距离通信(如微控制器与闪存),基于单控制器多外设模型,使用 COPI/CIPO/SCLK 三根主线加每外设一根 CS(片选)线。SPI 是全双工的,通过时钟同步无需起始/停止位,通常可每秒传输数 MB;
  • 无线协议:蓝牙(主要是 BLE)、LoRaWAN(远距离低功耗)、WiFi 等。商用土壤湿度传感器常用 LoRaWAN 将田间数据发给集线设备;Zigbee 则用 WiFi 构建网状网络,设备互为中继直至协调器接入互联网。

传感器标定(Calibration)

传感器测量的是电阻/电容等电气量,需经标定转换为实用单位。有的传感器出厂已标定(如温度传感器直接返回 °C)。土壤湿度可用重量含水量(gravimetric,每公斤干土中水的公斤数)或体积含水量(volumetric,每立方米干土中水的立方米数)衡量。由于土壤成分会影响电气特性,理想做法是把传感器读数与实验室科学测量对照,绘出"电压—土壤湿度"曲线并拟合直线,此后 IoT 设备的读数即可经该直线换算为真实土壤湿度。注意:电阻式传感器电压随湿度增大而升高,电容式则相反(曲线下倾)。

硬件实测引导见 2-farm/lessons/2-detect-soil-moisture,该课代码目录下包含 Wio Terminal(PlatformIO C++)、Raspberry Pi 与虚拟设备的完整示例。


第 3 课:自动化植物浇水

用继电器控制大功率设备

IoT 设备电压很低(通常提供 3.3V/5V,电流小于 1A),不足以驱动水泵等大功率硬件,强行驱动会烧毁开发板。解决办法是让水泵接外部电源,用一个执行器像"开关"一样控制其通断——这与手指拨动电灯开关接通 110V/240V 市电的原理相同。

继电器(Relay)是最常用的电机械开关:控制电路为电磁铁通电,电磁铁吸合杠杆闭合输出电路的一组触点;断电后杠杆释放、触点断开。继电器是数字执行器——高电平开启、低电平关闭。输出电路可承载远高于控制电路的功率:例如 Grove 继电器输出电路最高可处理 250V/10A。接线示例中,USB 电源 +5V 经继电器输出电路一端接入,另一端接水泵,水泵再接电源地;继电器闭合即向水泵供电。

注意:继电器存在多种类型,例如上电时是接通还是断开、是否有多个输出电路;杠杆吸合时通常能听到清脆的"咔嗒"声(早期电门铃的蜂鸣器正是利用继电器快速通断发出嗡嗡声)。

通过 MQTT 控制植物

商业灌溉系统的控制逻辑是集中式的:基于多个传感器数据做决策,配置只改一处。为模拟这一点,课程用 MQTT 控制继电器:

  1. soil-moisture-sensor项目中加入 MQTT 库,客户端 ID 为<ID>soilmoisturesensor_client
  2. 设备代码发送soil_moisture遥测属性;
  3. 新建soil-moisture-sensor-server服务端,订阅遥测并下发控制命令,命令消息属性为relay_on,客户端 ID 为<ID>soilmoisturesensor_server
  4. 设备端收到命令后按relay_on属性控制继电器;若soil_moisture大于 450 则发送relay_on: true,否则false

完整示例见 code-mqtt。

传感器与执行器的时序问题

用实体传感器做过土壤湿度实验会发现:浇水后读数需要几秒才下降——这不是传感器迟钝,而是水渗透土壤需要时间(靠近传感器浇水可能先快速下降又回升,那是水分扩散的结果)。同理,执行器动作到传感器读数变化之间存在延迟:若像小夜灯那样"读数低于阈值立刻开泵、达到阈值立刻关泵",水仍在继续渗透,导致过浇、浪费水甚至烂根。

因此最佳浇水循环应该是:

  • 开启水泵 5 秒
  • 等待 20 秒(让水渗透、读数稳定)
  • 重新检查土壤湿度
  • 若仍高于目标值(湿度仍不够),重复以上步骤

具体时长高度依赖你的设备、被测量属性与传感器/执行器;宁可少浇(还能再开泵),不可多浇(无法从土壤中取水)。更精细的做法是"每高于目标 100 就开泵 1 秒"而非固定 5 秒;露天种植还可结合天气预报——预报有雨则推迟浇水,雨后再评估是否仍需浇。

给植物控制服务端添加时序

服务端逻辑为:收到遥测 → 检查土壤湿度 → 若湿度不足,则发命令开继电器 → 等 5 秒 → 发命令关继电器 → 等 20 秒待湿度稳定。由于遥测每 10 秒一条,而整个浇水循环约 25 秒,需要避免重叠触发新循环。相比把设备改为每分钟上报一次(不利于大农场的水流分析),更好的方案是在浇水循环期间退订遥测:代码在无法处理时忽略遥测,但其他订阅同一数据的服务仍可读取。

完整实现见 code-timing/server/app.py:

water_time = 5 wait_time = 20 def send_relay_command(client, state): command = { 'relay_on' : state } print("Sending message:", command) client.publish(server_command_topic, json.dumps(command)) def control_relay(client): print("Unsubscribing from telemetry") mqtt_client.unsubscribe(client_telemetry_topic) send_relay_command(client, True) time.sleep(water_time) send_relay_command(client, False) time.sleep(wait_time) print("Subscribing to telemetry") mqtt_client.subscribe(client_telemetry_topic) def handle_telemetry(client, userdata, message): payload = json.loads(message.payload.decode()) print("Message received:", payload) if payload['soil_moisture'] > 450: threading.Thread(target=control_relay, args=(client,)).start()

要点:import threadingcontrol_relay在后台独立线程运行,避免阻塞 MQTT 消息循环;退订—浇水—等待—重新订阅的模式保证了循环期间不会处理新的遥测消息。


第 4 课:将植物迁移到云端

什么是云

云常常被戏称为"别人的计算机":你不必自建数据中心(涉及买机器、硬件维护、供电散热、网络、安全、软件安装更新等高昂成本),而是按需向云服务商租用算力——需求高峰多租、低谷少租。云数据中心遍布全球,一些大型中心面积可达数平方公里,甚至自带发电站;其规模化效应也使其比大量小型数据中心更环保。

本课使用Microsoft Azure。你需要一个云订阅,有两种免费方式:

  • Azure for Students(18 岁以上学生):无需信用卡,用学校邮箱验证,注册即获 US$100 额度及包含免费 IoT 服务在内的免费服务,有效期 12 个月、可逐年续期;
  • Azure 免费订阅(非学生):需信用卡验证身份但不会扣费,前 30 天有 US$200 额度及免费层服务,额度用完也不会自动扣款。

云 IoT 服务为什么优于公共测试 Broker

之前使用的公共 MQTT broker 适合学习,但在商用场景有四大缺陷:可靠性(免费无保障,随时可能关闭)、安全性(公开可窃听遥测或下发恶意命令)、性能(仅适合少量测试消息)、可发现性(无法得知哪些设备在线)。云 IoT 服务则具备高可靠性、内建安全、高性能可扩展,并知晓所有已注册设备——未注册设备连接会被拒绝。设备通过设备 SDK或直接走 MQTT/HTTP 等协议接入,应用其他组件通过服务 SDK与设备通信:

用 Azure CLI 创建 IoT Hub

  1. 安装 Azure CLI 后添加 IoT 扩展并登录:
az extension add --name azure-iot az login
  1. 多订阅环境下先列出并选择订阅:
az account list --output table az account set --subscription <SubscriptionId>
  1. 查询可用区域(本课写作时约 65 个)并创建资源组:
az account list-locations --output table az group create --name soil-moisture-sensor --location <location>
  1. 创建免费层 IoT Hub(免费层每日 8,000 条消息,每个订阅仅限一个):
az iot hub create --resource-group soil-moisture-sensor \ --sku F1 \ --partition-count 2 \ --name <hub_name>

--sku F1选择免费层;--partition-count 2定义 IoT Hub 支持的数据分区数(创建免费层 IoT Hub 的必需参数)。hub 名称需全局唯一,因为它会出现在访问 URL 中。

IoT Hub 的通信方式

IoT Hub 定义了若干设备↔云通信机制(底层可走 MQTT、HTTPS 或 AMQP):

  • 设备到云(D2C)消息:设备发往 IoT Hub 的遥测,应用代码可读取(底层基于 Azure Event Hubs,读取时通常称为"事件");
  • 云到设备(C2D)消息:应用代码经 IoT Hub 发给设备的消息;
  • 直连方法请求(Direct method):请求设备执行动作(如控制执行器),必须响应以便应用代码确认处理成功;
  • 设备孪生(Device twins):在设备与 IoT Hub 间保持同步的 JSON 文档,用于存储设备上报属性或云端期望(desired)配置。

IoT Hub 可暂存消息与直连方法请求(默认一天),设备离线重连后仍能取回离线期间的消息;设备孪生则永久保存在 IoT Hub。

注册设备并验证云端连通

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>

连接字符串是含服务标识(URL)与密钥(SharedAccessKey)的文本,由设备 SDK 使用——必须妥善保管。随后可监控设备遥测:

az iot hub monitor-events --hub-name <hub_name>

输出中可见payload{"soil_moisture": 376}等消息;加--properties anno可查看自动附加的注解(annotations),如iothub-connection-device-id(发送方设备 ID)与iothub-enqueuedtime(UNIX 时间戳)等。还可以下发直连方法控制继电器:

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。注意:设备每 10 秒发一条遥测,一天约 8,640 条,已接近免费层 8,000 条/日的上限——课程挑战题正是思考如何调整上报频率以留在免费层内。


第 5 课:将应用逻辑迁移到云端

什么是 Serverless

Serverless(无服务器计算,又称 FaaS)是指在云端以小块代码响应各类事件:事件发生时你的代码被加载运行,并传入事件数据;无事发生时代码不存活。因此它天然可扩展——大量事件同时发生时云提供商会并行运行你的函数实例。代价是函数间共享信息需存到数据库等外部存储,不能依赖内存。计费方式为"按代码运行时间与内存用量付费",不运行时零费用。

对 IoT 开发者而言,Serverless 是理想模型:写一个响应"任何已连接设备发到云 IoT 服务的消息"的函数即可,代码仅在需要时运行。

创建 Azure Functions 应用

Microsoft 的无服务器服务是Azure Functions,开箱支持 Python、JavaScript、TypeScript、C#、F#、Java 与 PowerShell(本课用 Python)。Functions 应用由若干触发器(triggers)组成,共享同一套配置。

本地开发链路:安装 Azure Functions Core Tools 与 VS Code 扩展 → 安装 Node.js → 全局安装存储模拟器:

npm install -g azurite mkdir azurite azurite --location azurite

Azurite 会在本地启动 Blob(10000 端口)、Queue(10001)、Table(10002)三个服务。然后创建项目:

mkdir soil-moisture-trigger cd soil-moisture-trigger python3 -m venv .venv source ./.venv/bin/activate # Linux/macOS;Windows 用 .venv\Scripts\activate.bat 或 Activate.ps1 func init --worker-runtime python soil-moisture-trigger pip install -r requirements.txt

func init会生成三个文件:host.json(应用级设置)、local.settings.json(本地运行设置,如 IoT Hub 连接字符串,不应提交到源码控制)、requirements.txt(Pip 依赖)。需把AzureWebJobsStorage设为UseDevelopmentStorage=true以连接 Azurite。

创建 IoT Hub 事件触发器

触发器要读取 IoT Hub 的消息流,需连接 IoT Hub 的Event Hub 兼容端点(IoT Hub 基于 Azure Event Hubs,读消息方式与 Event Hubs 相同)。获取连接字符串并写入local.settings.jsonValues段:

az iot hub connection-string show --default-eventhub --output table --hub-name <hub_name>
"IOT_HUB_CONNECTION_STRING": "<connection string>"

创建触发器(没有专门的 IoT Hub 触发器模板,使用 Event Hub 触发器):

func new --name iot-hub-trigger --template "Azure Event Hub trigger"

生成的iot-hub-trigger文件夹包含:

  • __init__.py:核心是main函数,每次有消息发到 IoT Hub 即被调用:
import logging import azure.functions as func def main(event: func.EventHubEvent): logging.info('Python EventHub trigger processed an event: %s', event.get_body().decode('utf-8'))
  • function.json:含bindings(绑定)配置。关键字段:"type": "eventHubTrigger"(监听事件)、"name": "events"(对应 Python 参数名)、"direction": "in"(输入绑定)、"connection": ""(从设置读取连接字符串——不能直接写入连接字符串)。

因模板缺陷,需手工修正三处:把cardinalitymany改为one(否则报'list' object has no attribute 'get_body')、把connection设为IOT_HUB_CONNECTION_STRING、把eventHubName清空(连接字符串已含EntityPath,重复指定会报错)。运行func start后,函数会处理过去一天内已发到 IoT Hub 的事件:

Functions: iot-hub-trigger: eventHubTrigger ... Python EventHub trigger processed an event: {"soil_moisture":628}

从 Serverless 代码下发直连方法

监听之外还需向设备发命令,这要通过Registry Manager(注册表管理器)——它管理已注册设备、可发送 C2D 消息/直连方法/更新设备孪生。用ServiceConnect策略获取连接字符串(该策略允许连接并发送消息给设备,遵循最小权限原则):

az iot hub connection-string show --policy-name service --output table --hub-name <hub_name>

requirements.txt中加入azure-iot-hubpip install -r requirements.txt。改造__init__.pymain函数(完整代码见 code/functions):

import json import os from azure.iot.hub import IoTHubRegistryManager from azure.iot.hub.models import CloudToDeviceMethod body = json.loads(event.get_body().decode('utf-8')) device_id = event.iothub_metadata['connection-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='{}') 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)

要点:设备 ID 从事件的iothub_metadata['connection-device-id']注解中取出,因此直连方法会发给正确的单一设备——比之前 MQTT 版"广播给所有设备"更适合多套传感器/继电器并存;连接字符串在本地从local.settings.json读为环境变量,部署后则从云端 Application Settings 读取。

部署到云端

  1. 创建存储账户(Functions 需要少量云存储,Standard_LRS为最低成本通用账户,无免费层但费用很低,存储最贵也不到每 GB 每月 US$0.05):
az storage account create --resource-group soil-moisture-sensor \ --sku Standard_LRS \ --name <storage_name>

存储账户名需全局唯一,仅限小写字母与数字,最多 24 字符。

  1. 创建 Functions App(Python 仅支持 Linux 托管):
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>
  1. 把本地设置上传为 Application Settings(部署后从环境变量读取):
az functionapp config appsettings set --resource-group soil-moisture-sensor \ --name <functions_app_name> \ --settings "IOT_HUB_CONNECTION_STRING=<connection string>" # 同样设置 REGISTRY_MANAGER_CONNECTION_STRING
  1. 发布:
func azure functionapp publish <functions_app_name>

输出Deployment successful.并列出已部署的触发器iot-hub-trigger - [eventHubTrigger]即成功。此后调整土壤湿度即可看到云端逻辑自动控制继电器开关。

💡 注意:IoT Hub 触发器无法退订,之前 MQTT 版"浇水循环期间退订"的时序策略在此不适用,需要另想他法(这正是课程的挑战题)。


第 6 课:保障植物安全

为什么要保护 IoT 设备

IoT 安全要确保:只有预期的设备能连入云 IoT 服务并发送遥测,只有你的云服务能向设备发命令。不安全的代价可能是:伪造设备持续上报高湿度导致灌溉永不启动、植物干死;黑客操控设备长时间浇水导致烂根并造成水费损失;读取个人/商业敏感数据;以设备为跳板入侵内部网络;甚至以个人数据勒索。历史上已有大量真实案例(如鱼缸恒温器漏洞被用于入侵赌场网络、Mirai 僵尸网络利用默认口令的 IoT 设备发起大规模 DDoS 攻击、联网玩具数据库泄露儿童语音消息、健身应用暴露用户住址等)。

密码学基础

设备用 ID 自证身份,但 ID 可被克隆。解决之道是加密:用只有设备与云端知晓的密钥(encryption key)把数据打乱成密文,云端用相同或配对的解密密钥还原;无法解密即视为被入侵而拒绝。

  • 早期密码学:替代密码,如凯撒密码(按固定位移替换字母)、维吉尼亚密码(用单词对不同字母作不同位移);
  • 现代密码学:基于复杂数学,密钥空间大到暴力破解不可行。HTTPS 即浏览器与服务器间加密通信的典型应用;
  • 对称加密:加密与解密用同一密钥,发送方与接收方需共享密钥,共享过程可能泄密,速度快但安全性较低;
  • 非对称加密:公钥/私钥对。公钥用于加密(可公开),私钥用于解密(仅持有者保留)。更安全但更慢;常见做法是用非对称加密安全地共享对称密钥,再用对称密钥加密数据。

用对称密钥保护设备:连接字符串与 SAS

此前连接 IoT Hub 用的连接字符串由三部分(分号分隔的键值对)组成:

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

密钥从不直接传输。设备首次连接时发送共享访问签名(SAS)令牌:含 IoT Hub URL、过期时间戳(通常为当前时间 +1 天)与签名(URL+过期时间用共享密钥加密)。IoT Hub 用共享密钥解密签名并核对 URL 与过期时间,同时验证当前时间早于过期时间,防止恶意设备截获真实设备的 SAS 令牌重放。也正因有过期时间,设备需要准确的时钟(通常从 NTP 服务器读取),时间不准会导致连接失败。

💁 密钥不应写死在代码里(代码泄露=密钥泄露,且发布时要为每台设备重新编译)。理想做法是从硬件安全模块读取;学习阶段写进代码可以,但切勿提交到公开源码库。设备有 2 个密钥与 2 条对应连接字符串,便于密钥轮换(rotate)。

X.509 证书:非对称加密的落地

非对称加密下,你需要把公钥交给发送方——但对方如何确认公钥确实属于你?解决方案是X.509 证书:把公钥放入由可信第三方(证书颁发机构,CA)签发并数字签名的数字文档中。证书含持有人信息、签发 CA、有效期与公钥本身,使用前应验证其确由原 CA 签发。CA 签发收费,测试可"自签名"(自己签发),但生产环境绝不可用自签名证书

X.509 证书可跨设备共享:创建一份证书上传到 IoT Hub,所有设备共用;每台设备只需知道私钥来解密来自 IoT Hub 的消息。设备用于加密发往 IoT Hub 消息的证书由微软发布,与许多 Azure 服务共用,甚至内置于 SDK。

实战:创建 X.509 设备身份

Azure CLI 可一步完成"生成密钥对 + 创建自签名证书 + 注册设备":

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

该命令在当前目录生成两个文件:soil-moisture-sensor-x509-key.pem(设备私钥,切勿提交到公开源码控制)与soil-moisture-sensor-x509-cert.pem(X.509 证书)。设备侧接入引导见 single-board-computer-x509.md 与 wio-terminal-x509.md。本课是项目最后一课,完成后应参照 clean-up.md 清理云端资源。


课程配套资源速查

整个项目贯穿"理论 + 三套硬件路径(Wio Terminal / Raspberry Pi / 虚拟设备)+ 服务端代码"的模式,可在各课目录中直接查阅与运行:

  • 第 1 课:发布温度并落盘 CSV 的完整服务端实现 app.py;GDD 可视化作业 gdd.ipynb;
  • 第 2 课:三种硬件的土壤湿度测量代码 2-farm/lessons/2-detect-soil-moisture/code;
  • 第 3 课:MQTT 遥控继电器 code-mqtt、继电器直控 code-relay、带时序控制的浇灌服务端 app.py;
  • 第 4 课:三种硬件连接 IoT Hub 的接入代码 2-farm/lessons/4-migrate-your-plant-to-the-cloud/code;
  • 第 5 课:Azure Functions 触发器完整工程 code/functions;
  • 第 6 课:X.509 认证的设备代码 2-farm/lessons/6-keep-your-plant-secure/code。

如果只完成部分课时而用到了云端资源,务必按 clean-up.md 中的az group delete --name <resource-group-name>删除整个资源组以清理全部云资源、避免潜在费用。从一棵盆栽的土壤湿度监测,到可部署于云端、按需灌溉整片农场的自动化系统——这 6 堂课恰好完整覆盖了数字农业 IoT 应用从原型到生产化的全链路。

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

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

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

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

立即咨询