定时插座

http://192.168.31.160/LED-Control?ledPwm=0&time=22:59:50

ledPwm:0.关机时间,1.开机时间,2.设置时间,3.手动开,4.手动关, 5.删除时间

现在时间: 00:01:19

开列表: []

 

关列表: []

 

继电器状态: 开启

 

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <Ticker.h>
#define MAX_SCHEDULES 20 // 假设最多支持20个时间计划

Ticker ticker;
int count;  // 计时器计数变量

String  onTimes[MAX_SCHEDULES];
int onTimesindex = 0;
String  offTimes[MAX_SCHEDULES];
int offTimesindex = 0;
int relay = 0; // 继电器引脚,NodeMCU D5
ESP8266WebServer esp8266_server(80);
String currentTime;

int hours = 0;    // 当前小时数
int minutes = 0;  // 当前分钟数
int seconds = 0;  // 当前秒数

void setup(void) {
  pinMode(relay, OUTPUT);
  Serial.begin(9600);
  Serial.println("");
  ticker.attach(1, tickerCount);
  digitalWrite(relay, HIGH);
  pinMode(LED_BUILTIN, OUTPUT);

  WiFi.begin("yang1234", "y123456789"); // 输入你的WiFi SSID和密码
  Serial.println("Connecting ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++);
    Serial.print(' ');
  }

  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

  if (SPIFFS.begin()) {
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }

  esp8266_server.on("/LED-Control", handleLEDControl);
//  esp8266_server.on("/", HTTP_GET, handleRoot); // 新增根路径的处理函数
  esp8266_server.onNotFound(handleUserRequest);

  esp8266_server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  esp8266_server.handleClient();
}

void tickerCount() {
//  if (count <= 0) {
//    digitalWrite(relay, LOW);
//    count = 0;
//  } else {
//    delay(1000);
//    count--;
//  }

  // 每秒增加1秒
  seconds++;
  if (seconds >= 60) {
    seconds = 0;
    minutes++;
    if (minutes >= 60) {
      minutes = 0;
      hours++;
      if (hours >= 24) { // 如果小时数大于等于24,重置为0点
        hours = 0;
      }
    }
  }
  
  // 构建时间字符串
  currentTime = String(hours < 10 ? "0" : "") + hours + ":" +
                      String(minutes < 10 ? "0" : "") + minutes + ":" +
                      String(seconds < 10 ? "0" : "") + seconds;
    Serial.print("currentTime=");
  Serial.println(currentTime);

  // 检查onTimes数组,如果有匹配则打开继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String onTime = String(onTimes[i]);
    if (onTime == currentTime) {
      Serial.println("off");

      digitalWrite(relay, HIGH);
      break;
    }
  }
  
  // 检查offTimes数组,如果有匹配则关闭继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String offTime = String(offTimes[i]);
    if (offTime == currentTime) {
      digitalWrite(relay, LOW);
      Serial.println("on");
      break;
    }
  }
}

void handleLEDControl() {
  String ledPwm = esp8266_server.arg("ledPwm");
  
  int ledPwmVal = ledPwm.toInt();

  if (ledPwmVal == 0) {
    String timeParam = esp8266_server.arg("time");

    offTimes[offTimesindex] = timeParam;
    offTimesindex ++;
    
  }
  else if (ledPwmVal == 2) {
    String timeParam = esp8266_server.arg("time");

    // 解析时间参数的格式为hh:mm:ss
    int parsedHours, parsedMinutes, parsedSeconds;
    if (sscanf(timeParam.c_str(), "%d:%d:%d", &parsedHours, &parsedMinutes, &parsedSeconds) == 3) {
      hours = parsedHours;
      minutes = parsedMinutes;
      seconds = parsedSeconds;
      currentTime = String(hours < 10 ? "0" : "") + hours + ":" + String(minutes < 10 ? "0" : "") + minutes + ":" +String(seconds < 10 ? "0" : "") + seconds;
    }
  } else if (ledPwmVal == 1) {
      String timeParam = esp8266_server.arg("time");
        onTimes[onTimesindex] = timeParam;
        onTimesindex++;

  }else if (ledPwmVal == 3) {
      digitalWrite(relay, HIGH);
  }
  else if (ledPwmVal == 4) {
      digitalWrite(relay, LOW);
  }  else if(ledPwmVal == 5){
    //删除时间日期
    String timeParam = esp8266_server.arg("time");
     int len = sizeof(offTimes) / sizeof(offTimes[0]);
      // 使用一个循环查找要删除的元素并删除
      int indexToDelete = -1;
        for (int i = 0; i < sizeof(offTimes) / sizeof(offTimes[0]); i++) {
          if (offTimes[i] == timeParam) {
            indexToDelete = i;
            offTimesindex --;
            break;
          }
        }
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(offTimes) / sizeof(offTimes[0]) - 1; i++) {
            offTimes[i] = offTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          offTimes[sizeof(offTimes) / sizeof(offTimes[0]) - 1] = "";
        }


      len = sizeof(onTimes) / sizeof(onTimes[0]);
      // 使用一个循环查找要删除的元素并删除
        indexToDelete = -1;
        for (int i = 0; i < sizeof(onTimes) / sizeof(onTimes[0]); i++) {
          if (onTimes[i] == timeParam) {
            indexToDelete = i;
            onTimesindex --;
            break;
          }
        }
        
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(onTimes) / sizeof(onTimes[0]) - 1; i++) {
            onTimes[i] = onTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          onTimes[sizeof(onTimes) / sizeof(onTimes[0]) - 1] = "";
        }
      
  
    }
  
  int digitalValue = digitalRead(relay); // 读取 0 号数字引脚的电平状态
  String relayStatus;
  if (digitalValue == 1){
    relayStatus = "开启";
    }
  else{
    relayStatus = "关闭";
    }



  // 生成HTML内容,包括onTimes和offTimes
  String htmlContent = R"(
        <!DOCTYPE html>
        <html>
        <head>
          <title>插座开关</title>
          <meta charset="UTF-8">

          <script>

            // 在这里插入Arduino生成的时间变量
            var currentTime = ")" + currentTime + R"(";
            var timeParts = currentTime.split(":");
            var hours = timeParts[0];
            var minutes = timeParts[1];
            var seconds = timeParts[2];

            function updateTime() {
              var timeDisplay = document.getElementById("timeDisplay");
              // 分割时间字符串为小时、分钟和秒
              
                    // 每次加1秒
              seconds++;
              // 如果秒数达到60,将秒数重置为0并增加分钟
              if (seconds === 60) {
                seconds = 0;
                minutes++;
        
                // 如果分钟达到60,将分钟重置为0并增加小时
                if (minutes === 60) {
                  minutes = 0;
                  hours++;
        
                  // 如果小时达到24,将小时重置为0
                  if (hours === 24) {
                    hours = 0;
                  }
                }
              }
              // 格式化小时、分钟和秒,确保它们始终显示两位数
              var formattedHours = ('0' + hours).slice(-2);
              var formattedMinutes = ('0' + minutes).slice(-2);
              var formattedSeconds = ('0' + seconds).slice(-2);
              
              // 构建格式化后的时间字符串
              var formattedTime = formattedHours + ":" + formattedMinutes + ":" + formattedSeconds;
//             console.log(formattedTime);
              // 更新时间显示
              timeDisplay.textContent = formattedTime;
            }
            
            // 每秒钟调用一次updateTime函数
            setInterval(updateTime, 1000);
            
            // 当页面加载完成时立即更新时间
            window.onload = updateTime;

            
          </script>
        </head>
        <body>
          <h1>ledPwm:0.关机时间,1.开机时间,2.设置时间,3.手动开,4.手动关, 5.删除时间</h1>
          <p>现在时间: <span id="timeDisplay">00:00:00</span></p>
          <p>开列表: )" + getScheduleString(onTimes, onTimesindex)  + R"(<p>
          <p>关列表: )" + getScheduleString(offTimes, offTimesindex) + R"(<p>
          <p><span id="relayStatus">继电器状态: )" + relayStatus + R"(</span></p>

        </body>
        </html>
      )";

  esp8266_server.send(200, "text/html", htmlContent);
}

