]> git.tnoah.ca Git - garden.git/commitdiff
Working API to start water
authorNoah Tomkins <noah@tnoah.ca>
Sun, 23 Aug 2026 22:56:12 +0000 (18:56 -0400)
committerNoah Tomkins <noah@tnoah.ca>
Sun, 23 Aug 2026 22:56:12 +0000 (18:56 -0400)
platformio.ini
src/main.cpp
src/main.h
src/secrets.h.example

index 2049f4b9d8597db7c31f213ea763ff81bedd9aae..bd72736b0871ea5197767aa1daabdafbc039b547 100644 (file)
@@ -12,6 +12,7 @@
 platform = espressif8266
 board = d1_mini_lite
 framework = arduino
+upload_speed = 115200
 monitor_speed = 115200
 lib_deps = 
        bblanchon/ArduinoJson@^7.2.2
@@ -19,3 +20,4 @@ lib_deps =
        esp32async/ESPAsyncTCP@^2.0.0
        adafruit/Adafruit BME280 Library@^2.3.0
        adafruit/Adafruit Unified Sensor@^1.1.15
+       arduino-libraries/NTPClient@^3.2.1
index 37f2a2e17b3b624a735e3da25fa04aed09094a02..ab394633985c9f3c1eb12ee3ae9f92c67b97ffa1 100644 (file)
@@ -1,9 +1,15 @@
 #include <Arduino.h>
 
+#include <iostream>
+#include <string>
+
 #include <ESP8266WiFi.h>
 #include <ESPAsyncTCP.h>
 #include <ESPAsyncWebServer.h>
 
+#include <WiFiUdp.h>
+#include <NTPClient.h>
+
 #include <Wire.h>
 #include <Adafruit_Sensor.h>
 #include <Adafruit_BME280.h>
 #include "main.h"
 #include "secrets.h"
 
+bool safeToRun();
+bool runWater(String override);
+void stopWater();
+void handleWaterRunning();
+
 void connectToWiFi();
 void setupWebServer();
 void setupBME280();
@@ -21,10 +32,143 @@ void setupBME280();
 WiFiClient wifiClient;
 AsyncWebServer server(80);
 
+WiFiUDP ntpUDP;
+NTPClient timeClient(ntpUDP, "pool.ntp.org");
+
 Adafruit_BME280 bme;
 
