@merlynb (merlynb) posted a new topic, subject "Irrigation system time management". It read:

   Hi,
    
    I managed to replicate and somewhat to modify (for GSM and 3G) an existing project however I have very limited coding knowledge and I need help in properly organizing irrigation procedure for my project.  
    This is the project in question:
    
    Instructables
    
    ### Automatic Gardening System With NodeMCU and Blynk, the
    
    Automatic Gardening System With NodeMCU and Blynk, the "ArduFarmBot 2": Some time ago, we published here a similar project: "ArduFarmBot: Controlling a Tomato Home Farm using Arduino and IoT". On this new version, the ArduFarmBot 2, the main...
    
    Github:
    
    github.com
    
    ### Mjrovai/ArduFarmBot-2
    
    master/ArduFarmBot2_Ext_Auto_Ctrl__V3_0
    
    Automatic Gardening System with NodeMCU and Blynk. Contribute to Mjrovai/ArduFarmBot-2 development by creating an account on GitHub.
    
    The project was designed for watering a small container (flower pot) and currently the pump switches on for 15 secs when soil moisture is below 66% (as defined in stationDefines.h) .
    [code] 
        	#define DRY_SOIL      66
        	#define WET_SOIL      85
        	#define COLD_TEMP     12
        	#define HOT_TEMP      22
        	#define TIME_PUMP_ON  15
        	#define TIME_LAMP_ON  15
        	
        
        Then soil moisture is measured again and if less than 66%  turn the pump switches on and so on until soil moisture is more than 66%.
        In stationDefines.h there is maximum soil moisture definition (85%) but nowhere used in the code.
        The modifications I need to make for my field drip irrigation system are the following:
        1/ I need the watering to start when soil moisture is below 66% AND to continue UNTIL soil moisture reaches or exceeds 85%.
        2/ Currently the soil moisture sensor takes 3 measurements and displays the average ( getSensors.ino ) which is somewhat problematic when values reach the low value (multiple turn on/offs). This has to be avoided so I need some modification that will produce stable results and no random switching. 
        For example if for n hours the average sensor value is below 66% THEN the watering to start.  This doesn't apply for the top value (85%) as it could cause overwatering. The time there should be minutes. Avoiding multiple/random turn on/offs applies though.
        
        ```void getSoilMoisterData(void)
        	{
        	  soilMoister = 0;
        	  digitalWrite (soilMoisterVcc, HIGH);
        	  delay (500);
        	  int N = 3;
        	  for(int i = 0; i < N; i++) // read sensor "N" times and get the average
        	  {
        	    soilMoister += analogRead(soilMoisterPin);   
        	    delay(150);
        	  }
        	  digitalWrite (soilMoisterVcc, LOW);
        	  soilMoister = soilMoister/N; 
        	  soilMoister = map(soilMoister, 600, 0, 0, 100); //LM393 on 5V (+Vin)
        	}
        
        3/ The other thing that I learned was problematic is using the delay function during which the MCU is dead and doesn't take measurements. I guess if the above mods are applied that can be avoided.
    [/code]

To which @paulpaulson (Paul-Paulson) replied

    And the question is?

Then @wildbill (wildbill) said

    Oftentimes in projects like this, it takes a while for the irrigation water to reach the sensor. It may be worth watering briefly and rechecking.

Then @merlynb (merlynb) said

    Thanks for your replies.
    
    > And the question is?
    
    The question is how to organize this cycle in order to achieve the goals I need.
    [code] 
        	{ 
        	  if (soilMoister < DRY_SOIL) 
        	  {
        	    turnPumpOn();
        	  }
        	
        	
        ```void turnPumpOn()
        	{
        	  pumpStatus = 1;
        	  aplyCmd();
        	  delay (TIME_PUMP_ON*1000);
        	  pumpStatus = 0;
        	  aplyCmd();
        	}
        
        > Oftentimes in projects like this, it takes a while for the irrigation water to reach the sensor. It may be worth watering briefly and rechecking.
        
        It's a drip irrigation system and from experience it usually takes an average of 2 hours in order to reach the max moisture level.
    [/code]

To which @wildbill (wildbill) replied

    Your dry and wet readings should make it easy to apply hysteresis and avoid the delays.
    
    if it's dry, turn the pump on and leave it on  
    if it's wet, turn it off.

@Idahowalker (Idahowalker) replied

    What I do is after the pump has run for X number of seconds, then I disallow the pump from running for 1 minute. That way the water has had time to soak into the dirt and get to the sensor.

Then @merlynb (merlynb) said

    > Your dry and wet readings should make it easy to apply hysteresis and avoid the delays.
    > 
    > if it's dry, turn the pump on and leave it on  
    >  if it's wet, turn it off.
    
    Yes, that's what I need.
    
    > What I do is after the pump has run for X number of seconds, then I disallow the pump from running for 1 minute. That way the water has had time to soak into the dirt and get to the sensor.
    
    The drip system nozzles' output is 2 l per hour per plant which means soaking is gradual enough (cone shaped). Adjusting the distance between the sensor and the nozzle and it's depth will be done by experiment.

So @paulpaulson (Paul-Paulson) replied

    Hello  
    go and check the IPO model for your project. Design a time handler and evetuell a FSM to run the irrigation system.
    
    en.wikipedia.org
    
    ### IPO model
    
    The input–process–output (IPO) model, or input-process-output pattern, is a widely used approach in systems analysis and software engineering for describing the structure of an information processing program or other process. Many introductory programming and systems analysis texts introduce this as the most basic structure for describing a process. A computer program is useful for another sort of process using the input-process-output model receives inputs from a user or other source, does s...
    
    One data struct() containing all relevant data per branch for sensor, pump, timer values would be usefull, too.

