Putting It All Together

We’re almost done with the sketch. Just a couple of other minor things to consider, and then we can put this all together.

First, what if the user wants to see the current valve schedule? That’s easy, but how does the user tell us? The user could type the letter P for “Print”, but now the sketch needs to be ready for either the letter P or a number. This is tricky; it would be easier if we always expect the first character to be a letter and then decide whether to expect a number. Let’s say that if the first letter is P, then we print the current settings, and if the first letter is S, then we expect a new setting to follow. If the user types anything other than P or S, we’d better remind them what’s OK to type:

/*
 * Check for user interaction, which will 
 * be in the form of something typed on 
 * the serial monitor. 
 *
 * If there is anything, make sure it's 
 * properly-formed, and perform the 
 * requested action.
 */
void checkUserInteraction() {

  // Check for user interaction
  while (Serial.available() > 0) {

    // The first character tells us what to expect for the 
    // rest of the line
    char temp = Serial.read();

    // If the first character is 'P' 
    // then print the current settings
    // and break out of the while() loop
    if ( temp == 'P') {

      printSettings();
      Serial.flush();
      break;

    } // end of printing current settings

    // If first character is 'S' 
    // then the rest will be a setting
    else if ( temp == 'S') {
      expectValveSetting();
    }

    // Otherwise, it's an error. Remind the user 
    // what the choices are and break out of the 
    // while() loop
    else
    {
      printMenu();
      Serial.flush();
      break;
    }

  } // end of processing user interaction

}c

The following code is the printMenu() function. It’s short, but we might want to use it elsewhere. Also, in my experience, the menu tends to grow as the project becomes more and more complex, so this function is actually a nice way to document the menu within the sketch. For instance, later you might want to add a menu item to set the RTC time:

void printMenu() {
  Serial.println(
    "Please enter P to print the current settings");
  Serial.println(
    "Please enter S2N13:45 to set valve 2 ON time to 13:34");
}
NOTE

Any time a block of code is to be used more than once, it is a good candidate for becoming a function, no matter how short it is.

Finally, Example 8-4 shows the entire sketch.

Example 8-4. The irrigation system sketch
/* Example 8-4. The irrigation system sketch */
 
#include <Wire.h>   // Wire library, used by RTC library
#include "RTClib.h" // RTC library
#include "DHT.h"    // DHT temperature/humidity sensor library

// Analog pin usage
const int RTC_5V_PIN = A3;
const int RTC_GND_PIN = A2;

// Digital pin usage
const int DHT_PIN  = 2;     // temperature/humidity sensor
const int WATER_VALVE_0_PIN = 8;
const int WATER_VALVE_1_PIN = 7;
const int WATER_VALVE_2_PIN = 4;

const int NUMBEROFVALVES = 3; // How many valves we have
const int NUMBEROFTIMES = 2;  // How many times we have

// Array to store ON and OFF times for each valve
// Store this time as the number of minutes since midnight
// to make calculations easier
int onOffTimes [NUMBEROFVALVES][NUMBEROFTIMES];
int valvePinNumbers[NUMBEROFVALVES];

// Which column is ON time and which is OFF time
const int ONTIME = 0;
const int OFFTIME = 1;

#define DHTTYPE DHT11
DHT dht(DHT_PIN, DHTTYPE); // Create a DHT object

RTC_DS1307 rtc;    // Create an RTC object

// Global variables set and used in different functions

DateTime dateTimeNow; // to store results from the RTC

float humidityNow; // humidity result from the DHT11 sensor

void setup(){

  // Power and ground to RTC
  pinMode(RTC_5V_PIN, OUTPUT);
  pinMode(RTC_GND_PIN, OUTPUT);
  digitalWrite(RTC_5V_PIN, HIGH);
  digitalWrite(RTC_GND_PIN, LOW);

  // Initialize the wire library
  #ifdef AVR
    Wire.begin();
  #else
    // Shield I2C pins connect to alt I2C bus on Arduino Due
    Wire1.begin(); 
  #endif

  rtc.begin();        // Initialize the RTC object
  dht.begin();        // Initialize the DHT object
  Serial.begin(9600); // Initialize the Serial object

  // Set the water valve pin numbers into the array
  valvePinNumbers[0] = WATER_VALVE_0_PIN;
  valvePinNumbers[1] = WATER_VALVE_1_PIN;
  valvePinNumbers[2] = WATER_VALVE_2_PIN;
  
  // and set those pins all to outputs
  for (int valve = 0; valve < NUMBEROFVALVES; valve++) {
    pinMode(valvePinNumbers[valve], OUTPUT);
  }

};

void loop() {

  // Remind user briefly of possible commands
  Serial.print("Type 'P' to print settings or ");
  Serial.println("'S2N13:45' to set valve 2 ON time to 13:34");

  // Get (and print) the current date, time, 
  // temperature, and humidity
  getTimeTempHumidity();

  checkUserInteraction(); // Check for request from the user

  // Check to see whether it's time to turn any valve ON or OFF
  checkTimeControlValves();

  delay(5000); // No need to do this too frequently
}


/* Get, and print, the current date, time, 
 * humidity, and temperature
 */
