#include <WiFi.h>
#include "time.h"
#include "FS.h"
#include "SD.h"
#include <SPI.h>
#include <algorithm>
// --- 設定區 ---
const char* ssid = "您的WiFi名稱";
const char* password = "您的WiFi密碼";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 28800; // 台灣時區 UTC+8
const int daylightOffset_sec = 0;
const int MAX_TASKS = 30;
const int RELAY_PIN = 26;
const char* filePath = "/schedule.txt";
struct Task {
uint8_t hr;
uint8_t mn;
bool state;
};
Task scheduleList[MAX_TASKS];
int taskCount = 0;
static int lastCheckedMinute = -1;
// --- 排序工具 ---
bool compareTasks(const Task &a, const Task &b) {
return (a.hr * 60 + a.mn) < (b.hr * 60 + b.mn);
}
// --- SD 卡讀寫 ---
void saveScheduleToSD() {
std::sort(scheduleList, scheduleList + taskCount, compareTasks);
File file = SD.open(filePath, FILE_WRITE);
if (!file) return;
for (int i = 0; i < taskCount; i++) {
file.printf("%02d:%02d:%s\n", scheduleList[i].hr, scheduleList[i].mn, scheduleList[i].state ? "on" : "off");
}
file.close();
Serial.println("💾 資料已同步至 SD 卡");
}
void loadSchedule() {
File file = SD.open(filePath);
if (!file) return;
taskCount = 0;
while (file.available() && taskCount < MAX_TASKS) {
String line = file.readStringUntil('\n');
line.trim();
int c1 = line.indexOf(':');
int c2 = line.lastIndexOf(':');
if (c1 != -1 && c2 != -1) {
scheduleList[taskCount].hr = (uint8_t)line.substring(0, c1).toInt();
scheduleList[taskCount].mn = (uint8_t)line.substring(c1 + 1, c2).toInt();
scheduleList[taskCount].state = (line.substring(c2 + 1) == "on");
taskCount++;
}
}
file.close();
std::sort(scheduleList, scheduleList + taskCount, compareTasks);
Serial.printf("📊 已載入 %d 組排程\n", taskCount);
}
// --- 指令處理 ---
void handleSerial() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
// 1. 顯示目前時間
if (input == "time") {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
Serial.printf("🕒 目前系統時間:%02d:%02d:%02d\n", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
} else {
Serial.println("❌ 無法取得時間,請檢查 WiFi/NTP 連線");
}
}
// 2. 強制開啟
else if (input == "on") {
digitalWrite(RELAY_PIN, HIGH);
Serial.println("💡 手動操作:開啟 (ON)");
}
// 3. 強制關閉
else if (input == "off") {
digitalWrite(RELAY_PIN, LOW);
Serial.println("🌑 手動操作:關閉 (OFF)");
}
// 4. 切換狀態
else if (input == "toggle") {
bool newState = !digitalRead(RELAY_PIN);
digitalWrite(RELAY_PIN, newState);
Serial.printf("🔄 手動切換:現在為 %s\n", newState ? "開啟" : "關閉");
}
// 5. 列出排程
else if (input == "list") {
Serial.println("\n--- 排程清單 ---");
if (taskCount == 0) Serial.println("(無資料)");
for (int i = 0; i < taskCount; i++) {
Serial.printf("[%d] %02d:%02d -> %s\n", i + 1, scheduleList[i].hr, scheduleList[i].mn, scheduleList[i].state ? "ON" : "OFF");
}
Serial.println("----------------");
}
// 6. 新增排程
else if (input.startsWith("add ")) {
uint8_t h, m;
char action[4];
if (sscanf(input.c_str() + 4, "%hhu:%hhu %3s", &h, &m, action) == 3) {
if (h > 23 || m > 59) {
Serial.println("❌ 時間格式錯誤 (小時0-23, 分鐘0-59)");
return;
}
bool state = (strcmp(action, "on") == 0);
// 重複檢查
bool duplicate = false;
for (int i = 0; i < taskCount; i++) {
if (scheduleList[i].hr == h && scheduleList[i].mn == m) {
duplicate = true;
break;
}
}
if (!duplicate && taskCount < MAX_TASKS) {
scheduleList[taskCount++] = {h, m, state};
saveScheduleToSD();
Serial.printf("✅ 已新增 %02d:%02d -> %s\n", h, m, state ? "ON" : "OFF");
} else {
Serial.println("❌ 重複時間或已達上限");
}
} else {
Serial.println("❌ 格式錯誤!範例:add 08:30 on");
}
}
// 7. 刪除排程
else if (input.startsWith("del ")) {
int idx = input.substring(4).toInt() - 1;
if (idx >= 0 && idx < taskCount) {
for (int i = idx; i < taskCount - 1; i++) scheduleList[i] = scheduleList[i + 1];
taskCount--;
saveScheduleToSD();
Serial.println("🗑️ 已刪除");
}
}
}
}
void setup() {
Serial.begin(115200);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
if (!SD.begin(5)) {
Serial.println("❌ SD 卡初始化失敗!請檢查接線或卡是否插入");
} else {
Serial.println("✅ SD 卡初始化成功");
loadSchedule();
}
/*
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
*/
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("\n🚀 系統啟動成功!指令: time, on, off, toggle, list, add, del");
}
void loop() {
handleSerial();
// 定時檢查邏輯
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
delay(1000);
return;
}
int currentMinute = timeinfo.tm_hour * 60 + timeinfo.tm_min;
if (currentMinute != lastCheckedMinute) {
for (int i = 0; i < taskCount; i++) {
int taskMinute = scheduleList[i].hr * 60 + scheduleList[i].mn;
if (taskMinute == currentMinute) {
digitalWrite(RELAY_PIN, scheduleList[i].state ? HIGH : LOW);
Serial.printf("\n⏰ 排程觸發: %02d:%02d -> %s\n",
scheduleList[i].hr, scheduleList[i].mn,
scheduleList[i].state ? "ON" : "OFF");
}
}
lastCheckedMinute = currentMinute;
}
delay(100); // 避免 loop 跑太快
}