String getScheduleString(String* scheduleArray, int scheduleCount) {
  String result = "[";
  for (int i = 0; i < scheduleCount; i++) {
    result += "\"" + scheduleArray[i] + "\"";
    if (i < scheduleCount - 1) {
      result += ", ";
    }
  }
  result += "]";
  return result;
}

void handleUserRequest() {
  
  String reqResource = esp8266_server.uri();
  Serial.print("reqResource: ");
  Serial.println(reqResource);

  bool fileReadOK = handleFileRead(reqResource);

  if (!fileReadOK) {
    esp8266_server.send(404, "text/plain", "404 Not Found");
  }
}

bool handleFileRead(String resource) {
  if (resource.endsWith("/")) {
    resource = "/index.html";
  }

  String contentType = getContentType(resource);

  if (SPIFFS.exists(resource)) {
    File file = SPIFFS.open(resource, "r");
    esp8266_server.streamFile(file, contentType);
    file.close();
    return true;
  }
  return false;
}

String getContentType(String filename) {
  if (filename.endsWith(".htm")) return "text/html";
  else if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".png")) return "image/png";
  else if (filename.endsWith(".gif")) return "image/gif";
  else if (filename.endsWith(".jpg")) return "image/jpeg";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".xml")) return "text/xml";
  else if (filename.endsWith(".pdf")) return "application/x-pdf";
  else if (filename.endsWith(".zip")) return "application/x-zip";
  else if (filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

 python 代码

import requests

import re

import ast

ip = "192.168.31.160"
# ESP-EBFB77 138
from datetime import datetime

now = datetime.now()
current_time = now.strftime("%H:%M:%S")

print(current_time)
off_list = ['07:22:00', "09:48:00","12:30:00", "17:51:00", "23:37:00", "23:50:00"]
on_list = ["07:25:00", "09:51:00", "17:56:00", "23:52:00"]

url = "http://%s/LED-Control?ledPwm=2&time=%s" % (ip, str(current_time))
print(url)
response = requests.post(url)
response.encoding = 'utf-8'  # 明确指定编码方式为UTF-8
html = response.text
print(html)

r_list = re.compile("开列表: (\\[.*?\\]).*?<p>关列表: (\\[.*?\\]).*?继电器状态: (.*?)<", re.S).findall(html)
print(r_list)

re_on_list = []
re_off_list = []
jdq = ''

if r_list:
    re_on_list = ast.literal_eval(r_list[0][0])
    re_off_list = ast.literal_eval(r_list[0][1])
    jdq = r_list[0][2]
    # print(re_on_list, off, jdq)

for one_time in off_list:
    if one_time in re_off_list:
        continue
    url = "http://%s/LED-Control?ledPwm=0&time=%s" % (ip, str(one_time))
    response = requests.post(url)


for one_time in on_list:
    if one_time in re_on_list:
        continue
    url = "http://%s/LED-Control?ledPwm=1&time=%s" % (ip, str(one_time))

    response = requests.post(url)

print(jdq)

2.河南古灯

有继电器,ws2812,光敏电阻,人体传感器HC-SR501,esp8266

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <Ticker.h>
#define MAX_SCHEDULES 20 // 假设最多支持20个时间计划

Ticker ticker;
int count;  // 计时器计数变量

String  onTimes[MAX_SCHEDULES];
int onTimesindex = 0;
String  offTimes[MAX_SCHEDULES];
int offTimesindex = 0;
int relay = D1; // 继电器引脚,NodeMCU D5
const int lresistorPin = A0; // 光敏电阻
const int irSensorPin = D2; // 将人体传感器连接到D2引脚


// 定义阈值
const int ldrThreshold = 800; // 光敏电阻阈值,可以根据环境调整
const int pirThreshold = 1; // 人体传感器阈值,根据需要调整

const int THREE_MINUTES_IN_SECONDS = 180;  // 全局变量定义3分钟的秒数,人体感应时候+3分钟
ESP8266WebServer esp8266_server(80);
String currentTime;

int hours = 0;    // 当前小时数
int minutes = 0;  // 当前分钟数
int seconds = 0;  // 当前秒数

void setup(void) {
  pinMode(relay, OUTPUT);
  pinMode(irSensorPin, INPUT);
  Serial.begin(9600);
  Serial.println("");
  ticker.attach(1, tickerCount);
  digitalWrite(relay, LOW);
  pinMode(LED_BUILTIN, OUTPUT);

  WiFi.begin("yang1234", "y123456789"); // 输入你的WiFi SSID和密码
  Serial.println("Connecting ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++);
    Serial.print(' ');
  }

  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

  if (SPIFFS.begin()) {
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }

  esp8266_server.on("/LED-Control", handleLEDControl);
//  esp8266_server.on("/", HTTP_GET, handleRoot); // 新增根路径的处理函数
  esp8266_server.onNotFound(handleUserRequest);

  esp8266_server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  esp8266_server.handleClient();
    // 读取光敏电阻值
  int ldrValue = analogRead(lresistorPin);

  // 读取人体传感器值
  int pirValue = digitalRead(irSensorPin);
//  Serial.print("人体传感器: ");
//  Serial.println(pirValue);
//  Serial.print("光敏电阻: ");
//  Serial.println(ldrValue);
  // 如果光敏电阻和人体传感器条件满足,则执行操作
  if (ldrValue > ldrThreshold && pirValue == pirThreshold) {
    digitalWrite(relay, HIGH);
    // 清空关数组offTimes
    for (int i = 0; i < MAX_SCHEDULES; i++) {
      offTimes[i] = "";
    }
    offTimesindex = 0;

    // 获取当前时间
    String currentTime = getCurrentTime();
    Serial.println("Current Time: " + currentTime);

    // 当前时间加3分钟
    int currentHours = hours;
    int currentMinutes = minutes +  (THREE_MINUTES_IN_SECONDS / 60); // 转换为分钟;
    if (currentMinutes >= 60) {
      currentHours++;
      currentMinutes -= 60;
    }

    // 构建关时间字符串
    String offTime = String(currentHours < 10 ? "0" : "") + currentHours + ":" +
                     String(currentMinutes < 10 ? "0" : "") + currentMinutes + ":" +
                     String(seconds < 10 ? "0" : "") + seconds;

    // 加入关数组offTimes
    offTimes[offTimesindex] = offTime;
    offTimesindex++;
  }
  
}

String getCurrentTime() {
  return String(hours < 10 ? "0" : "") + hours + ":" +
         String(minutes < 10 ? "0" : "") + minutes + ":" +
         String(seconds < 10 ? "0" : "") + seconds;
}

void tickerCount() {
//  if (count <= 0) {
//    digitalWrite(relay, LOW);
//    count = 0;
//  } else {
//    delay(1000);
//    count--;
//  }

  // 每秒增加1秒
  seconds++;
  if (seconds >= 60) {
    seconds = 0;
    minutes++;
    if (minutes >= 60) {
      minutes = 0;
      hours++;
      if (hours >= 24) { // 如果小时数大于等于24,重置为0点
        hours = 0;
      }
    }
  }
  
  // 构建时间字符串
  currentTime = String(hours < 10 ? "0" : "") + hours + ":" +
                      String(minutes < 10 ? "0" : "") + minutes + ":" +
                      String(seconds < 10 ? "0" : "") + seconds;
    Serial.print("currentTime=");
  Serial.println(currentTime);

  // 检查onTimes数组,如果有匹配则打开继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String onTime = String(onTimes[i]);
    if (onTime == currentTime) {
      Serial.println("off");

      digitalWrite(relay, HIGH);
      break;
    }
  }
  
  // 检查offTimes数组,如果有匹配则关闭继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String offTime = String(offTimes[i]);
    if (offTime == currentTime) {
      digitalWrite(relay, LOW);
      Serial.println("on");
      break;
    }
  }
}

void handleLEDControl() {
  String ledPwm = esp8266_server.arg("ledPwm");
  
  int ledPwmVal = ledPwm.toInt();

  if (ledPwmVal == 0) {
    String timeParam = esp8266_server.arg("time");

    offTimes[offTimesindex] = timeParam;
    offTimesindex ++;
    
  }
  else if (ledPwmVal == 2) {
    String timeParam = esp8266_server.arg("time");

    // 解析时间参数的格式为hh:mm:ss
    int parsedHours, parsedMinutes, parsedSeconds;
    if (sscanf(timeParam.c_str(), "%d:%d:%d", &parsedHours, &parsedMinutes, &parsedSeconds) == 3) {
      hours = parsedHours;
      minutes = parsedMinutes;
      seconds = parsedSeconds;
      currentTime = String(hours < 10 ? "0" : "") + hours + ":" + String(minutes < 10 ? "0" : "") + minutes + ":" +String(seconds < 10 ? "0" : "") + seconds;
    }
  } else if (ledPwmVal == 1) {
      String timeParam = esp8266_server.arg("time");
        onTimes[onTimesindex] = timeParam;
        onTimesindex++;

  }else if (ledPwmVal == 3) {
      digitalWrite(relay, HIGH);
  }
  else if (ledPwmVal == 4) {
      digitalWrite(relay, LOW);
  }  else if(ledPwmVal == 5){
    //删除时间日期
    String timeParam = esp8266_server.arg("time");
     int len = sizeof(offTimes) / sizeof(offTimes[0]);
      // 使用一个循环查找要删除的元素并删除
      int indexToDelete = -1;
        for (int i = 0; i < sizeof(offTimes) / sizeof(offTimes[0]); i++) {
          if (offTimes[i] == timeParam) {
            indexToDelete = i;
            offTimesindex --;
            break;
          }
        }
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(offTimes) / sizeof(offTimes[0]) - 1; i++) {
            offTimes[i] = offTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          offTimes[sizeof(offTimes) / sizeof(offTimes[0]) - 1] = "";
        }


      len = sizeof(onTimes) / sizeof(onTimes[0]);
      // 使用一个循环查找要删除的元素并删除
        indexToDelete = -1;
        for (int i = 0; i < sizeof(onTimes) / sizeof(onTimes[0]); i++) {
          if (onTimes[i] == timeParam) {
            indexToDelete = i;
            onTimesindex --;
            break;
          }
        }
        
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(onTimes) / sizeof(onTimes[0]) - 1; i++) {
            onTimes[i] = onTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          onTimes[sizeof(onTimes) / sizeof(onTimes[0]) - 1] = "";
        }
      
  
    }

  
  int digitalValue = digitalRead(relay); // 读取 0 号数字引脚的电平状态
  String relayStatus;
  if (digitalValue == 1){
    relayStatus = "开启";
    }
  else{
    relayStatus = "关闭";
    }



  // 生成HTML内容,包括onTimes和offTimes
  String htmlContent = R"(
        <!DOCTYPE html>
        <html>
        <head>
          <title>插座开关</title>
          <meta charset="UTF-8">

          <script>

            // 在这里插入Arduino生成的时间变量
            var currentTime = ")" + currentTime + R"(";
            var timeParts = currentTime.split(":");
            var hours = timeParts[0];
            var minutes = timeParts[1];
            var seconds = timeParts[2];

            function updateTime() {
              var timeDisplay = document.getElementById("timeDisplay");
              // 分割时间字符串为小时、分钟和秒
              
                    // 每次加1秒
              seconds++;
              // 如果秒数达到60,将秒数重置为0并增加分钟
              if (seconds === 60) {
                seconds = 0;
                minutes++;
        
                // 如果分钟达到60,将分钟重置为0并增加小时
                if (minutes === 60) {
                  minutes = 0;
                  hours++;
        
                  // 如果小时达到24,将小时重置为0
                  if (hours === 24) {
                    hours = 0;
                  }
                }
              }
              // 格式化小时、分钟和秒,确保它们始终显示两位数
              var formattedHours = ('0' + hours).slice(-2);
              var formattedMinutes = ('0' + minutes).slice(-2);
              var formattedSeconds = ('0' + seconds).slice(-2);
              
              // 构建格式化后的时间字符串
              var formattedTime = formattedHours + ":" + formattedMinutes + ":" + formattedSeconds;
//             console.log(formattedTime);
              // 更新时间显示
              timeDisplay.textContent = formattedTime;
            }
            
            // 每秒钟调用一次updateTime函数
            setInterval(updateTime, 1000);
            
            // 当页面加载完成时立即更新时间
            window.onload = updateTime;

            
          </script>
        </head>
        <body>
        <h1>ledPwm:0.设置关机时间列表,1.设置开机时间列表,2.设置现在时间,3.手动开,4.手动关, 5.删除时间</h1>
        <p>现在时间: <span id="timeDisplay">00:00:00</span></p>
        <p>光敏电阻阈值: )" + String(ldrThreshold) + R"(</p>
        <p>人体传感器阈值: )" + String(pirThreshold) + R"(</p>
        <p>开列表: )" + getScheduleString(onTimes, onTimesindex)  + R"(<p>
        <p>关列表: )" + getScheduleString(offTimes, offTimesindex) + R"(<p>
        <p><span id="relayStatus">继电器状态: )" + relayStatus + R"(</span></p>

        </body>
        </html>
      )";

  esp8266_server.send(200, "text/html", htmlContent);
}

