Advertisement
kodilivetv

esp32_autonomous_watering_system_c3SUPERMINI

Jul 4th, 2025
159
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 21.41 KB | None | 0 0
  1. /*
  2.   ========================================================================================
  3.                       Autonomous & Resilient Watering System for ESP32
  4.   ========================================================================================
  5.  
  6.   -- DESCRIPTION --
  7.   This sketch turns an ESP32 into a highly power-efficient and reliable automated
  8.   watering system. It is designed to be "set and forget." After an initial web-based
  9.   setup, it will run its schedule autonomously, with or without a WiFi connection.
  10.   All settings are saved to the ESP32's internal flash memory, making the system
  11.   resilient to power cuts.
  12.  
  13.   -- KEY FEATURES --
  14.   - Power-Efficient: Uses deep sleep to run for weeks or months on battery power.
  15.   - Web-Based Setup: Creates a WiFi hotspot to configure the schedule from your phone.
  16.   - Autonomous Offline Operation: Continues to run its schedule even if WiFi is down.
  17.   - Power-Cut Proof: Reloads saved settings from flash memory after a power failure.
  18.   - Smart Time Sync: Corrects clock drift with an NTP server when online.
  19.   - Telegram Notifications: Sends status messages when online.
  20.   - PUSHBUTTON RESET: A dedicated button can be pressed to wipe the settings and
  21.     force the device back into Configuration Mode without needing to re-upload code.
  22.  
  23.   ========================================================================================
  24.                                      HOW TO USE
  25.   ========================================================================================
  26.  
  27.   -- STEP 1: USER CONFIGURATION --
  28.   Before uploading, you MUST fill in the following credentials.
  29.  
  30.   1. WiFi Network:
  31.      - const char* network = "YOUR_WIFI_NAME";
  32.      - const char* pass    = "YOUR_WIFI_PASSWORD";
  33.  
  34.   2. Telegram Bot:
  35.      - const char* token   = "YOUR_TELEGRAM_BOT_TOKEN";
  36.      - int64_t userid      = YOUR_TELEGRAM_USER_ID;
  37.  
  38.   3. Timezone:
  39.      - #define MYTZ "YOUR_TIMEZONE_STRING" (e.g., "CET-1CEST,M3.5.0,M10.5.0/3")
  40.        Find yours at: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
  41.  
  42.  
  43.   -- STEP 2: HARDWARE SETUP --
  44.   1. Motor Driver: Connect to the pin defined by 'enableMotorPin' (default: GPIO 10).
  45.   2. Status LED: (Optional) Connect to the pin defined by 'ledPin' (default: GPIO 8).
  46.   3. Reset Button (IMPORTANT):
  47.      - Connect a momentary pushbutton between GPIO 4 and 3.3V.
  48.      - For stability, connect a 10k Ohm pull-down resistor between GPIO 4 and GND.
  49.        This prevents the pin from "floating" and causing accidental resets.
  50.  
  51.  
  52.   -- STEP 3: FIRST-TIME SETUP (CONFIGURATION PORTAL) --
  53.   1. Upload this code to your ESP32 and open the Serial Monitor.
  54.   2. The ESP32 will create a WiFi network named "WateringSystem_Setup".
  55.   3. Connect to this network with your phone. A configuration page should open automatically.
  56.   4. Set the schedule and motor run time, then click "Save Settings & Sync Time".
  57.   5. The ESP32 will save your settings, go to sleep, and begin its schedule.
  58.  
  59.  
  60.   -- STEP 4: RE-CONFIGURING THE DEVICE --
  61.   If you ever need to change the schedule or settings:
  62.   1. Press and hold the reset button (on GPIO 4) for about 2 seconds.
  63.   2. Release the button.
  64.   3. The device will wake up, wipe its old settings, and start the "WateringSystem_Setup"
  65.      WiFi network again.
  66.   4. Follow the steps from "FIRST-TIME SETUP" to re-configure it.
  67.  
  68. */
  69.  
  70. // =====================================================================================
  71. //                             FIRMWARE VERSION
  72. // =====================================================================================
  73. #define FW_VERSION      "3.3"
  74. #define FW_FEATURES     "C3, Window, Reset, VerID"
  75. // =====================================================================================
  76.  
  77. // --- Required Libraries ---
  78. #include <WiFi.h>
  79. #include <WiFiClientSecure.h>
  80. #include <AsyncTelegram2.h>
  81. #include "time.h"
  82. #include <WebServer.h>
  83. #include <DNSServer.h>
  84. #include <Preferences.h>
  85.  
  86. // --- Credentials and Configuration ---
  87. const char* network = "YOUR_WIFI_NAME";
  88. const char* pass = "YOUR_WIFI_PASSWORD";
  89. const char* token = "YOUR_TELEGRAM_BOT_TOKEN";
  90. int64_t userid = YOUR_TELEGRAM_USER_ID;
  91.  
  92. #define MYTZ "WET0WEST,M3.5.0/1,M10.5.0/2"
  93. #define uS_TO_S_FACTOR 1000000ULL
  94. const unsigned long PORTAL_TIMEOUT_MS = 240000;
  95. const int FALLBACK_SLEEP_SECONDS = 3600;
  96.  
  97. // --- RTC Memory ---
  98. RTC_DATA_ATTR int bootCount = 0;
  99.  
  100. // --- Global Configuration Variables ---
  101. uint32_t wateringSchedule = 0;
  102. int motorRunSeconds = 10;
  103.  
  104. // --- Hardware Pins ---
  105. // These pins are valid on most ESP32-C3 SuperMini boards.
  106. const int ledPin = 8;
  107. const int enableMotorPin = 10;
  108. const int resetButtonPin = 4; // GPIO4 is an RTC pin, valid for C3 wakeup.
  109.  
  110. // --- Core Objects ---
  111. WiFiClientSecure client; AsyncTelegram2 myBot(client);
  112. DNSServer dnsServer; WebServer server(80);
  113. Preferences preferences; bool portalSucceeded = false;
  114.  
  115. // --- Function Declarations ---
  116. void runMotor(); void blinkLED(int numBlinks, int blinkInterval);
  117. bool connectToWiFi(); void syncTimeAndSetupBot();
  118. void sendTelegramMessage(const char* msg); void startConfigurationPortal();
  119. void handleRoot(); void handleSave(); void handleNotFound();
  120.  
  121.  
  122. void setup() {
  123.   Serial.begin(115200);
  124.   delay(1000);
  125.  
  126.     // --- NEW: Print Firmware Version Banner ---
  127.   Serial.println("\n\n================================================");
  128.   Serial.printf("  Watering System FW Version: %s\n", FW_VERSION);
  129.   Serial.printf("  Features: %s\n", FW_FEATURES);
  130.   Serial.println("================================================");
  131.  
  132.   pinMode(ledPin, OUTPUT); pinMode(enableMotorPin, OUTPUT);
  133.   digitalWrite(ledPin, LOW); // SUPERMINI led is ON when LOW
  134.  
  135.   // --- Check Wakeup Reason ---
  136.   esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  137.  
  138.   // C3 CHANGE: Check for the generic GPIO wakeup cause instead of the specific EXT0.
  139.   if (wakeup_reason == ESP_SLEEP_WAKEUP_GPIO) {
  140.     Serial.println("\n\nWakeup caused by external signal on GPIO (Reset Button).");
  141.     Serial.println("Wiping configuration and starting portal...");
  142.     preferences.begin("water_cfg", false);
  143.     preferences.clear();
  144.     preferences.end();
  145.     bootCount = 0;
  146.     blinkLED(5, 100);
  147.   }
  148.  
  149.   configTzTime(MYTZ, "");
  150.  
  151.   // Load configuration from NVS.
  152.   preferences.begin("water_cfg", true);
  153.   wateringSchedule = preferences.getUInt("schedule", 0);
  154.   motorRunSeconds = preferences.getInt("motorTime", 10);
  155.   preferences.end();
  156.  
  157.   if (wateringSchedule != 0) { Serial.println("Found existing configuration in NVS."); }
  158.  
  159.   bootCount++; Serial.printf("\n--- Boot #%d ---\n", bootCount); blinkLED(2, 100);
  160.  
  161.   long long sleepDurationSeconds = FALLBACK_SLEEP_SECONDS;
  162.   bool timeIsValid = false;
  163.  
  164.   // --- Main Logic (unchanged) ---
  165.   if (wateringSchedule == 0) {
  166.     Serial.println("No schedule found. Starting Configuration Portal.");
  167.     startConfigurationPortal();
  168.     if (portalSucceeded) { timeIsValid = true; } else { timeIsValid = false; }
  169.   } else {
  170.     if (connectToWiFi()) {
  171.       Serial.println("WiFi Connected. Syncing time...");
  172.       syncTimeAndSetupBot(); timeIsValid = true;
  173.     } else {
  174.       Serial.println("WiFi failed. Proceeding autonomously."); timeIsValid = true;
  175.     }
  176.   }
  177.  
  178.   // --- Watering & Sleep Calculation Logic (unchanged) ---
  179.   if (timeIsValid) {
  180.     struct tm timeinfo;
  181.     if (!getLocalTime(&timeinfo)) {
  182.       Serial.println("Critical Error: Failed to obtain time. Using fallback sleep.");
  183.       sleepDurationSeconds = FALLBACK_SLEEP_SECONDS;
  184.     } else {
  185.       Serial.printf("Current time (source: %s): %s", (WiFi.status() == WL_CONNECTED ? "NTP" : "Internal RTC"), asctime(&timeinfo));
  186.       Serial.printf("Schedule Mask: %u, Motor Time: %d sec\n", wateringSchedule, motorRunSeconds);
  187.      
  188.       // =====================================================================================
  189.       // --- NEW: Watering Window Logic to Mitigate Drift and Save Power ---
  190.       // =====================================================================================
  191.       bool shouldWaterNow = false;
  192.  
  193.       // Check if THIS hour is a scheduled hour and we are in the first 5 minutes (e.g., woke up at 08:02)
  194.       if ((wateringSchedule & (1 << timeinfo.tm_hour)) && (timeinfo.tm_min < 5)) {
  195.         shouldWaterNow = true;
  196.       }
  197.       // Check if the NEXT hour is a scheduled hour and we are in the last 5 minutes of THIS hour (e.g., woke up at 07:58)
  198.       else {
  199.         int next_hour = (timeinfo.tm_hour + 1) % 24;
  200.         if ((wateringSchedule & (1 << next_hour)) && (timeinfo.tm_min >= 55)) {
  201.           shouldWaterNow = true;
  202.           Serial.println("Woke up a few minutes early, watering now to save a sleep cycle.");
  203.         }
  204.       }
  205.  
  206.       if (shouldWaterNow) {
  207.         Serial.println("Watering condition met.");
  208.         if (WiFi.status() == WL_CONNECTED) {
  209.           char msg[128];
  210.           // --- MODIFIED LINE ---
  211.           // The message is cleaned up and the version tag "(v%s)" is added.
  212.           snprintf(msg, sizeof(msg), "Watering at %02d:%02d for %d sec. (Boot #%d, v%s)",
  213.                    timeinfo.tm_hour, timeinfo.tm_min, motorRunSeconds, bootCount, FW_VERSION);
  214.           sendTelegramMessage(msg);
  215.         }
  216.         runMotor();
  217.       } else {
  218.         Serial.println("Woke up between events. Calculating next sleep.");
  219.       }
  220.       // =====================================================================================
  221.       // --- End of New Logic ---
  222.       // =====================================================================================
  223.      
  224.       time_t now = time(nullptr); struct tm nextEventTime = timeinfo;
  225.       nextEventTime.tm_min = 0; nextEventTime.tm_sec = 0; int nextHour = -1;
  226.       for (int i = 1; i <= 24; i++) {
  227.         int checkHour = (timeinfo.tm_hour + i) % 24;
  228.         if (wateringSchedule & (1 << checkHour)) { nextHour = checkHour; break; }
  229.       }
  230.       if (nextHour != -1) {
  231.         if (nextHour <= timeinfo.tm_hour) { nextEventTime.tm_mday++; }
  232.         nextEventTime.tm_hour = nextHour;
  233.         sleepDurationSeconds = mktime(&nextEventTime) - now;
  234.       } else { sleepDurationSeconds = FALLBACK_SLEEP_SECONDS; }
  235.     }
  236.   }
  237.  
  238.   if (sleepDurationSeconds <= 0) {
  239.     Serial.printf("Invalid sleep duration calculated (%llds). Using fallback.\n", sleepDurationSeconds);
  240.     sleepDurationSeconds = FALLBACK_SLEEP_SECONDS;
  241.   }
  242.   Serial.printf("Going to sleep for %lld seconds...\n", sleepDurationSeconds);
  243.  
  244.   // --- CONFIGURE WAKEUP SOURCES ---
  245.   esp_sleep_enable_timer_wakeup(sleepDurationSeconds * uS_TO_S_FACTOR);
  246.  
  247.   // C3 CHANGE: Use the C3-specific GPIO wakeup function.
  248.   // The first argument is a bitmask of the pins to wake up on.
  249.   // 1ULL << resetButtonPin creates a bitmask with only the bit for GPIO 4 set.
  250.   esp_deep_sleep_enable_gpio_wakeup(1ULL << resetButtonPin, ESP_GPIO_WAKEUP_GPIO_HIGH);
  251.  
  252.   blinkLED(3, 200); Serial.flush();
  253.   esp_deep_sleep_start();
  254. }
  255.  
  256.  
  257. // --- Helper Functions ---
  258.  
  259. void loop() {}
  260.  
  261. void runMotor() {
  262.   Serial.printf("Motor running for %d seconds...\n", motorRunSeconds);
  263.   digitalWrite(enableMotorPin, HIGH);
  264.   delay(motorRunSeconds * 1000);
  265.   digitalWrite(enableMotorPin, LOW);
  266.   Serial.println("Motor stopped.");
  267. }
  268.  
  269. void blinkLED(int numBlinks, int blinkInterval) {
  270.   for (int i = 0; i < numBlinks; i++) {
  271.     digitalWrite(ledPin, LOW); delay(blinkInterval);
  272.     digitalWrite(ledPin, HIGH); delay(blinkInterval);
  273.   }
  274. }
  275.  
  276. bool connectToWiFi() {
  277.   Serial.print("Connecting to WiFi...");
  278.   WiFi.mode(WIFI_STA);
  279.   WiFi.begin(network, pass);
  280.   int counter = 0;
  281.   while (WiFi.status() != WL_CONNECTED) {
  282.     delay(500); Serial.print(".");
  283.     if (++counter >= 20) { // 10-second timeout
  284.       Serial.println(" Timeout!"); return false;
  285.     }
  286.   }
  287.   Serial.println(" Connected!"); return true;
  288. }
  289.  
  290. void syncTimeAndSetupBot() {
  291.   configTzTime(MYTZ, "pool.ntp.org", "time.google.com");
  292.   struct tm timeinfo;
  293.   while (!getLocalTime(&timeinfo, 5000)) {
  294.     Serial.print(".");
  295.     delay(500);
  296.   }
  297.   Serial.println(" Time Synced!");
  298.   client.setCACert(telegram_cert);
  299.   myBot.setTelegramToken(token);
  300. }
  301.  
  302. void sendTelegramMessage(const char* msg) {
  303.   if (WiFi.status() != WL_CONNECTED) return;
  304.   Serial.printf("Sending to Telegram: \"%s\"\n", msg);
  305.   if (myBot.begin()) {
  306.     myBot.sendTo(userid, msg);
  307.   }
  308.   else {
  309.     Serial.println("Failed to initialize Telegram Bot.");
  310.   }
  311. }
  312.  
  313. void startConfigurationPortal() {
  314.   const char* ap_ssid = "WateringSystem_Setup";
  315.   WiFi.softAP(ap_ssid);
  316.   IPAddress apIP = WiFi.softAPIP();
  317.   Serial.printf("AP started. Connect to %s\n", ap_ssid);
  318.   dnsServer.start(53, "*", apIP);
  319.   server.on("/", HTTP_GET, handleRoot);
  320.   server.on("/save", HTTP_POST, handleSave);
  321.   server.onNotFound(handleNotFound);
  322.   server.begin();
  323.   Serial.printf("Portal active for %lu minutes...\n", PORTAL_TIMEOUT_MS / 60000);
  324.   unsigned long portalStartTime = millis();
  325.   while (!portalSucceeded && (millis() - portalStartTime < PORTAL_TIMEOUT_MS)) {
  326.     dnsServer.processNextRequest(); server.handleClient(); delay(10);
  327.   }
  328.   server.stop(); dnsServer.stop(); WiFi.softAPdisconnect(true); WiFi.mode(WIFI_OFF);
  329.   Serial.println("Configuration portal closed.");
  330. }
  331.  
  332. // =====================================================================================
  333. //                             WEB PAGE ASSETS
  334. // =====================================================================================
  335. // By defining the static CSS and JS here, we make the handleRoot() function much
  336. // cleaner and easier to manage. They are stored in PROGMEM to save RAM.
  337.  
  338. const char PAGE_STYLE[] PROGMEM = R"rawliteral(
  339. body {
  340.    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  341.    background-color: #f0f2f5;
  342.    color: #1c1e21;
  343.    margin: 0;
  344.    padding: 20px;
  345.    display: flex;
  346.    justify-content: center;
  347.    align-items: center;
  348.    min-height: 100vh;
  349.    box-sizing: border-box;
  350. }
  351. .container {
  352.    background-color: #fff;
  353.    border-radius: 8px;
  354.    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.1);
  355.    padding: 24px;
  356.    width: 100%;
  357.    max-width: 500px;
  358.    box-sizing: border-box;
  359. }
  360. h1 {
  361.    font-size: 24px;
  362.    color: #1877f2;
  363.    border-bottom: 1px solid #dddfe2;
  364.    padding-bottom: 15px;
  365.    margin: -24px -24px 20px -24px;
  366.    padding-left: 24px;
  367. }
  368. .form-group { text-align: left; margin-bottom: 20px; }
  369. label {
  370.    font-weight: bold;
  371.    display: block;
  372.    margin-bottom: 8px;
  373.    font-size: 15px;
  374. }
  375. input[type="number"] {
  376.    width: 100%;
  377.    padding: 10px;
  378.    border-radius: 6px;
  379.    border: 1px solid #dddfe2;
  380.    box-sizing: border-box;
  381.    font-size: 16px;
  382. }
  383. .grid-container {
  384.    display: grid;
  385.    grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
  386.    gap: 10px;
  387. }
  388. .grid-item {
  389.    position: relative;
  390.    background-color: #f0f2f5;
  391.    border: 1px solid #dddfe2;
  392.    border-radius: 6px;
  393.    padding: 10px;
  394.    cursor: pointer;
  395.    user-select: none;
  396.    transition: background-color 0.2s, border-color 0.2s;
  397.    text-align: center;
  398. }
  399. .grid-item:hover { background-color: #e4e6eb; }
  400. .grid-item input { display: none; }
  401. .grid-item span { font-size: 14px; position: relative; z-index: 1; }
  402. .grid-item input:checked + span { font-weight: bold; color: #fff; }
  403. .grid-item input:checked ~ .checkmark { background-color: #1877f2; border-color: #1877f2; }
  404. .checkmark {
  405.    display: block; height: 100%; width: 100%; position: absolute; top: 0;
  406.    left: 0; z-index: 0; border-radius: 6px;
  407. }
  408. button {
  409.    background-color: #1877f2;
  410.    color: white;
  411.    width: 100%;
  412.    padding: 12px 0;
  413.    font-size: 17px;
  414.    font-weight: bold;
  415.    border: none;
  416.    border-radius: 6px;
  417.    cursor: pointer;
  418.    transition: background-color 0.2s;
  419. }
  420. button:hover { background-color: #166fe5; }
  421. button:disabled { background-color: #a0b9d9; cursor: not-allowed; }
  422. #status {
  423.    margin-top: 15px;
  424.    font-weight: bold;
  425.    color: #d9534f; /* Red for errors by default */
  426. }
  427. )rawliteral";
  428.  
  429.  
  430. const char PAGE_SCRIPT[] PROGMEM = R"rawliteral(
  431. document.addEventListener('DOMContentLoaded', function() {
  432.    const form = document.getElementById('configForm');
  433.    const motorTimeInput = document.getElementById('motorTime');
  434.    const scheduleCheckboxes = document.querySelectorAll('input[name="schedule"]');
  435.    const statusEl = document.getElementById('status');
  436.    const submitBtn = document.querySelector('button[type="submit"]');
  437.  
  438.    form.addEventListener('submit', function(event) {
  439.        // --- Client-Side Validation (Robustness & Security) ---
  440.        statusEl.textContent = ''; // Clear previous errors
  441.  
  442.        // 1. Validate Motor Run Time
  443.        const motorTime = parseInt(motorTimeInput.value, 10);
  444.        if (isNaN(motorTime) || motorTime < 5 || motorTime > 300) {
  445.            statusEl.textContent = 'Error: Motor run time must be between 5 and 300.';
  446.            event.preventDefault(); // Stop form submission
  447.            return;
  448.        }
  449.  
  450.        // 2. Validate Schedule Selection
  451.        let isAnyChecked = false;
  452.        scheduleCheckboxes.forEach(function(checkbox) {
  453.            if (checkbox.checked) {
  454.                isAnyChecked = true;
  455.            }
  456.        });
  457.        if (!isAnyChecked) {
  458.            statusEl.textContent = 'Error: Please select at least one watering hour.';
  459.            event.preventDefault(); // Stop form submission
  460.            return;
  461.        }
  462.        
  463.        // If validation passes, add the timestamp
  464.        document.getElementById('timestamp').value = Math.floor(new Date().getTime() / 1000);
  465.        
  466.        // Give user feedback
  467.        submitBtn.textContent = 'Saving...';
  468.        submitBtn.disabled = true;
  469.    });
  470. });
  471. )rawliteral";
  472.  
  473. void handleRoot() {
  474.     // This function is now much more readable. It acts as a simple templating engine.
  475.     String html = "<!DOCTYPE html><html><head>";
  476.     html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  477.     html += "<title>Watering System Setup</title>";
  478.    
  479.     // Inject the CSS
  480.     html += "<style>";
  481.     html += FPSTR(PAGE_STYLE);
  482.     html += "</style>";
  483.    
  484.     html += "</head><body><div class='container'>";
  485.     html += "<h1>Watering System Setup</h1>";
  486.     html += "<form id='configForm' action='/save' method='post'>";
  487.    
  488.     // --- Dynamic Part 1: Motor Run Time ---
  489.     html += "<div class='form-group'><label for='motorTime'>Motor Run Time (5-300 seconds):</label>";
  490.     html += "<input type='number' id='motorTime' name='motorTime' min='5' max='300' value='";
  491.     html += motorRunSeconds;
  492.     html += "' required></div>";
  493.  
  494.     // --- Dynamic Part 2: Watering Schedule Grid ---
  495.     html += "<div class='form-group'><label>Watering Schedule (Hours):</label>";
  496.     html += "<div class='grid-container'>";
  497.     for (int i = 0; i < 24; i++) {
  498.         html += "<label class='grid-item'><input type='checkbox' name='schedule' value='" + String(i) + "'";
  499.         if (wateringSchedule & (1 << i)) { html += " checked"; }
  500.         char hourStr[4];
  501.         snprintf(hourStr, sizeof(hourStr), "%02d", i);
  502.         html += "><span>" + String(hourStr) + ":00</span><div class='checkmark'></div></label>";
  503.     }
  504.     html += "</div></div>"; // Close grid-container and form-group
  505.  
  506.     // --- Form Footer ---
  507.     html += "<input type='hidden' id='timestamp' name='timestamp' value=''>";
  508.     html += "<button type='submit'>Save Settings & Sync Time</button>";
  509.     html += "<p id='status'></p>"; // For validation messages
  510.     html += "</form></div>";
  511.  
  512.     // Inject the JavaScript
  513.     html += "<script>";
  514.     html += FPSTR(PAGE_SCRIPT);
  515.     html += "</script>";
  516.    
  517.     html += "</body></html>";
  518.    
  519.     server.send(200, "text/html", html);
  520. }
  521.  
  522. void handleSave() {
  523.   preferences.begin("water_cfg", false); // false = read/write mode
  524.  
  525.   // Save Motor Run Time
  526.   if (server.hasArg("motorTime")) {
  527.     int newMotorTime = server.arg("motorTime").toInt();
  528.     if (newMotorTime >= 5 && newMotorTime <= 300) {
  529.       motorRunSeconds = newMotorTime;
  530.       preferences.putInt("motorTime", motorRunSeconds);
  531.       Serial.printf("Saved new motor time to NVS: %d sec\n", motorRunSeconds);
  532.     }
  533.   }
  534.  
  535.   // Save Schedule Bitmask
  536.   uint32_t newSchedule = 0;
  537.   for (int i = 0; i < server.args(); i++) {
  538.     if (server.argName(i) == "schedule") {
  539.       int hour = server.arg(i).toInt();
  540.       if (hour >= 0 && hour <= 23) {
  541.         newSchedule |= (1 << hour);
  542.       }
  543.     }
  544.   }
  545.   wateringSchedule = newSchedule;
  546.   preferences.putUInt("schedule", wateringSchedule);
  547.   Serial.printf("Saved new schedule mask to NVS: %u\n", wateringSchedule);
  548.  
  549.   // IMPORTANT: Close preferences to commit the changes to flash memory!
  550.   preferences.end();
  551.  
  552.   // Set the system time from the phone's timestamp
  553.   if (server.hasArg("timestamp")) {
  554.     long long timestamp = atoll(server.arg("timestamp").c_str());
  555.     struct timeval tv; tv.tv_sec = timestamp; tv.tv_usec = 0;
  556.     settimeofday(&tv, NULL);
  557.     Serial.printf("Time set from phone: %lld\n", timestamp);
  558.   }
  559.  
  560.   portalSucceeded = true;
  561.   String successPage = R"rawliteral(<!DOCTYPE html><html><head><title>Success</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body{font-family:sans-serif;text-align:center;padding-top:50px}</style></head><body><h1>Success!</h1><p>Settings saved. The device will now go to sleep.</p></body></html>)rawliteral";
  562.   server.send(200, "text/html", successPage);
  563.   delay(1000);
  564. }
  565.  
  566. void handleNotFound() {
  567.   server.sendHeader("Location", "/", true);
  568.   server.send(302, "text/plain", "");
  569. }
  570.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement