Stuck or some kind of crash

So I’ve made an simple watering system with my arduino uno and it have been working for a couple of weeks but for some reason it got stuck when it started the pump and didn’t stop after 2 seconds as it should and at the same time, a LCD where the 2 first characters got changed to some sort of symbol.

I did a restart and its all good again, but I dont want it to happen if im not home when it happens.

Ive found my diagram its a picture, I hope it helps. Dont remember what website I used to make it, il try and see if i can find it.
BTW I dont use the high voltage, the idea was to switch on/off a grow light, but i scraped it.

billede_2021-04-28_144126billede_2021-04-28_144200

Post your code and wiring diagram.

Do You use any breadboard? The best is to solder all connections.

I dont use any breadboard its on a PCB

Some of the code is in danish
Code:


#include <LiquidCrystal.h>
#include <MovingAverageFilter.h>

MovingAverageFilter movingAverageFilter(10); //Gennemsnitet vil blive udregnet efter 10 målinger, dette kan forøges hvis der ønskes et mere præcist gennemsnit.

int Pumpe = A4;
int Niveau_kontakt = 11;

float tempPin = A5;

int Jordfugtighed = A3;

unsigned long currentMillis_Temp;
unsigned long previousMillis_Temp;
unsigned long currentMillis_Soil;
unsigned long previousMillis_Soil;

unsigned long currentMillis_TimerPump;
unsigned long previousMillis_TimerPump;


// LCD opsætning
const int  rs = 10, en = 9, d4 = 8, d5 = 7, d6 = 6, d7 = 5; // pins
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// Variabler til jordfugtighedsmåleren
const int AirValue = 592;   //you need to replace this value with Value_1
const int WaterValue = 278;  //you need to replace this value with Value_2
int intervals = (AirValue - WaterValue) / 3;

int soilMoistureValue = 0;
int SoilMoistureLevel = 0;
int SoilState;

bool PumpRunning;
bool TimerOn = false;
bool LowWaterLevel = false;

bool FirstScan = true;

// Variabler til temperatur måling
float TempVal;
float NuvaerendeTemp; // Den nuværende temperatur





//----------------------------------------------------------------------------------------//
void setup() {
  pinMode(Pumpe, OUTPUT);
  pinMode(Niveau_kontakt, INPUT);
  pinMode(tempPin, INPUT);
  pinMode(Jordfugtighed, INPUT);


  analogRead (A0); // force voltage reference to be turned on
  analogRead (A1); // force voltage reference to be turned on

  Serial.begin(9600);

  lcd.begin(20, 4);
  digitalWrite(Pumpe, HIGH);
}