String getScheduleString(String* scheduleArray, int scheduleCount) {
  String result = "[";
  for (int i = 0; i < scheduleCount; i++) {
    result += "\"" + scheduleArray[i] + "\"";
    if (i < scheduleCount - 1) {
      result += ", ";
    }
  }
  result += "]";
  return result;
}

void handleUserRequest() {
  
  String reqResource = esp8266_server.uri();
  Serial.print("reqResource: ");
  Serial.println(reqResource);

  bool fileReadOK = handleFileRead(reqResource);

  if (!fileReadOK) {
    esp8266_server.send(404, "text/plain", "404 Not Found");
  }
}

bool handleFileRead(String resource) {
  if (resource.endsWith("/")) {
    resource = "/index.html";
  }

  String contentType = getContentType(resource);

  if (SPIFFS.exists(resource)) {
    File file = SPIFFS.open(resource, "r");
    esp8266_server.streamFile(file, contentType);
    file.close();
    return true;
  }
  return false;
}

String getContentType(String filename) {
  if (filename.endsWith(".htm")) return "text/html";
  else if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".png")) return "image/png";
  else if (filename.endsWith(".gif")) return "image/gif";
  else if (filename.endsWith(".jpg")) return "image/jpeg";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".xml")) return "text/xml";
  else if (filename.endsWith(".pdf")) return "application/x-pdf";
  else if (filename.endsWith(".zip")) return "application/x-zip";
  else if (filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

3.人体感应彩灯

使用ws2812 人体传感器HC-SR501,esp8266.(使用光明电阻过一会wifi重启不知原因)

#include <DNSServer.h>
#include "FastLED.h"            // 此示例程序需要使用FastLED库
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <Ticker.h>
#define MAX_SCHEDULES 20 // 假设最多支持20个时间计划

Ticker ticker;
int count;  // 计时器计数变量

String  onTimes[MAX_SCHEDULES];
int onTimesindex = 0;
String  offTimes[MAX_SCHEDULES];
int offTimesindex = 0;

const int irSensorPin = D2; // 将人体传感器连接到D2引脚
String litLedsCountStr; // led数量,字符串

// 定义阈值

const int pirThreshold = 1; // 人体传感器阈值,根据需要调整
int pirValue;
const int THREE_MINUTES_IN_SECONDS =60;  // 全局变量定义3分钟的秒数,人体感应时候+60秒
ESP8266WebServer esp8266_server(80);
String currentTime;

int hours = 0;    // 当前小时数
int minutes = 0;  // 当前分钟数
int seconds = 0;  // 当前秒数


#define NUM_LEDS 60             // LED灯珠数量
#define LED_DT D6                // Arduino输出控制信号引脚
#define LED_TYPE WS2812         // LED灯带型号
#define COLOR_ORDER GRB         // RGB灯珠中红色、绿色、蓝色LED的排列顺序

uint8_t max_bright = 128;       // LED亮度控制变量,可使用数值为 0 ~ 255, 数值越大则光带亮度越高
CRGB leds[NUM_LEDS];            // 建立光带leds
uint8_t beginHue;



void setup(void) {

  pinMode(irSensorPin, INPUT);
  Serial.begin(115200);
  Serial.println("");
  ticker.attach(1, tickerCount);
  LEDS.addLeds<LED_TYPE, LED_DT, COLOR_ORDER>(leds, NUM_LEDS);  // 初始化光带
  
  FastLED.setBrightness(max_bright);                            // 设置光带亮度
  pinMode(LED_BUILTIN, OUTPUT);
  
  
  
  WiFi.begin("yang1234", "y123456789"); // 输入你的WiFi SSID和密码
  Serial.println("Connecting ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(i++);
    Serial.print(' ');
  }

  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

  if (SPIFFS.begin()) {
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }

  esp8266_server.on("/LED-Control", handleLEDControl);
//  esp8266_server.on("/", HTTP_GET, handleRoot); // 新增根路径的处理函数
  esp8266_server.onNotFound(handleUserRequest);

  esp8266_server.begin();
  Serial.println("HTTP server started");
  cdoff(); // 关闭所有LED

}

void loop(void) {
  esp8266_server.handleClient();

  // 读取人体传感器值
  int pirValue = digitalRead(irSensorPin);
  // 如果光敏电阻和人体传感器条件满足,则执行操作
  if ( pirValue == pirThreshold) {
    cdon();
    Serial.println("满足开");
    Serial.print("人体传感器: ");
    Serial.println(pirValue);

    // 清空关数组offTimes
    for (int i = 0; i < MAX_SCHEDULES; i++) {
      offTimes[i] = "";
    }
    offTimesindex = 0;

    // 获取当前时间
    String currentTime = getCurrentTime();
    Serial.println("Current Time: " + currentTime);

    // 当前时间加3分钟
    int currentHours = hours;
    int currentMinutes = minutes +  (THREE_MINUTES_IN_SECONDS / 60); // 转换为分钟;
    if (currentMinutes >= 60) {
      currentHours++;
      currentMinutes -= 60;
    }

    // 构建关时间字符串
    String offTime = String(currentHours < 10 ? "0" : "") + currentHours + ":" +
                     String(currentMinutes < 10 ? "0" : "") + currentMinutes + ":" +
                     String(seconds < 10 ? "0" : "") + seconds;

    // 加入关数组offTimes
    offTimes[offTimesindex] = offTime;
    offTimesindex++;
  }
  
}

String getCurrentTime() {
  return String(hours < 10 ? "0" : "") + hours + ":" +
         String(minutes < 10 ? "0" : "") + minutes + ":" +
         String(seconds < 10 ? "0" : "") + seconds;
}

void tickerCount() {

  // 每秒增加1秒
  seconds++;
  if (seconds >= 60) {
    seconds = 0;
    minutes++;
    if (minutes >= 60) {
      minutes = 0;
      hours++;
      if (hours >= 24) { // 如果小时数大于等于24,重置为0点
        hours = 0;
      }
    }
  }
  
  // 构建时间字符串
  currentTime = String(hours < 10 ? "0" : "") + hours + ":" +
                      String(minutes < 10 ? "0" : "") + minutes + ":" +
                      String(seconds < 10 ? "0" : "") + seconds;
    Serial.print("currentTime=");
  Serial.println(currentTime);

  // 检查onTimes数组,如果有匹配则打开继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String onTime = String(onTimes[i]);
    if (onTime == currentTime) {
      
      Serial.println("时间到on");
      cdon();
      
      break;
    }
  }
  
  // 检查offTimes数组,如果有匹配则关闭继电器
  
  for (int i = 0; i < MAX_SCHEDULES; i++) {
    String offTime = String(offTimes[i]);
    if (offTime == currentTime) {
      Serial.println("时间到off");
      cdoff();
      
      break;
    }
  }
}


