手把手教你用ESP32+Arduino+PHP,把传感器数据实时推送到网页(附完整代码)
从传感器到云端ESP32PHPMySQL实时数据可视化实战物联网技术的普及让硬件开发者能够轻松将物理世界的数据数字化并可视化。本文将带你完成一个完整的物联网项目使用ESP32采集传感器数据通过WiFi传输到云服务器存储到MySQL数据库最后用PHP和JavaScript构建实时更新的网页仪表盘。不同于简单的教程我们会深入每个环节的最佳实践和常见陷阱。1. 项目架构与核心组件一个典型的物联网数据流通常包含四个关键层级感知层ESP32开发板搭配传感器如DHT11温湿度传感器传输层WiFi网络连接和TCP Socket通信存储层云服务器上的MySQL数据库展示层PHP后端前端JavaScript动态渲染硬件选型对比表组件ESP32ESP8266Raspberry Pi Pico WWiFi双频单频单频蓝牙支持不支持不支持价格中等最低中等适用场景复杂项目简单连接MicroPython项目提示ESP32-C3系列在性价比和功耗方面表现优异是新项目的理想选择2. ESP32固件开发从传感器到网络传输使用Arduino IDE开发ESP32程序需要先配置开发环境// 示例温湿度传感器数据采集与传输 #include WiFi.h #include DHT.h #define DHTPIN 4 // GPIO4连接DHT11 #define DHTTYPE DHT11 const char* ssid YOUR_SSID; const char* password YOUR_PASSWORD; const char* serverIP YOUR_SERVER_IP; const int serverPort 8080; DHT dht(DHTPIN, DHTTYPE); WiFiClient client; void setup() { Serial.begin(115200); dht.begin(); connectToWiFi(); } void loop() { if (!client.connected()) { reconnectToServer(); } float h dht.readHumidity(); float t dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println(Failed to read from DHT sensor!); return; } String data String(t) , String(h); client.println(data); delay(5000); // 5秒间隔 } void connectToWiFi() { WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(WiFi connected); } void reconnectToServer() { if (!client.connect(serverIP, serverPort)) { Serial.println(Connection failed); delay(1000); return; } Serial.println(Connected to server); }关键优化点增加传感器数据校验实现断线自动重连机制采用非阻塞式延迟避免程序卡死数据格式化为CSV便于服务器解析3. 服务器端数据接收与存储云服务器需要运行一个TCP Socket服务来接收ESP32发送的数据。以下是Python3的实现示例# socket_server.py import socket import pymysql from datetime import datetime db_config { host: localhost, user: db_user, password: db_password, database: sensor_data, charset: utf8mb4 } def create_table(): conn pymysql.connect(**db_config) try: with conn.cursor() as cursor: sql CREATE TABLE IF NOT EXISTS environment ( id INT AUTO_INCREMENT PRIMARY KEY, temperature FLOAT, humidity FLOAT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) cursor.execute(sql) conn.commit() finally: conn.close() def save_to_db(temp, humi): conn pymysql.connect(**db_config) try: with conn.cursor() as cursor: sql INSERT INTO environment (temperature, humidity) VALUES (%s, %s) cursor.execute(sql, (temp, humi)) conn.commit() finally: conn.close() def start_server(): server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((0.0.0.0, 8080)) server.listen(1) print(Server started on port 8080) create_table() try: while True: conn, addr server.accept() print(fConnection from {addr}) try: data conn.recv(1024).decode().strip() if data: temp, humi map(float, data.split(,)) print(f[{datetime.now()}] Received: Temp{temp}C, Humi{humi}%) save_to_db(temp, humi) except ValueError as e: print(fData format error: {e}) finally: conn.close() except KeyboardInterrupt: print(Shutting down server) finally: server.close() if __name__ __main__: start_server()服务器部署注意事项配置防火墙规则开放相应端口使用systemd或supervisor管理进程实现日志轮转避免磁盘空间耗尽考虑使用数据库连接池提高性能4. 数据可视化从数据库到网页PHP后端负责从MySQL查询最新数据并返回给前端// api.php ?php header(Content-Type: application/json); $db new mysqli(localhost, db_user, db_password, sensor_data); if ($db-connect_error) { die(json_encode([error Database connection failed])); } $query SELECT temperature, humidity FROM environment ORDER BY created_at DESC LIMIT 1; $result $db-query($query); if ($result $result-num_rows 0) { $row $result-fetch_assoc(); echo json_encode([ temperature $row[temperature], humidity $row[humidity], timestamp date(Y-m-d H:i:s) ]); } else { echo json_encode([error No data available]); } $db-close(); ?前端使用JavaScript定时获取数据并更新图表!-- dashboard.html -- !DOCTYPE html html head title环境监测仪表盘/title script srchttps://cdn.jsdelivr.net/npm/chart.js/script style .dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-width: 1000px; margin: 0 auto; } .card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .value { font-size: 3em; text-align: center; margin: 20px 0; } /style /head body div classdashboard div classcard h2温度 (°C)/h2 div classvalue idtemp-value--/div canvas idtemp-chart/canvas /div div classcard h2湿度 (%)/h2 div classvalue idhumi-value--/div canvas idhumi-chart/canvas /div /div script const tempCtx document.getElementById(temp-chart).getContext(2d); const humiCtx document.getElementById(humi-chart).getContext(2d); const tempChart new Chart(tempCtx, { type: line, data: { labels: [], datasets: [{ label: 温度 (°C), data: [], borderColor: rgb(255, 99, 132), tension: 0.1 }]}, options: { responsive: true } }); const humiChart new Chart(humiCtx, { type: line, data: { labels: [], datasets: [{ label: 湿度 (%), data: [], borderColor: rgb(54, 162, 235), tension: 0.1 }]}, options: { responsive: true } }); function updateDashboard() { fetch(api.php) .then(response response.json()) .then(data { if (!data.error) { document.getElementById(temp-value).textContent data.temperature.toFixed(1); document.getElementById(humi-value).textContent data.humidity.toFixed(1); const now new Date().toLocaleTimeString(); updateChart(tempChart, now, data.temperature); updateChart(humiChart, now, data.humidity); } }) .catch(error console.error(Error:, error)); } function updateChart(chart, label, value) { if (chart.data.labels.length 10) { chart.data.labels.shift(); chart.data.datasets[0].data.shift(); } chart.data.labels.push(label); chart.data.datasets[0].data.push(value); chart.update(); } setInterval(updateDashboard, 5000); updateDashboard(); // 初始加载 /script /body /html性能优化技巧使用HTTP缓存头减少重复请求考虑WebSocket实现真正的实时通信对历史数据实现分页加载添加数据异常检测和报警功能5. 项目扩展与进阶方向基础功能实现后可以考虑以下增强功能多设备支持在数据库中添加device_id字段区分不同ESP32设备实现设备认证机制防止非法数据注入数据持久化与备份# 每日数据库备份脚本 mysqldump -u db_user -p db_password sensor_data /backups/sensor_data_$(date %Y%m%d).sql报警通知当温度超过阈值时发送邮件或短信通知实现简单的规则引擎配置报警条件移动端适配使用Bootstrap或Flexbox布局优化移动端显示添加PWA支持实现类原生应用体验数据分析-- 每日平均温湿度查询 SELECT DATE(created_at) as day, AVG(temperature) as avg_temp, AVG(humidity) as avg_humi FROM environment GROUP BY day ORDER BY day DESC LIMIT 7;在项目开发过程中使用版本控制系统管理代码至关重要。典型的项目目录结构如下/iot-dashboard ├── esp32/ │ └── sensor_client.ino ├── server/ │ ├── socket_server.py │ ├── api.php │ └── db_setup.sql ├── web/ │ ├── index.html │ ├── assets/ │ │ ├── css/ │ │ └── js/ └── README.md遇到连接不稳定问题时可以尝试以下排查步骤检查ESP32与路由器的信号强度验证服务器端口是否确实开放使用telnet或nc测试查看服务器系统日志排查连接错误在代码中添加更详细的日志输出考虑使用心跳机制检测连接状态