为了给家里实现远程自动开空调,也为了试试AI编程,顺便发个文章证明本人还活着。
实现的功能大概就是连wifi,监测室温,控制空调。单片机用的nodemcu,温度检测用的SHT30,红外发射等是专门在网上买的那个大功率版的发射模块,实际用起来还不错。
以前要写这样的一段程序还是挺费劲的,涉及到几个类库的使用,web前端样式怎么也得美化一下等等,但是现在只要把你的需求告诉AI,它就会把代码给你。我是用deepseek给我生成的代码,就随便写了需求如下:
帮我写程序,功能为使用ESP8266单片机,通过web端控制空调,wifi连接使用"ESP8266WiFiMulti"以便使用多个SSID,红外发送使用HeatpumpIR库里的GreeYAPHeatpumpIR类,通过SHT30模块获取当前温度并在网页上显示,监测到温度大于28度自动打开空调
然后deepseek就刷刷的出代码,还有教你怎么接线,注意什么问题,简直贴心好用。
所需硬件准备
ESP8266 开发板 (如 NodeMCU, Wemos D1 mini)
SHT30 温湿度传感器模块 (使用 I2C 接口)
红外发射二极管 (并串联一个 100Ω 左右的电阻)
NPN 三极管 (如 S8050),用于驱动红外二极管以增加发射距离
以及面包板、杜邦线等连接线材
硬件接线说明
建议接线如下,确保所有设备共地连接。
SHT30 传感器
VCC → ESP8266 3.3V
GND → ESP8266 GND
SCL → ESP8266 GPIO 5 (D1)
SDA → ESP8266 GPIO 4 (D2)
红外发射部分 (以 GPIO 14 (D5) 为例)
通过 1kΩ 电阻连接 GPIO 14 到三极管的基极 (B)。
发射极 (E) 连接到 GND。
集电极 (C) 连接到红外二极管的负极。
红外二极管正极通过 100Ω 限流电阻连接到 3.3V。
完整程序代码
修改WiFi列表:将代码开头的 wifiMulti.addAP() 函数里的 SSID 和密码换成你家里的。
安装必要库:通过 Arduino IDE 的库管理器,搜索并安装以下库:
HeatpumpIR by Toni Arte
Adafruit SHT31 (也能驱动 SHT30)
烧录与测试:
将代码烧录到 ESP8266 后,打开串口监视器 (115200 波特率)。
等待 WiFi 连接成功,记下打印出的 IP 地址。
在手机或电脑浏览器输入这个 IP 地址,就能看到控制界面了。
自动模式体验:把空调温度设为高于 28℃ 或低于 25℃,网页状态和空调会根据室温自动调节。你也可以在网页上随时“手动”或“自动”切换模式。
// 空调控制器 - 基于ESP8266,WebUI,SHT30温度和HeatpumpIR库 (Gree YAP协议)
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <GreeHeatpumpIR.h>
#include <Adafruit_SHT31.h>
// ========== WiFi 配置:在这里填入你要连接的网络 ==========
ESP8266WiFiMulti wifiMulti;
// ========== Web 服务器配置:监听端口 80 ==========
ESP8266WebServer server(80);
// ========== 空调配置 (Gree YAP 协议) ==========
#define IR_LED_PIN 14 // D5 (GPIO14),红外发射引脚
IRSenderBitBang irSender(IR_LED_PIN); // 红外信号发射器
HeatpumpIR *heatpump = new GreeYAPHeatpumpIR(); // 实际使用 Gree YAP 协议
// 空调运行参数
bool currentPowerState = false; // 当前电源状态 (true: 开, false: 关)
uint8_t currentMode = MODE_AUTO; // 运行模式 (MODE_AUTO, MODE_COOL, MODE_HEAT, MODE_FAN, MODE_DRY)
uint8_t currentFan = FAN_AUTO; // 风速 (FAN_AUTO, FAN_1, FAN_2, FAN_3, FAN_4, FAN_5, FAN_SILENT)
uint8_t currentTemp = 24; // 目标温度 (16-30℃)
bool currentSwingV = false; // 上下扫风
bool currentSwingH = false; // 左右扫风
// ========== 温湿度传感器 (SHT30) ==========
Adafruit_SHT31 sht31; // I2C 地址默认 0x44
float currentTemperature = 0.0; // 当前室温
float currentHumidity = 0.0;
// ========== 自动温控逻辑 ==========
bool autoModeEnabled = true; // 自动模式开关 (true: 自动, false: 手动)
unsigned long lastSensorRead = 0;
const unsigned long sensorReadInterval = 5000; // 每 5 秒读一次传感器
unsigned long lastAutoCheck = 0;
const unsigned long autoCheckInterval = 60000; // 每分钟进行一次自动控制检查
unsigned long lastIRSend = 0;
const unsigned long irRepeatInterval = 5000; // 每 5 秒重复发射上次指令,提高可靠性
uint8_t lastIRCommand[8]; // 存储上次发送的完整指令
bool lastIRCommandValid = false;
// ========== 红外指令发送函数 ==========
void sendIRCmd() {
if (!heatpump) return;
// 根据协议构建指令
heatpump->send(irSender, currentPowerState, currentMode, currentFan, currentTemp, currentSwingV, currentSwingH);
Serial.print("发送红外指令:");
Serial.print(currentPowerState ? "开" : "关");
Serial.print(",模式:");
switch(currentMode) {
case MODE_AUTO: Serial.print("自动"); break;
case MODE_COOL: Serial.print("制冷"); break;
case MODE_HEAT: Serial.print("制热"); break;
case MODE_FAN: Serial.print("送风"); break;
case MODE_DRY: Serial.print("除湿"); break;
}
Serial.print(",温度:"); Serial.print(currentTemp);
Serial.print("°C,风速:"); Serial.print(currentFan);
Serial.print(",扫风:"); Serial.print(currentSwingV ? "开" : "关");
Serial.println();
// 记录发送时间和状态
lastIRSend = millis();
}
// ========== 传感器数据读取 ==========
void readSensor() {
if (sht31.begin(0x44)) { // 尝试读取传感器
currentTemperature = sht31.readTemperature(); // 摄氏温度
currentHumidity = sht31.readHumidity(); // 相对湿度
// 数据有效性检查
if (isnan(currentTemperature)) currentTemperature = -999.0;
if (isnan(currentHumidity)) currentHumidity = -999.0;
Serial.printf("室温:%.1f°C,湿度:%.1f%%\n", currentTemperature, currentHumidity);
} else {
Serial.println("无法读取SHT30传感器数据,请检查I2C连接。");
currentTemperature = -999.0; // 标记为无效数据
currentHumidity = -999.0;
}
}
// ========== 自动空调控制逻辑(根据室温) ==========
void autoControl() {
// 如果自动模式未启用、传感器无效或未准备就绪,则跳过自动控制
if (!autoModeEnabled) return;
if (currentTemperature <= -990.0) return;
if (heatpump == nullptr) return;
bool needChange = false;
// 根据温度上下阈值自动调整空调状态
if (currentPowerState == false && currentTemperature > 28.0) {
Serial.println("【自动控制】室温超过28°C,自动开启空调。");
currentPowerState = true;
// 使用最后一次用户设定的模式(避免自动切换模式),如果从未设定则可默认为制冷
needChange = true;
}
else if (currentPowerState == true && currentTemperature < 25.0) {
Serial.println("【自动控制】室温低于25°C,自动关闭空调。");
currentPowerState = false;
needChange = true;
}
if (needChange) {
sendIRCmd();
updateWebStatus(); // 更新网页显示(通过自动刷新实现,也可以直接调用该函数,但需定义在前)
}
}
// ========== Web 页面生成(含实时状态) ==========
String generateWebPage() {
String modeStr = "";
switch(currentMode) {
case MODE_AUTO: modeStr = "自动"; break;
case MODE_COOL: modeStr = "制冷"; break;
case MODE_HEAT: modeStr = "制热"; break;
case MODE_FAN: modeStr = "送风"; break;
case MODE_DRY: modeStr = "除湿"; break;
}
String fanStr = "";
switch(currentFan) {
case FAN_AUTO: fanStr = "自动"; break;
case FAN_1: fanStr = "低档"; break;
case FAN_2: fanStr = "中低档"; break;
case FAN_3: fanStr = "中档"; break;
case FAN_4: fanStr = "中高档"; break;
case FAN_5: fanStr = "高档"; break;
case FAN_SILENT: fanStr = "静音"; break;
default: fanStr = "未知";
}
String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
page += "<title>ESP8266 智能空调控制器</title>";
page += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
page += "<style>";
page += "body{font-family:Arial;background:#f0f0f0;text-align:center;margin:20px;}";
page += ".container{max-width:500px;margin:auto;background:white;border-radius:20px;padding:20px;box-shadow:0 0 20px rgba(0,0,0,0.1);}";
page += "h1{color:#333;}";
page += ".sensor-info{background:#e8f5e9;border-radius:10px;padding:15px;margin-bottom:20px;font-size:18px;}";
page += ".control-group{margin:15px 0;text-align:left;}";
page += "label{font-weight:bold;display:block;margin-bottom:5px;color:#555;}";
page += "select,input[type='range'],input[type='number']{width:100%;padding:8px;border-radius:5px;border:1px solid #ccc;box-sizing:border-box;}";
page += "button{background:#4CAF50;color:white;border:none;padding:12px 20px;border-radius:25px;font-size:16px;cursor:pointer;margin-top:15px;width:100%;}";
page += "button:hover{background:#45a049;}";
page += ".auto-badge{display:inline-block;background:#ff9800;color:white;border-radius:15px;padding:5px 15px;margin:10px auto;font-size:14px;}";
page += "</style>";
page += "<script>";
page += "function updateTemp(value){document.getElementById('tempVal').innerText=value;}";
page += "async function sendCommand(){";
page += " const form=document.getElementById('ctrlForm');";
page += " const data=new FormData(form);";
page += " const params=new URLSearchParams(data);";
page += " const response=await fetch('/set?'+params);";
page += " const result=await response.text();";
page += " document.getElementById('statusMsg').innerHTML=result;";
page += " setTimeout(()=>{fetch('/status').then(r=>r.json()).then(j=>{";
page += " document.getElementById('statPower').innerText=j.power?'开':'关';";
page += " document.getElementById('statMode').innerText=j.modeStr;";
page += " document.getElementById('statTemp').innerText=j.temp+'°C';";
page += " document.getElementById('statFan').innerText=j.fanStr;";
page += " document.getElementById('autoModeState').innerText=j.autoMode?'已启用':'已禁用';";
page += " }).catch(e=>console.log(e));},500);";
page += "}";
page += "setInterval(async()=>{";
page += " const resp=await fetch('/status');";
page += " const data=await resp.json();";
page += " document.getElementById('statPower').innerText=data.power?'开':'关';";
page += " document.getElementById('statMode').innerText=data.modeStr;";
page += " document.getElementById('statTemp').innerText=data.temp+'°C';";
page += " document.getElementById('statFan').innerText=data.fanStr;";
page += " document.getElementById('roomTemp').innerText=data.roomTemp.toFixed(1);";
page += " document.getElementById('roomHumidity').innerText=data.roomHumidity.toFixed(1);";
page += " document.getElementById('autoModeState').innerText=data.autoMode?'已启用':'已禁用';";
page += "},2000);";
page += "</script>";
page += "</head><body>";
page += "<div class='container'>";
page += "<h1>❄️ 智能空调控制器</h1>";
page += "<div class='sensor-info'>";
page += "🌡️ 当前室温:<span id='roomTemp'>" + String(currentTemperature, 1) + "</span> °C<br>";
page += "💧 湿度:<span id='roomHumidity'>" + String(currentHumidity, 1) + "</span> %<br>";
page += "🔄 自动模式:<span id='autoModeState' class='auto-badge'>" + String(autoModeEnabled ? "已启用" : "已禁用") + "</span>";
page += "</div>";
page += "<div class='sensor-info'>";
page += "📡 空调状态:<br>";
page += "电源:<span id='statPower'>" + String(currentPowerState ? "开" : "关") + "</span><br>";
page += "模式:<span id='statMode'>" + modeStr + "</span><br>";
page += "温度:<span id='statTemp'>" + String(currentTemp) + "°C</span><br>";
page += "风速:<span id='statFan'>" + fanStr + "</span>";
page += "</div>";
page += "<form id='ctrlForm' onsubmit='event.preventDefault(); sendCommand();'>";
page += "<div class='control-group'><label>💡 电源:</label><select name='power'>";
page += "<option value='on'" + String(currentPowerState ? " selected" : "") + ">开</option>";
page += "<option value='off'" + String(!currentPowerState ? " selected" : "") + ">关</option>";
page += "</select></div>";
page += "<div class='control-group'><label>🌀 模式:</label><select name='mode'>";
page += "<option value='auto'" + String(currentMode == MODE_AUTO ? " selected" : "") + ">自动</option>";
page += "<option value='cool'" + String(currentMode == MODE_COOL ? " selected" : "") + ">制冷</option>";
page += "<option value='heat'" + String(currentMode == MODE_HEAT ? " selected" : "") + ">制热</option>";
page += "<option value='fan'" + String(currentMode == MODE_FAN ? " selected" : "") + ">送风</option>";
page += "<option value='dry'" + String(currentMode == MODE_DRY ? " selected" : "") + ">除湿</option>";
page += "</select></div>";
page += "<div class='control-group'><label>🌡️ 温度:</label>";
page += "<input type='range' name='temp' min='16' max='30' step='1' value='" + String(currentTemp) + "' oninput='updateTemp(this.value)'>";
page += "<span id='tempVal' style='display:inline-block;width:40px;text-align:center;'>" + String(currentTemp) + "</span> °C";
page += "</div>";
page += "<div class='control-group'><label>💨 风速:</label><select name='fan'>";
page += "<option value='auto'" + String(currentFan == FAN_AUTO ? " selected" : "") + ">自动</option>";
page += "<option value='low'" + String(currentFan == FAN_1 ? " selected" : "") + ">低档</option>";
page += "<option value='midlow'" + String(currentFan == FAN_2 ? " selected" : "") + ">中低档</option>";
page += "<option value='mid'" + String(currentFan == FAN_3 ? " selected" : "") + ">中档</option>";
page += "<option value='midhigh'"+ String(currentFan == FAN_4 ? " selected" : "") + ">中高档</option>";
page += "<option value='high'" + String(currentFan == FAN_5 ? " selected" : "") + ">高档</option>";
page += "<option value='silent'" + String(currentFan == FAN_SILENT ? " selected" : "") + ">静音</option>";
page += "</select></div>";
page += "<div class='control-group'><label>🔄 上下扫风:</label><select name='swingV'>";
page += "<option value='off'" + String(!currentSwingV ? " selected" : "") + ">关</option>";
page += "<option value='on'" + String(currentSwingV ? " selected" : "") + ">开</option>";
page += "</select></div>";
page += "<div class='control-group'><label>↔️ 左右扫风:</label><select name='swingH'>";
page += "<option value='off'" + String(!currentSwingH ? " selected" : "") + ">关</option>";
page += "<option value='on'" + String(currentSwingH ? " selected" : "") + ">开</option>";
page += "</select></div>";
page += "<div class='control-group'><label>🤖 控制模式:</label><select name='autoMode'>";
page += "<option value='auto'" + String(autoModeEnabled ? " selected" : "") + ">自动 (根据室温)</option>";
page += "<option value='manual'" + String(!autoModeEnabled ? " selected" : "") + ">手动</option>";
page += "</select></div>";
page += "<button type='submit'>📡 发送指令</button>";
page += "</form>";
page += "<div id='statusMsg' style='margin-top:15px;color:green;'></div>";
page += "</div></body></html>";
return page;
}
// ========== Web 服务器回调:主页 ==========
void handleRoot() {
server.send(200, "text/html", generateWebPage());
}
// ========== Web 服务器回调:接收控制指令 ==========
void handleSet() {
// 解析电源
if (server.hasArg("power")) {
String powerVal = server.arg("power");
currentPowerState = (powerVal == "on");
}
// 解析运行模式
if (server.hasArg("mode")) {
String modeVal = server.arg("mode");
if (modeVal == "auto") currentMode = MODE_AUTO;
else if (modeVal == "cool") currentMode = MODE_COOL;
else if (modeVal == "heat") currentMode = MODE_HEAT;
else if (modeVal == "fan") currentMode = MODE_FAN;
else if (modeVal == "dry") currentMode = MODE_DRY;
}
// 解析温度
if (server.hasArg("temp")) {
int newTemp = server.arg("temp").toInt();
if (newTemp >= 16 && newTemp <= 30) {
currentTemp = newTemp;
}
}
// 解析风速
if (server.hasArg("fan")) {
String fanVal = server.arg("fan");
if (fanVal == "auto") currentFan = FAN_AUTO;
else if (fanVal == "low") currentFan = FAN_1;
else if (fanVal == "midlow") currentFan = FAN_2;
else if (fanVal == "mid") currentFan = FAN_3;
else if (fanVal == "midhigh") currentFan = FAN_4;
else if (fanVal == "high") currentFan = FAN_5;
else if (fanVal == "silent") currentFan = FAN_SILENT;
}
// 解析扫风
if (server.hasArg("swingV")) {
currentSwingV = (server.arg("swingV") == "on");
}
if (server.hasArg("swingH")) {
currentSwingH = (server.arg("swingH") == "on");
}
// 解析自动模式切换
if (server.hasArg("autoMode")) {
String autoVal = server.arg("autoMode");
autoModeEnabled = (autoVal == "auto");
}
// 发送红外指令(手动触发)
sendIRCmd();
String reply = "✅ 指令已发送:";
reply += currentPowerState ? "开机" : "关机";
reply += "," + String(currentTemp) + "°C";
server.send(200, "text/plain", reply);
}
// ========== Web 服务器回调:返回 JSON 状态数据 ==========
void handleStatus() {
String modeStr, fanStr;
switch(currentMode) {
case MODE_AUTO: modeStr = "自动"; break;
case MODE_COOL: modeStr = "制冷"; break;
case MODE_HEAT: modeStr = "制热"; break;
case MODE_FAN: modeStr = "送风"; break;
case MODE_DRY: modeStr = "除湿"; break;
default: modeStr = "未知";
}
switch(currentFan) {
case FAN_AUTO: fanStr = "自动"; break;
case FAN_1: fanStr = "低档"; break;
case FAN_2: fanStr = "中低档"; break;
case FAN_3: fanStr = "中档"; break;
case FAN_4: fanStr = "中高档"; break;
case FAN_5: fanStr = "高档"; break;
case FAN_SILENT: fanStr = "静音"; break;
default: fanStr = "未知";
}
String json = "{";
json += "\"power\":" + String(currentPowerState ? "true" : "false") + ",";
json += "\"modeStr\":\"" + modeStr + "\",";
json += "\"temp\":" + String(currentTemp) + ",";
json += "\"fanStr\":\"" + fanStr + "\",";
json += "\"roomTemp\":" + String(currentTemperature) + ",";
json += "\"roomHumidity\":" + String(currentHumidity) + ",";
json += "\"autoMode\":" + String(autoModeEnabled ? "true" : "false");
json += "}";
server.send(200, "application/json", json);
}
// ========== 网页状态刷新辅助函数 ==========
void updateWebStatus() {
// 仅用于内部自动控制时刷新网页
// 实际是通过定时 AJAX 拉取实现,也可以在此发送 WebSocket 通知,但为了简化,保持 JSON 轮询。
}
// ========== WiFi 连接管理与重连 ==========
void manageWiFi() {
static unsigned long lastWiFiCheck = 0;
if (millis() - lastWiFiCheck > 10000) { // 每 10 秒检查一次
if (wifiMulti.run() != WL_CONNECTED) {
Serial.println("WiFi 连接丢失,尝试重连...");
} else {
// 已连接,确认 IP
if (WiFi.localIP().toString() != "0.0.0.0") {
Serial.print("当前连接的 WiFi: ");
Serial.println(WiFi.SSID());
Serial.print("IP 地址: ");
Serial.println(WiFi.localIP());
}
}
lastWiFiCheck = millis();
}
}
// ========== 初始化 ==========
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("\n智能空调控制器启动...");
// 初始化 WiFiMulti,添加多个 SSID 作为备选,遇断线自动切换最强信号
wifiMulti.addAP("你的主WiFi名称", "你的主WiFi密码");
wifiMulti.addAP("你的备用WiFi名称", "你的备用WiFi密码");
// 可继续添加更多网络
// 连接 WiFi (run 会阻塞直到连接成功或所有网络均失败,建议加超时逻辑)
Serial.println("正在连接 WiFi...");
int wifiAttempt = 0;
while (wifiMulti.run() != WL_CONNECTED && wifiAttempt < 50) { // 最多尝试 50 秒
delay(1000);
Serial.print(".");
wifiAttempt++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi 已连接");
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
Serial.print("IP 地址: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi 连接失败,检查网络配置后复位。");
}
// 初始化红外发射与空调协议(Gree YAP)
heatpump = new GreeYAPHeatpumpIR();
// 初始状态:关机,26°C,自动模式等
currentPowerState = false;
currentMode = MODE_AUTO; // 或根据你的喜好设定,如 MODE_COOL
currentFan = FAN_AUTO;
currentTemp = 26;
currentSwingV = false;
currentSwingH = false;
autoModeEnabled = true;
// 传感器初始化 (SHT30)
if (sht31.begin(0x44)) {
Serial.println("SHT30 传感器已初始化。");
} else {
Serial.println("SHT30 未找到,请检查 I2C 连接。");
}
// Web 服务器路由配置
server.on("/", handleRoot);
server.on("/set", handleSet);
server.on("/status", handleStatus);
server.begin();
Serial.println("Web 服务器已启动。");
// 首次读取传感器
readSensor();
// 如果自动模式启用,初次检查是否需要立即启动空调(比如室温已高于 28℃)
if (autoModeEnabled && currentTemperature > 28.0) {
currentPowerState = true;
sendIRCmd();
}
}
// ========== 主循环 ==========
void loop() {
// 处理 Web 请求
server.handleClient();
// 管理 WiFi 连接(自动重连、切换最佳 AP)
manageWiFi();
// 周期性读取传感器数据(每 5 秒)
if (millis() - lastSensorRead >= sensorReadInterval) {
readSensor();
lastSensorRead = millis();
}
// 周期性检查自动控制(每分钟一次)
if (autoModeEnabled && (millis() - lastAutoCheck >= autoCheckInterval)) {
autoControl();
lastAutoCheck = millis();
}
// 周期性重复发送最后指令,增强可靠性(每 5 秒,仅当已发送过有效指令且空调处于开启状态)
if (currentPowerState && lastIRCommandValid && (millis() - lastIRSend >= irRepeatInterval)) {
sendIRCmd(); // 重复发送当前状态指令
}
}
拿到代码你只需要安装编译工具和几个库就可以烧录了,编译时可能会有报错,但大概率都是小问题自己如果解决不了还可以发送报错信息给AI,AI会根据之前的对话信息分析并给出解决方法。


转载请注明:HANLEI'BLOG » 使用ESP8266实现WEB远程空调遥控
