ESP32+Flask实现智能农业自动灌溉系统实战
2026/9/1 16:46:58 网站建设 项目流程

分享一个很有意思的农业智能化场景:当园区里的大部分机械设备和自动化装置“不需要天天有人操作”,这套系统依然能通过环境数据感知、自动灌溉和远程告警,让作物“不靠人工照看也能正常生长”。这种思路不只是停留在概念阶段,而是可以用常见的嵌入式硬件加 Web 服务完整落地。本文会从原理、硬件选型到代码逐步展开,属于物联网与农业自动化结合的入门级实战教程。

适合正在学习物联网开发、Python Web 开发或者对智能农业感兴趣的同学。读完这篇文章后,你能独立搭建一套带自动灌溉、环境监测和远程告警的水培种植系统,也能理解设备端与服务端之间是如何协作的。

1. 背景与核心概念

1.1 什么是“机械人不上班,照样能吃饱饭”

这句话可以从两个层面理解。

第一层是字面意思:农业机械设备本身不需要像人一样“打卡上班”,电力充足、程序跑起来之后,它就能持续工作;只要控制系统稳定,设备就可以在夜间、节假日、无人值守时自动调节灌溉、补光、通风。

第二层是引申含义:系统具备“自主决策”能力。这里的“自主决策”不一定是多复杂的 AI 算法,更多是指基于阈值判断和传感数据闭环,让设备在没有人到场的情况下,也能完成浇水、施肥、温湿度调节这些基础农事操作。

因此,这类系统的核心价值不是完全替代人,而是把重复性、规律性的操作交给自动化装置,让人从“守在大棚里盯着”变成“有异常才介入”。这样一来,即使园区的常规劳动力减少,作物也依然能被照顾得很好。

1.2 自动化种植解决了什么问题

传统种植中,浇水、施肥、光照管理依赖人的经验,对新手来说容易出现几个问题:

  • 浇水量不好控制,水多了容易沤根,水少了叶片萎蔫。
  • 临时出差或外出,植物无人照看。
  • 凭感觉施肥,容易造成肥害或者营养不良。
  • 环境变化不能及时感知,等发现异常时作物已经受害。

自动水培监测系统通过传感器采集土壤湿度、空气温湿度、光照强度等数据,用继电器控制水泵、补光灯、风扇,再结合后端 Web 平台记录历史数据,让种植过程变成果断执行的控制逻辑。

1.3 常见应用场景

这类系统的应用范围很广,不只是“阳台种菜”这么简单:

  • 小型植物工厂:多层水培架,自动循环营养液。
  • 家庭阳台种植:自动补水和补光,出差也能远程查看状态。
  • 农业大棚:控制卷帘、风机、水帘等设备。
  • 科研实验:记录不同环境下作物生长差异。
  • 教育演示:适合物联网课程教学和课程设计。

1.4 系统整体架构

下面的 ASCII 图展示了系统各部分的连接关系:

[传感器] DHT11 + 土壤湿度 + 光敏传感器 | v [ESP32 开发板] <-- 运行 MicroPython / Arduino 固件 | | Wi-Fi v [后端服务] Python + Flask/Django + MySQL/SQLite | | Web 页面 v [浏览器 / 手机] 查看数据、手动控制、接收告警

逻辑上分成三层:

  1. 感知层:传感器负责采集环境数据。
  2. 控制层:ESP32 负责读取数据,并根据阈值控制继电器。
  3. 应用层:后端服务负责记录数据、展示图表、发送告警。

2. 环境准备与版本说明

2.1 硬件准备

在开始之前,我们需要准备好以下硬件:

名称型号数量说明
主控板ESP32 DevKitC 或 NodeMCU-32S1自带 Wi-Fi,适合物联网
温湿度传感器DHT11 或 DHT221测量空气温度与湿度
土壤湿度传感器电容式土壤湿度模块1测量基质或土壤含水量
光敏传感器光敏电阻模块或 BH17501检测光照强度
继电器模块单路或双路继电器2控制水泵和补光灯
小型水泵直流 3.3V 或 5V 潜水泵1用于自动灌溉
LED 补光灯植物补光灯或任意 LED 灯带1模拟补光
电源5V/2A USB 电源1给开发板供电
杜邦线公对公、公对母若干连接线路
面包板面包板1搭建电路原型

