Solar timer project to charge an EV

That's a great idea and I will look at adding it, so far I've just been using a timer to make sure it is below or above a point.

I've redone the code (below).

I want to be able to manually tune the level via the onboard pot, which means I use the DO to determine on.off points, BUT
I'm now using the analog to set drop off values, so when the DO goes low or high, I capture the output of the analog pin at that moment, and use it. A timer checks and if it remains X amount below the analog read of value at the moment of DO switch (x being called the Offset value), for the required period of time, then it turns it off. If it remains above the value for a length of time, it switches on.
It's testing fine right now with the timer values kept very short for testing purposes.
Here's the code (I've added wifi connection and some weblog features so I can manually change the timer value and the offset value when needed, sorry for the added verbosity).

//==============================================================================
#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h>

const char* ssid     = "xxxxx";      // Replace with your Wi-Fi SSID
const char* password = "xxxxx";  // Replace with your Wi-Fi password


AsyncWebServer server(80);

const int LDR_DIGITAL_PIN = 34;  // DO pin from LDR module
const int LDR_ANALOG_PIN  = 35;  // AO pin from LDR module
const int SWITCH_PIN      = 23;  // Manual override switch
const int RELAY_PIN       = 4;   // Relay control pin

unsigned long ON_DELAY  = 5UL * 1000UL;   // Brightness confirmation delay
unsigned long OFF_DELAY = 10UL * 1000UL;  // Darkness confirmation delay
int OFFSET = 100;                         // AO drop threshold

unsigned long brightStartTime = 0;
unsigned long darkStartTime   = 0;
int aoStart = -1;

bool relayState = false;
bool prev_state = false;
bool prev_ovr = false;

const int MAX_LOGS = 30;
String logBuffer[MAX_LOGS];
int logIndex = 0;
//==============================================================================
void logMessage(String msg) {
  String timestamp = "[" + String(millis() / 1000) + "s] ";
  logBuffer[logIndex] = timestamp + msg;
  logIndex = (logIndex + 1) % MAX_LOGS;
  Serial.println(msg);
}
//==============================================================================
String buildLogPage(String statusMsg = "") {
  int aoValue = analogRead(LDR_ANALOG_PIN);
  bool isBright = digitalRead(LDR_DIGITAL_PIN) == LOW;

  String html = "<html><head><meta http-equiv='refresh' content='30'><meta charset='UTF-8'><title>Relay Control</title></head><body>";
  html += "<h2>ESP32 Relay Control Panel</h2>";

  if (statusMsg.length() > 0) {
    html += "<p><b>" + statusMsg + "</b></p>";
  }

  html += "<form action='/update' method='GET'>";
  html += "ON_DELAY (ms): <input type='number' name='ON_DELAY' value='" + String(ON_DELAY) + "'><br>";
  html += "OFF_DELAY (ms): <input type='number' name='OFF_DELAY' value='" + String(OFF_DELAY) + "'><br>";
  html += "OFFSET: <input type='number' name='OFFSET' value='" + String(OFFSET) + "'><br>";
  html += "<input type='submit' value='Update Settings'>";
  html += "</form><br>";

  html += "<form action='/update' method='GET'>";
  html += "<button name='RELAY' value='ON'>Relay ON</button>";
  html += "<button name='RELAY' value='OFF'>Relay OFF</button>";
  html += "</form><hr>";

  html += "<h3>Sensor Readings</h3>";
  html += "DO (digital): " + String(isBright ? "LOW (bright)" : "HIGH (dark)") + "<br>";
  html += "AO (analog): " + String(aoValue) + "<br><hr>";

  html += "<h3>Recent Logs</h3><pre>";
  for (int i = 0; i < MAX_LOGS; i++) {
    int idx = (logIndex + i) % MAX_LOGS;
    if (logBuffer[idx].length() > 0) html += logBuffer[idx] + "\n";
  }
  html += "</pre></body></html>";
  return html;
}
//==============================================================================
void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  pinMode(LDR_DIGITAL_PIN, INPUT);
  pinMode(LDR_ANALOG_PIN, INPUT);
  digitalWrite(RELAY_PIN, LOW);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWi-Fi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  logMessage("System Initialized");

  ArduinoOTA.setHostname("esp32-relay");
  ArduinoOTA.begin();

  server.on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/html", buildLogPage());
  });

  server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) {
    String status = "";

    if (request->hasParam("ON_DELAY")) {
      ON_DELAY = request->getParam("ON_DELAY")->value().toInt();
      status += "ON_DELAY updated to " + String(ON_DELAY) + ". ";
      logMessage("ON_DELAY updated to " + String(ON_DELAY));
    }
    if (request->hasParam("OFF_DELAY")) {
      OFF_DELAY = request->getParam("OFF_DELAY")->value().toInt();
      status += "OFF_DELAY updated to " + String(OFF_DELAY) + ". ";
      logMessage("OFF_DELAY updated to " + String(OFF_DELAY));
    }
    if (request->hasParam("OFFSET")) {
      OFFSET = request->getParam("OFFSET")->value().toInt();
      status += "OFFSET updated to " + String(OFFSET) + ". ";
      logMessage("OFFSET updated to " + String(OFFSET));
    }
    if (request->hasParam("RELAY")) {
      String cmd = request->getParam("RELAY")->value();
      relayState = (cmd == "ON");
      digitalWrite(RELAY_PIN, relayState);
      status += "Relay manually set to " + cmd + ". ";
      logMessage("Relay manually set to " + cmd);
    }

    request->send(200, "text/html", buildLogPage(status));
  });

  server.begin();
}
//==============================================================================
void loop() {
  ArduinoOTA.handle();

  bool ovr = !digitalRead(SWITCH_PIN);
  bool isBright = digitalRead(LDR_DIGITAL_PIN) == LOW;
  int aoValue = analogRead(LDR_ANALOG_PIN);

  if (ovr != prev_ovr) {
    logMessage(String("Manual switch turned ") + (ovr ? "ON" : "OFF"));
    prev_ovr = ovr;

    if (!ovr) {
      brightStartTime = 0;
      darkStartTime = 0;
      aoStart = -1;
      relayState = false;
      logMessage("Manual override ended - relay OFF");
    }
  }

  if (ovr) {
    relayState = true;
    brightStartTime = 0;
    darkStartTime = 0;
    aoStart = -1;
  } else if (!relayState) {
    if (isBright) {
      if (brightStartTime == 0) brightStartTime = millis();
      else if (millis() - brightStartTime >= ON_DELAY) {
        relayState = true;
        brightStartTime = 0;
        logMessage("Sustained brightness - relay ON");
      }
    } else {
      brightStartTime = 0;
    }
  } else {
    if (!isBright) {
      if (darkStartTime == 0) {
        darkStartTime = millis();
        aoStart = aoValue;
      } else {
        if (millis() - darkStartTime >= OFF_DELAY) {
          int aoDrop = aoStart - aoValue;
          if (aoDrop >= OFFSET) {
            relayState = false;
            darkStartTime = 0;
            aoStart = -1;
            logMessage("AO drop detected (" + String(aoDrop) + ") - relay OFF");
          }
        }
      }
    } else {
      darkStartTime = 0;
      aoStart = -1;
    }
  }

  if (relayState != prev_state) {
    logMessage(String("Relay state changed: ") + (relayState ? "ON" : "OFF"));
    prev_state = relayState;
  }

  digitalWrite(RELAY_PIN, relayState);
  delay(20);
}
//==============================================================================