void getTimeTempHumidity() {
  // Get and print the current time
  dateTimeNow = rtc.now();

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running!");
    // Use this to set the RTC to the date and time this sketch
    // was compiled. Use this ONCE and then comment it out
    // rtc.adjust(DateTime(__DATE__, __TIME__));
    return; // if the RTC is not running don't continue
  }

  Serial.print(dateTimeNow.hour(), DEC);
  Serial.print(':');
  Serial.print(dateTimeNow.minute(), DEC);
  Serial.print(':');
  Serial.print(dateTimeNow.second(), DEC);

  // Get and print the current temperature and humidity
  humidityNow = dht.readHumidity();
  float t = dht.readTemperature();     // temperature Celsius
  float f = dht.readTemperature(true); // temperature Fahrenheit

  // Check if any reads failed and exit early (to try again).
  if (isnan(humidityNow) || isnan(t) || isnan(f)) {
    Serial.println("Failed to read from DHT sensor!");
    return; // if the DHT is not running don't continue;
  }

  Serial.print(" Humidity ");
  Serial.print(humidityNow);
  Serial.print("% ");
  Serial.print("Temp ");
  Serial.print(t);
  Serial.print("C ");
  Serial.print(f);
  Serial.print("F");
  Serial.println();
} // end of getTimeTempHumidity()

/*
 * Check for user interaction, which will be in the form of 
 * something typed on the serial monitor If there is anything,
 * make sure it's proper, and perform the requested action.
 */
void checkUserInteraction() {
  // Check for user interaction
  while (Serial.available() > 0) {

    // The first character tells us what to expect 
    // for the rest of the line
    char temp = Serial.read();

    // If the first character is 'P' then print the current
    // settings and break out of the while() loop.
    if ( temp == 'P') {
      printSettings();
      Serial.flush();
      break;
    } // end of printing current settings

    // If first character is 'S' then the rest will be a setting
    else if ( temp == 'S') {
      expectValveSetting();
    }

    // Otherwise, it's an error. Remind the user what the choices
    // are and break out of the while() loop
    else
    {
      printMenu();
      Serial.flush();
      break;
    }
  } // end of processing user interaction
}

/*
 * Read a string of the form "2N13:45" and separate it into the
 * valve number, the letter indicating ON or OFF, and the time.
 */
void expectValveSetting() {

  // The first integer should be the valve number
  int valveNumber = Serial.parseInt();

  // the next character should be either N or F
  char onOff = Serial.read();

  int desiredHour = Serial.parseInt(); // the hour

  // the next character should be ':'
  if (Serial.read() != ':') {
    Serial.println("no : found"); // Sanity check
    Serial.flush();
    return;
  }

  int desiredMinutes = Serial.parseInt(); // the minutes

  // finally expect a newline which is the end of the sentence:
  if (Serial.read() != '\N') { // Sanity check
    Serial.println(
      "Make sure to end your request with a Newline");
    Serial.flush();
    return;
  }

  // Convert the desired hour and minute time
  // to the number of minutes since midnight
  int desiredMinutesSinceMidnight 
    = (desiredHour*60 + desiredMinutes);
 
  // Put time into the array in the correct row/column
  if ( onOff == 'N') { // it's an ON time
    onOffTimes[valveNumber][ONTIME] 
    = desiredMinutesSinceMidnight;
  }
  else if ( onOff == 'F') { // it's an OFF time
    onOffTimes[valveNumber][OFFTIME] 
    = desiredMinutesSinceMidnight;
  }
  else { // user didn't use N or F
    Serial.print("You must use upper case N or F ");$
    Serial.println("to indicate ON time or OFF time");$
    Serial.flush();
    return;
  }

  printSettings(); // print the array so user can confirm settings
} // end of expectValveSetting()

void checkTimeControlValves() {

  // First, figure out how many minutes have passed since
  // midnight, since we store ON and OFF time as the number of
  // minutes since midnight. The biggest number will be at 2359
  // which is 23 * 60 + 59 = 1159 which is less than the maximum
  // that can be stored in an integer so an int is big enough
  int nowMinutesSinceMidnight =
    (dateTimeNow.hour() * 60) + dateTimeNow.minute();

  // Now check the array for each valve
  for (int valve = 0; valve < NUMBEROFVALVES; valve++) {
  Serial.print("Valve ");
    Serial.print(valve);
    
    Serial.print(" is now ");
    if ( ( nowMinutesSinceMidnight >= 
           onOffTimes[valve][ONTIME]) &&
         ( nowMinutesSinceMidnight < 
           onOffTimes[valve][OFFTIME]) ) {
           
      // Before we turn a valve on make sure it's not raining
      if ( humidityNow > 70 ) {
        // It's raining; turn the valve OFF
        Serial.print(" OFF ");
        digitalWrite(valvePinNumbers[valve], LOW);
      }
      else {
        // No rain and it's time to turn the valve ON
        Serial.print(" ON ");
        digitalWrite(valvePinNumbers[valve], HIGH);
      } // end of checking for rain
    } // end of checking for time to turn valve ON
    else {
      Serial.print(" OFF ");
      digitalWrite(valvePinNumbers[valve], LOW);
    }
    Serial.println();
  } // end of looping over each valve
  Serial.println();
}


void printMenu() {
  Serial.println(
    "Please enter P to print the current settings");
  Serial.println(
    "Please enter S2N13:45 to set valve 2 ON time to 13:34");
}


void printSettings(){

  // Print current on/off settings, converting # of minutes since 
  // midnight back to the time in hours and minutes
  Serial.println();
  for (int valve = 0; valve < NUMBEROFVALVES; valve++) {
    Serial.print("Valve ");
    Serial.print(valve);
    Serial.print(" will turn ON at ");

    // integer division drops remainder: divide by 60 to get hours
    Serial.print((onOffTimes[valve][ONTIME])/60);
    Serial.print(":");

    // minutes % 60 are the remainder (% is the modulo operator) 
    Serial.print((onOffTimes[valve][ONTIME])%(60));

    Serial.print(" and will turn OFF at ");
    Serial.print((onOffTimes[valve][OFFTIME])/60); // hours
    Serial.print(":");
    Serial.print((onOffTimes[valve][OFFTIME])%(60)); // minutes
    Serial.println();
  }
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *