Pedro D Sanchez Amaro
Published © MIT

Smart Plant Watering System

Take care of watering your plants by monitoring the moisture in the soil. You can also check temperature, humidity, moisture, air quality.

IntermediateFull instructions provided8 hours150
Smart Plant Watering System

Things used in this project

Story

Read more

Schematics

Smart Plant Watering diagram

Code

Smart Plant Watering System

C/C++
For Paricle Argon
/*
 * Project PlantWateringSystem
 * Description: Smart Plant Watering System
 * Author: Pedro Sanchez
 * Date: 9-Nov-2021 
 */

#include <Adafruit_MQTT.h>

#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_GFX_RK.h"
#include "Adafruit_SSD1306_RK.h"
#include "Adafruit_BME280.h"
#include "JsonParserGeneratorRK.h"
#include "Grove_Air_quality_Sensor.h"

#include "IoT_Timer.h"
#include "credentials.h"


#define OLED_RESET D4
Adafruit_SSD1306 display(OLED_RESET);  // I2C oled display
Adafruit_BME280 bme;                   // I2C temp_pressure_humidity

TCPClient TheClient;

// Setup the MQTT client class by passing in 
// the WiFi client and MQTT server and login details.
Adafruit_MQTT_SPARK mqtt(&TheClient, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

// Setup Feeds to publish or subscribe
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>

// Publis feeds
Adafruit_MQTT_Publish TempWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/temperaturePWS");
Adafruit_MQTT_Publish HumidityWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/humidityPWS");
Adafruit_MQTT_Publish PressureWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/pressurePWS");
Adafruit_MQTT_Publish AirQualityWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/airqualityPWS");
Adafruit_MQTT_Publish AirQualityValWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/airqualityvalPWS");
Adafruit_MQTT_Publish MoistureWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/moisturePWS");
Adafruit_MQTT_Publish DustWPS = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/dustPWS");
// Subscribe feeds
Adafruit_MQTT_Subscribe ButtonWPS = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/buttonWPS");

String DateTime, TimeOnly;

int value1 = 0;
int value2 = 0;

int myButton;
bool onOff = false;

const int MOIST_PIN      = A1;
const int BME290_addr    = 0x76;
const int OLED_addr      = 0x3C;
const int PUMP_PIN       = 11;
const int DUST_SENS_PIN  = A3;
const int AQS_PIN        = A2;

AirQualitySensor aqSensor(AQS_PIN);

const int DUST_SENSOR_READ = 30000; // every 30 sec

int moist; // moisture var

float tempF, humidRH, pressurePas;  // temp and humidity vars

String airQuality;     // airquality string message var
int airQualityVal;

// Dust vars
unsigned long lastInterval;
unsigned long lowpulseoccupancy = 0;
unsigned long lowpulseoccupancyLast = 0;

unsigned long last_lpo = 0;
unsigned long duration;
float ratio = 0;
float concentration = 0;

// Timer vars
IoT_Timer checkMoisture;
IoT_Timer pingMQQT;
IoT_Timer readDustSensor;
IoT_Timer writeToCloud;

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup()
{
  pinMode(MOIST_PIN, INPUT);  // Moisture device mode
  pinMode(D7, OUTPUT);        // Argon built-in LED mode
  pinMode(PUMP_PIN, OUTPUT);  // PUMP mode
  pinMode(DUST_SENS_PIN, INPUT); // Dust Sensor Input mode

  Serial.begin(9600);
  waitFor(Serial.isConnected, 15000); //wait for Serial Monitor to startup

  bme280Setup();  // Initialize bme280
  displaySetup(); // Initialize SSD1306 display
  aqsSetup();     // Initialize Air Quality Sensor

  Time.zone(-6);         // CST = -6   CDT = -5 ( Austin, Texas )
  Particle.syncTime();   // Get time fron Particle cloud
  

  //Connect to WiFi without going to Particle Cloud
  WiFi.connect();
  while (WiFi.connecting())
  {
    Serial.printf(".");
  }

  Serial.printf("\n\nStarted Smart Plant Watering System V1.0\n");
  // Setup MQTT subscription for onoff feed.
  mqtt.subscribe(&ButtonWPS);

  pingMQQT.startTimer(100);       // first time check immediately
  checkMoisture.startTimer(6000); // first time check immediately
  readDustSensor.startTimer(DUST_SENSOR_READ);
  writeToCloud.startTimer(14000); // Every 14 seconds
}

void loop()
{
  DateTime = Time.timeStr(); 
  TimeOnly = DateTime.substring(11, 19);

  MQTT_connect(); 
  keepAliveMQQT();    
  autoWaterPlant();
  duration = pulseIn(DUST_SENS_PIN, LOW);
  lowpulseoccupancy = lowpulseoccupancy + duration;
  airQuality = getAirQuality();
  readDustLevel();

  if (writeToCloud.isTimerReady()) { // every 14 secs, values are published to the cloud
    tempF = (bme.readTemperature() * (9 / 5.0)) + 32; //deg F
    humidRH = bme.readHumidity();                     // %RH
    pressurePas = bme.readPressure();
    Serial.printlnf("Air Quality is : %s", airQuality.c_str());
    if (mqtt.Update()) {
      // moist is calculates every 6 seconds, use last value
      // dust is read every 30 seconds, use last value: lowpulseoccupancyLast
      // Publish data to feeds
      TempWPS.publish(tempF);
      HumidityWPS.publish(humidRH);
      PressureWPS.publish(pressurePas);
      AirQualityWPS.publish(airQuality);
      AirQualityValWPS.publish(airQualityVal);
      MoistureWPS.publish(moist);
      DustWPS.publish(concentration);

      writeToCloud.startTimer(14000); // Write to cloud every 14 seconds;
    }
  }

  // 'Wait for incoming subscription packets' busy subloop
  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(1000)))
  {
    if (subscription == &ButtonWPS)
    {
      value2 = atoi((char *)ButtonWPS.lastread);
      Serial.printf("Received %i from Adafruit.io feed ButtonOnOff \n", value2);
      if (value2 == 0)
      {
        onOff = !onOff;
        turnLED(onOff);  // Turn on or Off =>  D7 led
        waterThePlant(); // Autowater the plant
      }
    }
  }
}

void readDustLevel() {
  if (readDustSensor.isTimerReady()) {
    getDustSensorReadings();
    lowpulseoccupancyLast = lowpulseoccupancy;
    lowpulseoccupancy = 0;
    readDustSensor.startTimer(DUST_SENSOR_READ); 
  }
}

void getDustSensorReadings() {
  if (lowpulseoccupancy == 0) {
    lowpulseoccupancy = last_lpo;
  }
  else {
    last_lpo = lowpulseoccupancy;
  }
  ratio = lowpulseoccupancy / (DUST_SENSOR_READ * 10.0);
  concentration = 1.1 * pow(ratio, 3) - 3.8 * pow(ratio, 2) + 520 * ratio + 0.62;
  Serial.printf("LPO: %li\n", lowpulseoccupancy);
  Serial.printf("Ratio: %f%%\n", ratio);
  Serial.printf("Concentration: %f pcs/L\n", concentration);
}


void autoWaterPlant()
// It also updates the OLED information
{
  if (checkMoisture.isTimerReady()) {
    moist = readDisplayMoisture();
    if (moist > 2850) {
      waterThePlant();
    }
    checkMoisture.startTimer(6000);

    updateOLED();
  }
}

void waterThePlant() {
  Serial.printf("Turn on the PUMP\n");
  digitalWrite(PUMP_PIN, HIGH);
  delay(500);
  digitalWrite(PUMP_PIN, LOW);
  delay(2000);
}

void keepAliveMQQT()
{
  // Ping MQTT Broker every 2 minutes to keep connection alive
  if (pingMQQT.isTimerReady()) {
    Serial.printf("Pinging MQTT \n");
    if (!mqtt.ping()) {
      Serial.printf("Disconnecting \n");
      mqtt.disconnect();
    }
    pingMQQT.startTimer(120000);
  }
}

String getAirQuality()
{
  int quality = aqSensor.slope();
  String qual = "None";

  airQualityVal = aqSensor.getValue();
  Serial.printf("Air quality Sensor value: %i\n", airQualityVal);

  if (quality == AirQualitySensor::FORCE_SIGNAL) {
    qual = "Danger";
  }
  else if (quality == AirQualitySensor::HIGH_POLLUTION) {
    qual = "High Pollution";
  }
  else if (quality == AirQualitySensor::LOW_POLLUTION){
    qual = "Low Pollution";
  }
  else if (quality == AirQualitySensor::FRESH_AIR) {
    qual = "Fresh Air";
  }
  return qual;
}

int readDisplayMoisture() {
  int moist;
  moist = analogRead(MOIST_PIN);  
  Serial.printf("Moist Reading = %i   Temp: %0.2f F  Humidity: %0.2f \n", moist, tempF, humidRH);
  return moist;
}

void updateOLED() {
  display.setCursor(3, 0);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.printf("%s\n", DateTime.c_str());
  display.printf("Moisture: %i\n", moist);
  display.printf("Temp: %0.2f F\n", tempF);
  display.printf("Humidity: %0.2f \n", humidRH);
  display.printf("Dust Conc.: %0.2f \n", concentration);
  display.printf("Air Quality: %s \n", airQuality.c_str());
  display.display();
}

void bme280Setup() {
  bool status = bme.begin(BME290_addr);
  if (status == false) {
  Serial.printf("BME280 at address 0x%02X failed to start", BME290_addr); }
}

void displaySetup() {
  display.begin(SSD1306_SWITCHCAPVCC, OLED_addr);
  display.display();
  delay(2000);
  display.clearDisplay();

  display.drawPixel(10, 10, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();
  Serial.printf("Started Moisture Reading\n");
}

void aqsSetup() {
  if (aqSensor.init()) {
    Serial.println("Air Quality Sensor ready.\n");
  }
  else {
    Serial.println("Air Quality Sensor ERROR!\n");
  }
}

void turnLED(bool onOff)
{
  if (onOff) {
    digitalWrite(D7, HIGH);
  }
  else {
    digitalWrite(D7, LOW);
  }
}

// Function to connect and reconnect as necessary to the MQTT server.
void MQTT_connect()
{
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }

  Serial.print("Connecting to MQTT... ");

  while ((ret = mqtt.connect()) != 0)
  { // connect will return 0 for connected
    Serial.printf("%s\n", (char *)mqtt.connectErrorString(ret));
    Serial.printf("Retrying MQTT connection in 5 seconds..\n");
    mqtt.disconnect();
    delay(5000); // wait 5 seconds
  }
  Serial.printf("MQTT Connected!\n");
}

// void myPayload(data myData)
// {
//   JsonWriterStatic<256> jw;
//   {
//     JsonWriterAutoObject obj(&jw);
//     jw.insertKeyValue("value1", myData.value1);
//     jw.insertKeyValue("value2", myData.value2);
//   }

//   Serial.printf("Publishing value1 = %i, value2 = %i \n",
//                 myData.value1, myData.value2);
//   mqttMoistureVal.publish(jw.getBuffer());
// }

Credits

Pedro D Sanchez Amaro

Pedro D Sanchez Amaro

2 projects • 1 follower

Comments