利用 ESP8266 架设智能环境风扇与远程升级系统
一、项目概述
随着物联网的普及,如何让设备在本地自动响应环境变化、并能远程维护升级,成为常见需求。本文示例展示了一套基于 ESP8266 的解决方案,集成了:
- DHT11 温度传感:实时采集室内温度
- PIR 人体红外检测:监测区域内是否有人活动
- 继电器控制:根据环境条件(温度与是否有人)自动切换负载
- Web 界面:通过浏览器查看状态、手动开关继电器
- OTA 无线升级:后续固件更新无需连线,直接通过 Wi‑Fi 推送
二、核心功能拆解
1. 硬件与引脚配置
- DHT11:数据线接 D5,负责温度采样
- PIR(SR505/HC‑SR501):输出接 D6,用于人体活动检测
- 继电器:控制脚接 D7,根据逻辑驱动外部电路
- 电源及 Wi‑Fi:模块以 5 V (VIN)供电,运行在 STA 模式,连入家庭网络
2. 自动控制逻辑
unsigned long lastMotionTime = 0;
const unsigned long detectionWindow = 0.5 * 60 * 1000UL; // 30 秒
// 每次检测到 PIR 输出高电平时,更新时间戳
if (digitalRead(PIRPIN)) {
lastMotionTime = millis();
}
// 读取温度
float newTemp = dht.readTemperature();
if (!isnan(newTemp)) temperature = newTemp;
// 根据“最后一次有人时间距今”与温度阈值决定继电器开关
if ((millis() - lastMotionTime) <= detectionWindow && temperature > 25.0) {
digitalWrite(RELAYPIN, HIGH); // 打开
} else {
digitalWrite(RELAYPIN, LOW); // 关闭
}
- 时间窗口:设为 0.5 分钟,保证只要有人经过的 30 秒内,且温度高于 25 ℃,继电器便处于“打开”状态。
- 温度保护:当温度低于阈值或无人离开超过窗口时自动关闭,避免长期开启。
3. 网页状态与手动控制
// 根路径:返回包含当前温度、继电器状态、上次检测到人时间的 HTML
server.on("/", handleRoot);
// /on 和 /off:通过 POST 请求手动打开/关闭继电器
server.on("/on", HTTP_POST, handleOn);
server.on("/off", HTTP_POST, handleOff);
- 页面上有“手动打开”“手动关闭”按钮,用户点击后触发相应处理函数,写入数字引脚状态。
- 同时提示“自动控制逻辑每 2 秒会再次评估”,保证本地传感逻辑的优先级。
4. OTA 无线升级
#include <ArduinoOTA.h>
ArduinoOTA.setHostname("esp8266-ota");
ArduinoOTA.onStart([]{ Serial.println("开始 OTA"); });
ArduinoOTA.onEnd( []{ Serial.println("OTA 完成"); });
ArduinoOTA.onError([](ota_error_t err){ /* 打印错误 */ });
ArduinoOTA.begin();
// 在 loop() 中
ArduinoOTA.handle();
- 首次烧录:需通过 USB 刷入上述代码,使设备具备 OTA 能力。
- 后续升级:Arduino IDE 会自动检测到局域网内的
esp8266-ota
设备,选择网络端口即可直接上传,无需物理连线。
三、性能与应用场景
- 资源消耗:OTA 与 WebServer 仅占用少量 RAM (~10 KB) 与极短的 CPU 调度时间,对原有传感与控制逻辑影响可忽略。
- 典型场景:
- 智能风扇或排气扇:当室内有人且温度高于阈值时自动启动
- 室内通风控制:结合湿度传感或 CO₂ 传感模块扩展
- 远程运维:固件更新、阈值调整可在线完成
四、后续可扩展方向
- HTTPS OTA:使用加密传输,保护固件完整性
- MQTT 接入:与云平台对接,实现多设备管理与告警
- 阈值动态配置:把温度阈值、时间窗口等参数暴露在网页上动态修改
- 多传感融合:加入光照、空气质量等传感器,提高环境智能化水平
五、代码
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoOTA.h>
#include <DHT.h>
// ===== WiFi 配置 =====
const char* ssid = "yang1234";
const char* password = "y123456789";
// ===== 引脚配置 =====
#define DHTPIN D5
#define DHTTYPE DHT11
#define PIRPIN D6
#define RELAYPIN D7
DHT dht(DHTPIN, DHTTYPE);
ESP8266WebServer server(80);
// ===== 控制变量 =====
const float temperatureThreshold = 25.0; // 温度阈值 (°C)
const unsigned long detectionWindow = 30 * 1000UL; // 30 秒窗口
const unsigned long checkInterval = 2000; // 检测间隔 (ms)
// ===== 状态变量 =====
unsigned long lastMotionTime = 0; // 上次有效触发时刻
unsigned long lastCheckTime = 0; // 上次逻辑检查时刻
float temperature = 0.0;
bool relayState = false;
bool lastRelayState = false;
// ===== 去抖参数 =====
static bool lastPirState = LOW;
static unsigned long lastPirTrigger = 0;
const unsigned long pirMinInterval = 1000; // 最少 1 秒内不重复触发
// ===== 网页处理 =====
void handleRoot() {
unsigned long secsSince = (millis() - lastMotionTime) / 1000;
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>ESP8266 控制界面</title>"
"<style>body{font-family:sans-serif;}</style></head><body>";
html += "<h2>ESP8266 状态查看</h2>";
html += "<p><strong>当前温度:</strong>" + String(temperature,1) + " °C (> "
+ String(temperatureThreshold) + "°C 时自动开)</p>";
html += "<p><strong>距离上次检测到活动:</strong>" + String(secsSince)
+ " 秒 (需<=" + String(detectionWindow/1000) + " 秒才有效)</p>";
html += "<p><strong>继电器:</strong>" + String(relayState?"打开 ✅":"关闭 ⛔") + "</p>";
html += "<p><em>⚙️ 每 " + String(checkInterval/1000)
+ " 秒检测一次,满足两条件才开继电器</em></p>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
dht.begin();
pinMode(PIRPIN, INPUT); // 外部 10k 下拉
pinMode(RELAYPIN, OUTPUT);
digitalWrite(RELAYPIN, LOW); // 默认关闭
// 防止刚上电就触发
lastMotionTime = millis() - detectionWindow - 1;
// 连接 WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("连接 WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 已连接,IP:" + WiFi.localIP().toString());
// OTA 配置
ArduinoOTA.setHostname("esp8266-ota");
ArduinoOTA.onStart([](){ Serial.println("开始 OTA"); });
ArduinoOTA.onEnd( [](){ Serial.println("\nOTA 完成,重启中"); });
ArduinoOTA.onError([](ota_error_t e){ Serial.printf("OTA 错误[%u]\n", e); });
ArduinoOTA.begin();
Serial.println("OTA 准备就绪");
// Web 服务器
server.on("/", handleRoot);
server.begin();
Serial.println("Web 服务器启动");
}
void loop() {
unsigned long now = millis();
ArduinoOTA.handle();
server.handleClient();
// 非阻塞定期检查
if (now - lastCheckTime < checkInterval) return;
lastCheckTime = now;
// —— PIR 上升沿 + 最小间隔去抖 ——
bool pir = digitalRead(PIRPIN);
if (pir && !lastPirState && (now - lastPirTrigger > pirMinInterval)) {
lastMotionTime = now;
lastPirTrigger = now;
Serial.println("👤 有效人体触发");
}
lastPirState = pir;
// —— 读取温度 ——
float t = dht.readTemperature();
if (!isnan(t)) {
temperature = t;
} else {
Serial.println("⚠️ 读取温度失败");
}
// —— 自动控制逻辑 ——
bool shouldOn = (now - lastMotionTime <= detectionWindow) && (temperature > temperatureThreshold);
if (shouldOn != relayState) {
relayState = shouldOn;
digitalWrite(RELAYPIN, relayState ? HIGH : LOW);
Serial.printf("%s 继电器\n", relayState ? "✅ 打开" : "⛔ 关闭");
}
}
本文作者: 永生
本文链接: https://yys.zone/detail/?id=432
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)