☰
从传感器到屏幕:智慧农业物联网全链路架构选型与工程实践复盘
2026/10/15 16:51:35 网站建设 项目流程

做智慧农业系统,最容易翻车的地方往往不在算法,也不在某一个单点功能,而在一整条数据链路。传感器采上来的土壤湿度、空气温度、光照强度,要经过边缘网关固件汇聚、MQTT上云、业务系统处理、时序库落盘,最后才到你手机屏幕上的那个小红点或者曲线图。中间任何一环松了,整个系统都像没骨头的伞,看着能用,风一吹就散。

这篇写的是我实际做完一套智慧农业平台后的技术架构选型复盘,方案是Spring Cloud + EMQX + TDengine + uni-app + 边缘网关固件,覆盖了大棚环境监测、设备远程控制、告警推送、历史数据分析和手机端管理。适合正准备做农业物联网、或者已经在做但卡在整体架构上的开发者参考。我会尽量把为什么这么选、中间踩过什么坑、哪些地方容易想当然讲清楚。

1. 从田间到屏幕:先摸清一条农业数据要经过哪几站

1.1 一条完整的农业数据流

做选型之前,得先把数据从传感器到用户手指之间的路画出来。农业物联网和普通互联网应用的差别在于,数据的源头不在服务器里,而在泥土和空气中。我这套系统里,整条链路是这样串起来的:

土壤传感器、空气温湿度传感器、光照传感器先通过RS485总线汇聚到边缘网关,网关固件完成Modbus RTU协议解析后,将数据打包成MQTT消息,通过4G或Wi-Fi上报到EMQX broker。EMQX收到消息后,通过桥接或订阅转发给Spring Cloud设备接入服务,设备接入服务做数据清洗、物模型映射,然后把时序数据写入TDengine。业务系统从TDengine读取聚合数据,向uni-app客户端提供查询接口,同时根据阈值规则触发告警,把控制指令下发给网关。

这条链路里,每个环节对应的技术栈和职责如下:

链路节点技术选型核心职责
数据采集RS485 + Modbus RTU传感器数据汇聚到边缘网关
边缘处理边缘网关固件(嵌入式Linux)协议解析、数据清洗、本地缓存、断网续传
消息接入EMQX海量设备连接、MQTT消息路由、遗嘱/保留消息
业务处理Spring Cloud物模型映射、设备管理、告警规则、控制指令
时序存储TDengine传感器历史数据落盘、聚合查询、自动清理
应用端uni-app数据可视化、设备控制、告警通知

从这张表能看出来,每个组件各管一段,谁都别越权。最容易犯的错误是让业务系统直接对接设备协议,或者让时序库承担事务逻辑,一旦链路职责混乱,后面排查问题会非常痛苦。

1.2 为什么这套选型是“农业场景特配版”

很多人看到这套架构会觉得重,尤其是一个农业项目,搞四个大组件是不是杀鸡用牛刀。我的观点是,农业物联网的复杂度不在业务逻辑,在海量设备接入和弱网环境。大棚里的传感器可能只有几十台,但如果做到区域级示范项目,几千台设备同时在线是常态,而且农场网络环境远不如机房,断网、弱信号、设备离线频繁出现。

先说EMQX。它是基于MQTT协议的broker,专门干海量设备接入这件事。用RabbitMQ或者Kafka做设备接入不是不行,但MQTT协议里的连接保活、遗嘱消息、QoS分级、离线消息这些语义,通用消息队列要么不支持,要么得自己造轮子。我用过RabbitMQ做IoT接入测试,设备上线、掉线的感知要做一堆额外开发才能补全,而EMQX天然把这些能力都给了你。

再说TDengine。农业产生的传感器数据特点非常明确:持续写入、追加模型、按时间查询、越老的数据价值越低。MySQL能存,但一个月的数据量就能到几千万行,聚合查询慢得离谱,还得定期手工清理。TDengine是专门的时序数据库,自动按时间分片,保留策略内置,聚合下推,这些能力正是农业历史数据场景需要的。

Spring Cloud在这套系统里不是最炫的部分,但它解决了设备接入服务和业务系统之间的横向扩展问题。智慧农业如果只有一两个大棚,单体应用绰绰有余,但一旦接入多个农场、多类设备,设备接入、告警计算、数据分析之间就要解耦。Spring Cloud的注册发现、配置中心、熔断降级,可以保证某农场网络抖动导致的消息积压不至于拖垮整个业务系统。

uni-app则是最实际的选型——农业用户手机机型普遍普通,现场管理者不可能人人配一台旗舰机。uni-app同一套代码出小程序和App,少养一个原生开发团队,这在To B的农业项目里节省的成本非常可观。