void loop() {

  lcd.setCursor(0, 1);
  lcd.print("Jorden = ");
  lcd.setCursor(0, 2);
  lcd.print("Temperatur = ");


  if (digitalRead(Pumpe) == HIGH) {
    PumpRunning = false;
  }
  if (digitalRead(Pumpe) == LOW) {
    PumpRunning = true;
  }
  // Når Jordfugtighedsmåleren skal testes bruges dette stykke kode, som måler værdien for hvad der er luft og hvad der er vand
  /*
    int val;
    val = analogRead(A3); //connect sensor to Analog 0
    Serial.println(val); //print the value to serial port
    delay(100);
  */
  // Hvis vandniveauet bliver for lavt vil en flydekontakt blive høj og slukke for pumpen
  if (digitalRead(Niveau_kontakt) == HIGH) { //Start if
    //   Serial.println("Vand niveau er lavt"); // Hvis vand niveauet bliver lavt skrives en besked til serial om at niveauet er lavt
    LowWaterLevel = true;
    TimerOn = false; 
    digitalWrite(Pumpe, HIGH); // Pumpe OFF
    lcd.setCursor(0,0);
    lcd.print("Vand niveau lavt");
  }//End if
  else {
    LowWaterLevel = false;
    lcd.setCursor(0,0);
    lcd.print("                ");
  }

  currentMillis_Soil = millis();
  if ((currentMillis_Soil - previousMillis_Soil) >= 60000 or FirstScan == true) { // Måling af jordfugtigheden måles hvert 30 sekund
    previousMillis_Soil = millis();

    float soilMoistureValue_Paletblad = analogRead(A3);
    float input = soilMoistureValue_Paletblad; // Input til udregning af gennemsnits jordfugtighed.
    float output = 0;


    for (int n = 0; n < 10 + 2; n++) // For loop for gennemsnits udregning
    {
      output = movingAverageFilter.process(input); // Udregning af det sammelagte gennemsnits resultat fra jordmåling
    }

    // Måling af jordfugtighed som om dannes til info om jorden er tør, våd eller meget våd som bliver sendt til serial.
    SoilState = Soil_Moisture(output, 1);
    Serial.println(output);
  }




  if (SoilState == 3) {
    SoilState = 0;
    TimerOn = true;
    previousMillis_TimerPump = millis();
  }

  if (TimerOn == true and LowWaterLevel == false) {
    currentMillis_TimerPump = millis();
    digitalWrite(Pumpe , LOW);
   // Serial.println("Pumpe ON");
    if ((currentMillis_TimerPump - previousMillis_TimerPump) >= 2000) {
      TimerOn = false;
      digitalWrite(Pumpe, HIGH);
     // Serial.println("Pumpe OFF");
    }

  }



  // Måling og udregning af temperaturen. Temperaturen kan visses med decimaltal.

  currentMillis_Temp = millis();
  if ((currentMillis_Temp - previousMillis_Temp) >= 1000 or FirstScan == true) { // Temperaturen opdateres hvert sekund i stedet for hvert scan, så står skræmen ikke og flimere
    previousMillis_Temp = millis();
    TempVal = analogRead(tempPin);
    float Voltage = TempVal * (5000 / 1024.0); // Udregning af spændnings opløsning. Da refferencen er 5V(5000mV) og det analog input bruger 10 bit som også svare til 0 - 1023.
    float cel = Voltage / 10; // Når temperaturen stiger 1 grad celsius stiger spændingen med 10 mV og ved 25 grader celsius er spændingen 250 mV.

    // Temperaturen skrives til LCD skræmen
    if ( cel !=  NuvaerendeTemp) {
      NuvaerendeTemp = cel;
      lcd.setCursor(14, 2);
      lcd.print(cel);
      lcd.print("C°");

      Serial.print("TEMPRATURE = ");
      Serial.print(cel);
      Serial.print("C°");
      Serial.println();
    }
  }



  FirstScan = false;
}

I do not have a complete diagram, but i’ve used a relaymodul to turn the pump ON/OFF and a capasitive soil moistoresensor to messure the moistore in the soil
My wirering diagram is a picture of the PCB.
projekt.pdf (144.0 KB)

123

Good.
Check the math when millis() is used. Does it work well when millis() wrapps around to zero? Note that unsigned long is never negative.

