作为一个计算机专业的学生,尤其是正在准备毕业设计或课程设计的人,你大概率在很多资源网站和群里看到过这样一类项目:“基于ESP32的物联网智能家居环境检测节点”。这类题目看起来不难,但真到自己动手时,往往卡在几个非常尴尬的位置:ESP32 的程序怎么烧都报错,WiFi 连不上,传感器读数始终是 0,后端接口写好了但前端页面死活拿不到数据。最后明明觉得“这东西原理也不难”,却连一个能完整演示的版本都跑不起来。
这篇文章要写的,就是这类物联网环境检测项目的完整拆解。我会基于这个“A86 基于 ESP32 的物联网智能家居环境检测节点”演示项目,把硬件端、后端接口、Vue 前端展示这三层链路分别讲清楚,并给出一套可以直接复制运行的示例代码。你读完以后,不仅知道每一段代码是什么意思,还能自己动手把整个闭环跑通,并且懂得怎么把它扩展成一份能通过答辩的毕业设计或课程设计项目。
为什么这件事值得专门写一篇文章?因为网上关于 ESP32 的教程很多,但大多数只停留在“点灯”“读取传感器串口打印”这一步。真正到了要做成“系统”的时候,就涉及到前后端联调、数据存储、接口设计、页面展示这些工程问题。这些内容恰恰是学生在校期间最缺、也最需要补的环节。本文的目的就是帮你把这些断层接上。
1. 这篇文章真正要解决的问题
如果你只是想学“ESP32 怎么用”,那你随便搜一个点灯教程就够了。但这篇文章面向的是更具体的一类需求:把这个硬件节点做成一个可以让老师或评委看到的完整系统。
项目标题里提到了几个关键词:ESP32、物联网、python、vue、管理系统、网页制作。把这些词拼起来,你就能得到一个非常典型的物联网毕设架构:
- ESP32 作为终端节点,负责采集温湿度、光照、空气质量等环境数据。
- ESP32 通过 WiFi 将数据上报到后端服务。
- 后端服务负责接收、存储和提供查询接口。
- 前端页面(Vue)负责展示实时数据和历史数据。
很多学生卡住的核心问题有三个:
第一,不知道怎么把传感器数据从 ESP32 送到后端。很多人会写 Arduino 代码读取传感器,但一到 HTTP POST、JSON 序列化就懵了。实际上这个环节的逻辑很简单,难在代码细节和环境配置。
第二,后端接口不知道怎么设计。有些同学用 Python 写了一个 Flask 接口,只写了接收数据,没有查询接口,前端根本用不了。有些写了接口,但没处理跨域,前端请求直接失败。
第三,前端不是不会写,而是不知道怎么和真实数据对接。Vue 的基本语法大家都能看懂,页面布局也能做,但到了用 axios 请求真实接口、处理异步数据、定时刷新这些环节,就开始手忙脚乱。
这篇文章会围绕这三件事展开。你可以把它当成一套“最小可运行版本”的完整参考实现。代码不追求复杂的架构设计,但每一步都能跑通。
2. 系统架构与核心概念
在写代码之前,先把系统架构讲清楚。这不是空谈,而是后面所有代码的组织依据。
2.1 整体架构
整个系统的数据流可以概括为一条链路:
传感器硬件 -> ESP32 开发板 -> WiFi 网络 -> 后端 API -> 数据库 -> Vue 前端页面从开发视角看,可以拆成三层:
| 层级 | 技术选型 | 职责 |
|---|---|---|
| 感知层 | ESP32 + DHT11/DHT22 + 光敏电阻/空气质量传感器 | 采集环境数据,通过 WiFi 上报 |
| 服务层 | Python Flask 后端 + MySQL/SQLite | 接收数据、存储数据、提供 REST API |
| 展示层 | Vue 3 + Vite + Element Plus | 调用后端接口,展示实时数据与历史趋势 |
这套架构最大的好处是每一层都可以单独调试。你可以先用串口监视器看 ESP32 的数据,再用 Postman 测后端接口,最后才让 Vue 页面联动。如果直接一把梭从头写到尾,出了问题很难定位。
2.2 为什么选这套技术栈
先说 ESP32。它和传统 Arduino Uno 相比,最大的优势是自带 WiFi 和蓝牙,主频也更高。做物联网环境检测节点,如果用 Arduino Uno 还要外接 ESP8266 模块,既麻烦又容易出接触不良的问题。ESP32 一片芯片就能搞定采集和联网,是目前物联网入门和毕设项目的主流选择。
再说 Python Flask。很多学生学过 Python,用 Flask 写一个轻量后端服务学习成本最低。它不像 Spring Boot 那样需要理解依赖注入和繁琐的配置,也不像 Node.js 那样要求你熟悉 JavaScript 生态。Flask 写一个能接收 POST 请求、提供 GET 查询的接口,核心代码不超过 50 行。
最后是 Vue。Vue 在国内前端开发中占有很高的地位,而且对新手相对友好。用 Vue 3 + Vite 搭建项目,配合 Element Plus 组件库,做一个环境数据看板非常快。如果你之前只写过静态 HTML 页面,这篇文章里的前端代码会让你看到动态数据渲染是怎么一回事。
2.3 技术选型的重要提醒
这套技术栈适合的是课程设计和毕业设计,不一定适合直接当生产级物联网平台。如果做真实的商用产品,你需要考虑 MQTT 协议、设备管理、数据加密、消息队列、时序数据库等问题。但作为学习和演示项目,用 HTTP + JSON + Flask + Vue 已经完全足够,而且更容易让评委看懂你的代码结构。
3. 环境准备与硬件清单
这里先把开发环境列清楚。不同版本的软件在配置上可能有差异,下面列的是比较通用的方案。
3.1 硬件清单
| 设备 | 说明 |
|---|---|
| ESP32 开发板 | 推荐 ESP32 DevKitC V4 或 NodeMCU-32S,带 USB 接口即可 |
| DHT11 或 DHT22 | 温湿度传感器,DHT22 精度更高 |
| 光敏电阻模块 | 检测光照强度,输出模拟量 |
| MQ-2 或 MQ-135 | 空气质量/烟雾检测(可选) |
| 杜邦线 | 连接传感器与开发板 |
| 面包板 | 方便接线 |
| USB 数据线 | 注意部分数据线只能充电不能传数据 |
3.2 软件环境
开发 ESP32 推荐用 Arduino IDE,同时需要安装 ESP32 开发板支持包。安装步骤网上有很多,这里提醒一个关键点:国内网络环境下,直接在 Arduino IDE 的“开发板管理器”中搜索 ESP32 可能很慢,甚至失败。常见方案是配置国内镜像源,或者下载离线安装包手动安装。
我建议你按以下顺序准备环境:
- 安装 Arduino IDE(建议 2.x 版本,界面更友好,也兼容旧教程)。
- 在“文件 -> 首选项 -> 附加开发板管理器网址”中添加 ESP32 的 JSON 地址。
- 打开“工具 -> 开发板 -> 开发板管理器”,搜索 ESP32 并安装。
- 安装完成后,在“工具 -> 开发板”中选择你的具体型号。
- 选择正确的 COM 口,这个在设备管理器里可以看到。
Python 环境方面,建议安装 Python 3.9 及以上版本,用pip安装 Flask、Flask-CORS、requests 等库。如果你不想把环境搞乱,可以用 venv 创建虚拟环境。
前端环境需要 Node.js。Node.js 的版本建议使用 18 或 20 的 LTS 版本,npm 会随 Node 一起安装。Vue 项目用 Vite 创建,具体命令在后面的章节会给出。
这里想说一句:环境问题占了这个项目 40% 以上的坑。很多同学卡在“IDE 上传报错”“Python 包装不上”“npm install 报错”,其实都不是代码问题。遇到环境报错不要慌,先看错误信息里的关键字,比如port、time out、No module named,搜索定位往往比瞎试更高效。
4. ESP32 传感器数据采集与上报
这一章是硬件端核心。我们的目标不是单纯在串口打印温度湿度,而是采集并发送到后端。
4.1 引脚接线
以 DHT11 和光敏电阻为例,典型接线如下:
| 传感器引脚 | ESP32 引脚 |
|---|---|
| DHT11 VCC | 3.3V |
| DHT11 GND | GND |
| DHT11 DATA | GPIO4 |
| 光敏模块 VCC | 3.3V |
| 光敏模块 GND | GND |
| 光敏模块 AO | GPIO34(ADC) |
注意:DHT11 数据引脚需要接一个 10k 欧姆上拉电阻到 VCC,部分模块已经集成,可以略过。
4.2 Arduino 代码实现
在 Arduino IDE 中新建一个工程,粘贴下面的参考代码。这个代码会读取温度、湿度和光照值,通过 WiFi 连接后,将这些数据以 JSON 格式 POST 到后端接口。
#include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> #include <DHT.h> // WiFi 配置 const char* ssid = "你的WiFi名称"; const char* password = "你的WiFi密码"; // 后端接口地址,注意不要写 localhost const char* serverUrl = "http://192.168.1.100:5000/api/sensor"; // DHT 配置 #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); // 光敏传感器 #define LIGHT_PIN 34 void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); Serial.print("WiFi 连接中"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("连接成功"); Serial.print("IP 地址: "); Serial.println(WiFi.localIP()); } void loop() { float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); int lightValue = analogRead(LIGHT_PIN); if (isnan(temperature) || isnan(humidity)) { Serial.println("DHT 传感器读取失败,检查接线"); delay(2000); return; } if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(serverUrl); http.addHeader("Content-Type", "application/json"); StaticJsonDocument<256> doc; doc["deviceId"] = "esp32-node-01"; doc["temperature"] = temperature; doc["humidity"] = humidity; doc["light"] = lightValue; String requestBody; serializeJson(doc, requestBody); Serial.println("发送数据: " + requestBody); int httpResponseCode = http.POST(requestBody); if (httpResponseCode > 0) { Serial.print("服务器响应码: "); Serial.println(httpResponseCode); } else { Serial.print("请求失败: "); Serial.println(http.errorToString(httpResponseCode).c_str()); } http.end(); } else { Serial.println("WiFi 已断开"); } delay(10000); }4.3 代码关键点讲解
这段代码里最重要的逻辑其实是loop函数中的那几个步骤:读取传感器、组装 JSON、发送 HTTP 请求。
数据读取部分,DHT 传感器读取温湿度要用dht.readTemperature()和dht.readHumidity(),这两个函数是 DHT 库提供的。光敏传感器读取的是模拟量 ADC 值,ESP32 的 ADC 精度是 12 位,所以读数范围是 0 到 4095。光照越强,数值越高。
JSON 组装部分,这里用的是 ArduinoJson 库。StaticJsonDocument<256>是预分配 256 字节的静态缓冲区,对于当前这几个字段足够了。如果未来要加更多字段,可以改成DynamicJsonDocument或提高静态大小。
HTTP 请求部分,http.begin(serverUrl)指定了后端接口地址。注意这里有一个特别容易踩的坑:在 ESP32 代码里不能用localhost或127.0.0.1,因为那是 ESP32 自己。你要填的是电脑或服务器在局域网中的 IP 地址。比如电脑连了路由器,IP 是192.168.1.100,那么这里就写http://192.168.1.100:5000/api/sensor。
4.4 上传和调试注意事项
上传代码之前,先确认 Arduino IDE 中选择的开发板型号和端口是正确的。如果上传失败,常见原因是没有按住开发板上的 BOOT 键,或者驱动没有安装好。
上传成功后,打开串口监视器,波特率选择 115200。你会看到 WiFi 连接信息和传感器数据的打印日志。如果串口输出乱码,说明串口监视器右上角的波特率选择不对,调到 115200 即可。
如果你发现 DHT 读取一直失败,优先检查两个地方:一是数据线是否插在正确的 GPIO 引脚,二是模块供电是否稳定。传感器模块的 VCC 接 3.3V,不要接 5V。
5. Python Flask 后端接口实现
后端是整个系统的数据中枢。ESP32 发数据过来,它负责接收存储;前端要展示数据,它负责查询返回。
5.1 创建项目结构
建议在后端项目文件夹中按下面的结构组织文件:
iot-backend/ ├── app.py ├── requirements.txt └── data/ └── sensor.dbsensor.db会在首次运行时由代码自动创建。
5.2 安装依赖
首先创建一个虚拟环境,然后安装依赖。这里用 Flask 提供 API,用 Flask-CORS 解决跨域问题,用 SQLite 存储数据。
python -m venv venvWindows 下激活虚拟环境:
venv\Scripts\activateMac/Linux 下激活:
source venv/bin/activate然后安装依赖库:
pip install flask flask-cors把依赖写入 requirements.txt:
pip freeze > requirements.txt5.3 编写后端代码
新建app.py,粘贴下面的代码。这是一个最小可运行的实现,已经包含了数据接收、历史查询、最新数据查询三个接口。
import sqlite3 import time from flask import Flask, request, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) DATABASE = 'data/sensor.db' def init_db(): conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, temperature REAL NOT NULL, humidity REAL NOT NULL, light INTEGER NOT NULL, create_time INTEGER NOT NULL ) ''') conn.commit() conn.close() @app.route('/api/sensor', methods=['POST']) def receive_sensor_data(): data = request.get_json() if not data: return jsonify({'code': 400, 'message': '请求体不能为空'}), 400 device_id = data.get('deviceId', 'unknown') temperature = data.get('temperature') humidity = data.get('humidity') light = data.get('light') if temperature is None or humidity is None or light is None: return jsonify({'code': 400, 'message': '缺少必要字段'}), 400 conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute( 'INSERT INTO sensor_data (device_id, temperature, humidity, light, create_time) VALUES (?, ?, ?, ?, ?)', (device_id, temperature, humidity, light, int(time.time())) ) conn.commit() conn.close() return jsonify({'code': 200, 'message': '数据接收成功'}) @app.route('/api/sensor/latest', methods=['GET']) def get_latest_sensor_data(): conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute( 'SELECT device_id, temperature, humidity, light, create_time FROM sensor_data ORDER BY id DESC LIMIT 1' ) row = cursor.fetchone() conn.close() if row is None: return jsonify({'code': 200, 'data': None}) return jsonify({ 'code': 200, 'data': { 'deviceId': row[0], 'temperature': row[1], 'humidity': row[2], 'light': row[3], 'createTime': row[4] } }) @app.route('/api/sensor/history', methods=['GET']) def get_sensor_history(): limit = request.args.get('limit', default=50, type=int) conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute( 'SELECT device_id, temperature, humidity, light, create_time FROM sensor_data ORDER BY id DESC LIMIT ?', (limit,) ) rows = cursor.fetchall() conn.close() result = [] for row in rows: result.append({ 'deviceId': row[0], 'temperature': row[1], 'humidity': row[2], 'light': row[3], 'createTime': row[4] }) return jsonify({'code': 200, 'data': result}) if __name__ == '__main__': init_db() app.run(host='0.0.0.0', port=5000, debug=True)5.4 代码关键逻辑解释
初始化数据库:init_db()函数在应用启动时创建 SQLite 数据表。SQLite 是文件型数据库,不需要单独安装服务,非常适合轻量级项目。
POST /api/sensor:ESP32 上报数据时调用这个接口。先判断请求体是否为空,再逐个检查字段是否存在,最后插入数据库。这里使用int(time.time())存储 Unix 时间戳,方便后续在有历史数据时做时间范围查询。
GET /api/sensor/latest:前端展示“当前温湿度”时调用,返回最新一条数据。如果没有数据,data字段返回None。
GET /api/sensor/history:前端展示历史记录或趋势图时调用,默认返回最近 50 条数据。
这里重点说一下跨域问题。如果你的 Vue 页面运行在http://localhost:5173,后端运行在http://localhost:5000,浏览器会默认拦截跨域请求。CORS(app)这行代码就是为了解决这个问题。如果没有它,前端控制台会出现类似的报错:Access to XMLHttpRequest at ... has been blocked by CORS policy。
5.5 启动后端并验证
在项目根目录执行:
python app.py看到类似下面的输出,说明后端启动成功:
* Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.1.100:5000此时可以用 Postman 或 curl 模拟 ESP32 上报数据。在终端中执行:
curl -X POST http://127.0.0.1:5000/api/sensor \ -H "Content-Type: application/json" \ -d '{"deviceId":"esp32-node-01","temperature":26.5,"humidity":65.3,"light":2048}'预期返回:
{"code": 200, "message": "数据接收成功"}再用浏览器访问http://127.0.0.1:5000/api/sensor/latest,如果能看到刚才插入的数据,说明后端接口已经工作正常。
6. Vue 前端环境监测看板实现
前端是展示层,也是评委最容易留下第一印象的部分。这里我们用 Vue 3 + Vite 搭建项目,写一个简单的环境数据看板,展示最新温湿度、光照强度和历史数据列表。
6.1 创建 Vue 项目
在终端中执行下面的命令:
npm create vite@latest iot-frontend -- --template vue进入项目目录并安装依赖:
cd iot-frontend npm install安装 axios,用于发送 HTTP 请求:
npm install axios启动开发服务器:
npm run devVite 默认会在http://localhost:5173启动页面。此时项目是一个基础模板,下面我们改造它。
6.2 编写页面组件
打开src/App.vue,替换为以下内容。这个组件实现了两个核心功能:轮询获取最新数据并展示,以及获取历史数据列表。
<template> <div class="dashboard"> <h1>智能家居环境检测看板</h1> <div class="cards"> <div class="card"> <h3>温度</h3> <p>{{ latestData ? latestData.temperature + ' °C' : '暂无数据' }}</p> </div> <div class="card"> <h3>湿度</h3> <p>{{ latestData ? latestData.humidity + ' %' : '暂无数据' }}</p> </div> <div class="card"> <h3>光照强度</h3> <p>{{ latestData ? latestData.light : '暂无数据' }}</p> </div> </div> <h2>设备信息</h2> <p>设备ID:{{ latestData ? latestData.deviceId : '未上报' }}</p> <p>最近上报时间:{{ latestData ? formatTime(latestData.createTime) : '未上报' }}</p> <h2>历史数据</h2> <table> <thead> <tr> <th>时间</th> <th>温度 (°C)</th> <th>湿度 (%)</th> <th>光照</th> </tr> </thead> <tbody> <tr v-for="item in historyData" :key="item.createTime"> <td>{{ formatTime(item.createTime) }}</td> <td>{{ item.temperature }}</td> <td>{{ item.humidity }}</td> <td>{{ item.light }}</td> </tr> <tr v-if="historyData.length === 0"> <td colspan="4">暂无历史数据</td> </tr> </tbody> </table> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; import axios from 'axios'; const API_BASE = 'http://localhost:5000/api'; const latestData = ref(null); const historyData = ref([]); let timer = null; const fetchLatestData = async () => { try { const response = await axios.get(`${API_BASE}/sensor/latest`); if (response.data.code === 200) { latestData.value = response.data.data; } } catch (error) { console.error('获取最新数据失败', error); } }; const fetchHistoryData = async () => { try { const response = await axios.get(`${API_BASE}/sensor/history?limit=20`); if (response.data.code === 200) { historyData.value = response.data.data; } } catch (error) { console.error('获取历史数据失败', error); } }; const formatTime = (timestamp) => { const date = new Date(timestamp * 1000); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; onMounted(() => { fetchLatestData(); fetchHistoryData(); timer = setInterval(() => { fetchLatestData(); fetchHistoryData(); }, 5000); }); onUnmounted(() => { clearInterval(timer); }); </script> <style scoped> .dashboard { max-width: 900px; margin: 0 auto; padding: 20px; font-family: 'Segoe UI', Arial, sans-serif; } .cards { display: flex; gap: 20px; margin-bottom: 30px; } .card { flex: 1; background: #f5f7fa; border-radius: 8px; padding: 20px; text-align: center; } .card h3 { margin: 0 0 10px; color: #606266; } .card p { font-size: 28px; font-weight: bold; color: #409eff; margin: 0; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ebeef5; padding: 10px; text-align: center; } th { background-color: #f5f7fa; } </style>6.3 前端代码逻辑讲解
script setup是 Vue 3 的组合式 API 写法。ref用于创建响应式变量,onMounted在组件挂载后执行数据请求,onUnmounted在组件销毁时清除定时器。
fetchLatestData和fetchHistoryData分别请求后端的最新数据接口和历史数据接口。拿到响应后,把response.data.data赋值给对应的响应式变量。
轮询逻辑是每隔 5 秒请求一次接口。这样做的好处是 ESP32 每隔 10 秒上报一次数据,前端每 5 秒拉取一次,基本可以保证页面数据接近实时刷新。实际项目中如果节点数量多,可以用 WebSocket 或 MQTT 做更实时的推送,但演示项目中轮询已经够用。
这里有一个需要留意的点:API_BASE我写的是http://localhost:5000/api。如果你的后端跑在别的 IP 或端口,需要对应修改。如果用局域网内其他电脑访问这个前端页面,这里的localhost会指向访问者自己的机器,导致请求失败。这种情况可以把API_BASE改成后端的局域网 IP。
7. 运行效果与联调验证
到这里,三个端口的代码都有了。下面讲怎么把它们串起来做完整的联调。
7.1 启动顺序推荐
建议按照下面的顺序启动,避免出现“不知道是哪个环节出了问题”的情况:
- 启动后端:
python app.py,用 curl 或 Postman 测试接口,确认数据能写入。 - 启动前端:
npm run dev,在浏览器里打开页面,确认历史接口能返回数据。 - 最后给 ESP32 上电,观察串口监视器中的上报日志。
之所以最后启动 ESP32,是因为前两步可以通过模拟数据验证系统链路是否正常。如果前两步已经跑通,ESP32 只要上报一次,前端页面就会出现新的温度和光照数据。
7.2 预期的完整演示流程
当三个端都正常运行时,整个演示过程应该像下面这样:
- ESP32 上电,串口监视器显示 WiFi 连接成功,并每 10 秒打印一条 JSON 数据。
- 后端控制台显示
POST /api/sensor的访问日志。 - Vue 页面上的温度、湿度、光照卡片每隔 5 秒自动刷新。
- 历史数据表格新增一条记录,时间戳是 ESP32 上报的时间。
- 如果用手靠近光敏电阻遮挡光线,光照数值会明显下降,下一次刷新页面时数据发生变化。
7.3 联调失败的常见表现
如果页面一直显示“暂无数据”,可以从三个方向排查:
- 打开浏览器开发者工具,切换到 Network 标签,看请求
latest接口是否返回 200。如果是 404,说明后端接口路径不对。 - 如果请求返回 500,查看后端控制台报错信息,通常是数据库写入问题。
- 如果请求被 CORS 拦截,确认后端是否添加了
CORS(app),同时确认浏览器访问的地址是http://localhost:5000而不是http://127.0.0.1:5000。
8. 常见问题与排查思路
下面整理这个项目中比较常见的问题,尽量具体到一个可行的排查路径。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Arduino IDE 上传代码报错 | 未选择正确的开发板型号或 COM 口 | 查看设备管理器中的端口,确认板子型号 | 选择正确的 COM 口和开发板型号;必要时按住 BOOT 键再上传 |
| 串口监视器乱码 | 波特率设置错误 | 查看串口监视器右下角波特率 | 将波特率设置为 115200 |
| ESP32 连不上 WiFi | SSID 或密码错误;路由器不开放 2.4G | 串口打印检查WiFi.begin后的状态 | 确认 WiFi 名称密码,尽量不要用纯 5G WiFi |
| DHT 传感器一直返回 NaN | 接线不良或引脚错误 | 检查 DATA 是否接到 GPIO4;上拉电阻是否接好 | 重新拔插杜邦线,更换面包板插孔 |
| 后端接口返回 400 | JSON 请求体中缺少字段 | 用 Postman 打印请求体查看字段名 | 确认deviceId、temperature、humidity、light字段都存在 |
| 前端请求浏览器报 CORS 错误 | 后端未启用 CORS | 查看后端app.py中是否有CORS(app) | 添加from flask_cors import CORS并实例化 |
| 前端页面显示“暂无数据” | 后端数据库为空表 | 用 curl 发一条测试数据 | 先手动 POST 一条数据,看前端能否展示 |
| ESP32 请求返回 -1 | 后端地址不可达或服务未启动 | 用手机/电脑 ping ESP32 写入的 IP 地址 | 确认后端已启动,且 ESP32 填的 IP 是本机局域网 IP |
9. 最佳实践与工程建议
即使这是一个毕设演示项目,好的编码习惯和工程组织方式仍然会让你在答辩和后续扩展中受益。
9.1 引脚定义统一管理
ESP32 代码中,把引脚定义放在文件开头统一管理。如果需要换引脚,只需要改一处。不要在很多函数里直接写4或34这种魔法数字,时间久了很容易维护混乱。
9.2 数据上报加上时间戳
目前示例代码中,时间戳是后端生成的。实际项目中更合理的做法是 ESP32 也把自己的时间信息或设备标识一起上传,方便做断网补传和设备区分。如果节点数量多,deviceId还可以扩展成具体的房间号或设备位置。
9.3 后端接口要统一返回格式
我在这里用了{ "code": 200, "message": "...", "data": ... }这种结构。好处是前端可以根据code判断请求是否成功,而不是依赖 HTTP 状态码。真实项目中还可以在code中定义业务错误码,比如40001表示参数缺失,50001表示数据库异常。
9.4 配置信息不要硬编码
WiFi 的 SSID 和密码,后端的 IP 地址,这些配置信息不要直接写在代码里。对毕设项目来说,可以放到一个头文件中统一管理;对更正式的项目,可以使用环境变量或配置文件。这样做的好处是方便其他人使用你的代码时修改,也避免不小心把密码传到公开仓库。
9.5 安全边界与生产环境差异
这个项目中的设备上报没有做任何身份认证,任何知道接口地址的人都可以伪造数据。这在局域网演示环境中问题不大,但如果要部署到公网,必须增加 token 或签名机制。同时,SQLite 适合数据量较小的场景,如果数据量大了或者要支持多设备高频率上报,建议换成 MySQL 或时序数据库。
9.6 从演示项目到毕业设计的扩展思路
如果你的题目是“基于 ESP32 的物联网智能家居环境检测系统”,光有环境检测节点可能不够完整。可以按下面几个方向扩展:
- 增加执行设备:比如检测到温度过高时,通过继电器控制风扇或空调。
- 增加报警机制:温湿度超限时,通过后端发送邮件或钉钉机器人通知。
- 增加用户登录:Vue 前端接入一个简单的登录注册功能,区分用户查看权限。
- 增加图表展示:使用 ECharts 将历史温度湿度数据渲染成折线图,视觉冲击力更强。
- 增加设备配置页面:在前端页面能够修改 ESP32 的上报频率或传感器阈值。
扩展时注意保持现有分层不变,每新增一个功能,尽量独立到一个模块中,不要让后端接口和前端页面耦合得太深。
10. 源码获取方式与使用建议
这个项目的源码如果从公开渠道获取,拿到手后不要急着跑,先按下面的顺序做三件事。
第一,看目录结构。先搞清楚每个文件的作用,哪些是后端代码,哪些是前端代码,哪些是 ESP32 的 Arduino 工程。如果目录混乱,说明项目组织得不太好,你需要先整理再使用。
第二,修改配置。重点检查这几处:ESP32 代码里的 WiFi 名称和密码、后端接口地址、前端src/App.vue里的API_BASE地址。这三处是项目和环境绑定最深的地方,不改必然跑不通。
第三,分步运行。不要想着“代码拿过来直接一键跑通”。按照前文说的顺序,后端先启动,用 curl 或 Postman 验证接口,再启动前端,最后给 ESP32 上电。每一步都验证通过以后,再进入整体联调。
最后说一句经验之谈:这类项目的难点不在于某一个技术点有多深,而在于把硬件、后端、前端三个环节串联起来。很多同学答辩时不是说“这个功能我不会”,而是“代码能跑,但解释不清为什么这么设计”。所以拿到代码后,建议你用一个晚上把所有代码读一遍,特别是后端接口和前端请求的对应关系。真到答辩的时候,能够完整讲清楚数据从传感器到页面展示的整个过程,比项目本身多几个炫酷功能更能给评委留下好印象。
这篇文章把 ESP32 物联网环境检测项目从架构、硬件、后端到前端做了一个完整拆解。你可以把它当作一份可运行的参考手册,也可以当作毕业设计的起步框架。建议收藏备用,遇到配置问题随时回来查阅。