CREATE TABLE `relay_schedules` ( `id` int(11) NOT NULL, `relay_num` int(11) NOT NULL, `target_time` time NOT NULL, `days` varchar(255) NOT NULL, `is_active` tinyint(1) DEFAULT 1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; -- -- Đang đổ dữ liệu cho bảng `relay_schedules` -- INSERT INTO `relay_schedules` (`id`, `relay_num`, `target_time`, `days`, `is_active`) VALUES (1, 1, '21:15:00', 'Sun,Mon,Tue,Wed,Thu,Fri', 1), (2, 2, '18:30:00', 'Sat,Sun', 1), (3, 3, '12:00:00', 'Mon,Wed,Fri', 1), (4, 4, '22:00:00', 'Sun,Mon,Tue,Wed,Thu,Fri,Sat', 1), (5, 5, '00:00:00', 'Sun', 0); connect_error) { die(json_encode(["status" => "error", "message" => "Database connection failed"])); } $sql = "SELECT relay_num, target_time, days, is_active FROM relay_schedules"; $result = $conn->query($sql); $schedules = []; while($row = $result->fetch_assoc()) { $schedules[] = [ "relay" => (int)$row['relay_num'], "time" => substr($row['target_time'], 0, 5), // Lấy định dạng HH:MM "days" => explode(',', $row['days']), // Chuyển chuỗi thành mảng "active" => (int)$row['is_active'] ]; } echo json_encode(["status" => "success", "schedules" => $schedules]); $conn->close(); ?> #include #include #include #include #include "time.h" // Cấu hình Wi-Fi và API const char* ssid = "XM"; const char* password = "79797979"; const char* apiUrl = "https://hivemq.nongnghiep24h.com/get_schedule.php"; // Định nghĩa chân GPIO cho 5 Relay const int relayPins[5] = {22, 23, 19, 18, 5}; // Cấu hình NTP Server để lấy thời gian thực const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 25200; // Múi giờ Việt Nam (GMT+7 = 7 * 3600) const int daylightOffset_sec = 0; Preferences preferences; // Khai báo mảng tên các thứ trong tuần trùng với định dạng của bạn const char* daysOfWeek[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; struct RelaySchedule { char targetTime[6]; // HH:MM char days[50]; // "Mon,Wed,Fri" bool active; }; RelaySchedule localSchedules[5]; void setup() { Serial.begin(115200); // Khởi tạo các chân Relay for(int i=0; i<5; i++){ pinMode(relayPins[i], OUTPUT); digitalWrite(relayPins[i], LOW); // Mặc định tắt } // Khởi tạo Preferences preferences.begin("relay_pref", false); loadSchedulesFromFlash(); // Kết nối Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi Connected"); // Cấu hình thời gian thực configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Tải lịch trình mới từ API fetchScheduleFromAPI(); } void loop() { static unsigned long lastCheck = 0; if (millis() - lastCheck >= 1000) { // Kiểm tra mỗi giây lastCheck = millis(); checkSchedules(); } // Mỗi 5 phút cập nhật lại lịch trình từ API một lần static unsigned long lastApiUpdate = 0; if (millis() - lastApiUpdate >= 300000) { lastApiUpdate = millis(); if(WiFi.status() == WL_CONNECTED) { fetchScheduleFromAPI(); } } } void fetchScheduleFromAPI() { HTTPClient http; http.begin(apiUrl); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); DynamicJsonDocument doc(2048); deserializeJson(doc, payload); JsonArray array = doc["schedules"].as(); for (JsonVariant v : array) { int rNum = v["relay"].as() - 1; // Chuyển từ (1-5) sang index (0-4) if(rNum >= 0 && rNum < 5) { String tTime = v["time"].as(); bool act = v["active"].as() == 1; String daysStr = ""; JsonArray daysArray = v["days"].as(); for(JsonVariant d : daysArray) { daysStr += d.as() + ","; } // Lưu vào cấu trúc tạm thời strcpy(localSchedules[rNum].targetTime, tTime.c_str()); strcpy(localSchedules[rNum].days, daysStr.c_str()); localSchedules[rNum].active = act; // Lưu vĩnh viễn vào Flash thông qua Preferences.h String keyBase = "r" + String(rNum); preferences.putString((keyBase + "_time").c_str(), tTime); preferences.putString((keyBase + "_days").c_str(), daysStr); preferences.putBool((keyBase + "_act").c_str(), act); } } Serial.println("Schedules updated from API and saved to Flash."); } http.end(); } void loadSchedulesFromFlash() { for(int i=0; i<5; i++) { String keyBase = "r" + String(i); String tTime = preferences.getString((keyBase + "_time").c_str(), "00:00"); String daysStr = preferences.getString((keyBase + "_days").c_str(), ""); bool act = preferences.getBool((keyBase + "_act").c_str(), false); strcpy(localSchedules[i].targetTime, tTime.c_str()); strcpy(localSchedules[i].days, daysStr.c_str()); localSchedules[i].active = act; } Serial.println("Schedules loaded from Flash OMA."); } void checkSchedules() { struct tm timeinfo; if(!getLocalTime(&timeinfo)){ Serial.println("Failed to obtain time"); return; } // Định dạng thời gian hiện tại thành HH:MM và Thứ char currentTime[6]; sprintf(currentTime, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); String currentDay = daysOfWeek[timeinfo.tm_wday]; // "Sun", "Mon", v.v. for(int i=0; i<5; i++) { if(localSchedules[i].active) { String scheduledDays = String(localSchedules[i].days); // Kiểm tra nếu thời gian trùng khớp VÀ ngày hiện tại nằm trong danh sách được chọn if(strcmp(localSchedules[i].targetTime, currentTime) == 0 && scheduledDays.indexOf(currentDay) != -1) { if(timeinfo.tm_sec == 0) { // Kích hoạt chính xác vào giây thứ 0 của phút đó digitalWrite(relayPins[i], HIGH); Serial.printf("Relay %d TURNED ON\n", i+1); } } else { // Bạn có thể tùy biến logic TẮT dựa trên thời gian kết thúc hoặc tắt thủ công. // Ví dụ này kích hoạt ON khi đến giờ trùng khớp. } } else { digitalWrite(relayPins[i], LOW); // Luôn tắt nếu lịch trình không active } } }