2. Spring Cloud微服务按农业业务切片:别把IoT平台拆成一地鸡毛

2.1 智慧农业确实需要微服务,但不是照搬电商那套

很多团队一上Spring Cloud,就按订单、库存、用户那套电商思路来拆。智慧农业的微服务边界应该按“设备生命周期”和“农业生产环节”来切,而不是按普通业务模块切。我最终定的几个服务是:设备接入服务、告警服务、农事管理服务、数据分析服务、用户与权限服务。

设备接入服务吃EMQX转发的所有上行消息,做报文解析、物模型字段映射、设备状态更新。告警服务消费清洗后的设备数据,跑阈值规则,触发告警事件。农事管理服务管日常农事记录、用药施肥记录。数据分析服务读TDengine做统计报表。用户与权限服务管农场工人、管理员的账号和角色。

这样拆有几个好处。第一,设备接入服务是所有数据的入口,调整协议解析逻辑不影响上层业务;第二,告警服务可以独立扩容,因为告警计算吃CPU,高峰期需要更多实例;第三,农事管理和数据分析之间几乎没有同步依赖,异步走消息管道就行。

2.2 网关与注册中心:所有入口走一道门

Spring Cloud Gateway在这套系统里负责路由转发和统一鉴权。手机端、小程序端的所有请求都过网关,用的是JWT鉴权。设备端的MQTT连接则走EMQX的鉴权回调,网关只做HTTP层面的入口控制。

注册中心用的是Nacos。农业现场会出现网关和设备的网络断断续续,服务实例上下线频繁,Nacos的临时实例和心跳机制在这种场景下表现稳定。服务发现还有一个容易忽略的作用:设备接入服务如果做了多实例部署,EMQX的消息分发要保证同一个设备的消息固定落到同一个实例,否则设备状态会在不同实例间被反复覆盖。这个我当时没有特别注意,后来在设备上下线频繁的时候,出现了状态错乱的问题。

2.3 告警链路里最容易忽视的异步边界

告警是智慧农业里高频需求,比如土壤湿度低于阈值就触发灌溉提醒,棚内温度过高就报警。一开始很容易把告警判断直接写在设备接入服务的同步处理流程里,也就是消息一来,先判断规则,再发告警,全部在一个事务里完成。数据量小的时候没问题,上千台设备并发上报时,告警逻辑会阻塞设备接入主流程,直接把消息消费拖垮。

我的做法是告警服务独立消费一份转发出来的数据流。EMQX收到设备数据后,设备接入服务做正常的物模型处理和入库,同时把原始数据重新投递到另一个topic,告警服务异步消费,跑规则引擎后推送告警。这样即使告警规则写错了导致消费卡住,设备数据和业务主流程不受影响。物联网场景的第一原则就是:别让边缘环节影响核心数据链路。

3. 边缘网关固件:整套架构里最不起眼却最决定成败的一环

3.1 边缘网关承担的真实分量

Spring Cloud、EMQX、TDengine这些组件都有大量文档和社区讨论,真正容易被低估的是边缘网关固件的复杂度。农业现场的情况是:传感器品牌五花八门,协议各不相同,数据格式乱七八糟;网络环境不稳定,4G信号时好时坏;供电可能不稳,凌晨断电是家常便饭。

边缘网关要做好三件事:一是把底层五花八门的协议统一成标准格式;二是在网络中断时保证数据不丢,本地缓存待网络恢复后回传;三是响应云平台下发的控制指令,比如打开卷帘、启动水泵。第一件事考验的是协议解析能力,第二件事考验的是固件的可靠性和存储策略,第三件事考验的是实时性和指令确认机制。

3.2 为什么Modbus RTU解析必须下沉到网关

农用传感器现场最常用的是Modbus RTU,走RS485总线。Modbus RTU本身非常简洁:地址码、功能码、数据区、CRC校验。问题在于不同传感器的寄存器地址定义不一样,有的温度传感器数据在寄存器0x0001,有的在0x0034,数据格式有16位有32位,还有的有符号和无符号的区别。

如果把这些解析逻辑全部放在云平台做,云平台就得为每一种传感器维护一套解析模板。我的方案是边缘网关固件内部维护一张设备配置表,把各传感器的Modbus寄存器映射关系预配置好,网关定时轮询采集,然后将物理量转换成统一的JSON格式上报。云端只认JSON,不必关心Modbus协议细节。这样的设计让云平台从无休止的协议适配中解脱出来,新接入一种传感器时,只需要更新网关固件配置,云端服务一行代码都不用改。

