最新消息:重新回归WordPress,我要比较认真的开始更新我的博客了。

使用ESP8266实现WEB远程空调遥控

杂七杂八 hanlei 12浏览

为了给家里实现远程自动开空调,也为了试试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会根据之前的对话信息分析并给出解决方法。

单片机
单片机和发射模块
AI生成的WEB

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