void handleLEDControl() {
  String ledPwm = esp8266_server.arg("ledPwm");
  
  int ledPwmVal = ledPwm.toInt();

  if (ledPwmVal == 0) {
    String timeParam = esp8266_server.arg("time");

    offTimes[offTimesindex] = timeParam;
    offTimesindex ++;
    
  }
  else if (ledPwmVal == 2) {
    String timeParam = esp8266_server.arg("time");

    // 解析时间参数的格式为hh:mm:ss
    int parsedHours, parsedMinutes, parsedSeconds;
    if (sscanf(timeParam.c_str(), "%d:%d:%d", &parsedHours, &parsedMinutes, &parsedSeconds) == 3) {
      hours = parsedHours;
      minutes = parsedMinutes;
      seconds = parsedSeconds;
      currentTime = String(hours < 10 ? "0" : "") + hours + ":" + String(minutes < 10 ? "0" : "") + minutes + ":" +String(seconds < 10 ? "0" : "") + seconds;
    }
  } else if (ledPwmVal == 1) {
      String timeParam = esp8266_server.arg("time");
        onTimes[onTimesindex] = timeParam;
        onTimesindex++;

  }else if (ledPwmVal == 3) {
      cdon();
  }
  else if (ledPwmVal == 4) {
      cdoff();
  }  else if(ledPwmVal == 5){
    //删除时间日期
    String timeParam = esp8266_server.arg("time");
     int len = sizeof(offTimes) / sizeof(offTimes[0]);
      // 使用一个循环查找要删除的元素并删除
      int indexToDelete = -1;
        for (int i = 0; i < sizeof(offTimes) / sizeof(offTimes[0]); i++) {
          if (offTimes[i] == timeParam) {
            indexToDelete = i;
            offTimesindex --;
            break;
          }
        }
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(offTimes) / sizeof(offTimes[0]) - 1; i++) {
            offTimes[i] = offTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          offTimes[sizeof(offTimes) / sizeof(offTimes[0]) - 1] = "";
        }


      len = sizeof(onTimes) / sizeof(onTimes[0]);
      // 使用一个循环查找要删除的元素并删除
        indexToDelete = -1;
        for (int i = 0; i < sizeof(onTimes) / sizeof(onTimes[0]); i++) {
          if (onTimes[i] == timeParam) {
            indexToDelete = i;
            onTimesindex --;
            break;
          }
        }
        
        // 如果找到要删除的元素,就删除它
        if (indexToDelete != -1) {
          for (int i = indexToDelete; i < sizeof(onTimes) / sizeof(onTimes[0]) - 1; i++) {
            onTimes[i] = onTimes[i + 1];
          }
          // 将最后一个元素设置为空字符串或者你需要的默认值
          onTimes[sizeof(onTimes) / sizeof(onTimes[0]) - 1] = "";
        }
      
  
    }

// String relayStatus = "开启";
litLedsCountStr = getLitLedsCountAsString(leds, NUM_LEDS);

  // 生成HTML内容,包括onTimes和offTimes
  String htmlContent = R"(
        <!DOCTYPE html>
        <html>
        <head>
          <title>插座开关</title>
          <meta charset="UTF-8">

          <script>

            // 在这里插入Arduino生成的时间变量
            var currentTime = ")" + currentTime + R"(";
            var timeParts = currentTime.split(":");
            var hours = timeParts[0];
            var minutes = timeParts[1];
            var seconds = timeParts[2];

            function updateTime() {
              var timeDisplay = document.getElementById("timeDisplay");
              // 分割时间字符串为小时、分钟和秒
              
                    // 每次加1秒
              seconds++;
              // 如果秒数达到60,将秒数重置为0并增加分钟
              if (seconds === 60) {
                seconds = 0;
                minutes++;
        
                // 如果分钟达到60,将分钟重置为0并增加小时
                if (minutes === 60) {
                  minutes = 0;
                  hours++;
        
                  // 如果小时达到24,将小时重置为0
                  if (hours === 24) {
                    hours = 0;
                  }
                }
              }
              // 格式化小时、分钟和秒,确保它们始终显示两位数
              var formattedHours = ('0' + hours).slice(-2);
              var formattedMinutes = ('0' + minutes).slice(-2);
              var formattedSeconds = ('0' + seconds).slice(-2);
              
              // 构建格式化后的时间字符串
              var formattedTime = formattedHours + ":" + formattedMinutes + ":" + formattedSeconds;
//             console.log(formattedTime);
              // 更新时间显示
              timeDisplay.textContent = formattedTime;
            }
            
            // 每秒钟调用一次updateTime函数
            setInterval(updateTime, 1000);
            
            // 当页面加载完成时立即更新时间
            window.onload = updateTime;

            
          </script>
        </head>
        <body>
        <h1>ledPwm:0.设置关机时间列表,1.设置开机时间列表,2.设置现在时间,3.手动开,4.手动关, 5.删除时间</h1>
        <p>现在时间: <span id="timeDisplay">00:00:00</span></p>
        <p>人体传感器阈值: )" + String(pirValue) + R"(</p>
        <p>开列表: )" + getScheduleString(onTimes, onTimesindex)  + R"(<p>
        <p>关列表: )" + getScheduleString(offTimes, offTimesindex) + R"(<p>
        <p><span id="relayStatus">灯珠数量: )" + litLedsCountStr + R"(</span></p>

        </body>
        </html>
      )";

  esp8266_server.send(200, "text/html", htmlContent);
}

String getScheduleString(String* scheduleArray, int scheduleCount) {
  String result = "[";
  for (int i = 0; i < scheduleCount; i++) {
    result += "\"" + scheduleArray[i] + "\"";
    if (i < scheduleCount - 1) {
      result += ", ";
    }
  }
  result += "]";
  return result;
}

void handleUserRequest() {
  
  String reqResource = esp8266_server.uri();
  Serial.print("reqResource: ");
  Serial.println(reqResource);

  bool fileReadOK = handleFileRead(reqResource);

  if (!fileReadOK) {
    esp8266_server.send(404, "text/plain", "404 Not Found");
  }
}

bool handleFileRead(String resource) {
  if (resource.endsWith("/")) {
    resource = "/index.html";
  }

  String contentType = getContentType(resource);

  if (SPIFFS.exists(resource)) {
    File file = SPIFFS.open(resource, "r");
    esp8266_server.streamFile(file, contentType);
    file.close();
    return true;
  }
  return false;
}

String getContentType(String filename) {
  if (filename.endsWith(".htm")) return "text/html";
  else if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".png")) return "image/png";
  else if (filename.endsWith(".gif")) return "image/gif";
  else if (filename.endsWith(".jpg")) return "image/jpeg";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".xml")) return "text/xml";
  else if (filename.endsWith(".pdf")) return "application/x-pdf";
  else if (filename.endsWith(".zip")) return "application/x-zip";
  else if (filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

//色
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm };
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns

#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))


void cdon() {
      gPatterns[gCurrentPatternNumber]();
    // send the 'leds' array out to the actual LED strip
    Serial.println("执行开");
    FastLED.show();  
    // insert a delay to keep the framerate modest
    FastLED.delay(1000/max_bright); 
    // do some periodic updates
    EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow
    EVERY_N_SECONDS( 10 ) { nextPattern(); } // change
}

void cdoff() {
      Serial.println("执行关");
      fill_gradient_RGB(leds, 0, CRGB(0,0,0), 59, CRGB(0, 0, 0));;
      FastLED.show();
      delay(500);
      turnOffAllLeds();

}
void turnOffAllLeds() {
  // 设置所有LED颜色为黑色(关闭)
  for(int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB::Black;
  }
  // 更新LED灯的状态
  FastLED.show();
}

String getLitLedsCountAsString(CRGB leds[], int numLeds) {
  int litCount = 0; // 用于计数亮灯的数量
  for (int i = 0; i < numLeds; i++) {
    // 如果LED不是黑色(关闭)表示它是亮的
    if (leds[i]) { // 这里隐式检查leds[i]是否不等于CRGB::Black
      litCount++;
    }
  }
  // 将整数转换为String对象并返回
  return String(litCount);
}

void nextPattern()
{
  // add one to the current pattern number, and wrap around at the end
  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}

void rainbow() 
{
  // FastLED's built-in rainbow generator
  fill_rainbow( leds, NUM_LEDS, gHue, 7);
}

void rainbowWithGlitter() 
{
  // built-in FastLED rainbow, plus some random sparkly glitter
  rainbow();
  addGlitter(80);
}

void addGlitter( fract8 chanceOfGlitter) 
{
  if( random8() < chanceOfGlitter) {
    leds[ random16(NUM_LEDS) ] += CRGB::White;
  }
}

void confetti() 
{
  // random colored speckles that blink in and fade smoothly
  fadeToBlackBy( leds, NUM_LEDS, 10);
  int pos = random16(NUM_LEDS);
  leds[pos] += CHSV( gHue + random8(64), 200, 255);
}

void sinelon()
{
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds, NUM_LEDS, 20);
  int pos = beatsin16( 13, 0, NUM_LEDS-1 );
  leds[pos] += CHSV( gHue, 255, 192);
}

void bpm()
{
  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
  uint8_t BeatsPerMinute = 62;
  CRGBPalette16 palette = PartyColors_p;
  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
  for( int i = 0; i < NUM_LEDS; i++) { //9948
    leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
  }
}

void juggle() {
  // eight colored dots, weaving in and out of sync with each other
  fadeToBlackBy( leds, NUM_LEDS, 20);
  byte dothue = 0;
  for( int i = 0; i < 8; i++) {
    leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255);
    dothue += 32;
  }
}

升级版带ota

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h> // 用于网页固件更新
#include <Ticker.h>

#define MAX_SCHEDULES 20 // 最多支持20个时间计划

// --- 全局变量 ---
Ticker ticker;
String onTimes[MAX_SCHEDULES];
int onTimesindex = 0;
String offTimes[MAX_SCHEDULES];
int offTimesindex = 0;

int relay = 0; // 继电器引脚, ESP-01S 对应 GPIO0
ESP8266WebServer esp8266_server(80);
ESP8266HTTPUpdateServer httpUpdater; // 创建网页更新服务器对象
String currentTime = "00:00:00";

int hours = 0;    // 当前小时数
int minutes = 0;  // 当前分钟数
int seconds = 0;  // 当前秒数

