IoT-For-Beginners 智慧农场实战:Raspberry Pi 与虚拟设备通过 MQTT 发布温度遥测数据
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
本篇指南基于 IoT-For-Beginners 课程第 2 周农场场景(预测植物生长)中"发布温度"这一实战环节,讲解如何把 Raspberry Pi 或虚拟 IoT 设备(CounterFit)上 DHT11 传感器采集到的温度值,通过 MQTT 发布到公共 Broker,供服务端订阅并存储,最终用于计算植物的生长度日(GDD,Growing Degree Days)。学完本篇,你将掌握:复用第 4 课的 MQTT 连接模式改造遥测应用、按正确字段名与 JSON 结构发布温度数据、为传感器轮询设定合理的休眠周期,并在真实 Pi 与虚拟设备两种环境下验证发布结果。
背景:为什么要把温度发出去
在 预测植物生长课程 中,温度是计算 GDD 的核心输入。GDD 的简化公式为:
- Tmax—— 当日最高温度(摄氏度)
- Tmin—— 当日最低温度(摄氏度)
- Tbase—— 该植物的基础温度(base temperature),如草莓约为 10°C
要算出当日的 GDD,就必须持续、长期地收集温度数据。因此本课的架构是:IoT 设备测量温度后,通过 MQTT 把遥测数据(telemetry)发布到互联网上的 Broker;服务端代码订阅这些数据并追加保存到 CSV 文件,之后(例如夜间任务)再基于历史数据计算 GDD、累计作物生长进度并在接近成熟时发出告警。原文档中对这一步的概括是:温度被读取后,就可以通过 MQTT 发布给某个"服务端"代码,由它读取数值并存储好,等待被用于 GDD 计算。
本篇对应的正是整个链路中的"设备侧发布"环节,官方指南为 single-board-computer-temp-publish.md,适用对象是Raspberry Pi 单板计算机和虚拟 IoT 设备(使用 Seeed 的 CounterFit 应用模拟硬件);Wio Terminal(Arduino)走的是另一份指南 wio-terminal-temp-publish.md。
前提:已建好的 temperature-sensor 项目
发布温度之前,你需要已经完成本课前面"测量温度"的小节,拥有一个名为temperature-sensor的项目,其中app.py能够读取 DHT11 传感器并打印温度。Pi 与虚拟设备两条路线的前置条件分别见:
- Raspberry Pi 路线:pi-temp.md —— DHT11 Grove 传感器插在 Grove Base Hat 的数字口D5上,代码侧通过
pip3 install seeed-python-dht安装驱动,并创建sensor = DHT("11", 5)实例(第一个参数指定 DHT11 型号,第二个参数指定 D5 引脚),用sensor.read()返回(humidity, temperature)元组。 - 虚拟设备路线:virtual-device-temp.md —— 安装
counterfit-shims-seeed-python-dht垫片包,并在 CounterFit 应用中创建两个传感器:湿度传感器放在 Pin 5、温度传感器放在 Pin 6(shim 会把DHT("11", 5)模拟为这两个组合传感器),代码以counterfit_shims_seeed_python_dht替代seeed_dht导入DHT类。
任务:改造程序以发布温度数据
按原文档,任务是"给设备编程,让它发布温度数据"。完整步骤如下。
第 1 步:打开项目,复用第 4 课的 MQTT 连接步骤
打开temperature-sensor应用项目(如尚未打开)。然后重复你在 第 4 课"连接互联网" 中做过的两件事:连接 MQTT Broker和发送遥测,使用的仍然是同一个公共 Mosquitto Broker(test.mosquitto.org)。这两步各自包含的细分为:
- 添加 MQTT 的 pip 包:
pip3 install paho-mqtt - 添加连接 MQTT Broker 的代码
- 添加发布遥测的代码
如果需要回看细节,可参考第 4 课的两份指南:single-board-computer-mqtt.md(连接 MQTT)与 single-board-computer-telemetry.md(发送遥测)。其中与本项目直接相关的要点:
- 在
app.py顶部导入import paho.mqtt.client as mqtt; test.mosquitto.org是公共 Broker,很多使用者(包括其他学习本课程的同学)同时在线,因此必须给自己分配一个唯一 ID作为客户端名和主题名的前缀,避免与他人的客户端/主题冲突;client_name是本 MQTT 客户端在 Broker 上的唯一名称;mqtt_client.connect('test.mosquitto.org')建立连接,mqtt_client.loop_start()启动后台线程处理订阅消息。
第 2 步:让 client_name 体现本项目名称
把第 4 课小夜灯项目里的客户端名后缀改成本项目的名称:
client_name = id + 'temperature_sensor_client'从仓库中 Pi 版完整示例 code-publish-temperature/pi/temperature-sensor/app.py 可以看到,与客户端名配套的主题定义是:
id = '<ID>' client_telemetry_topic = id + '/telemetry' client_name = id + 'temperature_sensor_client' mqtt_client = mqtt.Client(client_name) mqtt_client.connect('test.mosquitto.org') mqtt_client.loop_start() print("MQTT connected!")注意<ID>需要替换成你自己的唯一标识(可以用 GUID 生成器等工具产生),这个 ID 之后写服务端订阅代码时还会用到——客户端与服务端必须共用同一个 ID 才能对上同一个/telemetry主题。
第 3 步:发布温度遥测(字段名为 temperature)
与第 4 课发送光照值({'light': ...})不同,这里要发送从 DHT 传感器读到的温度值,放在 JSON 文档中名为temperature的属性上:
_, temp = sensor.read() telemetry = json.dumps({'temperature' : temp})这里sensor.read()返回(湿度, 温度)元组,下划线丢弃湿度,temp是摄氏温度。json.dumps把它编码成 JSON 字符串,如{"temperature": 25}。这个字段名不是随意的:服务端代码在解析消息时会直接按payload['temperature']取值(见下文服务端剖析),字段名不一致会导致KeyError。
第 4 步:把轮询间隔设为 10 分钟
温度值不需要高频读取——短时间内变化很小,所以把time.sleep设为 10 分钟:
time.sleep(10 * 60)time.sleep以秒为单位,因此传一个计算式的结果:60 秒/分钟 × 10 分钟 = 600 秒。这个低频策略同时兼顾了数据用途(GDD 只关心每日最高/最低温)和设备能耗。
第 5 步:运行并验证
像之前运行本项目代码的方式一样运行:Pi 上在用户家目录的temperature-sensor文件夹里执行python3 app.py;虚拟设备则在激活虚拟环境的终端中执行python app.py,并确保CounterFit 应用在运行、湿度与温度传感器已创建在正确引脚(湿度 Pin 5、温度 Pin 6)。
预期输出:
pi@raspberrypi:~/temperature-sensor $ python3 app.py MQTT connected! Sending telemetry {"temperature": 25} Sending telemetry {"temperature": 25}看到MQTT connected!与每 10 分钟一次的Sending telemetry {"temperature": ...}日志,即表示温度已成功作为遥测从设备发布出去。如果用的是虚拟设备,建议在 CounterFit 里勾选Random复选框并设置一个 Min/Max 区间(参考课程中的截图 select-the-random-checkbox-and-set-a-range.png),这样每次读取会得到不同的温度值,便于后续验证服务端是否正确累计了多条记录。
💡 完整可运行代码可在仓库中找到:code-publish-temperature/virtual-device(虚拟设备版)或 code-publish-temperature/pi(Pi 版)。
源码纵深:客户端完整代码剖析
把上面 5 步合起来,Pi 版 app.py 全文只有 28 行,结构非常清晰:
import time from seeed_dht import DHT import paho.mqtt.client as mqtt import json sensor = DHT("11", 5) id = '<ID>' client_telemetry_topic = id + '/telemetry' client_name = id + 'temperature_sensor_client' mqtt_client = mqtt.Client(client_name) mqtt_client.connect('test.mosquitto.org') mqtt_client.loop_start() print("MQTT connected!") while True: _, temp = sensor.read() telemetry = json.dumps({'temperature' : temp}) print("Sending telemetry ", telemetry) mqtt_client.publish(client_telemetry_topic, telemetry) time.sleep(10 * 60)几个值得注意的实现细节:
- 连接是一次性的,发布是循环的:
connect/loop_start在while True之外只执行一次;循环体内依次是"读传感器 → 编码 JSON → 打印 → publish → 休眠"。paho 的loop_start()让网络收发在后台线程进行,因此即使主线程sleep600 秒也不会丢失连接处理。 - 虚拟设备版与 Pi 版仅两处差异:对比 虚拟设备版 app.py,它额外在前两行初始化 CounterFit 连接(
CounterFitConnection.init('127.0.0.1', 5000)),并把传感器库从seeed_dht换成垫片包counterfit_shims_seeed_python_dht,其余 MQTT 逻辑完全一致。这说明"应用层代码与硬件解耦"是本课程的刻意设计:同一套发布逻辑可以无缝跑在真机与模拟器上。 time.sleep(10 * 60)位于 publish 之后:先发送再休眠,保证程序启动后立即产生第一条遥测,而不是空等 10 分钟。
数据流向哪:服务端订阅与 CSV 落盘
发布的温度并不是终点。同一课程提供了一份配套服务端代码 code-server/temperature-sensor-server/app.py,它展示了设备侧发布的数据如何被消费,理解它对把握"发布端字段约定"很有帮助:
client_telemetry_topic = id + '/telemetry' server_command_topic = id + '/commands' client_name = id + 'temperature_sensor_server' mqtt_client = mqtt.Client(client_name) mqtt_client.connect('test.mosquitto.org') mqtt_client.loop_start() temperature_file_name = 'temperature.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']}) mqtt_client.subscribe(client_telemetry_topic) mqtt_client.on_message = handle_telemetry从源码结构看,服务端做三件与设备侧呼应的事:
- 同名主题订阅:
subscribe(id + '/telemetry'),即设备端 publish 的那个主题。两边id必须一致,这是公共 Broker 上避免串数据的关键约定; - 服务端自己补时间戳:很多 IoT 设备缺乏可靠的时钟,所以 CSV 中的
date列取的是服务端收到消息的时刻(带时区、去掉微秒的 ISO 8601 格式),而非设备端时间; - 追加写入 CSV:文件不存在时先建表头
date,temperature,之后每条消息append一行。这正是 README 中"服务端代码可以增强数据(augment the data)"的体现——设备只发温度,位置、时间等上下文由服务端补齐。
积累一天的数据后(理想情况),就可按 课程 README 中的方法找出当日最高/最低温并套用 GDD 公式,最终用 Jupyter Notebook 作业 可视化 GDD 数据。
常见问题与注意事项
- ID 不唯一:公共 Broker 上若客户端名或主题与他人重复,会互相干扰甚至导致断连。务必使用唯一 ID,并在设备端与服务端保持一致。
- 虚拟设备传感器引脚:
DHT("11", 5)在 CounterFit 中隐含要求湿度传感器在 Pin 5、温度传感器在 Pin 6(shim 假设温度传感器在"下一个引脚")。只建了温度传感器会导致读数异常。 - sleep 单位是秒:
time.sleep(10 * 60)是 600 秒。调试时可以先改成更短的值(如 10 秒)快速验证,确认无误后再改回 10 分钟。 - 长时间采集:如果计划让服务端连续运行一整天采集 GDD 数据,需要确保运行服务端代码的电脑不会进入睡眠(调整电源设置或运行保持唤醒的脚本)。
小结
本篇完成了"预测植物生长"课程中设备侧的关键一步:temperature-sensor应用从"打印温度"升级为"通过 MQTT 发布{"temperature": ...}遥测",核心改动只有三处——客户端名id + 'temperature_sensor_client'、遥测字段改为temperature、休眠周期拉长到 10 分钟;发布链路复用第 4 课的paho-mqtt+test.mosquitto.org模式。数据落地端(服务端订阅 + CSV 落盘)的对应代码在 code-server/temperature-sensor-server 中可以直接对照阅读,两者拼合起来就是"传感器 → MQTT → 存储 → GDD 计算"这条数字农业数据链的完整最小实现。
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考