· 3 months ago · Jul 04, 2025, 09:55 AM
1/*
2 ESP32 Scheduled Motor Control with Deep Sleep
3
4 DESCRIPTION:
5 • Run a motor at two configurable times of day while deep-sleeping between operations.
6
7 FEATURES:
8 • Schedules motor to run for 30 seconds at two daily times (configurable)
9 • Uses deep sleep between operations for maximum battery efficiency
10 • Web-based setup interface for time sync and schedule configuration
11 • Built-in LED status indicators during setup and operation
12 • RTC memory storage preserves settings through deep sleep cycles
13 • Automatic timeout protection (enters sleep if no configuration within 4 minutes)
14
15 HARDWARE REQUIREMENTS:
16 • ESP32 development board
17 • MOSFET (for motor control) connected to GPIO 33
18 • Motor connected through MOSFET
19 • Built-in LED on GPIO 2 (most ESP32 boards)
20
21 SETUP PROCESS:
22 1. Upload sketch to ESP32
23 2. Connect to WiFi network "ESP32-Motor-Timer" (password: 12345678)
24 3. Navigate to the IP address shown in Serial Monitor, usually 192.168.4.1
25 4. Sync current time from your browser
26 5. Set morning and evening schedule times
27 6. ESP32 automatically enters scheduled operation mode
28
29 OPERATION:
30 • ESP32 sleeps in deep sleep mode (uses ~10µA)
31 • Wakes up at scheduled times
32 • Runs motor for 30 seconds with LED indication
33 • Calculates next wake time and returns to deep sleep
34 • Cycle repeats indefinitely
35
36 DEFAULT SCHEDULE:
37 • Morning: 12:30 (configurable via web interface)
38 • Evening: 19:30 (configurable via web interface)
39
40 TROUBLESHOOTING:
41 • If setup doesn't complete, ESP32 sleeps for 3 hours then restarts setup
42 • Power cycle to restart setup process
43 • Check Serial Monitor (115200 baud) for debugging information
44 • Time is automatically adjusted for Portuguese Summer Time (+1 hour)
45
46*/
47
48#include <ESP32Time.h> // https://github.com/fbiego/ESP32Time
49#include <WiFi.h>
50
51#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds
52#define MOTOR_ON_TIME 30 // Motor run time in seconds
53#define MOTOR_PIN 33 // GPIO pin connected to MOSFET gate
54#define LED_PIN 2 // Built-in LED for status indication
55
56// WiFi credentials for initial time sync
57const char *ssid = "ESP32-Motor-Timer";
58const char *password = "12345678";
59
60//ESP32Time rtc;
61ESP32Time rtc(3600); // offset in seconds GMT+1 Portugal Summer Time
62WiFiServer server(80);
63
64// RTC memory variables (survive deep sleep)
65RTC_DATA_ATTR bool timeWasSet = false;
66RTC_DATA_ATTR bool scheduleWasSet = false;
67RTC_DATA_ATTR int bootCount = 0;
68RTC_DATA_ATTR unsigned long webServerStartTime = 0;
69
70// Default values for schedule (adjust in portal)
71RTC_DATA_ATTR int MORNING_HOUR = 12;
72RTC_DATA_ATTR int MORNING_MINUTE = 30;
73RTC_DATA_ATTR int EVENING_HOUR = 19;
74RTC_DATA_ATTR int EVENING_MINUTE = 30;
75
76void setup() {
77 Serial.begin(115200);
78 delay(1000);
79
80 bootCount++;
81 Serial.println("Boot count: " + String(bootCount));
82
83 // Initialize pins
84 pinMode(MOTOR_PIN, OUTPUT);
85 pinMode(LED_PIN, OUTPUT);
86 digitalWrite(MOTOR_PIN, LOW); // Ensure motor is off initially
87
88 printWakeupReason();
89
90 // Check if this is a scheduled wake-up and both time and schedule have been set
91 if (timeWasSet && scheduleWasSet && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
92 handleScheduledWakeup();
93 } else {
94 // First boot, manual reset, or incomplete setup - need web interface
95 webServerStartTime = millis();
96 setupWebServer();
97 }
98}
99
100void loop() {
101 // Handle web server for time and schedule setting
102 if (!timeWasSet || !scheduleWasSet) {
103 // Check for 4-minute timeout
104 if (millis() - webServerStartTime > 240000) { // 4 minutes = 120,000 ms
105 Serial.println("Web server timeout - entering 3-hour sleep to save battery");
106 esp_sleep_enable_timer_wakeup(3 * 3600 * uS_TO_S_FACTOR); // 3 hours
107 esp_deep_sleep_start();
108 }
109 handleWebClient();
110 }
111}
112
113void handleScheduledWakeup() {
114 Serial.println("\n=== Scheduled Wake-up ===");
115 Serial.println("Current time: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
116
117 // Run the motor
118 runMotor();
119
120 // Calculate next wake-up time
121 scheduleNextWakeup();
122}
123
124void runMotor() {
125 Serial.println("Starting motor...");
126
127 // Blink LED to indicate motor operation
128 digitalWrite(LED_PIN, HIGH);
129
130 // Turn on motor via MOSFET
131 digitalWrite(MOTOR_PIN, HIGH);
132
133 // Run for specified time with status updates
134 for (int i = MOTOR_ON_TIME; i > 0; i--) {
135 Serial.println("Motor running... " + String(i) + "s remaining");
136 delay(1000);
137
138 // Blink LED every 5 seconds during operation
139 if (i % 5 == 0) {
140 digitalWrite(LED_PIN, LOW);
141 delay(100);
142 digitalWrite(LED_PIN, HIGH);
143 }
144 }
145
146 // Turn off motor
147 digitalWrite(MOTOR_PIN, LOW);
148 digitalWrite(LED_PIN, LOW);
149
150 Serial.println("Motor stopped.");
151}
152
153void scheduleNextWakeup() {
154 struct tm timeinfo = rtc.getTimeStruct();
155 int currentHour = timeinfo.tm_hour;
156 int currentMinute = timeinfo.tm_min;
157
158 // Calculate seconds until next scheduled time
159 unsigned long sleepTime = calculateSleepTime(currentHour, currentMinute);
160
161 Serial.println("Next wake-up in " + String(sleepTime / 3600) + " hours and " +
162 String((sleepTime % 3600) / 60) + " minutes");
163
164 // Configure and enter deep sleep
165 esp_sleep_enable_timer_wakeup(sleepTime * uS_TO_S_FACTOR);
166 Serial.println("Entering deep sleep...");
167 Serial.flush();
168 esp_deep_sleep_start();
169}
170
171unsigned long calculateSleepTime(int currentHour, int currentMinute) {
172 int currentTotalMinutes = currentHour * 60 + currentMinute;
173 int morningTotalMinutes = MORNING_HOUR * 60 + MORNING_MINUTE;
174 int eveningTotalMinutes = EVENING_HOUR * 60 + EVENING_MINUTE;
175
176 int nextWakeupMinutes;
177
178 if (currentTotalMinutes < morningTotalMinutes) {
179 // Before morning time - wake up at morning time
180 nextWakeupMinutes = morningTotalMinutes;
181 } else if (currentTotalMinutes < eveningTotalMinutes) {
182 // Between morning and evening - wake up at evening time
183 nextWakeupMinutes = eveningTotalMinutes;
184 } else {
185 // After evening time - wake up next morning
186 nextWakeupMinutes = morningTotalMinutes + (24 * 60); // Next day
187 }
188
189 int sleepMinutes = nextWakeupMinutes - currentTotalMinutes;
190 return sleepMinutes * 60; // Convert to seconds
191}
192
193void setupWebServer() {
194 Serial.println("\n=== Setting up Web Server for Configuration ===");
195 Serial.println("Connect to WiFi network: " + String(ssid));
196 Serial.println("Password: " + String(password));
197 Serial.println("TIMEOUT: 4 minutes (will sleep for 3 hours if no configuration)");
198
199 WiFi.softAP(ssid, password);
200 IPAddress IP = WiFi.softAPIP();
201 Serial.println("Web interface: http://" + IP.toString());
202 server.begin();
203
204 // Blink LED to indicate setup mode
205 for (int i = 0; i < 10; i++) {
206 digitalWrite(LED_PIN, HIGH);
207 delay(200);
208 digitalWrite(LED_PIN, LOW);
209 delay(200);
210 }
211}
212
213void handleWebClient() {
214 WiFiClient client = server.available();
215
216 if (client) {
217 Serial.println("Client connected");
218 String currentLine = "";
219
220 while (client.connected()) {
221 if (client.available()) {
222 char c = client.read();
223 if (c == '\n') {
224 if (currentLine.length() == 0) {
225 sendWebPage(client);
226 break;
227 } else {
228 currentLine = "";
229 }
230 } else if (c != '\r') {
231 currentLine += c;
232 }
233
234 if (currentLine.endsWith("POST /syncTime")) {
235 handleTimeSyncRequest(client);
236 } else if (currentLine.endsWith("POST /setSchedule")) {
237 handleScheduleRequest(client);
238 }
239 }
240 }
241 client.stop();
242 }
243}
244
245void sendWebPage(WiFiClient &client) {
246 // Calculate remaining time
247 unsigned long elapsed = millis() - webServerStartTime;
248 unsigned long remaining = (120000 - elapsed) / 1000; // seconds remaining
249
250 client.println("HTTP/1.1 200 OK");
251 client.println("Content-type:text/html");
252 client.println();
253 client.println("<!DOCTYPE html><html>");
254 client.println("<head><title>ESP32 Motor Timer Setup</title>");
255 client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
256 client.println("<style>");
257 client.println("body{font-family:Arial;text-align:center;padding:20px;background:#f0f0f0;}");
258 client.println(".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.1);}");
259 client.println("input,button{padding:10px;margin:5px;font-size:16px;border:1px solid #ddd;border-radius:5px;}");
260 client.println("button{background:#4CAF50;color:white;border:none;cursor:pointer;padding:15px 30px;}");
261 client.println("button:hover{background:#45a049;}");
262 client.println(".timeout{color:red;font-weight:bold;}");
263 client.println(".step{margin:20px 0;padding:15px;background:#f9f9f9;border-radius:5px;}");
264 client.println("</style></head>");
265 client.println("<body>");
266 client.println("<div class='container'>");
267 client.println("<h1>ESP32 Motor Timer Setup</h1>");
268 client.println("<div class='timeout'> Timeout: <span id='countdown'>" + String(remaining) + "</span> seconds</div>");
269
270 // Step 1: Time sync
271 client.println("<div class='step'>");
272 client.println("<h3>Step 1: Set Current Time</h3>");
273 if (timeWasSet) {
274 client.println("<p>Time set: " + rtc.getTime("%H:%M:%S %d/%m/%Y") + "</p>");
275 } else {
276 client.println("<h4 id='currentTime'></h4>");
277 client.println("<form action='/syncTime' method='POST'>");
278 client.println("<input type='hidden' name='epochTime' id='hiddenEpochTime'>");
279 client.println("<button type='submit'>Sync Time</button>");
280 client.println("</form>");
281 }
282 client.println("</div>");
283
284 // Step 2: Schedule setup
285 client.println("<div class='step'>");
286 client.println("<h3>Step 2: Set Motor Schedule</h3>");
287 if (scheduleWasSet) {
288 client.println("<p>✅ Schedule set:</p>");
289 client.println("<p>Morning: " + String(MORNING_HOUR) + ":" + String(MORNING_MINUTE < 10 ? "0" : "") + String(MORNING_MINUTE) + "</p>");
290 client.println("<p>Evening: " + String(EVENING_HOUR) + ":" + String(EVENING_MINUTE < 10 ? "0" : "") + String(EVENING_MINUTE) + "</p>");
291 client.println("<p>Motor runs for 30 seconds each time</p>");
292 } else {
293 client.println("<form action='/setSchedule' method='POST'>");
294 client.println("<div>");
295 client.println("<h4>Morning Time:</h4>");
296 client.println("Hour: <input type='number' name='morningHour' min='0' max='23' value='" + String(MORNING_HOUR) + "' required> ");
297 client.println("Minute: <input type='number' name='morningMinute' min='0' max='59' value='" + String(MORNING_MINUTE) + "' required>");
298 client.println("</div>");
299 client.println("<div>");
300 client.println("<h4>Evening Time:</h4>");
301 client.println("Hour: <input type='number' name='eveningHour' min='0' max='23' value='" + String(EVENING_HOUR) + "' required> ");
302 client.println("Minute: <input type='number' name='eveningMinute' min='0' max='59' value='" + String(EVENING_MINUTE) + "' required>");
303 client.println("</div>");
304 client.println("<button type='submit'>Set Schedule</button>");
305 client.println("</form>");
306 }
307 client.println("</div>");
308
309 if (timeWasSet && scheduleWasSet) {
310 client.println("<div class='step'>");
311 client.println("<h3>✅ Setup Complete!</h3>");
312 client.println("<p>ESP32 will now enter scheduled operation mode.</p>");
313 client.println("<button onclick='window.location.reload()'>Start Operation</button>");
314 client.println("</div>");
315 }
316
317 client.println("</div>");
318
319 // JavaScript for time updates and countdown
320 client.println("<script>");
321 client.println("var countdown = " + String(remaining) + ";");
322 client.println("function updateTime(){");
323 client.println("var now=new Date();");
324 client.println("document.getElementById('currentTime').innerHTML='Current Time: '+now.toLocaleString();");
325 client.println("var epoch=Math.floor(now.getTime()/1000);");
326 client.println("document.getElementById('hiddenEpochTime').value=epoch;");
327 client.println("}");
328 client.println("function updateCountdown(){");
329 client.println("countdown--;");
330 client.println("document.getElementById('countdown').innerHTML=countdown;");
331 client.println("if(countdown<=0){");
332 client.println("document.body.innerHTML='<h2>Timeout reached - ESP32 entering sleep mode</h2>';");
333 client.println("}");
334 client.println("}");
335 client.println("setInterval(updateTime,1000);");
336 client.println("setInterval(updateCountdown,1000);");
337 client.println("updateTime();");
338 client.println("</script></body></html>");
339 client.println();
340}
341
342void handleTimeSyncRequest(WiFiClient &client) {
343 String requestBody = "";
344 while (client.available()) {
345 requestBody += (char)client.read();
346 }
347
348 int epochIndex = requestBody.indexOf("epochTime=");
349 if (epochIndex != -1) {
350 long epochTime = requestBody.substring(epochIndex + 10).toInt();
351
352 rtc.setTime(epochTime);
353 timeWasSet = true;
354
355 Serial.println("Time synchronized: " + rtc.getTime("%A, %B %d %Y %H:%M:%S"));
356
357 // Send redirect back to main page
358 client.println("HTTP/1.1 302 Found");
359 client.println("Location: /");
360 client.println();
361 }
362}
363
364void handleScheduleRequest(WiFiClient &client) {
365 String requestBody = "";
366 while (client.available()) {
367 requestBody += (char)client.read();
368 }
369
370 // Parse form data
371 int morningHourIndex = requestBody.indexOf("morningHour=");
372 int morningMinuteIndex = requestBody.indexOf("morningMinute=");
373 int eveningHourIndex = requestBody.indexOf("eveningHour=");
374 int eveningMinuteIndex = requestBody.indexOf("eveningMinute=");
375
376 if (morningHourIndex != -1 && morningMinuteIndex != -1 &&
377 eveningHourIndex != -1 && eveningMinuteIndex != -1) {
378
379 // Extract values
380 String morningHourStr = extractFormValue(requestBody, "morningHour=");
381 String morningMinuteStr = extractFormValue(requestBody, "morningMinute=");
382 String eveningHourStr = extractFormValue(requestBody, "eveningHour=");
383 String eveningMinuteStr = extractFormValue(requestBody, "eveningMinute=");
384
385 MORNING_HOUR = morningHourStr.toInt();
386 MORNING_MINUTE = morningMinuteStr.toInt();
387 EVENING_HOUR = eveningHourStr.toInt();
388 EVENING_MINUTE = eveningMinuteStr.toInt();
389
390 // Validate times
391 if (MORNING_HOUR >= 0 && MORNING_HOUR <= 23 && MORNING_MINUTE >= 0 && MORNING_MINUTE <= 59 &&
392 EVENING_HOUR >= 0 && EVENING_HOUR <= 23 && EVENING_MINUTE >= 0 && EVENING_MINUTE <= 59) {
393
394 scheduleWasSet = true;
395
396 Serial.println("Schedule set:");
397 Serial.println("Morning: " + String(MORNING_HOUR) + ":" + String(MORNING_MINUTE));
398 Serial.println("Evening: " + String(EVENING_HOUR) + ":" + String(EVENING_MINUTE));
399
400 // If both time and schedule are set, start operation
401 if (timeWasSet && scheduleWasSet) {
402 delay(2000); // Allow user to see confirmation
403 struct tm timeinfo = rtc.getTimeStruct();
404 scheduleNextWakeup();
405 }
406 }
407 }
408
409 // Send redirect back to main page
410 client.println("HTTP/1.1 302 Found");
411 client.println("Location: /");
412 client.println();
413}
414
415String extractFormValue(String data, String fieldName) {
416 int startIndex = data.indexOf(fieldName);
417 if (startIndex == -1) return "";
418
419 startIndex += fieldName.length();
420 int endIndex = data.indexOf("&", startIndex);
421 if (endIndex == -1) endIndex = data.length();
422
423 return data.substring(startIndex, endIndex);
424}
425
426void printWakeupReason() {
427 esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
428
429 switch (wakeup_reason) {
430 case ESP_SLEEP_WAKEUP_TIMER:
431 Serial.println("Wake-up: Scheduled timer");
432 break;
433 case ESP_SLEEP_WAKEUP_EXT0:
434 Serial.println("Wake-up: External signal RTC_IO");
435 break;
436 case ESP_SLEEP_WAKEUP_EXT1:
437 Serial.println("Wake-up: External signal RTC_CNTL");
438 break;
439 default:
440 Serial.println("Wake-up: Power on or reset");
441 break;
442 }
443}
444