// --- HTML 页面模板 (已添加更新页面的入口) ---
const char HTML_TEMPLATE[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>ESP-01S 智能插座</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #f4f7f6; margin: 0; padding: 20px; color: #333; }
    .container { max-width: 600px; margin: 0 auto; background: #fff; padding: 25px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
    h1, h2 { text-align: center; color: #007bff; }
    h2 { border-top: 1px solid #eee; padding-top: 20px; margin-top: 20px; }
    .status-box, .control-box, .schedule-box { margin-bottom: 25px; }
    p { font-size: 1.2em; text-align: center; }
    #timeDisplay { font-weight: bold; color: #333; }
    .status-on { color: #28a745; font-weight: bold; }
    .status-off { color: #dc3545; font-weight: bold; }
    .btn-group { display: flex; justify-content: space-around; gap: 15px; }
    button, .button { display: inline-block; padding: 12px 20px; font-size: 1em; cursor: pointer; border: none; border-radius: 5px; color: #fff; text-align: center; text-decoration: none; }
    .btn-on { background-color: #28a745; }
    .btn-on:hover { background-color: #218838; }
    .btn-off { background-color: #dc3545; }
    .btn-off:hover { background-color: #c82333; }
    .btn-add { background-color: #007bff; }
    .btn-add:hover { background-color: #0069d9; }
    .btn-sync { background-color: #ffc107; color: #212529; }
    .btn-sync:hover { background-color: #e0a800; }
    .btn-update { background-color: #6c757d; }
    .btn-update:hover { background-color: #5a6268; }
    .input-group { display: flex; gap: 10px; margin-top: 10px; }
    input[type="time"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 5px; font-size: 1em; }
    ul { list-style: none; padding: 0; }
    li { background: #f9f9f9; border: 1px solid #eee; padding: 10px; margin-bottom: 8px; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; }
    li button { background-color: #6c757d; font-size: 0.8em; padding: 5px 10px; }
    li button:hover { background-color: #5a6268; }
  </style>
</head>
<body>
  <div class="container">
    <h1>智能插座控制器</h1>
    <div class="status-box">
      <p>当前时间: <span id="timeDisplay">##CURRENT_TIME##</span></p>
      <p>继电器状态: ##RELAY_STATUS##</p>
    </div>

    <div class="control-box">
      <h2>手动控制</h2>
      <div class="btn-group">
        <button class="button btn-on" onclick="manualControl(3)">手动开启</button>
        <button class="button btn-off" onclick="manualControl(4)">手动关闭</button>
      </div>
    </div>

    <div class="schedule-box">
      <h2>添加定时任务</h2>
      <div class="input-group">
        <input type="time" id="scheduleTime" step="1" value="12:00:00">
        <button class="button btn-add" onclick="addSchedule(1)">添加开机</button>
        <button class="button btn-add" onclick="addSchedule(0)">添加关机</button>
      </div>
    </div>

    <div class="schedule-box">
        <h2>同步设备时间</h2>
        <div class="input-group">
          <input type="time" id="syncTime" step="1">
          <button class="button btn-sync" onclick="syncDeviceTime()">同步时间</button>
        </div>
      </div>

    <div class="schedule-box">
      <h2>任务列表</h2>
      <h3>开机任务</h3>
      <ul id="on-list">##ON_TASKS##</ul>
      <h3>关机任务</h3>
      <ul id="off-list">##OFF_TASKS##</ul>
    </div>

    <div class="schedule-box">
        <h2>固件更新</h2>
        <p><a href="/update" class="button btn-update">前往固件更新页面</a></p>
    </div>
  </div>

  <script>
    let hours = parseInt('##HOURS##');
    let minutes = parseInt('##MINUTES##');
    let seconds = parseInt('##SECONDS##');

    function updateTime() {
      seconds++;
      if (seconds >= 60) { seconds = 0; minutes++; }
      if (minutes >= 60) { minutes = 0; hours++; }
      if (hours >= 24) { hours = 0; }
      
      const pad = (num) => String(num).padStart(2, '0');
      document.getElementById('timeDisplay').textContent = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
    }
    setInterval(updateTime, 1000);

    function manualControl(pwm) {
      window.location.href = `/LED-Control?ledPwm=${pwm}`;
    }

    function addSchedule(pwm) {
      const time = document.getElementById('scheduleTime').value;
      if (time) {
        window.location.href = `/LED-Control?ledPwm=${pwm}&time=${time}`;
      } else {
        alert('请先选择时间!');
      }
    }

    function deleteSchedule(time) {
      if (confirm(`确定要删除时间点 ${time} 吗?`)) {
        window.location.href = `/LED-Control?ledPwm=5&time=${time}`;
      }
    }
    
    function syncDeviceTime() {
        const time = document.getElementById('syncTime').value;
        if (time) {
             let fullTime = time.split(':').length === 2 ? time + ':00' : time;
             window.location.href = `/LED-Control?ledPwm=2&time=${fullTime}`;
        } else {
            alert('请选择要同步的时间!');
        }
    }

    document.addEventListener('DOMContentLoaded', (event) => {
        const now = new Date();
        const pad = (num) => String(num).padStart(2, '0');
        const currentTimeString = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
        document.getElementById('syncTime').value = currentTimeString;
    });
  </script>
</body>
</html>
)rawliteral";

// --- 函数声明 ---
String generatePage();

void setup(void) {
  pinMode(relay, OUTPUT);
  digitalWrite(relay, LOW); // 默认打开继电器 (低电平触发的继电器)
  
  Serial.begin(115200);
  Serial.println("\nBooting...");

  WiFi.begin("yang1234", "y123456789"); // 输入你的WiFi SSID和密码
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // --- Web服务器路由 ---
  esp8266_server.on("/LED-Control", []() {
    String ledPwm = esp8266_server.arg("ledPwm");
    int ledPwmVal = ledPwm.toInt();
    String timeParam = esp8266_server.arg("time");

    switch(ledPwmVal) {
      case 0: if (offTimesindex < MAX_SCHEDULES) offTimes[offTimesindex++] = timeParam; break;
      case 1: if (onTimesindex < MAX_SCHEDULES) onTimes[onTimesindex++] = timeParam; break;
      case 2: sscanf(timeParam.c_str(), "%d:%d:%d", &hours, &minutes, &seconds); break;
      case 3: digitalWrite(relay, LOW); break;
      case 4: digitalWrite(relay, HIGH); break;
      case 5: {
        int indexToDelete = -1;
        for (int i = 0; i < offTimesindex; i++) if (offTimes[i] == timeParam) { indexToDelete = i; break; }
        if (indexToDelete != -1) { for (int i = indexToDelete; i < offTimesindex - 1; i++) offTimes[i] = offTimes[i + 1]; offTimes[--offTimesindex] = ""; }
        indexToDelete = -1;
        for (int i = 0; i < onTimesindex; i++) if (onTimes[i] == timeParam) { indexToDelete = i; break; }
        if (indexToDelete != -1) { for (int i = indexToDelete; i < onTimesindex - 1; i++) onTimes[i] = onTimes[i + 1]; onTimes[--onTimesindex] = ""; }
        break;
      }
    }

    String referer = esp8266_server.header("Referer");
    if (referer.indexOf(WiFi.localIP().toString()) > -1) {
      esp8266_server.sendHeader("Location", "/", true);
      esp8266_server.send(302, "text/plain", "");
    } else {
      esp8266_server.send(200, "text/html", generatePage());
    }
  });

  esp8266_server.on("/", [](){ esp8266_server.send(200, "text/html", generatePage()); });

  // --- Web Updater 初始化 ---
  // 设置更新页面的URL为 /update
  // 也可以为更新页面设置用户名和密码,增加安全性,例如: httpUpdater.setup(&esp8266_server, "/update", "admin", "password");
  httpUpdater.setup(&esp8266_server, "/update");
  Serial.println("HTTPUpdateServer ready! Open http://[IP]/update");

  esp8266_server.onNotFound([](){ esp8266_server.send(404, "text/plain", "404 Not Found"); });
  esp8266_server.begin();
  Serial.println("HTTP server started.");

  // --- Ticker 定时器 ---
  ticker.attach(1, []() {
    seconds++;
    if (seconds >= 60) { seconds = 0; minutes++; if (minutes >= 60) { minutes = 0; hours++; if (hours >= 24) hours = 0; } }

    char timeBuffer[9];
    sprintf(timeBuffer, "%02d:%02d:%02d", hours, minutes, seconds);
    currentTime = String(timeBuffer);

    for (int i = 0; i < onTimesindex; i++) if (onTimes[i] == currentTime) { digitalWrite(relay, LOW); Serial.println("Scheduled ON"); break; }
    for (int i = 0; i < offTimesindex; i++) if (offTimes[i] == currentTime) { digitalWrite(relay, HIGH); Serial.println("Scheduled OFF"); break; }
  });
}

void loop(void) {
  // 这一行会同时处理普通网页请求和固件更新请求
  esp8266_server.handleClient();
}

// --- 动态生成HTML页面的函数 ---
String generatePage() {
  String page = FPSTR(HTML_TEMPLATE); // 从PROGMEM加载HTML模板

  String relayStatus = (digitalRead(relay) == LOW) ? "<span class='status-on'>开启</span>" : "<span class='status-off'>关闭</span>";
  
  String onTasksHtml = "";
  if (onTimesindex == 0) onTasksHtml = "<li>无</li>";
  else { for (int i = 0; i < onTimesindex; i++) onTasksHtml += "<li>" + onTimes[i] + "<button onclick='deleteSchedule(\"" + onTimes[i] + "\")'>删除</button></li>"; }

  String offTasksHtml = "";
  if (offTimesindex == 0) offTasksHtml = "<li>无</li>";
  else { for (int i = 0; i < offTimesindex; i++) offTasksHtml += "<li>" + offTimes[i] + "<button onclick='deleteSchedule(\"" + offTimes[i] + "\")'>删除</button></li>"; }

  page.replace("##CURRENT_TIME##", currentTime);
  page.replace("##RELAY_STATUS##", relayStatus);
  page.replace("##ON_TASKS##", onTasksHtml);
  page.replace("##OFF_TASKS##", offTasksHtml);
  page.replace("##HOURS##", String(hours));
  page.replace("##MINUTES##", String(minutes));
  page.replace("##SECONDS##", String(seconds));

  return page;
}