简介:这是一份围绕智慧农业物联网系统建设的完整方案文档,面向农业园区规划者、设施农业工程师、物联网项目设计与投标人员,重点解决温室、大田、水产养殖场景下环境精准监测、自动化控制与远程管理落地难题。内容以托普云农园区案例为蓝本,覆盖无线信息采集、实时视频监控、物联网自动控制、病虫害信息采集预警和拼接屏展示等模块,详细说明采集器布设、传感器耐高温高湿选型、光纤组网、24路智能控制柜配置及自动、定时、远程手动控制模式,并兼顾基地平台与管理展示平台的分层建设思路,提供系统性技术框架。资源为单个docx文件,大小约1.01MB,目前已有385人学习。若用于智慧农业园区规划、项目方案编写或设施农业课程设计,可借鉴其中的系统架构、设备清单与控制逻辑,减少前期调研与方案编制时间,提升完整度和专业度。
1. 智慧农业物联网系统建设:先想清楚闭环再选设备
我见过不少农业物联网项目,第一批传感器装得满满当当,半年后变成“电子标牌”,因为只做了数据展示,没有把采集和控制串成闭环。某个大棚基地就吃过亏:风机继电器由人工看温度操作,凌晨值班人员睡着,棚温冲到 40℃,一垄番茄苗全蔫。所以智慧农业物联网系统建设的第一件工作,不是挑传感器,而是定义闭环——从环境感知、数据传输、平台分析到设备控制,哪些环节需要自动,哪些只做告警。这篇文章按一条可复现的路径来讲:先定架构与选型,再落到硬件接入和平台配置,最后给出一套验收和排错方法。适合农业园区信息化负责人、系统集成商、以及正在做物联网工程相关开发的人。
2. 智慧农业物联网系统的四层架构与通信选型
2.1 四层架构怎么划,哪里放算力
常见的做法是把系统分成感知层、传输层、平台层和应用层。感知层主要是土壤、空气、光照、二氧化碳等传感器,加上采集控制器;传输层解决数据怎么从地里出来,包括短距离总线、低功耗广域网和互联网上行;平台层负责设备管理、数据存储和规则运算;应用层就是大屏、手机端、告警和控制。
算力分配要看现场条件。大棚里供电方便,可以在每个大棚放一个边缘网关,用 Modbus RS485 把所有传感器串起来,网关做轮询采集和阈值判断,即使断网也能本地启停风机。大田地块分散、没有市电,就要用电池供电的低功耗节点,尽量只在数据变化超过门限时上报,把算力放到云端。养殖舍环境相对集中,但氨气、硫化氢这些气体传感器需要频繁校准,通常把校准算法放在边缘,云端只收趋势数据。另外,无源物联网这两年慢慢从实验室走向试点,利用环境能量采集给传感器供电,对果园这种不便布线的场景很有吸引力,但目前成熟度和成本还不适合作为主方案。
2.2 传输层三种组网方式怎么选
传输层是很多项目烂尾的重灾区。我一般会根据供电条件和地块面积来选。这里有一个常用的对比表:
| 组网方式 | 典型设备 | 覆盖范围 | 功耗 | 带宽/时延 | 适用场景 |
|---|---|---|---|---|---|
| WiFi | ESP32 + 路由器/AP | 几十米 | 中 | 高/低 | 大棚群、园区办公区 |
| LoRa | LoRa 节点 + 网关 | 1~5 公里 | 低 | 低/高 | 大田、果园、分散地块 |
| NB-IoT | NB 模块 + 运营商基站 | 广域 | 低 | 低/中 | 无网关覆盖的偏远田块 |
| 4G | DTU/工业网关 | 广域 | 中高 | 中/低 | 视频监控、实时控制 |
WiFi 方案优点是开发调试快,ESP32 生态成熟,做环境监测的小规模试点基本一天就能跑通。LoRa 适合自己建网关、想长期不换电池的场景,但要注意网关高度和天线位置,2 米和 6 米高度覆盖差很多。NB-IoT 不需要自建网关,但要注意运营商网络覆盖,尤其是地下温室或半地下大棚,信号可能很弱。4G 成本最高,通常是给视频或大流量传感器用的。
2.3 平台层选型的两个方向:自建 Broker 还是用公有云
平台层现在有两个主流方向。一种是自建消息服务,典型的组合是 EMQX + 时序数据库 + Grafana,适合对数据主权要求高、有运维能力的团队。另一种是接入公有云物联网平台,平台自带设备影子、规则引擎和应用 SDK,开发快,但要考虑连接数计费和平台生命周期问题。我一般这样选:项目节点数少于 200、现场有能跑 Docker 的工控机,就自建;超过 500 或需要多个基地跨地域接入,优先用公有云。如果已选定的公有云平台停止新购,迁到自建 EMQX 或者换一家云平台是常规操作,迁移的主要工作量在设备端连接配置、Topic 映射和告警规则,JSON 报文规范如果一开始就定义好,这部分能省不少时间。
自建时我用 Docker 起一个 EMQX 实例,命令固定为:
docker run -d --name emqx \ -p 1883:1883 -p 8083:8083 -p 18083:18083 \ emqx:5.0端口 1883 是 MQTT 设备接入端口,8083 是 WebSocket 端口,给浏览器里的可视化大屏用,18083 是管理控制台端口。启动后先登录控制台创建用户名密码,并关闭匿名接入,否则公网上的扫描器会不断尝试连接。镜像标签5.0按实际可用版本调整,但建议固定大版本,避免小版本升级带来行为差异。
3. 感知层与传输层落地:传感器配置、网关接入与数据上云
3.1 传感器选型与接线:从 RS485 到 I2C
典型的大棚监测参数包括空气温湿度、土壤温度水分、光照强度和二氧化碳。下面是我常用的一组传感器选型参考:
| 传感器 | 接口 | 供电 | 精度参考 | 成本档位 |
|---|---|---|---|---|
| 空气温湿度 SHT30 | I2C | 3.3V | 温度 ±0.3℃,湿度 ±2%RH | 低 |
| 土壤温度水分探头 | RS485/Modbus | 12V | 水分 ±2% | 中 |
| 光照强度 BH1750 | I2C | 3.3V | 1~65535 lx | 低 |
| CO2 MH-Z19 | 串口 UART | 5V | ±50ppm + 读数的3% | 中 |
土壤传感器多数是 RS485 接口、走 Modbus 协议;空气温湿度用 I2C 的 SHT30 很划算;光照用 BH1750;CO2 用串口型 MH-Z19。接线时注意 RS485 是半双工总线,A/B 线不能接反,总线末端建议加 120Ω 终端电阻。一个采集节点上 IO 不够时,可以用 TCA9548A I2C 多路开关扩展,或者用 ULN2003A 驱动继电器,避免 GPIO 直驱大功率设备。这些细节在基于 ESP32 的物联网环境监测项目里经常会踩坑。
3.2 基于 ESP32 的采集节点代码框架
先给一个最小可跑的 Arduino 代码框架,采集空气温湿度并通过 MQTT 上报:
#include <WiFi.h> #include <PubSubClient.h> #include "Wire.h" #include "Adafruit_SHT31.h" Adafruit_SHT31 sht31 = Adafruit_SHT31(); const char* ssid = "your_ssid"; const char* password = "your_password"; const char* mqtt_server = "your_broker_ip"; const int mqtt_port = 1883; WiFiClient espClient; PubSubClient client(espClient); char payload[128]; void setup() { Serial.begin(115200); Wire.begin(21, 22); // SDA, SCL if (!sht31.begin(0x44)) { Serial.println("SHT31 not found"); } WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } client.setServer(mqtt_server, mqtt_port); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); float t = sht31.readTemperature(); float h = sht31.readHumidity(); if (isnan(t) || isnan(h)) return; snprintf(payload, sizeof(payload), "{\"temp\":%.2f,\"hum\":%.2f}", t, h); client.publish("agri/greenhouse/01/env", payload); delay(10000); } void reconnect() { while (!client.connected()) { if (client.connect("esp32-gw-01")) { client.subscribe("agri/greenhouse/01/cmd"); } else { delay(5000); } } }这段代码的逻辑是:上电连接 WiFi,创建 MQTT 客户端;每 10 秒读一次 SHT31,将温度湿度打包成 JSON,发布到agri/greenhouse/01/env;同时订阅cmd主题,用于接收云端或网关下发的控制指令。注意client.connect("esp32-gw-01")里的 clientId 必须保证在同一 Broker 内唯一,否则后连接的设备会顶掉前一个。如果后期要接多个传感器,可以在同一个loop里把读数和发布拆成两个任务,避免串行阻塞造成上报周期抖动,比如用millis()做一个非阻塞的定时器。
3.3 网关协议转换:用 Python 脚本把 Modbus 数据转成 JSON
大棚里如果有多路 RS485 设备,直接用 ESP32 写轮询逻辑会很啰嗦。常见做法是让边缘网关(比如工控机或树莓派)用 pymodbus 轮询,再统一以 MQTT 上送。下面是一个简化的读取循环:
import time import json import pymodbus.client as ModbusClient import paho.mqtt.client as mqtt # 连接 Modbus 从站设备,串口 /dev/ttyUSB0,波特率 9600 client = ModbusClient.ModbusSerialClient( port='/dev/ttyUSB0', baudrate=9600, parity='N', stopbits=1, timeout=1 ) client.connect() def read_sensor(addr, reg, count=2): rr = client.read_input_registers(reg, count, slave=addr) if rr.isError(): return None # 两个寄存器拼成一个 32 位无符号数 return (rr.registers[0] << 16) | rr.registers[1]代码里read_input_registers的第三个参数slave是从站地址,需要在传感器配置和拨码开关上对应;寄存器地址要查传感器手册,比如土壤水分的寄存器是0x0006、温度是0x0007。拿到原始值后,按手册里的量程公式换算成物理量,再和其他传感器数据合并封装成 JSON 上报。这里的timeout=1不能设得太小,RS485 总线上设备越多,轮询周期越长,把超时设到 1.5 秒更稳妥。对于快速原型,把 Modbus 转 MQTT 的网关也可以用 Node-RED 的 Modbus 节点拼出来,但在字段较多、需要做单位换算的场合,写脚本比拖节点更可控。
3.4 LoRa/NB-IoT 模块的上报参数怎么配
如果节点用 LoRa,节点和网关要约定频点、扩频因子和带宽。常见的配置是 470MHz 频段、SF7、125kHz 带宽,对应速率高、功耗低,适合大棚这种近距离;远距离大田可以用 SF10 以上,但单包传输时间变长,要注意不能超出当地无线电管理的占空比限制。LoRa 数据包建议控制在 64 字节以内,JSON 要尽量压缩字段名和精度。
NB-IoT 模块用 AT 指令连接网络时,常见流程如下:
AT+CFUN=1 // 开启射频 AT+CGATT=1 // 附着网络 AT+CEREG? // 查询注册状态,返回值 1 或 5 表示已注册 AT+QMTCFG="recv/mode",0,0,1 AT+QMTOPEN=0,"your_broker_ip",1883 // 打开 MQTT 连接 AT+QMTCONN=0,"nb-device-01" // 建立连接 AT+QMTPUB=0,0,0,0,"agri/field/02/env" // 发布主题 <数据内容>每个 AT 指令都有时序要求,AT+CGATT=1之后要轮询AT+CEREG?,确认网络注册成功再去做 MQTT 连接,否则会报错。如果模块连不上平台,先发AT+CSQ查信号强度,返回值小于 10 基本就是信号问题,要换天线位置或改外置天线。注意AT+QMTPUB里的<数据内容>在发布结束要以十六进制方式发送结束符,具体看模块手册,写程序时不要漏掉这一步。
4. 平台层与应用层建设:数据入库、规则引擎与可视化
4.1 Topic 设计与 JSON 报文规范
设备接入前要先把 Topic 树定下来,否则每加一种设备就要改一次代码。我用的规范是agri/{基地编号}/{棚号}/{节点类型}/{功能},比如下面这样:
| Topic | 方向 | 用途 |
|---|---|---|
agri/001/G01/env/upload | 设备→平台 | 上报环境数据 |
agri/001/G01/cmd | 平台→设备 | 下发控制指令 |
agri/001/G01/alarm | 设备→平台 | 上报告警事件 |
agri/001/G01/config | 平台→设备 | 参数配置下发 |
JSON 统一用扁平结构,方便规则引擎直接取字段:
{ "time": "2025-01-15T10:30:00+08:00", "temp": 25.6, "hum": 68.2, "soil_moisture": 35.1, "co2": 812, "lux": 23000 }时间戳必须带时区,字段名用小写加下划线,单位固定(温度摄氏度、湿度百分比、CO2 ppm、光照 lux)。这样后面做数据清洗和建模时不用猜单位。还要约定异常值表达方式,比如上报失败时用null而不是 0,因为 0 在农业气象里是有意义的数值,会把后续阈值判断带偏。
4.2 数据入库:MySQL 时序表与 InfluxDB 的取舍
数据量不大时,MySQL 也能扛住。一个 200 节点的基地,每 5 分钟上报一条,一天约 5.7 万条记录,分区表加索引足够。下面的建表语句按天分区:
CREATE TABLE `agri_env` ( `id` bigint NOT NULL AUTO_INCREMENT, `base_id` varchar(16) NOT NULL, `greenhouse_id` varchar(16) NOT NULL, `node_id` varchar(32) NOT NULL, `temp` decimal(5,2) DEFAULT NULL, `hum` decimal(5,2) DEFAULT NULL, `soil_moisture` decimal(5,2) DEFAULT NULL, `co2` int DEFAULT NULL, `lux` int DEFAULT NULL, `event_time` datetime NOT NULL, PRIMARY KEY (`id`,`event_time`), KEY `idx_device_time` (`node_id`,`event_time`) ) PARTITION BY RANGE (TO_DAYS(`event_time`)) (PARTITION p_start VALUES LESS THAN (TO_DAYS('2025-01-01')));这个表按event_time做 RANGE 分区,之后可以定期用ALTER TABLE agri_env DROP PARTITION p_old删除过期数据,避免 DELETE 造成大事务。如果以后要按天做多维聚合或者原始数据吞吐超过每秒几千条,再换成 InfluxDB 这类时序库。InfluxDB 里建议用一个 measurement 存所有环境量,tag 存base_id, greenhouse_id, node_id,field 存数值,时间戳用纳秒。注意不要把设备名放 field,tag 才适合做过滤条件,否则查询会把所有序列都扫一遍。
4.3 规则引擎:阈值告警与自动控制逻辑
4.3.1 边缘规则与云端规则的分工
自动控制指令必须放在边缘,因为云端链路一旦中断,温控不能等。比如大棚温度超过 32℃ 启动风机,边缘网关只需一条判断:if (temp > 32) gpio_on(FAN);云端规则适合做告警和联动,比如连续 30 分钟土壤湿度低于 30% 时触发浇水建议。边缘规则要加“回差”,比如温度降到 28℃ 才停风机,避免继电器频繁吸合烧坏触点。
云端规则用 Node-RED 或云平台规则引擎都能做。下面是一个等价的 Python 异步处理片段:
async def handle_env(msg): data = json.loads(msg.payload) # 取最近 6 次上报,即半小时窗口 history = await get_history(data['node_id'], 6) avg_hum = sum(h['soil_moisture'] for h in history) / len(history) if avg_hum < 30 and data['soil_moisture'] < 30: await alarm_service.send(data['base_id'], data['greenhouse_id'], "soil_dry", avg_hum)这里的get_history从 Redis 或数据库读取最近记录;alarm_service.send负责把告警推到钉钉群或短信。注意云端的轮询窗口要考虑上报延迟和网络抖动,不要用严格的“连续 N 分钟”去卡,给一点容差,比如允许 6 次里有 1 次缺失。还可以加最大次数限制,防止告警风暴。
4.4 可视化面板与告警推送的连通
数据入库后,给园区负责人看的东西决定项目能不能继续。Grafana 连 MySQL 或 InfluxDB 做监控大屏很成熟,仪表盘按棚选时间范围展示温湿度曲线、土壤水分热力图。配置数据源时,要设置min interval为 1m,避免用户把时间范围拉太长导致数据库聚合出大量点数。
告警推送到手机,低成本方案是用钉钉或企业微信的自定义机器人 Webhook。规则引擎触发时 POST 一条 JSON 到 webhook 地址即可。注意限制频率:同一设备同一告警类型 10 分钟内最多推一次,否则凌晨连续抖动会让值班人员直接关掉告警。这里有一个简单的频率限制逻辑:
last_alarm = cache.get(f"alarm:{node}:{alarm_type}") if last_alarm and time.time() - last_alarm < 600: return cache.set(f"alarm:{node}:{alarm_type}", time.time(), ex=600)cache可以用 Redis 或内存缓存,600是秒数。告警文案里要带时间、棚号、当前值和动作建议,不要只发一个“湿度低”。
5. 从试点到复制:智慧农业物联网系统建设的验收与排错
5.1 最小闭环验证:感知-上报-告警-控制一次跑通
新装完一个棚,先不要急着铺开。我会做这样的闭环测试:把温度传感器握在手心,等待 3 秒,确认平台上温度曲线上升;然后人为把阈值改到当前温度以上 1℃,观察风机是否在 10 秒内启动;最后把土壤传感器用湿布包住,确认浇水电磁阀动作。这个测试同时验证了传感器精度、网关上报链路、云端规则和边缘控制,任何一环卡住都能当场定位。
5.2 设备离线与数据漂移的排查步骤
设备离线先判断是“没上报”还是“上报失败”。命令行里用 MQTT 客户端订阅主题很快能看清:
mosquitto_sub -h your_broker_ip -t 'agri/001/+/env/upload' -v如果能收到数据,说明设备、网络、Broker 都没问题,问题在平台接入或存储;如果收不到,再用tcpdump -i eth0 port 1883抓包,看 TCP 握手和 MQTT PUBLISH 包,确认数据是否到达网关。下面是一张常用排查表:
| 现象 | 可能原因 | 先查什么 |
|---|---|---|
| 设备全部离线 | 网关断电或网络中断 | 检查交换机、SIM 卡流量 |
| 单节点离线 | 电源或天线信号弱 | 查该节点供电和 RSSI |
| 有发布无存储 | 平台订阅 Topic 不匹配 | 查规则引擎的 Topic 过滤 |
| 数据跳动大 | 传感器进水或接线干扰 | 查接线端子、屏蔽层接地 |
数据漂移常见于土壤水分传感器:长期埋在土里,电极附近离子浓度变化导致读数偏高。每两个月把传感器洗净晾干,在空气中读数,再与标准盐溶液标定,把偏移量写入边缘网关的补偿系数。
5.3 参数整定:上报频率、阈值回差与校准
上报频率不是越快越好。大棚环境变化慢,5 分钟一次足够;极端天气时云端可以临时下发interval命令,让节点加密上报,事后再恢复。发布主题约定为agri/001/G01/cmd/interval,payload 是一个 JSON 对象,节点收到后修改自己的上报周期。阈值回差方面,风机启动温度 32℃、停止 28℃,温差至少 4℃,否则继电器动作会过于频繁。CO2 与光照联动时,要考虑控制顺序,先补光再放风还是先通风再补光,不同季节不一样。
5.4 规模化复制时的网络规划要点
从 1 个棚复制到 50 个棚时,LoRa 网关要按扇区布局,两个网关之间间隔保持在覆盖半径的三分之二以内,避免边缘设备频繁切换;每个网关建议最多挂 200 个节点,并按位置分配不同的扩频因子,同频发送时用不同信道错峰。WiFi 方案在大棚复制时,别把所有 AP 都设成同一信道,用 1、6、11 三个信道隔开。NB-IoT 节点要注意物联网卡的流量套餐,按上报间隔估算单节点月流量,一般 30 分钟一条、每天几十 KB 就够,不用买大流量包。
上线前最后一遍验收,我习惯把这张表打印出来逐项打勾:
| 检查项 | 达标标准 |
|---|---|
| 设备在线率 | 大于 98% |
| 数据延迟 | 小于 30 秒 |
| 控制指令时延 | 小于 5 秒 |
| 告警准确率 | 连续 7 天无重复误报 |
每一项都能量化,才能判断系统是真正在干活,还是只是“看起来在干活”。安装调试人员在现场按表执行,比盯着平台看曲线更容易发现设备层面的隐患。
本文还有配套的精品资源,点击获取