这段话的意思是:协议解析是典型的“离线计算”工作,放在边缘最合理。云端做协议解析等于把田间的脏活累活全部搬到数据中心,既不经济也不实时。

3.3 固件上报载荷、会话保持与本地缓存的关键设计

固件上报的数据格式,我踩过简化不够的坑。一开始的JSON里带了一堆冗余字段,设备名称、描述信息全在里面,一条消息几百字节,上千设备上报时流量消耗非常可观。最终压缩后的格式大致如下:

{ "devId": "D-10001", "type": "env", "ts": 1715210000, "data": { "temp": 28.5, "hum": 62.3, "soilH": 45.2, "lux": 12000 } }

devId是设备编号,ts是Unix时间戳,data里只放传感器物理量。字段名尽量短,对端解析时用固定映射表。这套格式下来,一条消息控制在100字节以内,4G网络下的传输和流量成本都能接受。

MQTT连接设计方面,网关固件用Clean Session = false,客户端ID固定为设备唯一标识。这样网络波动导致连接断开时,EMQX侧能保留会话和离线消息,重连后设备不会错过云平台下发的控制指令。同时固件使用MQTT遗嘱消息,设备异常断电时,EMQX能第一时间感知并把设备标记为离线,这个能力对农业现场判断设备状态非常关键。

网络中断时,固件把采到的时序数据写入本地缓存队列,并记录断网起始时间。恢复联网后按批次补传,补传时要带上原始采样时间戳,这样在TDengine里归档的数据顺序才不会错。批次大小要根据网络质量动态调整,一次性传太多容易把好不容易恢复的网络又堵死。

关于本地缓存,还要考虑掉电保护。纯RAM队列断电就没,对于重要控制类数据必须及时落盘到Flash或SD卡。推荐用环形写指针方案,初始化时分配固定大小的环形存储区,写满之后覆盖最旧的数据,优先保证最新数据的完整性。

4. TDengine时序建模:一张超级表管全部传感器

4.1 为什么农业历史数据必须单独进时序库

农业数据写入量其实远超一般人的预期。一个标准大棚假设装6个传感器,采样间隔5分钟,一天就是1728条记录;一个示范园区按50个大棚算,一天86240条。MySQL存这些数据没问题,但真正的敌人是查询。要算“过去一个月每天平均温湿度”,MySQL的group by查询全表扫描加上磁盘IO,一个查询跑几十秒很正常。而TDengine这类时序库设计目标就是干这件事。

另一个维度是数据过期策略。农业历史数据的价值随时间递减,三年前的逐分钟采样数据基本没人看,但你又不能彻底删掉,因为可能有年度对比需求。TDengine的keep参数自动管理数据保留时长,老数据自动清理,不需要人工写后台定时任务去delete。

4.2 超级表、子表和标签的正确建模方式

TDengine的建模核心是超级表、子表和标签,理解三者的关系是使用第一步。模拟项目里我建了这样一张超级表:

CREATE STABLE sensor_data ( ts TIMESTAMP, value FLOAT, quality INT ) TAGS ( device_id VARCHAR(32), sensor_type VARCHAR(16), area VARCHAR(32), greenhouse_id VARCHAR(32) );

每台设备的一个传感器建立一张子表,比如D-1201这台设备的温度传感器对应子表sensor_d_1201_temp,在其tags里记录设备编号、传感器类型、所属区域和大棚编号。

CREATE TABLE sensor_d_1201_temp USING sensor_data TAGS ( 'D-1201', 'temp', 'EAST', 'GH-01' );

查询时直接对超级表按tags筛选即可,比如查1号大棚所有温度传感器最近一周的平均温度:

SELECT AVG(value) FROM sensor_data WHERE greenhouse_id = 'GH-01' AND sensor_type = 'temp' AND ts >= NOW() - 7d;

标签和字段的区别是很多新手会搞混的。标签用来存“不会高频变化的元数据”,比如设备编号、所属区域、大棚编号;字段用来存实际的采样数值。如果把设备编号放在字段里,明明只是元数据,查询却每次都得多带一个条件过滤,而且白占了存储和索引开销。

4.3 保留策略和聚合下推:三个月数据也能飞速算完

TDengine还支持自动降采样,也就是在写入时直接保留分钟级原始数据,同时生成小时级和天级聚合结果,存储在另外的库或表中。这样后期算日均温湿度、月均降雨量时,不需要扫原始表,直接从降采样表里查就可以。这是时序库比传统数据库在OLAP场景下快一个数量级的原因之一。

