Published: February 06, 2021, Edited by: Nicolas Padfield

Plant monitoring / digital farming with HiGrow ESP32

For checking up on our plants, vertical farming walls etc. during corona, we have a monitoring system based on the HiGrow ESP32 capacitative soil moisture measuring system.

sand  mulch/soil
Dry 2747 2958
Slight moisture   2501 2731
Moist 2108 2362
Very moist 1636 1856
Wet/soaking 1513 1488

Source

Here is the code:
/*

  Code for Higrow to read the temp & RH sensor and call a URL on a server with all data

  Reads DHT11/22 temp & rh sensors from an ESP32 using multitasking.
  This example depends on the ESP32Ticker library to wake up the task every 20 seconds. Please install Ticker-esp32 library

  Thanks:
  lucafabbri for original highgrow board and code
  https://github.com/lucafabbri/HiGrow-Arduino-Esp/blob/master/HiGrowEsp32/HiGrowEsp32.ino

  jenschr updated simpler code
  https://gist.github.com/jenschr/711899fe286ba5a63c044191b63813ca

  https://flashgamer.com/blog/comments/review-of-higrow-esp32-moisture-and-temperature-sensor

*/


#define UNITNAME "Plant%20soil%2002" // remember no spaces. Spaces may be replaced with URLencoded %20 

//#define UPDATEINTERVAL 60000 // how often to connect on the internet and update data. 60000 = once a minute 3600000 = once an hour
#define UPDATEINTERVAL 3600000
#define DISPLAYPLACE 1200
#define UNIT "Soil%20cap"
#define MAXDOWN 14400
#define SERVERNAME "serverName"
#define ALARMDRYTHRESHOLD 2450
#define ALARMWETTHRESHOLD 1700


// credentials are stored in include file credentials.h , must look like this:
//#define WIFIPASS "secretPassword"
//#define WIFISSID "theSsid"

//#define REALM "theRealm"
//#define SRVPASS "theRealmPassword"

#include "DHTesp.h"
#include "Ticker.h"
#include <HTTPClient.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include "credentials.h"

WiFiMulti wifiMulti;

int waterlevel = 0;
unsigned long updateTimer = 0;
float temp = 0;
float rh = 0;
float dewPoint = 0;
unsigned long dat = 0;
char url[512]; // character buffer for storing URL to get in
char dataText[1024]; // character buffer for storing text in
char text[1024]; // character buffer for storing text in
unsigned int alarmstatus = 0;
char netStatus[512]; // buffer for storing the network status in
long last_display_update_millis = 0;
boolean toggle = 0;
int disconnected_seconds = 0;

HTTPClient http;

DHTesp dht;

const int LEDPIN = 16;
const int SOILPIN = 32;
int dhtPin = 22;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */

/**
   initTemp
   Setup DHT library
   Setup task and timer for repeated measurement
   @return bool
      true if task and timer are started
      false if task or timer couldn't be started
*/
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
  dht.setup(dhtPin, DHTesp::DHT11);
  Serial.println("DHT initiated");

  // Start task to get temperature
  xTaskCreatePinnedToCore(
    tempTask,                       /* Function to implement the task */
    "tempTask ",                    /* Name of the task */
    4000,                           /* Stack size in words */
    NULL,                           /* Task input parameter */
    5,                              /* Priority of the task */
    &tempTaskHandle,                /* Task handle. */
    1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every 20 seconds
    tempTicker.attach(20, triggerGetTemp);
  }
  return true;
}

/**
   triggerGetTemp
   Sets flag dhtUpdated to true for handling in loop()
   called by Ticker getTempTimer
*/
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
    xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
   Task to reads temperature from DHT11 sensor
   @param pvParameters
      pointer to task parameters
*/
void tempTask(void *pvParameters) {
  Serial.println("tempTask loop started");
  while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
      getTemperature();
    }
    // Go to sleep again
    vTaskSuspend(NULL);
  }
}

/**
   getTemperature
   Reads temperature from DHT11 sensor
   @return bool
      true if temperature could be aquired
      false if aquisition failed
*/

