☰
微信小程序+物联网智慧农业平台:从传感器到MQTT的完整数据链路实战
2026/10/22 3:57:02 网站建设 项目流程

简介:这是一套面向计算机相关专业学生与开发者的微信小程序智慧农业平台完整项目源码,采用前端小程序、后端服务与物联网设备集成三层架构,适合作为毕业设计、课程设计、学科竞赛或项目立项演示的参考方案,也便于初学者在真实工程中进阶学习。压缩包共441个文件,约3.59MB,以js逻辑脚本、json配置、wxss样式与wxml页面结构为主,辅以png图片资源、tsx与ts组件代码、md说明文档及少量wxs、html、css文件,前后端与设备通信模块划分清晰,便于按目录检索与二次开发。项目代码完整、资料齐全,含设计文档,并经过严格测试可正常运行,读者可借鉴其页面组织、接口调用与物联网数据交互思路,在此基础上修改扩展新功能,或直接用于毕设、课设与作业提交。目前已有56人学习关注,遇到配置与运行问题还可获得远程教学支持。

1. 从一份“智慧农业平台”压缩包说起:它到底能跑通什么

如果你手里拿到一个名为“微信小程序智慧农业平台(前端+后端+物联网设备集成).zip”的压缩包,第一反应大概率是:这玩意儿到底能不能跑起来?它是不是又一个只有界面、没有真实数据流的演示壳子?我最初接触这类项目时也踩过同样的坑——前端页面漂亮,后端接口齐全,但一提到“物联网设备集成”,就只剩一个写死的 JSON 文件在假装传感器上报。真正能落地的智慧农业平台,核心不在页面多好看,而在于一条完整的数据链路:传感器采集 → 网关上报 → 后端解析入库 → 小程序拉取展示 → 用户远程控制。这条链路里任何一环断了,项目就只是个“大作业”。

这个标题里的关键词拆开看:微信小程序是用户触达层,智慧农业是业务场景,前端+后端+物联网设备集成是技术栈范围。它适合两类人:一是想学完整 IoT 项目架构的学生或转行者,二是需要快速搭一套农业监测原型的开发者。你不需要真的有一片农田,用 ESP32 加几个传感器就能模拟出土壤湿度、空气温湿度、光照强度的真实数据流。接下来我会按“先跑通最小闭环,再补细节”的思路,把这份压缩包背后的技术选型、代码实现和踩坑点讲清楚。

2. 技术选型与最小闭环:为什么是 MQTT + Spring Boot + 微信小程序

2.1 物联网设备侧:ESP32 + DHT22 + 土壤湿度传感器的组合逻辑

在农业场景里,最常采集的物理量是空气温湿度、土壤含水量和光照强度。我一般会选 ESP32 作为主控,原因很直接:它自带 Wi-Fi,能直接跑 MQTT 协议栈,价格低,社区资料多。传感器方面,DHT22 测空气温湿度,电容式土壤湿度传感器测含水率,BH1750 测光照。这三个传感器都是数字接口或 I2C 接口,接线简单,不容易受模拟噪声干扰。

设备端的核心任务只有两个:定时采集数据并通过 MQTT 发布到 Broker;订阅控制主题,收到指令后驱动继电器或舵机。下面是一个最小可用的 Arduino 代码框架,基于 PubSubClient 库。

#include <WiFi.h> #include <PubSubClient.h> #include <DHT.h> #define DHTPIN 4 #define DHTTYPE DHT22 #define SOIL_PIN 34 #define LIGHT_PIN 35 DHT dht(DHTPIN, DHTTYPE); WiFiClient espClient; PubSubClient client(espClient); const char* ssid = "your_wifi_ssid"; const char* password = "your_wifi_password"; const char* mqtt_server = "your_broker_ip"; void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } client.setServer(mqtt_server, 1883); client.setCallback(callback); } void callback(char* topic, byte* payload, unsigned int length) { // 解析控制指令,比如 {"pump":1} 表示打开水泵 String msg; for (unsigned int i = 0; i < length; i++) { msg += (char)payload[i]; } Serial.println("Control message: " + msg); // 这里根据 msg 内容驱动 GPIO } void loop() { if (!client.connected()) { reconnect(); } client.loop(); float airTemp = dht.readTemperature(); float airHum = dht.readHumidity(); int soilRaw = analogRead(SOIL_PIN); int lightRaw = analogRead(LIGHT_PIN); // 土壤湿度原始值转百分比,需根据实际传感器标定 float soilPercent = map(soilRaw, 4095, 0, 0, 100); String payload = "{\"airTemp\":" + String(airTemp) + ",\"airHum\":" + String(airHum) + ",\"soil\":" + String(soilPercent) + ",\"light\":" + String(lightRaw) + "}"; client.publish("farm/sensor/data", payload.c_str()); delay(10000); // 10 秒上报一次,实际农业场景可放宽到 5 分钟 }

这段代码里最关键的参数是delay(10000)和土壤湿度的map映射。农业环境变化慢,10 秒上报一次对演示足够,但真实部署时建议 5 到 15 分钟一次,否则电池撑不住。土壤湿度传感器的原始值范围因供电电压和传感器批次而异,必须做两点标定:把传感器放在空气中读一个最大值,插进水里读一个最小值,然后用map反向映射。不标定的话,百分比数据毫无意义。

2.2 后端服务:Spring Boot 接收 MQTT 消息并落库

后端我选 Spring Boot,因为它对 MQTT 的集成方案成熟,用 Spring Integration MQTT 或者 Eclipse Paho 客户端都能快速接入。核心逻辑是:订阅farm/sensor/data主题,收到消息后解析 JSON,写入 MySQL 或时序数据库,同时通过 WebSocket 推送给小程序端做实时刷新。

下面是一个基于 Paho 客户端的最小消费者代码。

@Component public class MqttConsumer { private static final String BROKER = "tcp://your_broker_ip:1883"; private static final String TOPIC = "farm/sensor/data"; @PostConstruct public void subscribe() throws MqttException { MqttClient client = new MqttClient(BROKER, MqttClient.generateClientId()); MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(true); options.setCleanSession(true); client.connect(options); client.setCallback(new MqttCallback() { @Override public void messageArrived(String topic, MqttMessage message) throws Exception { String payload = new String(message.getPayload()); // 解析 JSON 并保存到数据库 SensorData data = JSON.parseObject(payload, SensorData.class); sensorDataRepository.save(data); // 通过 WebSocket 推送给前端 webSocketHandler.broadcast(payload); } @Override public void connectionLost(Throwable cause) { System.out.println("MQTT connection lost: " + cause.getMessage()); } @Override public void deliveryComplete(IMqttDeliveryToken token) {} }); client.subscribe(TOPIC, 1); // QoS 1,至少送达一次 } }

这里有两个参数值得注意:setAutomaticReconnect(true)和 QoS 等级。自动重连是必须的,农业现场网络不稳定,没有重连机制的话,断网后数据就永久丢失了。QoS 设为 1 表示“至少送达一次”,适合传感器数据场景;如果设成 2,开销太大,设成 0 又可能丢数据。数据库表结构至少包含id、device_id、air_temp、air_hum、soil_percent、light_raw、create_time这几个字段,create_time建索引,方便按时间范围查询历史曲线。

2.3 微信小程序端:实时数据展示与远程控制

小程序端不需要复杂的框架,原生 WXML + JS 就够。核心页面有两个:一个是仪表盘,展示当前温湿度和土壤湿度;另一个是控制页,提供水泵、补光灯的开关按钮。数据获取方式有两种:轮询后端 REST 接口,或者用 WebSocket 接收推送。我一般会优先用 WebSocket,因为农业监控对实时性有一定要求,轮询太浪费请求。

// pages/dashboard/dashboard.js Page({ data: { airTemp: '--', airHum: '--', soil: '--', light: '--' }, onLoad() { this.connectWebSocket(); }, connectWebSocket() { const that = this; wx.connectSocket({ url: 'ws://your_server_ip:8080/ws/farm' }); wx.onSocketMessage(function(res) { const data = JSON.parse(res.data); that.setData({ airTemp: data.airTemp, airHum: data.airHum, soil: data.soil, light: data.light }); }); wx.onSocketClose(function() { // 断线后 3 秒重连 setTimeout(() => that.connectWebSocket(), 3000); }); }, togglePump(e) { const status = e.detail.value ? 1 : 0; wx.request({ url: 'http://your_server_ip:8080/api/control/pump', method: 'POST', data: { status: status }, success(res) { wx.showToast({ title: '指令已发送', icon: 'success' }); } }); } });

小程序端的坑主要在 WebSocket 的域名校验和断线重连。开发阶段可以在微信开发者工具里勾选“不校验合法域名”,但上线前必须配置 HTTPS 和 WSS。断线重连逻辑一定要写,否则用户切到后台再回来,页面就卡死了。控制指令下发后,后端需要把指令通过 MQTT 发布到farm/control/pump主题,设备端订阅该主题并执行动作。

3. 把压缩包跑起来:环境搭建与联调步骤

3.1 本地 MQTT Broker 的安装与配置

压缩包里通常不会自带 Broker,你需要自己搭一个。最省事的是用 Mosquitto,Windows 和 Linux 都有现成安装包。安装完成后,默认只监听 localhost,要让 ESP32 和小程序后端都能连上,必须改配置文件。

# mosquitto.conf 关键配置 listener 1883 0.0.0.0 allow_anonymous true

listener 1883 0.0.0.0表示监听所有网卡,allow_anonymous true允许匿名连接。生产环境当然要加用户名密码,但本地联调阶段先跑通再说。改完配置后重启 Mosquitto 服务,用mosquitto_sub -t "farm/sensor/data" -v命令测试订阅,如果能看到 ESP32 上报的数据,说明 Broker 这一环通了。

3.2 后端数据库表结构与接口联调

数据库我建议用 MySQL 8.0,建一张sensor_data表和三张控制状态表。表结构不用太复杂,关键是字段类型要匹配。温度用DECIMAL(5,2),湿度用DECIMAL(5,2),土壤百分比用TINYINT,光照原始值用INT。时间字段用DATETIME并建索引。

CREATE TABLE sensor_data ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(64) NOT NULL, air_temp DECIMAL(5,2), air_hum DECIMAL(5,2), soil_percent TINYINT, light_raw INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_device_time (device_id, create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

后端接口至少提供三个:GET /api/sensor/latest返回最新一条数据,GET /api/sensor/history?hours=24返回历史曲线数据,POST /api/control/pump下发控制指令。联调顺序建议是:先用 Postman 测后端接口,确保数据库有数据;再用 MQTT 客户端模拟设备上报,确认后端能收到;最后打开小程序开发者工具,看 WebSocket 是否推送成功。

3.3 小程序开发者工具的真机调试要点

微信开发者工具模拟器里 WebSocket 和 HTTP 请求都能跑,但真机调试时经常翻车。最常见的问题是手机和电脑不在同一个局域网,或者电脑防火墙拦了 8080 端口。解决办法很简单:确保手机和电脑连同一个 Wi-Fi,在电脑上关闭防火墙或放行 8080 和 1883 端口,小程序里请求的 IP 换成电脑的局域网 IP,比如192.168.1.100。

另一个坑是微信小程序对ws://的限制。开发阶段可以在开发者工具里勾选“不校验合法域名”,但真机预览时这个选项不生效。如果一定要真机测试 WebSocket,要么用wss://加自签名证书,要么用微信开发者工具的真机调试功能,它允许临时绕过域名校验。我一般会建议先在模拟器里把逻辑跑通,真机调试只验证 UI 和交互,网络层用工具代理。

4. 避坑与排查:那些让项目卡住的真实问题

4.1 传感器数据跳变严重,曲线像心电图

现象:土壤湿度读数在 30% 到 80% 之间反复横跳,空气温度偶尔跳到 80 度。原因通常有两个:一是传感器供电不稳,ESP32 的 3.3V 输出带不动多个传感器;二是没有做软件滤波。解决办法是给传感器单独供电,或者在代码里加滑动平均滤波。我一般会连续读 5 次,去掉最大值和最小值,剩下的取平均。DHT22 本身有 2 秒的采样间隔限制,读太快会返回 NaN,必须加delay(2000)以上。

4.2 MQTT 消息丢失,数据库里缺数据

现象:设备端明明发了 100 条数据,数据库里只有 70 条。原因可能是 QoS 设成了 0,或者 Broker 的max_queued_messages太小。解决方法是把发布和订阅的 QoS 都设为 1,并在 Broker 配置里把max_queued_messages调到 1000 以上。另外,后端消费者如果处理太慢,也会导致消息积压,建议把入库操作改成异步,用线程池处理。

4.3 小程序 WebSocket 频繁断连

现象:小程序切到后台再回来,WebSocket 就断了,页面数据不再更新。原因是微信小程序在后台运行超过 5 秒会挂起网络连接。解决办法是在onShow生命周期里重新连接 WebSocket,并在onHide里主动关闭旧连接。重连逻辑要加指数退避,第一次 1 秒,第二次 2 秒,第三次 4 秒,避免频繁重连把服务器打挂。

4.4 控制指令下发后设备没反应

现象:小程序点了“打开水泵”,后端日志显示指令已发布,但设备端没动作。原因通常是主题不匹配或 payload 格式不对。设备端订阅的是farm/control/pump,后端发布到了farm/control/pump/1,主题多了一层。或者 payload 是{"status":1},设备端却按纯文本1解析。排查方法是用mosquitto_sub订阅控制主题,看后端到底发了什么,再对照设备端代码的解析逻辑。

4.5 数据库时间字段时区不对

现象:小程序展示的历史曲线时间比实际时间少了 8 小时。原因是 MySQL 的DATETIME不存时区,Java 的LocalDateTime默认用 UTC。解决办法是在 JDBC 连接串里加serverTimezone=Asia/Shanghai,或者统一用timestamp类型并在应用层做时区转换。我一般会在后端返回给小程序之前,把时间格式化成yyyy-MM-dd HH:mm:ss字符串,避免前端再处理时区。

5. 进阶技巧:让平台从“能跑”变成“好用”

5.1 用规则引擎做自动控制,而不是手动点按钮

手动控制只是演示,真实农业场景需要自动决策。最简单的规则是:土壤湿度低于 30% 且空气温度高于 25 度时,自动打开水泵 5 分钟。这个逻辑可以放在后端,用 Spring 的@Scheduled定时任务每 5 分钟检查一次最新数据,满足条件就发布控制指令。更复杂的场景可以引入 Drools 或 Easy Rules,把规则写成配置文件,不用改代码就能调整阈值。

@Scheduled(fixedRate = 300000) // 每 5 分钟执行一次 public void autoIrrigation() { SensorData latest = sensorDataRepository.findTopByOrderByCreateTimeDesc(); if (latest.getSoilPercent() < 30 && latest.getAirTemp() > 25) { mqttPublisher.publish("farm/control/pump", "{\"status\":1,\"duration\":300}"); // 5 分钟后自动关闭 scheduler.schedule(() -> { mqttPublisher.publish("farm/control/pump", "{\"status\":0}"); }, 5, TimeUnit.MINUTES); } }

这段代码的关键是duration参数和延迟关闭逻辑。设备端收到{"status":1,"duration":300}后,可以自己计时 300 秒后关闭,也可以等后端再发一条关闭指令。我倾向于让设备端自己计时,因为网络断了也不影响关闭动作,更安全。

5.2 历史数据降采样,让曲线加载快 10 倍

小程序查 24 小时历史数据时,如果每 10 秒一条,一天就是 8640 条,前端渲染会卡。解决办法是在后端做降采样:按 5 分钟粒度聚合,取平均值。SQL 里用GROUP BY加FLOOR(UNIX_TIMESTAMP(create_time)/300)就能实现。

SELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(create_time)/300)*300) AS time_bucket, AVG(air_temp) AS avg_temp, AVG(air_hum) AS avg_hum, AVG(soil_percent) AS avg_soil FROM sensor_data WHERE device_id = 'device_001' AND create_time > DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY time_bucket ORDER BY time_bucket;

这样 24 小时的数据量从 8640 条降到 288 条,小程序画曲线丝滑很多。降采样的粒度可以根据时间范围动态调整:查 1 小时用 1 分钟粒度,查 7 天用 30 分钟粒度。

5.3 设备离线告警:用 Redis 过期键做心跳检测

设备是否在线,不能靠数据库里的最后一条数据时间来判断,因为查询太频繁。我一般用 Redis 的过期键:设备每次上报数据时,后端执行SET device:001:online 1 EX 60,60 秒内没有新数据,键自动过期。后端定时任务扫描所有设备键,发现某个键不存在就发告警。

public void checkDeviceOnline() { Set<String> keys = redisTemplate.keys("device:*:online"); List<String> allDevices = deviceRepository.findAllDeviceIds(); for (String deviceId : allDevices) { String key = "device:" + deviceId + ":online"; if (!redisTemplate.hasKey(key)) { alertService.sendOfflineAlert(deviceId); } } }

这个方案的好处是零额外查询开销,Redis 的过期机制天然适合心跳场景。唯一要注意的是 Redis 的notify-keyspace-events配置,如果需要实时告警而不是定时扫描,可以开启键过期事件监听。

5.4 小程序端用 ECharts 画曲线,注意 canvas 层级问题

小程序里画历史曲线,最顺手的是 ECharts 的微信小程序版本。但有个坑:canvas 是原生组件,层级最高,会盖住弹窗和按钮。解决办法是用cover-view包裹需要浮在 canvas 上的元素,或者把 canvas 放在页面底部,弹窗用position: fixed加z-index但依然可能被盖。我一般会把曲线图放在单独页面,避免和交互元素混在一起。

// 引入 echarts 小程序组件后 import * as echarts from '../../components/ec-canvas/echarts'; function initChart(canvas, width, height, dpr) { const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: dpr }); canvas.setChart(chart); const option = { xAxis: { type: 'category', data: timeList }, yAxis: { type: 'value' }, series: [{ data: soilList, type: 'line', smooth: true }] }; chart.setOption(option); return chart; }

devicePixelRatio一定要传,否则在高分屏上曲线会模糊。数据量大的时候,把smooth设为false,减少渲染计算。

5.5 用 Docker Compose 一键拉起整套后端

最后分享一个我常用的部署技巧:把 MySQL、Mosquitto、Spring Boot 后端写成 Docker Compose,换台机器也能三分钟跑起来。

version: '3.8' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: smart_farm ports: - "3306:3306" mosquitto: image: eclipse-mosquitto:2.0 ports: - "1883:1883" volumes: - ./mosquitto.conf:/mosquitto/config/mosquitto.conf backend: build: ./backend ports: - "8080:8080" depends_on: - mysql - mosquitto

这个 Compose 文件里,mosquitto.conf就是前面提到的监听配置。后端 Dockerfile 里把 Spring Boot 的 jar 包复制进去,用java -jar启动。唯一要注意的是后端连接 MySQL 和 Mosquitto 的地址要改成服务名mysql和mosquitto,而不是localhost。

这套方案我从头跑通过三次,每次卡住的地方都不一样,但归根结底都是网络配置和参数标定。最深的教训是:不要一上来就追求功能全,先把“传感器→Broker→后端→小程序”这条线跑通,哪怕数据是假的,链路通了再逐个替换真实模块。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询