@merlynb (merlynb) replied

    Thanks for your suggestion however as I already noted my coding skills are very limited so I was looking more for a copy/paste solution or minor tweaks if possible and/or not too complicated..

@wildbill (wildbill) replied

    You have everything you need already I think. You can read the soilmoisture and turn the pump on. Take the delay out and move the code that turns the pump off into its own function.
    
    Then apply the logic I suggested earlier.

Then @Idahowalker (Idahowalker) said

    For a reference here is the code I am using to water plants:
    [code] 
        #include <ESP32Time.h>
        #include <WiFi.h>
        #include <PubSubClient.h>
        #include "certs.h" // include the connection info 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 5 seconds 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;
              }
              // manual pump control
              if ( str_eTopic == topicPumpState )
              {
                if ( String(strPayload) == "off" )
                {
                  WaterPump0_off();
                  manualPumpOn = false;
                }
                if ( String(strPayload) == "on" )
                {
                  WaterPump0_on();
                  manualPumpOn = true;
                }
              }
            }
          } //for(;;)
          vTaskDelete ( NULL );
        } // void fparseMQTT( void *pvParameters )
        ////
        void loop() {}
        
    [/code]

Then @merlynb (merlynb) said

    wildbill:
    
    > You have everything you need already I think. You can read the soilmoisture and turn the pump on. Take the delay out and move the code that turns the pump off into its own function.
    > 
    > Then apply the logic I suggested earlier.
    
    OK, how about this:
    [code] 
        if (soilMoister < DRY_SOIL) 
        	  {
        	    pumpStatus = 1;
        	    aplyCmd();
        	  }
        if (soilMoister < WET_SOIL)
        	{
        	    pumpStatus = 0;
        	    aplyCmd();
        	  }
        
    [/code]
    
    and uncomment this part:
    [code] 
        void turnPumpOn()
        	{
        	  pumpStatus = 1;
        	  aplyCmd();
        	  delay (TIME_PUMP_ON*1000);
        	  pumpStatus = 0;
        	  aplyCmd();
        	}
        
    [/code]

@wildbill (wildbill) replied

    Nearly. You can get rid of the turnPumpOn function altogether.
    
    Check for greater than the wet value to turn off.

So @Idahowalker (Idahowalker) replied

    The problem I found with waiting for the wet value is that once the wet value is felt at the sensor, more wet value is to follow. I'd stop at 40% wet value, to find, 2 minutes later, the wet value is 60%+.
    
    I found the best way was to wet the plants for x amount of seconds, wait 1 minute, if more wetness was need run pump again for x seconds. Repeat as necessary.
    
    You can see in the code I posted how I wait for 1 minute after running for 11 seconds.

@merlynb (merlynb) replied

    wildbill:
    
    > Check for greater than the wet value to turn off.
    
    Did you mean this:
    [code] 
        if (soilMoister < DRY_SOIL) 
        	  {
        	    pumpStatus = 1;
        	    aplyCmd();
        	  }
        if (soilMoister > WET_SOIL)
        	{
        	    pumpStatus = 0;
        	    aplyCmd();
        	  }
        
    [/code]
    
    > The problem I found with waiting for the wet value is that once the wet value is felt at the sensor, more wet value is to follow. I'd stop at 40% wet value, to find, 2 minutes later, the wet value is 60%+.
    
    I didn't quite get that.
    
    > I found the best way was to wet the plants for x amount of seconds, wait 1 minute, if more wetness was need run pump again for x seconds. Repeat as necessary.
    
    Similar to how the original code works only no upper value is defined.

Then @wildbill (wildbill) said

    That's it.

After which, @Idahowalker (Idahowalker) added

    Idahowalker:
    
    > 
    [code]
    >       if ( RemainingMoisture >= highMoisture )
    >         {
    >           WaterPump0_off();
    >         }
    >     
    [/code]
    
    In the code above, is what I use as an upper value reading not to be exceeded no matter what other task, operation or function is in progress. The check for a maximum watered condition is done once every 10ms.

To which @merlynb (merlynb) replied

    Are you suggesting using
    
    `>= WET_SOIL`
    
    instead of
    
    `> WET_SOIL`
    
    in my code above?

@wildbill (wildbill) replied

    The point being made is that usually, irrigation systems add too much water before the sensors notice. So it makes sense to water a bit and wait to see what effect it had.
    
    Your drip irrigation method doesn't suffer from that issue from what you have said, so you needn't worry about it.

So @merlynb (merlynb) replied

    So does it make sense then to use hysteresis for my moisture sensor?

To which @wildbill (wildbill) replied

    Yes. The code you have now does use it. The pump goes on when moisture falls into the dry zone and doesn't stop until it's wet so you shouldn't get any rapid switching of the pump.

After which, @merlynb (merlynb) added

    OK. Thanks to all who chimed in.  
    Now it's time for some real life experiments and adjustments.

So @system (system) replied

    This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.


{'source': 'discourse', 'url': 'https://forum.arduino.cc/t/irrigation-system-time-management/870098', 'title': 'Irrigation system time management', 'views': 729, 'has_accepted_answer': False, 'word_count': 2743, 'like_count': 0, 'reply_count': 3, 'participant_count': 4, 'posts': 23, 'tags': '', 'author': '@merlynb (merlynb)', 'participants': '@merlynb (merlynb), @paulpaulson (Paul-Paulson), @wildbill (wildbill), @Idahowalker (Idahowalker), @system (system)', 'image_url': ['https://aws1.discourse-cdn.com/arduino/optimized/4X/e/0/3/e03205ab846ff8bbce5ac5a1c27b24ba22073567_2_1024x853.jpeg'], 'created_at': '2021-06-03T11:10:56.624Z', 'last_posted_at': '2021-10-02T20:27:00.104Z'}