bool getTemperature() {
  // Reading temperature for humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();

  // Check if any reads failed and exit early (to try again).
  if (dht.getStatus() != 0) {
    Serial.println("DHT11 error status: " + String(dht.getStatusString()));
    return false;
  }

  temp = newValues.temperature;
  rh = newValues.humidity;
  dewPoint = dht.computeDewPoint(newValues.temperature, newValues.humidity);
  waterlevel = analogRead(SOILPIN);

  Serial.print("Soil wetness: ");
  Serial.print(waterlevel);
  Serial.println(" Temp C: " + String(temp) + " Humidity %RH: " + String(rh) + " Dewpoint: " + String(dewPoint));

  sprintf(dataText, "Temp%%20%.1f%%20Humidity%%20RH%%20%.1f%%20Dewpoint%%20%.1f",
          temp,
          rh,
          dewPoint
         );

  //text = " Temp C: " + String(temp) + " Humidity %RH: " + String(rh) + " Dewpoint: " + String(dewPoint);

  return true;
}

void displayUpdate() {
  netStatus[0] = '\0';
  sprintf(netStatus, "%s %s %s %ddBm %i %s",
          WIFISSID,
          WiFi.macAddress().c_str(),
          WiFi.localIP().toString().c_str(),
          WiFi.RSSI(),
          WiFi.status(),
          UNITNAME
         );

  Serial.println(netStatus);

}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("Boot");
  initTemp();
  wifiMulti.addAP(WIFISSID, WIFIPASS);
  // Signal end of setup() to tasks
  tasksEnabled = true;
}

void loop() {

  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
      vTaskResume(tempTaskHandle);
    }
  }
  yield();

  if (wifiMulti.run() == WL_CONNECTED) { // keep wifimulti connected and happy

  }

  if (millis() - last_display_update_millis > 1000) { // update display/serial debugging
    last_display_update_millis = millis();

    displayUpdate();
    digitalWrite(LEDPIN, toggle);

    if (WiFi.status() == WL_CONNECTED) { // flash led if connected to wifi
      toggle = !toggle;
      disconnected_seconds = 0;
    }
    else {
      toggle = 0; // turn led fixed on if not connected to wifi
      disconnected_seconds++; // count how many seconds we have been disconnected from wifi
    }

  }


  if (disconnected_seconds > 30) { // if we have been disconnected for a while, reboot the whole processor
    ESP.restart();
  }


  //------- function specific

  // dry warning
  if (waterlevel > ALARMDRYTHRESHOLD) {

    sprintf(text, "%s%s",
            "Warning%20soil%20dry%20",
            dataText
           );
    alarmstatus = 2;
  }
  else if (waterlevel < ALARMWETTHRESHOLD) {
    // wet warning
    sprintf(text, "%s%s",
            "Warning%20soil%20too%20wet%20",
            dataText
           );
    alarmstatus = 2;
  }
  else {
    // ok, no warning

    sprintf(text, "%s%s",
            "Soil%20probably%20ok%20",
            dataText
           );
    alarmstatus = 0;
  }


  if (millis() - updateTimer > UPDATEINTERVAL || updateTimer == 0) // do something every x milliseconds)
  {

    statusUpdate(); // do it!

    updateTimer = millis();
  }


  /*
    int waterlevel = analogRead(SOILPIN);
    Serial.print("waterlevel: ");
    Serial.println(waterlevel);
  */
}


void statusUpdate() {

  dat = waterlevel;

  Serial.print("[HTTP] begin...\n");

  sprintf(url, "%s?realm=%s&pass=%s&device=%s&dpos=%i&data=%i&unit=%s&alarmlevel=%i&descr=%s&maxdown=%i",
          SERVERNAME,
          REALM,
          SRVPASS,
          UNITNAME,
          DISPLAYPLACE,
          dat, //data
          UNIT, // unit
          alarmstatus, // alarmlevel
          text, // descr
          MAXDOWN // maxdown
         );


  http.begin(url);
  //http.begin("https://www.howsmyssl.com/a/check");

  Serial.print("[HTTP] GET...\n");
  Serial.println(url);
  // start connection and send HTTP header
  int httpCode = http.GET();

  // httpCode will be negative on error
  if (httpCode > 0) {
    // HTTP header has been send and Server response header has been handled
    Serial.printf("[HTTP] GET... code: %d\n", httpCode);

    // file found at server
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println(payload);
    }
  } else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end();

} // end statusUpdate