#include <ESP32Time.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h" // include the connection infor for WiFi and MQTT
#include "sdkconfig.h" // used for log printing
#include "esp_system.h"
#include "freertos/FreeRTOS.h" //freeRTOS items to be used
#include "freertos/task.h"
#include <driver/adc.h>
#include <SimpleKalmanFilter.h>
////
WiFiClient      wifiClient; // do the WiFi instantiation thing
PubSubClient    MQTTclient( mqtt_server, mqtt_port, wifiClient ); //do the MQTT instantiation thing
ESP32Time       rtc;
////
#define evtDoParticleRead  ( 1 << 0 ) // declare an event
#define evtADCreading      ( 1 << 3 )
EventGroupHandle_t eg; // variable for the event group handle
////
SemaphoreHandle_t sema_MQTT_KeepAlive;
SemaphoreHandle_t sema_mqttOK;
////
QueueHandle_t xQ_RemainingMoistureMQTT;
QueueHandle_t xQ_RM;
QueueHandle_t xQ_Message;
////
struct stu_message
{
  char payload [150] = {'\0'};
  String topic;
} x_message;
////
int    mqttOK = 0;
bool   TimeSet = false;
bool   manualPumpOn = false;
////
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
////
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  // clear locations
  memset( x_message.payload, '\0', 150 );
  x_message.topic = ""; //clear string buffer
  x_message.topic = topic;
  int i = 0;
  for ( i; i < length; i++)
  {
    x_message.payload[i] = ((char)payload[i]);
  }
  x_message.payload[i] = '\0';
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
void setup()
{
  x_message.topic.reserve(150);
  //
  xQ_Message = xQueueCreate( 1, sizeof(stu_message) );
  xQ_RemainingMoistureMQTT = xQueueCreate( 1, sizeof(float) ); // sends a queue copy
  xQ_RM = xQueueCreate( 1, sizeof(float) );
  //
  eg = xEventGroupCreate(); // get an event group handle
  //
  sema_mqttOK =  xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  //
  gpio_config_t io_cfg = {}; // initialize the gpio configuration structure
  io_cfg.mode = GPIO_MODE_INPUT; // set gpio mode. GPIO_NUM_0 input from water level sensor
  io_cfg.pull_down_en = GPIO_PULLDOWN_ENABLE; // enable pull down
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_0) ); //bit mask of the pins to set, assign gpio number to be configured
  gpio_config(&io_cfg); // configure the gpio based upon the parameters as set in the configuration structure
  //
  io_cfg = {}; //set configuration structure back to default values
  io_cfg.mode = GPIO_MODE_OUTPUT;
  io_cfg.pin_bit_mask = ( 1ULL << GPIO_NUM_4 | (1ULL << GPIO_NUM_5) ); //bit mask of the pins to set, assign gpio number to be configured
  gpio_config(&io_cfg);
  gpio_set_level( GPIO_NUM_4, LOW); // deenergize relay module
  gpio_set_level( GPIO_NUM_5, LOW); // deenergize valve
  // set up A:D channels  https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);// using GPIO 39
  //
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 10000, NULL, 6, NULL, 1 );
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 10000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fPublish, "fPublish", 9000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fReadAD, "fReadAD", 9000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fDoMoistureDetector, "fDoMoistureDetector", 70000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 3000, NULL, 2, NULL, 1 );
} //void setup()
////
void fReadAD( void * parameter )
{
  float    ADbits = 4096.0f;
  float    uPvolts = 3.3f;
  float    adcValue_b = 0.0f; //plant in yellow pot
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  float    WetValue = 1.07f; // value found by putting sensor in water
  float    DryValue = 2.732f; // value of probe when held in air
  float    Range = DryValue - WetValue;
  float    RemainingMoisture = 100.0f;
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtADCreading, pdTRUE, pdTRUE, portMAX_DELAY ); //
    adcValue_b = float( adc1_get_raw(ADC1_CHANNEL_3) ); //take a raw ADC reading
    adcValue_b = ( adcValue_b * uPvolts ) / ADbits; //calculate voltage
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue_b = KF_ADC_b.updateEstimate( adcValue_b ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    RemainingMoisture = 100.0f * (1 - ((adcValue_b - WetValue) / (DryValue - WetValue))); //remaining moisture =  1-(xTarget - xMin) / (xMax - xMin) as a percentage of the sensor wet dry volatges
    xQueueOverwrite( xQ_RM, (void *) &RemainingMoisture );
    //log_i( "adcValue_b = %f remaining moisture %f%", adcValue_b, RemainingMoisture );
  }
  vTaskDelete( NULL );
}
////
void fPublish( void * parameter )
{
  float  RemainingMoisture = 100.0f;
  for (;;)
  {
    if ( xQueueReceive(xQ_RemainingMoistureMQTT, &RemainingMoisture, portMAX_DELAY) == pdTRUE )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.publish( topicRemainingMoisture_0, String(RemainingMoisture).c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
  } // for (;;)
  vTaskDelete( NULL );
} //void fPublish( void * parameter )
////
void WaterPump0_off()
{
  gpio_set_level( GPIO_NUM_4, LOW); //denergize relay module
  vTaskDelay( 1 );
  gpio_set_level( GPIO_NUM_5, LOW); //denergize/close valve
}
////
void WaterPump0_on()
{
  gpio_set_level( GPIO_NUM_5, HIGH); //energize/open valve
  vTaskDelay( 1 );
  gpio_set_level( GPIO_NUM_4, HIGH); //energize relay module
}
////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 85000; // get another reading when = UpdateTimeTrigger
  int maxNonMQTTresponse = 12;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 5000; //delay for mS
  for (;;)
  {
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY ); // update mqttOK
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
} //void fmqttWatchDog( void * paramater )
////
void fDoMoistureDetector( void * parameter )
{
  //wait for a mqtt connection
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  int      TimeToPublish = 5000000; //5000000uS
  int      TimeForADreading = 100 * 1000; // 100mS
  uint64_t TimePastPublish = esp_timer_get_time(); // used by publish
  uint64_t TimeADreading   = esp_timer_get_time();
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 10; //delay for 10mS
  float    RemainingMoisture = 100.0f; //prevents pump turn on during start up
  bool     pumpOn = false;
  uint64_t PumpOnTime = esp_timer_get_time();
  int      PumpRunTime = 11000000;
  uint64_t PumpOffWait = esp_timer_get_time();
  uint64_t PumpOffWaitFor = 60000000; //one minute
  float    lowMoisture = 23.0f;
  float    highMoisture = 40.0f;
  for (;;)
  {
    //read AD values every 100mS.
    if ( (esp_timer_get_time() - TimeADreading) >= TimeForADreading )
    {
      xEventGroupSetBits( eg, evtADCreading );
      TimeADreading = esp_timer_get_time();
    }
    xQueueReceive(xQ_RM, &RemainingMoisture, 0 ); //receive queue stuff no waiting
    //read gpio 0 is water level good. Yes: OK to run pump : no pump off.   remaining moisture good, denergize water pump otherwise energize water pump.
    if ( RemainingMoisture >= highMoisture ) 
    {
      WaterPump0_off();
    }
    if ( !pumpOn )
    {
      log_i( "not pump on ");
      if ( gpio_get_level( GPIO_NUM_0 ) )
      {
        if ( RemainingMoisture <= lowMoisture )
        {
          //has one minute passed since last pump energize, if so then allow motor to run
          if ( (esp_timer_get_time() - PumpOffWait) >= PumpOffWaitFor )
          {
            WaterPump0_on();
            log_i( "pump on " );
            pumpOn = !pumpOn;
            PumpOnTime = esp_timer_get_time();
          }
        }
        //xSemaphoreGive( sema_RemainingMoisture );
      } else {
        log_i( "water level bad " );
        WaterPump0_off();
        PumpOffWait = esp_timer_get_time();
      }
    } else {
      /*
         pump goes on runs for 5 seconds then turn off, then wait PumpOffWaitTime before being allowed to energize again
      */
      if ( (esp_timer_get_time() - PumpOnTime) >= PumpRunTime )
      {
        log_i( "pump off " );
        WaterPump0_off(); // after PumpRunTime  turn pump off
        pumpOn = !pumpOn;
        PumpOffWait = esp_timer_get_time();
      }
    }
    // publish to MQTT every 5000000uS
    if ( (esp_timer_get_time() - TimePastPublish) >= TimeToPublish )
    {
      xQueueOverwrite( xQ_RemainingMoistureMQTT, (void *) &RemainingMoisture );// data for mqtt publish
      TimePastPublish = esp_timer_get_time(); // get next publish time
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
}// end fDoMoistureDetector()
////
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 250; // 250mS
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
  // create client ID from mac address
  byte mac[5];
  int count = 0;
  WiFi.macAddress(mac); // get mac address
  String clientID = String(mac[0]) + String(mac[4]);
  log_i( "connect to mqtt as client %s", clientID );
  while ( !MQTTclient.connected() )
  {
    MQTTclient.disconnect();
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
    vTaskDelay( 250 );
    count++;
    if ( count == 5 )
    {
      ESP.restart();
    }
  }
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( topicOK );
}
////
void connectToWiFi()
{
  int TryCount = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 4000 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
} // void connectToWiFi()
//////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      if ( px_message.topic == topicOK )
      {
        xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
        mqttOK = 0; // clear mqtt ok count
        xSemaphoreGive( sema_mqttOK );
      }
      if ( !TimeSet )
      {
        String temp = "";
        temp = px_message.payload[0];
        temp += px_message.payload[1];
        temp += px_message.payload[2];
        temp += px_message.payload[3];
        int year =  temp.toInt();
        temp = "";
        temp = px_message.payload[5];
        temp += px_message.payload[6];
        int month =  temp.toInt();
        temp = "";
        temp = px_message.payload[8];
        temp += px_message.payload[9];
        int day =  temp.toInt();
        temp = "";
        temp = px_message.payload[11];
        temp += px_message.payload[12];
        int hour =  temp.toInt();
        temp = "";
        temp = px_message.payload[14];
        temp += px_message.payload[15];
        int min =  temp.toInt();
        rtc.setTime( 0, min, hour, day, month, year );
        log_i( "%s  ", rtc.getTime() );
        TimeSet = true;
      }
    }
  } //for(;;)
  vTaskDelete ( NULL );
} // void fparseMQTT( void *pvParameters )
////
void loop() {}