考虑到农场的存储成本,我把原始数据只保留30天,小时级聚合保留180天,天级聚合保留两年。这个配置可以根据实际情况调整,但思路是原始数据够排查近期问题、聚合数据支撑分析即可。很多人在这一步犹豫,觉得原始数据丢了可惜,实际上30天足够覆盖绝大多数调参和故障排查周期;真要长期分析,应该依赖聚合数据而不是原始采样。

5. 联调阶段的“版本答案”级坑:每次对接都是一次排雷

5.1 现场9000条历史数据暴击,把网关缓存直接打爆

第一次联调边缘网关和EMQX时,带了一个容灾场景测试:断开网络4小时,让网关持续采集和缓存数据,然后恢复网络。结果网络恢复后固件开始补传,9000多条积压数据瞬间向EMQX发送,EMQX本身没问题,但设备接入服务的消费速度跟不上,消息积压越来越严重,内存一路飙高。

这个问题的本质是边缘网关补传没有做流量控制。后来在固件侧加了两个机制:一是分批发送,每次最多传100条,发送完一批后等待确认再继续下一批;二是发送速率根据网络质量动态调整,网络信号弱时主动降速。同时EMQX侧对每个设备设置最大inflight窗口,避免单设备消息堆积打爆服务端。经过调整,9000条数据分几十批在十分钟内平稳补传完成,整个过程无积压无丢包。

5.2 EMQX在线状态判断:不是连上就万事大吉

设备上线容易判断,MQTT连接建立就是在线。但设备闪断、弱网导致的频繁重连会让状态判断变得不可靠。比如网关因信号弱断开了连接,都没有来得及发遗嘱消息,EMQX按Clean Session=false的会话逻辑还能维持会话,但实际连接已断开,此时设备到底算在线还是离线?

正确做法是结合MQTT遗嘱消息和Keep Alive机制。设备连接时携带遗嘱消息,遗嘱topic为device/status,payload为offline;设备正常下线时发送online=false,异常断开时EMQX在Keep Alive超时后自动发布遗嘱消息。Spring Cloud设备接入服务同时订阅设备状态topic和遗嘱topic,将设备最后一次心跳时间写入Redis。业务系统判断设备离线时,依据“最近心跳时间超过阈值且遗嘱已触发”双重条件,避免单独依赖某个信号导致的误判。

5.3 TDengine数据“看着丢了”——其实是时区惹的祸

现场反馈某一天的历史数据缺了好几个小时。排查后发现,边缘网关固件上报的时间戳用的是北京时间,但TDengine默认时区是UTC,写入时的行为在某些版本下会产生偏移。查看数据表里记录的时间,发现每条记录的时间都往前偏了8小时,正好和时区差吻合。

最初不清楚TDengine的正确做法,后来统一约定:所有写入TDengine的时间戳一律使用UTC+0,展示层由Spring Cloud根据用户所在时区转换后返回。边缘网关固件上报时,时间戳字段统一用UTC Unix时间戳,不带时区偏移。MySQL时代习惯用本地时间存储,迁移到时序库后这个习惯必须改掉,否则时区问题会反复咬人。

6. 选型只是入场券,链路稳定才是主战场

这套系统跑通后,最深刻的体会是,智慧农业的技术难点不在某一个组件的使用,而在整条链路在弱网、断电、设备异动等真实环境下的稳定性。Spring Cloud解决的是业务系统的伸缩性和可用性;EMQX解决的是设备接入层的连接管理;TDengine解决的是时序数据的存储和聚合效率;uni-app解决的是多端触达;边缘网关固件则决定了这套系统能不能在真实农场活下来。

如果再让我做一遍这个项目,有几个地方会调整。边缘网关固件会下沉更多边缘计算逻辑,比如把简单的阈值告警直接放在网关侧,即使断网也能本地触发声光报警,这样对农场的实际保护能力会更强。告警去重也会在消费端加一层窗口去重,避免同一告警在短时间内重复触发刷屏。生产环境中,控制指令下发后的执行反馈是必须做的,云平台下发“打开水泵”指令后,网关要回报“已执行”还是“执行失败”,否则前端就永远不知道设备到底动没动。

最后给正在做类似项目的朋友一个建议:不要一上来就想着把架构做到多大多全,先用最简单的技术组合把端到端的数据跑通——一个传感器、一个网关、一个MQTT broker、一个数据库、一个展示页面。链路通了之后,再逐步加入微服务、规则引擎、边缘计算这些复杂组件。加组件时想清楚它解决的是哪个具体痛点,而不是为了架构而架构。农业系统的成败不在架构的规模,而在每一次数据的准确到达和每一台设备的可靠控制。先把这条路走稳,再谈扩展和优化。

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

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

立即咨询