+// NTP Timer
+unsigned long last_ran = 0;
+unsigned long end_time = 0;
+
+// Fallback
+unsigned long timer_setpoint = 0;
+unsigned long timer_current = 0;
+
+bool waterState = false;
+unsigned long loopTime = 10; // Seconds
+
+int maxNTPRetry = 5;
+int currentNTPRetry = 0;
+
+unsigned long lastCheckedTime = 0;
+
+void setup() {
+       Serial.begin(115200);
+       Serial.println();
+
+       connectToWiFi();
+       setupWebServer();
+       setupBME280();
+
+       timeClient.begin();
+       timeClient.update();
+       last_ran = timeClient.getEpochTime();
+       Serial.printf("\nCurrent Time (s): %ld", last_ran);
+
+       pinMode(SOAKER, OUTPUT);
+       digitalWrite(SOAKER, LOW);
+}
+
+void loop() {
+       
+       handleWaterRunning();
+
+       delay(loopTime * 1000);
+}
+
+bool safeToRun() {
+       timeClient.update();
+       unsigned long ctime = timeClient.getEpochTime();
+
+       if (ctime > last_ran + water_safe_time)
+               return true;
+
+       return false;
+}
+
+bool runWater(unsigned long runtime, String override = "") {
+       if (!safeToRun()) {
+               if (override != water_override) {
+                       Serial.print("Water Not Safe to Run. Cannot Start Water.");
+                       return false;
+               }
+       }
+
+       // Run Water
+       Serial.printf("\n\nRunning Water for %lds (%ldm)", runtime, runtime / 60);
+
+       // Set Endtime
+       timeClient.update();
+       unsigned long cTime = timeClient.getEpochTime();
+       end_time = cTime + runtime;
+       Serial.printf("\nCurrent Time: %ld. Run until: %ld.", cTime, end_time);
+
+       // Reset Fallback
+       timer_current = 0;
+       timer_setpoint = runtime;
+
+       waterState = true;
+       digitalWrite(SOAKER, HIGH);
+
+       return true;
+}
+
+void handleWaterRunning() {
+       timeClient.update();
+       unsigned long cTime = timeClient.getEpochTime();
+
+       // STOP water if unable to connect to NTP Server
+       if (lastCheckedTime != cTime) 
+       {
+               currentNTPRetry = 0;
+               lastCheckedTime = cTime;
+       } 
+       else if (currentNTPRetry >= maxNTPRetry)
+       {
+               Serial.print("\nFailed to connect to NTP Server. Water turning off!");
+               stopWater();
+       }
+       else
+       {
+               currentNTPRetry++;
+               Serial.printf("\nRetying NTP %d/%d", currentNTPRetry, maxNTPRetry);
+       }
+
+       if (waterState) {
+               Serial.printf("\nTime Remaining: %ld seconds.", end_time - cTime);
+
+               if (cTime >= end_time) {
+                       stopWater();
+                       Serial.print("Water Finished!");
+               }
+               
+               // Fallback Timer
+               timer_current += loopTime;
+               if (timer_current > timer_setpoint) {
+                       stopWater();
+                       Serial.print("\nFAILSAFE TRIGGERED!");
+               }
+       } else {
+               stopWater();
+       }
+}
+
+void stopWater() {
+       waterState = false;
+       digitalWrite(SOAKER, LOW);
+
+       last_ran = timeClient.getEpochTime();
+
+       timer_current = 0;
+       timer_setpoint = 0;
+       end_time = 0;
+
+       Serial.print("\n\nWater Stopped.");
+}
+
 void connectToWiFi() {
-               WiFi.begin(ssid, pass);
+       WiFi.begin(ssid, pass);
 
        Serial.print("Connecting");
        while (WiFi.status() != WL_CONNECTED)
@@ -32,28 +176,79 @@ void connectToWiFi() {
                delay(500);
                Serial.print(".");
        }
-       Serial.println();
-
-       Serial.print("Connected, IP address: ");
+       Serial.print("\nConnected, IP address: ");
        Serial.println(WiFi.localIP());
 }
 
 void setupWebServer() {
-       server.on("/api/sensors", HTTP_GET, [] (AsyncWebServerRequest *request) {
+       server.on("/status", HTTP_GET, [] (AsyncWebServerRequest *request) {
+               if (!request->authenticate(http_username, http_password))
+                       return request->requestAuthentication();
                
                AsyncResponseStream *response = request->beginResponseStream("application/json");
                JsonDocument doc;
 
-               doc["temperature"] = bme.readTemperature();
-               doc["pressure"] = bme.readPressure() / 1000;
-               doc["humidity"] = bme.readHumidity();
-               doc["altitude"] = bme.readAltitude(SEALEVELPRESSURE_HPA);
+               JsonDocument bmeDoc;
+               bmeDoc["temperature"] = bme.readTemperature();
+               bmeDoc["pressure"] = bme.readPressure() / 1000;
+               bmeDoc["humidity"] = bme.readHumidity();
+               doc["bme280"] = bmeDoc;
 
                serializeJson(doc, *response);
                request->send(response);
 
        });
 
+       server.on("/water", HTTP_GET, [] (AsyncWebServerRequest *request) {
+               if (!request->authenticate(http_username, http_password))
+                       return request->requestAuthentication();
+
+               String time_str = "";
+               String override_pass = "";
+
+               bool safe = safeToRun();
+
+               if (request->hasParam(PARAM_WATER_TIME)) {
+                       time_str = request->getParam(PARAM_WATER_TIME)->value();
+
+                       // If not safe, check override password.
+                       if (!safe) {
+                               if (request->hasParam(PARAM_WATER_OVERRIDE)) {
+                                       override_pass = request->getParam(PARAM_WATER_OVERRIDE)->value();
+
+                                       if (override_pass == water_override) 
+                                               safe = true;
+                               }
+                       }
+
+                       // Fail If not Safe
+                       if (!safe) {
+                               request->send(400, "text/plain", "Not safe to run water or wrong password.");
+                               return;
+                       }
+
+                       // Convert Time String to Int
+                       int time = -1;
+                       if (!sscanf(time_str.c_str(), "%d", &time)) {
+                               request->send(400, "text/plain", "Time unable to be parsed.");
+                               return;
+                       }
+
+                       // Check Time Within Safe Range
+                       if (time < 1 || time > 25) {
+                               request->send(400, "text/plain", "Time must be between 1 and 25 mins");
+                               return;
+                       }
+
+                       Serial.printf("\nHTTP request to run water for %d mins.", time);
+                       runWater(time * 60, override_pass);
+
+               } else {
+                       request->send(400, "text/plain", "No Time Provided.");
+                       return;
+               }
+       });
+
        server.begin();
 }
 
@@ -64,25 +259,9 @@ void setupBME280() {
        for (int i = 10; i < 10; i++) {
                if (status) continue;
                delay(100);
-               Serial.print("Could not connect to BME, retrying");
+               Serial.print("\nCould not connect to BME, retrying");
        }
 
-       Serial.print("Connected to BME280!");
-}
-
-void setup() {
-       Serial.begin(115200);
-       Serial.println();
-
-       connectToWiFi();
-       setupWebServer();
-       setupBME280();
-
-       pinMode(SOAKER, OUTPUT);
-}
-
-void loop() {
-       
-       delay(1000);
+       Serial.print("\nConnected to BME280!");
 }
 
index 84b49c58a32e3b7c82c2895ed38be02ace2cdf9e..f9e00236caba183dea92897ad366a92e6f2ea2ff 100644 (file)
@@ -1,8 +1,18 @@
-// Soaker Hose Solenoid
-#define SOAKER         D8
+
+\// Soaker Hose Solenoid
+#define SOAKER         D3
+
+// Buttons
+#define BUTTON_A D0
+#define BUTTON_B D5
 
 // BME280 I2C
 #define BME_SCL        D1
 #define BME_SDA D2
 
-#define SEALEVELPRESSURE_HPA (1013.25)
\ No newline at end of file
+#define SEALEVELPRESSURE_HPA (1013.25)
+
+const char* PARAM_WATER_TIME = "time";
+const char* PARAM_WATER_OVERRIDE = "override";
+
+const unsigned long water_safe_time = 72000; // 20 hours
\ No newline at end of file
index 90a1225fa72f73d21df1ec696595092e2af6462c..d96384892773d0c7a2862407bb37abebefb08106 100644 (file)
@@ -1,3 +1,8 @@
 // WiFi settings
 const char *ssid = "ssid";
-const char *pass = "password";
\ No newline at end of file
+const char *pass = "password";
+
+const char* http_username = "admin";
+const char* http_password = "admin";
+
+const char* water_override = "override";
\ No newline at end of file