· 4 months ago · Jun 04, 2025, 08:10 AM
1/*
2 ESP32 Scheduled Motor Control with Deep Sleep - Hourly Scheduling
3
4 DESCRIPTION:
5 This sketch creates an automated motor control system that runs a motor at user-selected
6 hours of the day while maintaining ultra-low power consumption between operations.
7
8 FEATURES:
9 • Allows selection of any hours (00:00-23:00) for motor operation via web interface
10 • Motor runs for 30 seconds at each selected hour
11 • Uses deep sleep between operations for maximum battery efficiency
12 • Web-based setup interface with 24 hourly checkboxes
13 • Built-in LED status indicators during setup and operation
14 • RTC memory storage preserves settings through deep sleep cycles
15 • Automatic timeout protection (enters sleep if no configuration within 4 minutes)
16
17 HARDWARE REQUIREMENTS:
18 • ESP32 development board
19 • MOSFET (for motor control) connected to GPIO 33
20 • Motor connected through MOSFET
21 • Built-in LED on GPIO 2 (most ESP32 boards)
22 • Optional: External RTC battery for better timekeeping
23
24 SETUP PROCESS:
25 1. Upload sketch to ESP32
26 2. Connect to WiFi network "ESP32-Motor-Timer" (password: 12345678)
27 3. Navigate to the IP address shown in Serial Monitor
28 4. Sync current time from your browser
29 5. Check boxes for desired operation hours (e.g., 09:00, 19:00, 21:00)
30 6. ESP32 automatically enters scheduled operation mode
31
32 OPERATION:
33 • ESP32 sleeps in deep sleep mode (uses ~10µA)
34 • Wakes up at each selected hour
35 • Runs motor for 30 seconds with LED indication
36 • Calculates next scheduled hour and returns to deep sleep
37 • Cycle repeats based on selected schedule
38
39 POWER CONSUMPTION:
40 • Deep sleep: ~10µA (months of battery life)
41 • Active operation: ~80mA for 30 seconds
42 • Setup mode: ~150mA (4 minute timeout)
43
44 TROUBLESHOOTING:
45 • If setup doesn't complete, ESP32 sleeps for 3 hours then restarts setup
46 • Press reset button to restart setup process
47 • Check Serial Monitor (115200 baud) for debugging information
48 • Time is automatically adjusted for Portuguese Summer Time (+1 hour)
49
50 LIBRARIES REQUIRED:
51 • ESP32Time (for RTC functionality)
52 • WiFi (built-in ESP32 library)
53
54 MIT License - Free to use and modify
55 Based on ESP32Time library examples and ESP32 deep sleep functionality
56*/
57
58/*
59 Major Changes:
60
61 Replaced morning/evening variables with a 24-hour boolean array hourlySchedule[24]
62 New Web Interface with:
63
64 24 checkboxes arranged in a 4-column grid (00:00 to 23:00)
65 Visual feedback showing selected hours and total count
66 Better styling for the checkbox grid
67
68
69 Updated Sleep Calculation (calculateSleepTime()):
70
71 Searches for the next enabled hour starting from current hour + 1
72 If no hours found today, searches from 00:00 next day
73 Returns 0 if no hours are scheduled (enters 24-hour sleep)
74
75
76 Enhanced Schedule Handling:
77
78 Parses checkbox form data (checks for hour0=1, hour1=1, etc.)
79 Resets all hours first, then enables checked ones
80 Shows detailed feedback in Serial Monitor
81
82
83
84 How It Works:
85
86 Setup: User checks boxes for desired hours (e.g., 09:00, 19:00, 21:00)
87 Operation: Motor runs for 30 seconds at each selected hour
88 Sleep: ESP32 calculates time until next scheduled hour and sleeps
89 Repeat: Wakes up at next scheduled time and repeats
90
91 Example Usage:
92
93 Check boxes for 09:00, 19:00, and 21:00
94 Motor will run 3 times per day at those exact hours
95 ESP32 sleeps between operations for maximum battery life
96
97 The interface now provides much more flexibility, allowing users to create custom schedules like:
98
99 Every 4 hours: 00:00, 04:00, 08:00, 12:00, 16:00, 20:00
100 Business hours: 09:00, 12:00, 15:00, 18:00
101 Or any other combination of hours
102*/
103
104#include <ESP32Time.h>
105#include <WiFi.h>
106
107#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds
108#define MOTOR_ON_TIME 30 // Motor run time in seconds
109#define MOTOR_PIN 33 // GPIO pin connected to MOSFET gate
110#define LED_PIN 2 // Built-in LED for status indication
111
112// WiFi credentials for initial time sync
113const char *ssid = "ESP32-Motor-Timer";
114const char *password = "12345678";
115
116//ESP32Time rtc;
117ESP32Time rtc(3600); // offset in seconds GMT+1 Portugal Summer Time
118WiFiServer server(80);
119
120// RTC memory variables (survive deep sleep)
121RTC_DATA_ATTR bool timeWasSet = false;
122RTC_DATA_ATTR bool scheduleWasSet = false;
123RTC_DATA_ATTR int bootCount = 0;
124RTC_DATA_ATTR unsigned long webServerStartTime = 0;
125
126// Hourly schedule - 24 hour array (0=disabled, 1=enabled)
127RTC_DATA_ATTR bool hourlySchedule[24] = {0}; // All hours disabled by default
128
129void setup() {
130 Serial.begin(115200);
131 delay(1000);
132
133 bootCount++;
134 Serial.println("Boot count: " + String(bootCount));
135
136 // Initialize pins
137 pinMode(MOTOR_PIN, OUTPUT);
138 pinMode(LED_PIN, OUTPUT);
139 digitalWrite(MOTOR_PIN, LOW); // Ensure motor is off initially
140
141 printWakeupReason();
142
143 // Check if this is a scheduled wake-up and both time and schedule have been set
144 if (timeWasSet && scheduleWasSet && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
145 handleScheduledWakeup();
146 } else {
147 // First boot, manual reset, or incomplete setup - need web interface
148 webServerStartTime = millis();
149 setupWebServer();
150 }
151}
152
153void loop() {
154 // Handle web server for time and schedule setting
155 if (!timeWasSet || !scheduleWasSet) {
156 // Check for 4-minute timeout
157 if (millis() - webServerStartTime > 240000) { // 4 minutes = 240,000 ms
158 Serial.println("Web server timeout - entering 3-hour sleep to save battery");
159 esp_sleep_enable_timer_wakeup(3 * 3600 * uS_TO_S_FACTOR); // 3 hours
160 esp_deep_sleep_start();
161 }
162 handleWebClient();
163 }
164}
165
166void handleScheduledWakeup() {
167 Serial.println("\n=== Scheduled Wake-up ===");
168 Serial.println("Current time: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
169
170 // Run the motor
171 runMotor();
172
173 // Calculate next wake-up time
174 scheduleNextWakeup();
175}
176
177void runMotor() {
178 Serial.println("Starting motor...");
179
180 // Blink LED to indicate motor operation
181 digitalWrite(LED_PIN, HIGH);
182
183 // Turn on motor via MOSFET
184 digitalWrite(MOTOR_PIN, HIGH);
185
186 // Run for specified time with status updates
187 for (int i = MOTOR_ON_TIME; i > 0; i--) {
188 Serial.println("Motor running... " + String(i) + "s remaining");
189 delay(1000);
190
191 // Blink LED every 5 seconds during operation
192 if (i % 5 == 0) {
193 digitalWrite(LED_PIN, LOW);
194 delay(100);
195 digitalWrite(LED_PIN, HIGH);
196 }
197 }
198
199 // Turn off motor
200 digitalWrite(MOTOR_PIN, LOW);
201 digitalWrite(LED_PIN, LOW);
202
203 Serial.println("Motor stopped.");
204}
205
206void scheduleNextWakeup() {
207 struct tm timeinfo = rtc.getTimeStruct();
208 int currentHour = timeinfo.tm_hour;
209 int currentMinute = timeinfo.tm_min;
210
211 // Calculate seconds until next scheduled hour
212 unsigned long sleepTime = calculateSleepTime(currentHour, currentMinute);
213
214 if (sleepTime > 0) {
215 Serial.println("Next wake-up in " + String(sleepTime / 3600) + " hours and " +
216 String((sleepTime % 3600) / 60) + " minutes");
217
218 // Configure and enter deep sleep
219 esp_sleep_enable_timer_wakeup(sleepTime * uS_TO_S_FACTOR);
220 Serial.println("Entering deep sleep...");
221 Serial.flush();
222 esp_deep_sleep_start();
223 } else {
224 Serial.println("No scheduled hours found - entering 24-hour sleep");
225 esp_sleep_enable_timer_wakeup(24 * 3600 * uS_TO_S_FACTOR); // 24 hours
226 esp_deep_sleep_start();
227 }
228}
229
230unsigned long calculateSleepTime(int currentHour, int currentMinute) {
231 // Find next scheduled hour
232 int nextHour = -1;
233
234 // First, check if there are any hours scheduled later today
235 for (int h = currentHour + 1; h < 24; h++) {
236 if (hourlySchedule[h]) {
237 nextHour = h;
238 break;
239 }
240 }
241
242 // If no hours found later today, check from beginning of next day
243 if (nextHour == -1) {
244 for (int h = 0; h < 24; h++) {
245 if (hourlySchedule[h]) {
246 nextHour = h + 24; // Add 24 to indicate next day
247 break;
248 }
249 }
250 }
251
252 // If still no hours found, return 0 (no schedule set)
253 if (nextHour == -1) {
254 return 0;
255 }
256
257 // Calculate sleep time
258 int currentTotalMinutes = currentHour * 60 + currentMinute;
259 int nextTotalMinutes = (nextHour % 24) * 60; // Target minute is always 0
260
261 // If next hour is tomorrow, add 24 hours worth of minutes
262 if (nextHour >= 24) {
263 nextTotalMinutes += 24 * 60;
264 }
265
266 int sleepMinutes = nextTotalMinutes - currentTotalMinutes;
267 return sleepMinutes * 60; // Convert to seconds
268}
269
270void setupWebServer() {
271 Serial.println("\n=== Setting up Web Server for Configuration ===");
272 Serial.println("Connect to WiFi network: " + String(ssid));
273 Serial.println("Password: " + String(password));
274 Serial.println("TIMEOUT: 4 minutes (will sleep for 3 hours if no configuration)");
275
276 WiFi.softAP(ssid, password);
277 IPAddress IP = WiFi.softAPIP();
278 Serial.println("Web interface: http://" + IP.toString());
279 server.begin();
280
281 // Blink LED to indicate setup mode
282 for (int i = 0; i < 10; i++) {
283 digitalWrite(LED_PIN, HIGH);
284 delay(200);
285 digitalWrite(LED_PIN, LOW);
286 delay(200);
287 }
288}
289
290void handleWebClient() {
291 WiFiClient client = server.available();
292
293 if (client) {
294 Serial.println("Client connected");
295 String currentLine = "";
296
297 while (client.connected()) {
298 if (client.available()) {
299 char c = client.read();
300 if (c == '\n') {
301 if (currentLine.length() == 0) {
302 sendWebPage(client);
303 break;
304 } else {
305 currentLine = "";
306 }
307 } else if (c != '\r') {
308 currentLine += c;
309 }
310
311 if (currentLine.endsWith("POST /syncTime")) {
312 handleTimeSyncRequest(client);
313 } else if (currentLine.endsWith("POST /setSchedule")) {
314 handleScheduleRequest(client);
315 }
316 }
317 }
318 client.stop();
319 }
320}
321
322void sendWebPage(WiFiClient &client) {
323 // Calculate remaining time
324 unsigned long elapsed = millis() - webServerStartTime;
325 unsigned long remaining = (240000 - elapsed) / 1000; // seconds remaining
326
327 client.println("HTTP/1.1 200 OK");
328 client.println("Content-type:text/html");
329 client.println();
330 client.println("<!DOCTYPE html><html>");
331 client.println("<head><title>ESP32 Motor Timer Setup</title>");
332 client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
333 client.println("<style>");
334 client.println("body{font-family:Arial;text-align:center;padding:20px;background:#f0f0f0;}");
335 client.println(".container{max-width:600px;margin:0 auto;background:white;padding:30px;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.1);}");
336 client.println("input,button{padding:10px;margin:5px;font-size:16px;border:1px solid #ddd;border-radius:5px;}");
337 client.println("button{background:#4CAF50;color:white;border:none;cursor:pointer;padding:15px 30px;}");
338 client.println("button:hover{background:#45a049;}");
339 client.println(".timeout{color:red;font-weight:bold;}");
340 client.println(".step{margin:20px 0;padding:15px;background:#f9f9f9;border-radius:5px;}");
341 client.println(".schedule-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:20px 0;}");
342 client.println(".hour-checkbox{display:flex;align-items:center;padding:8px;background:white;border:1px solid #ddd;border-radius:5px;}");
343 client.println(".hour-checkbox input{margin-right:8px;}");
344 client.println(".hour-checkbox:hover{background:#f0f8ff;}");
345 client.println(".selected-hours{background:#e8f5e8;padding:10px;border-radius:5px;margin:10px 0;}");
346 client.println("</style></head>");
347 client.println("<body>");
348 client.println("<div class='container'>");
349 client.println("<h1>ESP32 Motor Timer Setup</h1>");
350 client.println("<div class='timeout'>Timeout: <span id='countdown'>" + String(remaining) + "</span> seconds</div>");
351
352 // Step 1: Time sync
353 client.println("<div class='step'>");
354 client.println("<h3>Step 1: Set Current Time</h3>");
355 if (timeWasSet) {
356 client.println("<p>✅ Time set: " + rtc.getTime("%H:%M:%S %d/%m/%Y") + "</p>");
357 } else {
358 client.println("<h4 id='currentTime'></h4>");
359 client.println("<form action='/syncTime' method='POST'>");
360 client.println("<input type='hidden' name='epochTime' id='hiddenEpochTime'>");
361 client.println("<button type='submit'>Sync Time</button>");
362 client.println("</form>");
363 }
364 client.println("</div>");
365
366 // Step 2: Schedule setup
367 client.println("<div class='step'>");
368 client.println("<h3>Step 2: Select Operation Hours</h3>");
369 client.println("<p>Check the boxes for hours when the motor should run (30 seconds each time):</p>");
370
371 if (scheduleWasSet) {
372 client.println("<div class='selected-hours'>");
373 client.println("<p>✅ Schedule set! Motor will run at:</p>");
374 String selectedHours = "";
375 int count = 0;
376 for (int h = 0; h < 24; h++) {
377 if (hourlySchedule[h]) {
378 if (count > 0) selectedHours += ", ";
379 selectedHours += String(h < 10 ? "0" : "") + String(h) + ":00";
380 count++;
381 }
382 }
383 if (count == 0) {
384 client.println("<p>No hours selected</p>");
385 } else {
386 client.println("<p>" + selectedHours + "</p>");
387 client.println("<p>Total: " + String(count) + " times per day</p>");
388 }
389 client.println("</div>");
390 } else {
391 client.println("<form action='/setSchedule' method='POST'>");
392 client.println("<div class='schedule-grid'>");
393
394 // Create 24 checkboxes for each hour
395 for (int h = 0; h < 24; h++) {
396 client.println("<div class='hour-checkbox'>");
397 client.println("<input type='checkbox' name='hour" + String(h) + "' value='1'" +
398 (hourlySchedule[h] ? " checked" : "") + ">");
399 client.println("<label>" + String(h < 10 ? "0" : "") + String(h) + ":00</label>");
400 client.println("</div>");
401 }
402
403 client.println("</div>");
404 client.println("<button type='submit'>Set Schedule</button>");
405 client.println("</form>");
406 }
407 client.println("</div>");
408
409 if (timeWasSet && scheduleWasSet) {
410 client.println("<div class='step'>");
411 client.println("<h3>✅ Setup Complete!</h3>");
412 client.println("<p>ESP32 will now enter scheduled operation mode.</p>");
413 client.println("<button onclick='startOperation()'>Start Operation</button>");
414 client.println("</div>");
415 }
416
417 client.println("</div>");
418
419 // JavaScript for time updates and countdown
420 client.println("<script>");
421 client.println("var countdown = " + String(remaining) + ";");
422 client.println("function updateTime(){");
423 client.println("var now=new Date();");
424 client.println("if(document.getElementById('currentTime'))");
425 client.println("document.getElementById('currentTime').innerHTML='Current Time: '+now.toLocaleString();");
426 client.println("if(document.getElementById('hiddenEpochTime'))");
427 client.println("document.getElementById('hiddenEpochTime').value=Math.floor(now.getTime()/1000);");
428 client.println("}");
429 client.println("function updateCountdown(){");
430 client.println("countdown--;");
431 client.println("document.getElementById('countdown').innerHTML=countdown;");
432 client.println("if(countdown<=0){");
433 client.println("document.body.innerHTML='<h2>Timeout reached - ESP32 entering sleep mode</h2>';");
434 client.println("}");
435 client.println("}");
436 client.println("function startOperation(){");
437 client.println("document.body.innerHTML='<h2>Starting scheduled operation...</h2><p>ESP32 entering deep sleep mode</p>';");
438 client.println("setTimeout(function(){window.location.reload();},2000);");
439 client.println("}");
440 client.println("setInterval(updateTime,1000);");
441 client.println("setInterval(updateCountdown,1000);");
442 client.println("updateTime();");
443 client.println("</script></body></html>");
444 client.println();
445}
446
447void handleTimeSyncRequest(WiFiClient &client) {
448 String requestBody = "";
449 while (client.available()) {
450 requestBody += (char)client.read();
451 }
452
453 int epochIndex = requestBody.indexOf("epochTime=");
454 if (epochIndex != -1) {
455 long epochTime = requestBody.substring(epochIndex + 10).toInt();
456 rtc.setTime(epochTime);
457 timeWasSet = true;
458
459 Serial.println("Time synchronized: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
460
461 // Send redirect back to main page
462 client.println("HTTP/1.1 302 Found");
463 client.println("Location: /");
464 client.println();
465 }
466}
467
468void handleScheduleRequest(WiFiClient &client) {
469 String requestBody = "";
470 while (client.available()) {
471 requestBody += (char)client.read();
472 }
473
474 Serial.println("Schedule request body: " + requestBody);
475
476 // Reset all hours first
477 for (int h = 0; h < 24; h++) {
478 hourlySchedule[h] = false;
479 }
480
481 // Parse checkbox data
482 for (int h = 0; h < 24; h++) {
483 String hourParam = "hour" + String(h) + "=1";
484 if (requestBody.indexOf(hourParam) != -1) {
485 hourlySchedule[h] = true;
486 Serial.println("Hour " + String(h) + " enabled");
487 }
488 }
489
490 scheduleWasSet = true;
491
492 Serial.println("Schedule updated:");
493 int enabledCount = 0;
494 for (int h = 0; h < 24; h++) {
495 if (hourlySchedule[h]) {
496 Serial.println("- " + String(h) + ":00");
497 enabledCount++;
498 }
499 }
500 Serial.println("Total enabled hours: " + String(enabledCount));
501
502 // If both time and schedule are set, start operation
503 if (timeWasSet && scheduleWasSet) {
504 delay(2000); // Allow user to see confirmation
505 scheduleNextWakeup();
506 }
507
508 // Send redirect back to main page
509 client.println("HTTP/1.1 302 Found");
510 client.println("Location: /");
511 client.println();
512}
513
514void printWakeupReason() {
515 esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
516
517 switch (wakeup_reason) {
518 case ESP_SLEEP_WAKEUP_TIMER:
519 Serial.println("Wake-up: Scheduled timer");
520 break;
521 case ESP_SLEEP_WAKEUP_EXT0:
522 Serial.println("Wake-up: External signal RTC_IO");
523 break;
524 case ESP_SLEEP_WAKEUP_EXT1:
525 Serial.println("Wake-up: External signal RTC_CNTL");
526 break;
527 default:
528 Serial.println("Wake-up: Power on or reset");
529 break;
530 }
531}
532