· 4 months ago · Jun 05, 2025, 09:45 PM
1/*
2 ========================================================================
3 ESP32 SCHEDULED MOTOR CONTROL WITH DEEP SLEEP & EEPROM BACKUP
4 ========================================================================
5
6 OVERVIEW:
7 Automatically runs a motor at scheduled hours with deep sleep power saving.
8 Survives power failures with EEPROM backup and includes GPIO reset functionality.
9
10 HARDWARE REQUIREMENTS:
11 • ESP32 development board
12 • Motor/relay connected to GPIO33 (MOTOR_PIN)
13 • Status LED connected to GPIO32 (LED_PIN)
14 • Reset button: GPIO14 (WAKEUP_GPIO) - connect to 3.3V for reset
15
16 FEATURES:
17 ✓ Web-based configuration interface (no coding required)
18 ✓ Hourly scheduling (select any combination of 24 hours)
19 ✓ Adjustable motor run time (5-300 seconds per activation)
20 ✓ Ultra-low power deep sleep between activations
21 ✓ Automatic time synchronization from web browser
22 ✓ EEPROM backup for power failure recovery
23 ✓ Hardware reset via GPIO14 to clear all settings
24 ✓ Visual feedback with LED status indicators
25
26 FIRST TIME SETUP:
27 1. Upload this sketch to ESP32
28 2. Connect to WiFi network "ESP32-Motor-Timer" (password: 12345678)
29 3. Open web browser to 192.168.4.1
30 4. Follow 3-step setup wizard:
31 - Sync current time from your device
32 - Set motor run time (5-300 seconds)
33 - Select operation hours (checkboxes for each hour 00:00-23:00)
34 5. Click "Start Operation" - ESP32 enters automatic mode
35
36 NORMAL OPERATION:
37 • ESP32 sleeps in deep sleep mode (uses <1mA power)
38 • Wakes up automatically at scheduled times
39 • Runs motor for configured duration
40 • LED blinks during motor operation
41 • Returns to deep sleep until next scheduled time
42
43 POWER FAILURE RECOVERY:
44 • Settings automatically saved to EEPROM after first configuration
45 • If power is lost, ESP32 recovers settings from EEPROM on restart
46 • Time estimation helps resume operation even without WiFi
47 • System continues running with last known schedule
48
49 MANUAL RESET (Clear All Settings):
50 1. Connect GPIO14 to 3.3V (use jumper wire or switch)
51 2. Press ESP32 reset button OR power cycle
52 3. ESP32 will detect GPIO14 HIGH and clear all EEPROM settings
53 4. Automatically enters setup mode for reconfiguration
54 5. Disconnect GPIO14 from 3.3V after setup
55
56 TROUBLESHOOTING:
57 • No WiFi connection? ESP32 creates its own hotspot for setup
58 • Setup timeout (4 minutes)? System tries to use EEPROM backup
59 • Wrong schedule? Use GPIO14 reset to reconfigure
60 • Motor not working? Check GPIO33 connection and power supply
61 • LED not blinking? Check GPIO32 connection
62
63 TECHNICAL SPECIFICATIONS:
64 • Deep sleep power consumption: <1mA
65 • Setup timeout: 4 minutes
66 • WiFi network: "ESP32-Motor-Timer"
67 • WiFi password: "12345678"
68 • Web interface: 192.168.4.1
69 • Motor pin: GPIO33 (active HIGH)
70 • LED pin: GPIO32 (active HIGH)
71 • Reset pin: GPIO14 (trigger HIGH)
72 • EEPROM backup: 512 bytes
73 • Time zone: GMT+1 (Portugal)
74 • Schedule resolution: 1 hour
75 • Run time range: 5-300 seconds
76 • Maximum daily activations: 24
77
78 WIRING DIAGRAM:
79 ESP32 GPIO33 → Motor/Relay IN (or transistor base)
80 ESP32 GPIO32 → LED + Resistor → GND
81 ESP32 GPIO14 → Reset Switch → 3.3V (normally open)
82 ESP32 GND → Motor/LED GND
83 ESP32 VIN → External power supply (if needed)
84
85 USAGE EXAMPLES:
86 • Irrigation system: Water plants 3 times daily for 30 seconds each
87 • Feeding system: Dispense food every 4 hours for 10 seconds
88 • Ventilation: Run fan for 2 minutes every hour during day
89 • Lighting: Turn on grow lights for 15 minutes at specific times
90
91 SAFETY NOTES:
92 • Motor voltage must match ESP32 output capability (3.3V logic)
93 • Use appropriate relay or transistor for high-power motors
94 • Ensure adequate power supply for motor and ESP32
95 • GPIO14 must be disconnected during normal operation
96 • Deep sleep mode saves battery but requires wake-up triggers
97
98 VERSION HISTORY:
99 v1.0 - Basic timer functionality
100 v2.0 - Added EEPROM backup and power failure recovery
101 v3.0 - Added GPIO14 hardware reset functionality
102
103 ========================================================================
104*/
105#include "soc/soc.h" // Brownout error fix
106#include "soc/rtc_cntl_reg.h" // Brownout error fix
107
108#include "driver/rtc_io.h" // https://github.com/pycom/esp-idf-2.0/blob/master/components/driver/include/driver/rtc_io.h
109
110#include <ESP32Time.h>
111#include <WiFi.h>
112#include <EEPROM.h>
113
114#define uS_TO_S_FACTOR 1000000ULL
115#define DEFAULT_MOTOR_ON_TIME 5
116#define MIN_MOTOR_ON_TIME 5
117#define MAX_MOTOR_ON_TIME 300
118#define MOTOR_PIN 33
119#define LED_PIN 32
120#define WAKEUP_GPIO GPIO_NUM_14 // Only RTC IO are allowed
121#define EEPROM_SIZE 512
122
123// EEPROM Memory Map
124#define EEPROM_MAGIC_ADDR 0
125#define EEPROM_SCHEDULE_ADDR 4
126#define EEPROM_RUNTIME_ADDR 28
127#define EEPROM_LAST_SYNC_ADDR 32
128#define EEPROM_MAGIC_NUMBER 0xDEADBEEF
129#define MAX_TIME_STALENESS 7 * 24 * 3600
130
131const char *ssid = "ESP32-Motor-Timer";
132const char *password = "12345678";
133
134ESP32Time rtc(3600);
135WiFiServer server(80);
136
137// RTC memory variables
138RTC_DATA_ATTR bool timeWasSet = false;
139RTC_DATA_ATTR bool scheduleWasSet = false;
140RTC_DATA_ATTR bool runTimeWasSet = false;
141RTC_DATA_ATTR int bootCount = 0;
142RTC_DATA_ATTR unsigned long webServerStartTime = 0;
143RTC_DATA_ATTR bool usingBackupSettings = false;
144RTC_DATA_ATTR bool hourlySchedule[24] = {0};
145RTC_DATA_ATTR int motorRunTime = DEFAULT_MOTOR_ON_TIME;
146
147struct EEPROMSettings {
148 uint32_t magic;
149 bool schedule[24];
150 int runTime;
151 uint32_t lastSyncTime;
152};
153
154void setup() {
155 Serial.begin(115200);
156 delay(1000);
157 EEPROM.begin(EEPROM_SIZE);
158
159 bootCount++;
160 Serial.println("Boot count: " + String(bootCount));
161
162 pinMode(MOTOR_PIN, OUTPUT);
163 pinMode(LED_PIN, OUTPUT);
164 digitalWrite(MOTOR_PIN, LOW);
165
166 printWakeupReason();
167
168 // Check if woken up by GPIO14 - clear EEPROM and reset
169 if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) {
170 Serial.println("=== GPIO RESET TRIGGERED ===");
171 Serial.println("Clearing EEPROM settings...");
172 clearEEPROMSettings();
173
174 // Reset RTC memory flags
175 timeWasSet = false;
176 scheduleWasSet = false;
177 runTimeWasSet = false;
178 usingBackupSettings = false;
179
180 Serial.println("EEPROM cleared - entering setup mode");
181 webServerStartTime = millis();
182 setupWebServer();
183 return;
184 }
185
186 // Normal wakeup logic
187 if (timeWasSet && scheduleWasSet && runTimeWasSet && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
188 handleScheduledWakeup();
189 } else {
190 if (tryLoadBackupSettings()) {
191 Serial.println("=== POWER FAILURE RECOVERY ===");
192 Serial.println("Loaded backup settings from EEPROM");
193 printCurrentSettings();
194
195 timeWasSet = false;
196 usingBackupSettings = true;
197
198 EEPROMSettings settings;
199 loadSettingsFromEEPROM(settings);
200 uint32_t currentEpoch = settings.lastSyncTime + (millis() / 1000);
201 rtc.setTime(currentEpoch);
202 timeWasSet = true;
203
204 Serial.println("Estimated time: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
205 scheduleNextWakeup();
206 } else {
207 Serial.println("No backup settings found - entering setup mode");
208 webServerStartTime = millis();
209 setupWebServer();
210 }
211 }
212}
213
214void loop() {
215 if (!timeWasSet || !scheduleWasSet || !runTimeWasSet) {
216 if (millis() - webServerStartTime > 240000) {
217 if (tryLoadBackupSettings()) {
218 Serial.println("Timeout reached - falling back to EEPROM settings");
219 usingBackupSettings = true;
220
221 EEPROMSettings settings;
222 loadSettingsFromEEPROM(settings);
223 uint32_t estimatedTime = settings.lastSyncTime + (millis() / 1000);
224 rtc.setTime(estimatedTime);
225 timeWasSet = true;
226
227 scheduleNextWakeup();
228 } else {
229 Serial.println("No backup settings - entering 3-hour sleep");
230 setupSleepWakeup();
231 esp_deep_sleep_start();
232 }
233 }
234 handleWebClient();
235 }
236}
237
238void clearEEPROMSettings() {
239 // Clear magic number to invalidate settings
240 uint32_t clearMagic = 0x00000000;
241 EEPROM.put(EEPROM_MAGIC_ADDR, clearMagic);
242 EEPROM.commit();
243 Serial.println("EEPROM settings cleared");
244}
245
246bool tryLoadBackupSettings() {
247 EEPROMSettings settings;
248 if (loadSettingsFromEEPROM(settings)) {
249 for (int i = 0; i < 24; i++) {
250 hourlySchedule[i] = settings.schedule[i];
251 }
252 motorRunTime = settings.runTime;
253 scheduleWasSet = true;
254 runTimeWasSet = true;
255 return true;
256 }
257 return false;
258}
259
260bool loadSettingsFromEEPROM(EEPROMSettings &settings) {
261 uint32_t magic;
262 EEPROM.get(EEPROM_MAGIC_ADDR, magic);
263
264 if (magic != EEPROM_MAGIC_NUMBER) {
265 Serial.println("No valid EEPROM settings found");
266 return false;
267 }
268
269 EEPROM.get(EEPROM_MAGIC_ADDR, settings.magic);
270 for (int i = 0; i < 24; i++) {
271 settings.schedule[i] = EEPROM.read(EEPROM_SCHEDULE_ADDR + i);
272 }
273 EEPROM.get(EEPROM_RUNTIME_ADDR, settings.runTime);
274 EEPROM.get(EEPROM_LAST_SYNC_ADDR, settings.lastSyncTime);
275
276 if (settings.runTime < MIN_MOTOR_ON_TIME || settings.runTime > MAX_MOTOR_ON_TIME) {
277 Serial.println("Invalid run time in EEPROM: " + String(settings.runTime));
278 return false;
279 }
280
281 Serial.println("Valid EEPROM settings loaded");
282 return true;
283}
284
285void saveSettingsToEEPROM() {
286 Serial.println("Saving settings to EEPROM...");
287
288 EEPROMSettings settings;
289 settings.magic = EEPROM_MAGIC_NUMBER;
290
291 for (int i = 0; i < 24; i++) {
292 settings.schedule[i] = hourlySchedule[i];
293 }
294 settings.runTime = motorRunTime;
295 settings.lastSyncTime = rtc.getEpoch();
296
297 EEPROM.put(EEPROM_MAGIC_ADDR, settings.magic);
298 for (int i = 0; i < 24; i++) {
299 EEPROM.write(EEPROM_SCHEDULE_ADDR + i, settings.schedule[i]);
300 }
301 EEPROM.put(EEPROM_RUNTIME_ADDR, settings.runTime);
302 EEPROM.put(EEPROM_LAST_SYNC_ADDR, settings.lastSyncTime);
303 EEPROM.commit();
304
305 Serial.println("Settings saved to EEPROM successfully");
306 printCurrentSettings();
307}
308
309void printCurrentSettings() {
310 Serial.println("Current Settings:");
311 Serial.println("- Motor run time: " + String(motorRunTime) + " seconds");
312 Serial.print("- Schedule: ");
313
314 int count = 0;
315 for (int h = 0; h < 24; h++) {
316 if (hourlySchedule[h]) {
317 if (count > 0) Serial.print(", ");
318 Serial.print(String(h < 10 ? "0" : "") + String(h) + ":00");
319 count++;
320 }
321 }
322
323 if (count == 0) {
324 Serial.println("No hours scheduled");
325 } else {
326 Serial.println(" (" + String(count) + " times/day)");
327 Serial.println("- Total daily runtime: " + String(count * motorRunTime) + " seconds");
328 }
329}
330
331void handleScheduledWakeup() {
332 Serial.println("\n=== Scheduled Wake-up ===");
333 Serial.println("Current time: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
334 Serial.println("Motor run time: " + String(motorRunTime) + " seconds");
335
336 if (usingBackupSettings) {
337 Serial.println("Running on backup settings from EEPROM");
338 }
339
340 runMotor();
341 scheduleNextWakeup();
342}
343
344void runMotor() {
345 Serial.println("Starting motor for " + String(motorRunTime) + " seconds...");
346
347 digitalWrite(LED_PIN, HIGH);
348 digitalWrite(MOTOR_PIN, HIGH);
349
350 for (int i = motorRunTime; i > 0; i--) {
351 Serial.println("Motor running... " + String(i) + "s remaining");
352 delay(1000);
353
354 int blinkInterval = (motorRunTime < 10) ? 1 : 5;
355 if (i % blinkInterval == 0) {
356 digitalWrite(LED_PIN, LOW);
357 delay(100);
358 digitalWrite(LED_PIN, HIGH);
359 }
360 }
361
362 digitalWrite(MOTOR_PIN, LOW);
363 digitalWrite(LED_PIN, LOW);
364 Serial.println("Motor stopped.");
365}
366
367void setupSleepWakeup() {
368 // Setup timer wakeup
369 esp_sleep_enable_timer_wakeup(3 * 3600 * uS_TO_S_FACTOR);
370
371 // Setup GPIO wakeup for reset functionality
372 esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1); // 1 = High trigger
373 rtc_gpio_pullup_dis(WAKEUP_GPIO);
374 rtc_gpio_pulldown_en(WAKEUP_GPIO);
375 Serial.println("Setup ESP32 to wake up on GPIO14 trigger (reset mode)");
376}
377
378void scheduleNextWakeup() {
379 struct tm timeinfo = rtc.getTimeStruct();
380 int currentHour = timeinfo.tm_hour;
381 int currentMinute = timeinfo.tm_min;
382
383 unsigned long sleepTime = calculateSleepTime(currentHour, currentMinute);
384
385 if (sleepTime > 0) {
386 Serial.println("Next wake-up in " + String(sleepTime / 3600) + " hours and " +
387 String((sleepTime % 3600) / 60) + " minutes");
388
389 esp_sleep_enable_timer_wakeup(sleepTime * uS_TO_S_FACTOR);
390
391 // Always enable GPIO wakeup for reset functionality
392 esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1);
393 rtc_gpio_pullup_dis(WAKEUP_GPIO);
394 rtc_gpio_pulldown_en(WAKEUP_GPIO);
395
396 Serial.println("Entering deep sleep... (GPIO14 HIGH = reset)");
397 Serial.flush();
398 esp_deep_sleep_start();
399 } else {
400 Serial.println("No scheduled hours found - entering 24-hour sleep");
401 esp_sleep_enable_timer_wakeup(24 * 3600 * uS_TO_S_FACTOR);
402
403 esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1);
404 rtc_gpio_pullup_dis(WAKEUP_GPIO);
405 rtc_gpio_pulldown_en(WAKEUP_GPIO);
406
407 esp_deep_sleep_start();
408 }
409}
410
411unsigned long calculateSleepTime(int currentHour, int currentMinute) {
412 int nextHour = -1;
413
414 for (int h = currentHour + 1; h < 24; h++) {
415 if (hourlySchedule[h]) {
416 nextHour = h;
417 break;
418 }
419 }
420
421 if (nextHour == -1) {
422 for (int h = 0; h < 24; h++) {
423 if (hourlySchedule[h]) {
424 nextHour = h + 24;
425 break;
426 }
427 }
428 }
429
430 if (nextHour == -1) return 0;
431
432 int currentTotalMinutes = currentHour * 60 + currentMinute;
433 int nextTotalMinutes = (nextHour % 24) * 60;
434
435 if (nextHour >= 24) {
436 nextTotalMinutes += 24 * 60;
437 }
438
439 int sleepMinutes = nextTotalMinutes - currentTotalMinutes;
440 return sleepMinutes * 60;
441}
442
443void setupWebServer() {
444 Serial.println("\n=== Setting up Web Server for Configuration ===");
445 Serial.println("Connect to WiFi network: " + String(ssid));
446 Serial.println("Password: " + String(password));
447 Serial.println("TIMEOUT: 4 minutes");
448 Serial.println("RESET: Connect GPIO14 to 3.3V and reset ESP32");
449
450 WiFi.softAP(ssid, password);
451 IPAddress IP = WiFi.softAPIP();
452 Serial.println("Web interface: http://" + IP.toString());
453 server.begin();
454
455 for (int i = 0; i < 10; i++) {
456 digitalWrite(LED_PIN, HIGH);
457 delay(200);
458 digitalWrite(LED_PIN, LOW);
459 delay(200);
460 }
461}
462
463void handleWebClient() {
464 WiFiClient client = server.available();
465
466 if (client) {
467 Serial.println("Client connected");
468 String currentLine = "";
469
470 while (client.connected()) {
471 if (client.available()) {
472 char c = client.read();
473 if (c == '\n') {
474 if (currentLine.length() == 0) {
475 sendWebPage(client);
476 break;
477 } else {
478 currentLine = "";
479 }
480 } else if (c != '\r') {
481 currentLine += c;
482 }
483
484 if (currentLine.endsWith("POST /syncTime")) {
485 handleTimeSyncRequest(client);
486 } else if (currentLine.endsWith("POST /setRunTime")) {
487 handleRunTimeRequest(client);
488 } else if (currentLine.endsWith("POST /setSchedule")) {
489 handleScheduleRequest(client);
490 }
491 }
492 }
493 client.stop();
494 }
495}
496
497// Simplified web page (keeping core functionality)
498void sendWebPage(WiFiClient &client) {
499 unsigned long elapsed = millis() - webServerStartTime;
500 unsigned long remaining = (240000 - elapsed) / 1000;
501
502 client.println("HTTP/1.1 200 OK");
503 client.println("Content-type:text/html");
504 client.println();
505 client.println("<!DOCTYPE html><html>");
506 client.println("<head><title>ESP32 Motor Timer Setup</title>");
507 client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
508 client.println("<style>");
509 client.println("body{font-family:Arial;text-align:center;padding:20px;background:#f0f0f0;}");
510 client.println(".container{max-width:600px;margin:0 auto;background:white;padding:30px;border-radius:10px;}");
511 client.println("input,button{padding:10px;margin:5px;font-size:16px;border:1px solid #ddd;border-radius:5px;}");
512 client.println("button{background:#4CAF50;color:white;border:none;cursor:pointer;padding:15px 30px;}");
513 client.println(".timeout{color:red;font-weight:bold;}");
514 client.println(".step{margin:20px 0;padding:15px;background:#f9f9f9;border-radius:5px;}");
515 client.println(".schedule-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:20px 0;}");
516 client.println(".hour-checkbox{display:flex;align-items:center;padding:8px;background:white;border:1px solid #ddd;border-radius:5px;}");
517 client.println(".reset-info{background:#FFE6E6;padding:10px;border-radius:5px;margin:10px 0;border:1px solid #FF9999;}");
518 client.println("</style></head>");
519 client.println("<body>");
520 client.println("<div class='container'>");
521 client.println("<h1>ESP32 Motor Timer Setup</h1>");
522
523 client.println("<div class='reset-info'>");
524 client.println("<strong>🔄 Reset Instructions:</strong><br>");
525 client.println("To clear settings: Connect GPIO14 to 3.3V and reset ESP32");
526 client.println("</div>");
527
528 client.println("<div class='timeout'>Timeout: <span id='countdown'>" + String(remaining) + "</span> seconds</div>");
529
530 // Step 1: Time sync
531 client.println("<div class='step'>");
532 client.println("<h3>Step 1: Set Current Time</h3>");
533 if (timeWasSet) {
534 client.println("<p>✅ Time set: " + rtc.getTime("%H:%M:%S %d/%m/%Y") + "</p>");
535 } else {
536 client.println("<h4 id='currentTime'></h4>");
537 client.println("<form action='/syncTime' method='POST'>");
538 client.println("<input type='hidden' name='epochTime' id='hiddenEpochTime'>");
539 client.println("<button type='submit'>Sync Time</button>");
540 client.println("</form>");
541 }
542 client.println("</div>");
543
544 // Step 2: Motor run time
545 client.println("<div class='step'>");
546 client.println("<h3>Step 2: Set Motor Run Time</h3>");
547 if (runTimeWasSet) {
548 client.println("<p>✅ Run time set: " + String(motorRunTime) + " seconds</p>");
549 } else {
550 client.println("<form action='/setRunTime' method='POST'>");
551 client.println("<input type='range' name='runTime' min='5' max='300' value='" + String(motorRunTime) + "'>");
552 client.println("<span id='runTimeDisplay'>" + String(motorRunTime) + "s</span>");
553 client.println("<button type='submit'>Set Run Time</button>");
554 client.println("</form>");
555 }
556 client.println("</div>");
557
558 // Step 3: Schedule
559 client.println("<div class='step'>");
560 client.println("<h3>Step 3: Select Operation Hours</h3>");
561 if (scheduleWasSet) {
562 client.println("<p>✅ Schedule set!</p>");
563 } else {
564 client.println("<form action='/setSchedule' method='POST'>");
565 client.println("<div class='schedule-grid'>");
566 for (int h = 0; h < 24; h++) {
567 client.println("<div class='hour-checkbox'>");
568 client.println("<input type='checkbox' name='hour" + String(h) + "' value='1'>");
569 client.println("<label>" + String(h < 10 ? "0" : "") + String(h) + ":00</label>");
570 client.println("</div>");
571 }
572 client.println("</div>");
573 client.println("<button type='submit'>Set Schedule</button>");
574 client.println("</form>");
575 }
576 client.println("</div>");
577
578 if (timeWasSet && scheduleWasSet && runTimeWasSet) {
579 client.println("<div class='step'>");
580 client.println("<h3>✅ Setup Complete!</h3>");
581 client.println("<p>ESP32 will now enter scheduled operation mode.</p>");
582 client.println("<button onclick='startOperation()'>Start Operation</button>");
583 client.println("</div>");
584 }
585
586 client.println("</div>");
587
588 // Simplified JavaScript
589 client.println("<script>");
590 client.println("var countdown=" + String(remaining) + ";");
591 client.println("function updateTime(){");
592 client.println("var now=new Date();");
593 client.println("if(document.getElementById('currentTime'))");
594 client.println("document.getElementById('currentTime').innerHTML='Current Time: '+now.toLocaleString();");
595 client.println("if(document.getElementById('hiddenEpochTime'))");
596 client.println("document.getElementById('hiddenEpochTime').value=Math.floor(now.getTime()/1000);");
597 client.println("}");
598 client.println("function updateCountdown(){");
599 client.println("countdown--;document.getElementById('countdown').innerHTML=countdown;");
600 client.println("if(countdown<=0)document.body.innerHTML='<h2>Timeout reached</h2>';");
601 client.println("}");
602 client.println("function startOperation(){");
603 client.println("document.body.innerHTML='<h2>Starting operation...</h2>';");
604 client.println("}");
605 client.println("setInterval(updateTime,1000);setInterval(updateCountdown,1000);updateTime();");
606 client.println("</script></body></html>");
607}
608
609void handleTimeSyncRequest(WiFiClient &client) {
610 String requestBody = "";
611 while (client.available()) {
612 requestBody += (char)client.read();
613 }
614
615 int epochIndex = requestBody.indexOf("epochTime=");
616 if (epochIndex != -1) {
617 long epochTime = requestBody.substring(epochIndex + 10).toInt();
618 rtc.setTime(epochTime);
619 timeWasSet = true;
620 Serial.println("Time synchronized: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
621 }
622
623 client.println("HTTP/1.1 302 Found");
624 client.println("Location: /");
625 client.println();
626}
627
628void handleRunTimeRequest(WiFiClient &client) {
629 String requestBody = "";
630 while (client.available()) {
631 requestBody += (char)client.read();
632 }
633
634 int runTimeIndex = requestBody.indexOf("runTime=");
635 if (runTimeIndex != -1) {
636 int newRunTime = requestBody.substring(runTimeIndex + 8).toInt();
637 if (newRunTime >= MIN_MOTOR_ON_TIME && newRunTime <= MAX_MOTOR_ON_TIME) {
638 motorRunTime = newRunTime;
639 runTimeWasSet = true;
640 Serial.println("Motor run time set to: " + String(motorRunTime) + " seconds");
641 }
642 }
643
644 client.println("HTTP/1.1 302 Found");
645 client.println("Location: /");
646 client.println();
647}
648
649void handleScheduleRequest(WiFiClient &client) {
650 String requestBody = "";
651 while (client.available()) {
652 requestBody += (char)client.read();
653 }
654
655 for (int h = 0; h < 24; h++) {
656 hourlySchedule[h] = false;
657 }
658
659 for (int h = 0; h < 24; h++) {
660 String hourParam = "hour" + String(h) + "=1";
661 if (requestBody.indexOf(hourParam) != -1) {
662 hourlySchedule[h] = true;
663 }
664 }
665
666 scheduleWasSet = true;
667
668 // Save to EEPROM when configuration is complete
669 if (timeWasSet && scheduleWasSet && runTimeWasSet) {
670 saveSettingsToEEPROM();
671 delay(2000);
672 scheduleNextWakeup();
673 }
674
675 client.println("HTTP/1.1 302 Found");
676 client.println("Location: /");
677 client.println();
678}
679
680void printWakeupReason() {
681 esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
682
683 switch (wakeup_reason) {
684 case ESP_SLEEP_WAKEUP_TIMER:
685 Serial.println("Wake-up: Scheduled timer");
686 break;
687 case ESP_SLEEP_WAKEUP_EXT0:
688 Serial.println("Wake-up: GPIO14 external signal (RESET MODE)");
689 break;
690 default:
691 Serial.println("Wake-up: Power on or reset");
692 break;
693 }
694}
695