DHT11 精度较低但便宜,适合学习验证;DHT22 精度更高,适合实际种植环境。土壤湿度传感器建议选电容式,这类传感器不容易被电解腐蚀,使用寿命更长。

2.2 软件准备

软件方面,我建议按下面的组合来安装:

  • Python 3.8 或以上版本,用于编写后端服务。
  • Flask 或 Django,本文示例使用 Flask,轻量易上手。
  • MicroPython 固件,刷入 ESP32 开发板。
  • 一个串口终端工具,比如 Putty 或 VS Code 里的 Serial Monitor。
  • 数据库使用 SQLite,零配置,适合教学演示。

版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。

2.3 开发板刷写 MicroPython

新买的 ESP32 通常不带 MicroPython 固件。我们可以先用esptool.py擦除与烧录。

安装 esptool:

pip install esptool

先按着开发板上的 BOOT 键,再插入 USB,然后运行:

esptool.py --port COM3 erase_flash

注意:Windows 端口名可能是COM3COM4,macOS 和 Linux 下通常是/dev/ttyUSB0/dev/tty.usbserial-xxxx

烧录固件:

esptool.py --chip esp32 --port COM3 write_flash -z 0x1000 esp32-20220117-v1.18.bin

这里的固件文件名需要改成你实际下载的版本。烧录完成后,打开串口终端,设置波特率 115200,就能看到 MicroPython 的交互提示符>>>

2.4 后端运行环境

为了快速验证,我们可以在项目根目录创建一个虚拟环境:

python -m venv venv

激活虚拟环境:

Windows:

venv\Scripts\activate

Linux/macOS:

source venv/bin/activate

然后安装依赖:

pip install flask flask-cors pymysql requests

如果你不想用 MySQL,可以先用 SQLite,后面再迁移。

3. 核心语法、配置或原理拆解

3.1 传感器数据读取原理

传感器本质上把物理量转换成电信号。例如 DHT11 通过单总线协议输出数字信号,ESP32 通过 GPIO 读取时序波形来解析温湿度数据。

在用 MicroPython 时,我们可以直接编写一个简单的 DHT 读取方法:

from machine import Pin import dht dht_sensor = dht.DHT11(Pin(4)) def read_dht(): try: dht_sensor.measure() temp = dht_sensor.temperature() humi = dht_sensor.humidity() return temp, humi except OSError as e: print("读取失败", e) return None, None

需要注意的是,DHT11 的采样间隔建议大于 1 秒,频繁读取容易造成传感器无响应。

3.2 阈值控制逻辑

自动控制不需要复杂的 PID 算法,对大多数家庭种植场景来说,阈值控制就够了。所谓的阈值控制,就是设置上限和下限,当超过或低于目标值时执行对应动作。

一个简单的逻辑如下:

SOIL_DRY = 40 PUMP_GPIO = 15 if soil_value < SOIL_DRY: relay_pin.value(1) # 开启水泵 time.sleep(5) # 持续浇水 5 秒 relay_pin.value(0) # 关闭水泵

这种策略的核心是“死区控制”,简单可靠,但不太适合精确调节营养液浓度。如果需要更平滑的控制,后续可以引入 PID 或模糊控制,不过对入门项目来说,阈值控制已经能解决 80% 的问题。

3.3 Wi-Fi 连接与数据上报

ESP32 连接 Wi-Fi 后,可以使用urequests库向后端发送 HTTP 请求。示例代码如下:

import network import urequests def connect_wifi(ssid, password): station = network.WLAN(network.STA_IF) station.active(True) if not station.isconnected(): station.connect(ssid, password) while not station.isconnected(): time.sleep(1) return station def report_data(data): url = "http://你的服务器IP:5000/api/upload" headers = {"Content-Type": "application/json"} response = urequests.post(url, json=data, headers=headers) response.close()

这里不推荐在程序里频繁使用同步urequests,因为网络阻塞会影响控制响应。但作为入门示例,这种写法最容易理解。

3.4 后端接口设计

后端服务提供两个核心接口:

  1. /api/upload:接收设备上报的环境数据。
  2. /api/status:Web 前端查询最新数据和设备状态。

我们可以再用一个/api/control接口实现手动控制,让用户在页面上点击“开泵”和“关泵”。

3.5 数据库表设计

建表不需要太复杂,一张环境数据表加一张控制记录表即可。以 MySQL 或 SQLite 为例:

CREATE TABLE sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, temp REAL NOT NULL, humi REAL NOT NULL, soil INTEGER NOT NULL, light INTEGER NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

这样的表结构已经可以支撑日常查询、画趋势图、生成日报等功能。

4. 完整实战案例

下面我们完整搭建一个“ESP32 + Flask”自动水培监测系统。

4.1 创建项目结构

建议先创建如下目录结构:

auto-plant/ ├── device/ │ └── main.py # ESP32 设备端程序 ├── server/ │ ├── app.py # Flask 后端服务 │ ├── requirements.txt │ └── plant.db # SQLite 数据库(自动生成) └── web/ └── index.html # 前端展示页面(可选)

这里的device/main.py是写入 ESP32 的 MicroPython 代码,server/app.py是运行在电脑或云服务器上的 Flask 服务。

4.2 编写 ESP32 设备端代码

将下面的代码保存为main.py,上传到 ESP32 开发板。

# 文件路径:device/main.py import time import network import urequests from machine import Pin, ADC import dht # 配置区 WIFI_SSID = "你的WiFi名称" WIFI_PASSWORD = "你的WiFi密码" SERVER_URL = "http://你的服务器IP:5000/api/upload" # 引脚定义 DHT_PIN = Pin(4) SOIL_PIN = ADC(Pin(34)) LIGHT_PIN = ADC(Pin(35)) RELAY_PUMP = Pin(15, Pin.OUT) RELAY_LIGHT = Pin(16, Pin.OUT) # 初始化 dht_sensor = dht.DHT11(DHT_PIN) SOIL_PIN.atten(ADC.ATTN_11DB) LIGHT_PIN.atten(ADC.ATTN_11DB) RELAY_PUMP.value(0) RELAY_LIGHT.value(0) # 连续读取次数,用于滤波 SAMPLES = 5 def connect_wifi(): station = network.WLAN(network.STA_IF) station.active(True) if not station.isconnected(): print("正在连接Wi-Fi...") station.connect(WIFI_SSID, WIFI_PASSWORD) while not station.isconnected(): time.sleep(1) print("连接成功,IP地址:", station.ifconfig()[0]) def read_soil(): values = [] for _ in range(SAMPLES): values.append(SOIL_PIN.read()) time.sleep(0.1) return sum(values) // len(values) def read_light(): values = [] for _ in range(SAMPLES): values.append(LIGHT_PIN.read()) time.sleep(0.1) return sum(values) // len(values) def auto_control(soil, temp): # 土壤湿度阈值,数值越小代表越干,需要根据不同传感器校准 if soil < 800: print("土壤偏干,开启水泵5秒") RELAY_PUMP.value(1) time.sleep(5) RELAY_PUMP.value(0) if temp > 35: print("温度偏高,打开风扇或降低光照强度") RELAY_LIGHT.value(1) else: RELAY_LIGHT.value(0) def report(temp, humi, soil, light): try: payload = { "temp": temp, "humi": humi, "soil": soil, "light": light } headers = {"Content-Type": "application/json"} response = urequests.post(SERVER_URL, json=payload, headers=headers) print("上报结果:", response.status_code) response.close() except Exception as e: print("上报失败:", e) def main(): connect_wifi() while True: try: dht_sensor.measure() temp = dht_sensor.temperature() humi = dht_sensor.humidity() except OSError as e: print("DHT读取失败,跳过本轮") temp = 0 humi = 0 soil = read_soil() light = read_light() print("温度: {}°C, 湿度: {}%, 土壤: {}, 光照: {}".format( temp, humi, soil, light )) auto_control(soil, temp) report(temp, humi, soil, light) time.sleep(30) if __name__ == "__main__": main()

代码说明:

  • 土壤湿度读取采用多次采样取平均值,减少传感器波动。
  • 自动控制逻辑写在auto_control函数中,方便修改策略。
  • 数据每 30 秒上报一次,避免频繁请求给服务器造成压力。
  • DHT11 读取失败时跳过本轮,以免程序崩溃。

4.3 编写 Flask 后端服务

server/app.py中编写后端服务。

# 文件路径:server/app.py from flask import Flask, request, jsonify, render_template_string import sqlite3 import time app = Flask(__name__) DB_PATH = "plant.db" def init_db(): conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, temp REAL NOT NULL, humi REAL NOT NULL, soil INTEGER NOT NULL, light INTEGER NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) """) conn.commit() conn.close() @app.route("/api/upload", methods=["POST"]) def upload(): data = request.get_json() temp = data.get("temp") humi = data.get("humi") soil = data.get("soil") light = data.get("light") if temp is None or humi is None or soil is None or light is None: return jsonify({"code": 1, "msg": "参数不完整"}), 400 conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() cursor.execute( "INSERT INTO sensor_data (temp, humi, soil, light) VALUES (?, ?, ?, ?)", (temp, humi, soil, light) ) conn.commit() conn.close() return jsonify({"code": 0, "msg": "ok"}) @app.route("/api/status", methods=["GET"]) def status(): conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() cursor.execute( "SELECT temp, humi, soil, light, create_time " "FROM sensor_data ORDER BY id DESC LIMIT 10" ) rows = cursor.fetchall() conn.close() if not rows: return jsonify({"code": 0, "data": []}) result = [] for row in rows: result.append({ "temp": row[0], "humi": row[1], "soil": row[2], "light": row[3], "create_time": row[4] }) return jsonify({"code": 0, "data": result}) @app.route("/", methods=["GET"]) def index(): return render_template_string(""" <!DOCTYPE html> <html> <head> <title>自动水培监控系统</title> <meta charset="utf-8"> </head> <body> <h2>自动水培监控系统</h2> <p>设备上报数据后,这里会展示最近10条记录。</p> <pre id="data"></pre> <script> fetch('/api/status') .then(res => res.json()) .then(data => { document.getElementById('data').innerText = JSON.stringify(data, null, 2); }); </script> </body> </html> """) if __name__ == "__main__": init_db() app.run(host="0.0.0.0", port=5000, debug=True)

Flask 默认监听 127.0.0.1,这里设置host="0.0.0.0"是为了让局域网内的 ESP32 或手机都能访问。

4.4 运行与验证

  1. 启动后端服务:
cd server python app.py

启动成功后控制台会显示:

* Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000
  1. device/main.py中 WiFi 和服务器地址改成实际值,上传到 ESP32。

  2. 打开浏览器访问http://127.0.0.1:5000,可以看到最新的设备上报数据。

  3. 观察 ESP32 串口输出,应该能看到类似内容:

温度: 26°C, 湿度: 58%, 土壤: 620, 光照: 2100 上报结果: 200

4.5 结果说明

到这里,整套系统已经跑通:

  • ESP32 每隔 30 秒采集一次环境数据。
  • 数据通过 Wi-Fi 上报到 Flask 服务。
  • 服务端把数据存入 SQLite 数据库。
  • 用户可以通过浏览器查看最近的状态记录。
  • 控制逻辑会在土壤偏干时自动打开水泵,补水 5 秒后关闭。

这套流程的核心价值在于:即使没有人在现场,“确定性的控制策略”让设备独立完成了一次灌溉决策。

5. 常见问题与排查思路

在实际部署过程中,最容易遇到下面几个问题。

5.1 ESP32 无法连接 Wi-Fi

问题现象常见原因解决思路
串口打印反复出现“正在连接Wi-Fi”密码错误、Wi-Fi 频段不兼容、信号弱检查 SSID 和密码;确认路由器是否为 2.4GHz;靠近路由器重试
连接后不久掉线电源供电不足换成 5V/2A 稳定电源,避免使用劣质 USB 线

ESP32 部分开发板只支持 2.4GHz Wi-Fi,如果路由器开了 5GHz 和 2.4GHz 双频合一,建议在开发测试阶段单独开启一个 2.4GHz 热点。

5.2 传感器读数一直不变

问题现象常见原因解决思路
土壤湿度读数始终是最大值或最小值引脚配置错误、传感器接线松动检查 ADC 引脚是否可读;换一个引脚重试
温湿度读到 0 或异常值DHT11 供电电压不稳、数据线过长给传感器独立供电;缩短接线距离;检查上拉电阻

ADC 引脚在读取模拟传感器时,需要配置atten(ADC.ATTN_11DB),否则输入电压范围默认很小,读数结果会异常。

5.3 继电器不动作

问题现象常见原因解决思路
代码执行了value(1),但继电器不响继电器高电平触发/低电平触发搞反先查模块规格书,将value(1)改为value(0)测试
开发板复位后继电器误动作GPIO 默认电平不确定初始化阶段显式把继电器引脚设为 0

继电器模块建议使用光耦隔离版本,板载信号指示灯的型号更方便排查。

5.4 后端接口 404 或 500

问题现象常见原因解决思路
访问/api/upload返回 404路由写错或服务未重启检查 URL 是否拼写正确,重启 Flask 服务
返回 500数据库表未创建手动执行init_db(),或检查数据库路径权限

5.5 设备端上报失败

问题现象常见原因解决思路
打印“上报失败”服务器地址不可达ESP32 与电脑连同一个局域网;检查防火墙是否允许 5000 端口
上报很慢DNS 解析或网络延迟直接用 IP 地址代替域名,减少 DNS 查询

如果服务器部署在云主机,需要到云控制台的安全组放行 5000 端口,并确认服务监听了0.0.0.0

6. 最佳实践与工程建议

6.1 硬件层面

  • 传感器供电和继电器供电尽量独立,避免电机启动瞬间拉低电压导致开发板复位。
  • 水泵必须放在低处且保证进水口浸没在水中,防止空转烧毁。
  • 接线使用不同颜色区分正负极,并在关键节点做标记。
  • 长期使用场景中,定期校准土壤湿度传感器,因为湿润环境会让电极状态漂移。

6.2 软件层面

  • 控制逻辑要设置“手动模式”和“自动模式”的切换开关,方便调试。
  • 水泵每次开启时间不要太长,建议在程序里加上最大运行时长限制,防止传感器故障时一直浇水。
  • 上报数据增加设备 ID 字段,方便未来接入多个设备。
  • 传感器读取失败要重试而不是直接跳过,否则容易造成控制策略误判。
  • 不要在生产环境使用 Flask 自带服务器,稳定性不够,建议改用 Gunicorn 或 uWSGI。

6.3 数据层面

  • 记录数据时应该一并记录设备状态、控制动作和操作人,方便追溯。
  • 定期清理旧数据,避免 SQLite 表无限膨胀。
  • 如果数据需要长期分析,建议从 SQLite 迁移到 MySQL 或 TimescaleDB。

6.4 安全与可靠性

  • 设备端上报接口不需要暴露到公网时,尽量只在局域网内部访问。
  • 如果必须远程访问,不要直接开放数据库端口;建议通过后端 API 加简单 Token 鉴权。
  • 涉及自动灌溉的代码,要谨慎处理液位传感器,没有液位数据时不要盲目启动水泵。
  • 任何涉及实际生产环境的变更,都应该先在测试设备上验证,再上真实种植场景。

6.5 控制策略升级

阈值控制是第一步。如果希望系统更智能,可以逐步引入:

  • 分段式灌溉:根据一天中的不同时段调整干湿阈值。
  • 模糊控制:把温湿度、土壤湿度组合成模糊规则表。
  • PID 控制:用在营养液 EC 值调节等需要稳定控制的场景。
  • 简单预测:结合未来几小时的天气预报,决定是否提前补水。

这些升级方向都不需要推翻现有架构,只需要调整服务端策略模块。

7. 总结与学习路线

通过这套实战案例,你已经掌握了一条完整的物联网应用链路:从传感器采集数据、ESP32 上报数据,到 Flask 后端接收存储,再到浏览器展示数据。同时你也理解了阈值控制在自动化种植中的应用价值。

下一步可以往这几个方向继续深入:

  • 学习 MQTT 协议,用 EMQX 或 Mosquitto 取代 HTTP 上报,降低设备功耗,提升实时性。
  • 学习 Vue 或 React,做一个更美观的数据大屏。
  • 学习 Docker 部署,把后端打包成容器镜像,方便迁移到云服务器。
  • 学习 PID 控制算法,应用到营养液循环系统中。
  • 接入摄像头,利用图像识别判断作物生长状态,真正做到“不用人看,也能知道长得好不好”。

自动化种植的难点往往不在硬件本身,而在于对环境变化的准确感知和可靠控制。建议你在真实环境中多跑几天收集数据,再根据数据调整灌溉阈值。每一次控制记录的积累,都是后续优化策略的基础。

如果这篇文章对你有帮助,欢迎收藏备用。实际搭建过程中遇到其他报错,也可以顺着接口、数据库、网络三个方向逐层排查。设备没反应就先看串口,接口不对就先看日志,数据没进来就先看数据库,大多数问题都能用这套思路快速定位。

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

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

立即咨询