The code, broken up into independently running tasks that I use, hopefully you can use it as a model for your own system.

I run the pump for PumpRunTime. I am fine with leaving my house for weeks with the above code.

I use both a motor to pump the water and a solenoid valve energized by a relay. The solenoid is used to stop the siphon formed when the pump is moving water.

Suggest you break you code up into tasks as shown in this tutorial Multi-tasking in Arduino
I use small class to handle the timers/delays see How to write Timers and Delays in Arduino
One thing to note is that it is not a good idea encase timer tests in other logical conditions.
Keep the timers separate and set flags to drive other code as needed.

As i understand millis is an unsigned long so when it overflows it goes back to zero. But that havent happend yet. I think its only been like 3 weeks its been running with no trouble

This should be a clue. If the problem coincided with the pump turning on you likely had a power supply disturbance/transient when the pump started that crashed the Arduino.

More details of the wiring, power supply and motor etc would help. At the very least consider enabling and using the hardware watchdog timer to handle such cases.

1 Like

Wauw :blush: , thanks for all the replies. Ive got some stuff to read about and understand that might help me. If not il make a full diagram for the hole system :smile:

il look into that :smiley:

I don’t remember how many days hex FFFFFFFFH gives. I’ll make code snippet later to find out.

I say You have 49 day and night until wrap around. Test code below.

[code]
unsigned long longTest;
void setup() {
  Serial.begin(115200);
  longTest = 0;
  longTest--;// Force a wrap around backwards.
//                        sec     min   tim    dagar
  Serial.println(longTest / 1000L / 60L / 60L / 24L);
  Serial.println(longTest / 1000L / 60L / 60L);
  Serial.println(longTest / 1000L / 60L );
  Serial.println(longTest / 1000L );
}

void loop() {

  // put your main code here, to run repeatedly:

}
[/code]

Hi, @matti1999
PCB pattern images;

Your PCB is double sided, please make each layer a different colour, RED and Blue.

Tom… :grinning: :+1: :coffee: :australia:

Please post a schematic, I’m surprised you don’t as you have produced a PCB design, even if it is an image of a hand drawing of your project.

Tom… :grinning: :+1: :coffee: :australia:

And to emphasise what @Blackfin says: use the watchdog timer. Any system which needs to run for long periods unattended should be made resilient to crashes and at least attempt to recover automatically.

I have a commercial project using ESP32 that needs to recover from power failures (and reboots)
It has an RTC (real time clock) for the time keeping and an SD card that stores the current state every time it changes (and every 10mins in my case)
Depending on the life of the project and the number of state changes you could look at using EEPROM instead of the SD card.
The code does an auto reboot every 24hrs to clear up any memory leaks due to the https code

I just watched a video about the watchdog timer. I can set it to a time that it will do an interrupt or a reset or both. Then I can save variables in the Eeprom when the watchdog timer interrupt reset occurs.

What happens to the millis will it go back to zero, when the cpu resets? Thats what i think it will.
Can i use the watchdog timer to only happen if the code gets stuck?

when L.O.P. (loss of power) millis() starts counting all over again.

Better to find where/why the code gets stuck.