Appendix 2 The main program code

/* Author: Olivia De Meo ()

Virginia Commonwealth University, Richmond, VA

Version: March 2016 (written with Arduino 1.0.6)

Description: This program controls the automated irrigation system installed at

Cumberland Marsh in New Kent, VA. The program runs continuously, filling the holding

tanks at each high tide, and dispensing 25 L of water to our experimental plots twice daily

at low tide. The program also monitors several components of the system and will send

an error message if any malfunctions occur.

*/

//------------------------------------------------------------------------------------

// Add Libraries

#include <GSM.h> // For texting results to cell phone

#include <NewPing.h> // For use with the ultrasonic sensor

#include <Smoother.h> // For averaging 10 values from the ultrasonic sensor

#include <Wire.h> // For communication with the real time clock

#include <SPI.h> // Needed to prevent a RTClib compile error

#include <RTClib.h> // Real time clock for keeping time

#include <elapsedMillis.h> // Determines the number of elapsed milliseconds

/* Several of these libraries are not included with Arduino but can be downloaded at the

following sites:

NewPing: http://code.google.com/p/arduino-new-ping/

Smoother: https://github.com/fughilli/MagEncoderTest

RTClib: https://github.com/millerlp/Thermocouple_datalogger/tree/master/RTClib

elapsedMillis: http://playground.arduino.cc/Code/ElapsedMillis

*/

// Declare time clock variables

RTC_DS3231 RTC;

float initialHoursMinutesPing = 0.0;

float currentMinutesPing;

float currentHoursPing;

float currentHoursMinutesPing;

float initialHoursMinutesText = 0.0;

float currentMinutesText;

float currentHoursText;

float currentHoursMinutesText;

float hoursTime;

float minutesTime;

float hoursMinutesTime;

elapsedMillis timeElapsed; // Counts time elapsed

unsigned long dispenseInterval_valve = 240000; // Shut off solenoid after 4 minutes

unsigned long dispenseInterval = 900000; // Shut off pump after 15 minutes

// Declare ping sensor variables

long distance; // Distance calculated by the ping sensor

Smoother<int, 10> mySmoother; // Create a smoother to smooth integer values from a history of 10 pushed values

long avg_distance; // Averaged distance from smoothing function

float ping_interval = 0.05; // Ping interval for the ultrasonic sensor set to once every three minutes (MUST BE ENTERED IN DECIMAL HOURS)

float pump1000gph_height = 137; // Distance in centimeters from the ultrasonic sensor to the pump in the creek where the water level is high enough to activate the pumps

float lowTideHeight = 147; // Distance in centimeters from the ultrasonic sensor to the water when the experimental plots are no longer flooded

// Declare float switch variables

int floatSwitchPin_fresh = 42;

int floatSwitchPin_salt = 44;

int floatSwitchPin_lowBrine = 46;

int floatSwitchState_fresh; // Variables to represent the state of the float switches

int floatSwitchState_salt;

int floatSwitchState_lowBrine;

boolean freshTankFilled; // Flag for the freshwater tank float switch

boolean saltTankFilled; // Flag for the saltwater tank float switch

// Declare flow meter variables

char flowdataFresh [20]; // A 20 byte character array to hold incoming data from the flow meter

char flowdataSalt [20]; // A 20 byte character array to hold incoming data from the flow meter

byte receivedFresh = 0; // Number of characters received from the flow meter

byte receivedSalt = 0; // Number of characters received from the flow meter

byte serial_event_fresh = 0; // A flag to signal when data has been received from the flow meter

byte serial_event_salt = 0; // A flag to signal when data has been received from the flow meter

char *totalVolumeFresh; // Char pointer used in string parsing

char *totalVolumeSalt; // Char pointer used in string parsing

float plotWaterVolume = 25; // Dispense 25 liters to each plot

float flowMeterVolume_fresh = 0; // Volume read from flow meter after conversion to a float

float flowMeterVolume_salt = 0; // Volume read from flow meter after conversion to a float

// Declare pump variables

int brinePumpRelayPin = 32; // Peristaltic Pump

int freshPumpRelayPin = 33; // Creek Pump

int saltPumpRelayPin = 34; // Creek Pump

unsigned long brinePumpTime = 660000; // Add brine water to the saltwater tank for 11 minutes. Peristaltic pump rate is about 4.8 mL/second.

boolean FWstopLoop; // Flag to stop freshwater loop

boolean SWstopLoop; // Flag to stop saltwater loop

// Declare tank mixer variables

int brineMixerRelayPin = 35; // Brine tank mixer

int saltMixerRelayPin = 36; // Salt tank mixer

unsigned long saltMixerTime = 300000; // Mix saltwater tank for 5 minutes

unsigned long mixingTime = 300000; // Mix brine tank for 5 minutes

// Declare solenoid valve variables

int solenoidRelayPin1 = 22; // Freshwater Solenoids

int solenoidRelayPin2 = 23;

int solenoidRelayPin3 = 24;

int solenoidRelayPin4 = 25;

int solenoidRelayPin5 = 26;

int solenoidRelayPin6 = 27; // Saltwater Solenoids

int solenoidRelayPin7 = 28;

int solenoidRelayPin8 = 29;

int solenoidRelayPin9 = 30;

int solenoidRelayPin10 = 31;

// Create arrays with the pin numbers for the solenoid valves that need to open when dispensing water

int freshSolenoids[] = {solenoidRelayPin1, solenoidRelayPin2, solenoidRelayPin3, solenoidRelayPin4, solenoidRelayPin5};

int saltSolenoids[] = {solenoidRelayPin6, solenoidRelayPin7, solenoidRelayPin8, solenoidRelayPin9, solenoidRelayPin10};

// Declare text messaging variables

GSM gsmAccess; // Initialize the library instance

GSM_SMS sms;

#define PINNUMBER #### // Pin number for the SIM card to access the cell phone provider network

char remoteNum[20] = "##########"; // Telephone number that text messages will be sent to

boolean notConnected; // SIM card connection status

boolean brineTextSent; // Brine text message status

String relayMessage; // Relay text message

boolean relayState; // State of the relays

boolean relayTextSent; // Relay text message status

String textFW; // Text string

String textSW; // Text string

String textPing; // Text string

String pingMonitorMessage; // Full text message for monitoring the ping sensor and switches

String textTime; // Text string

String fillMessageFW; // Full text message for filling the FW tank

String fillMessageSW; // Full text message for filling the SW tank

String dispenseMessageFW; // Full text message for dispensing FW tank

String dispenseMessageSW; // Full text message for dispensing SW tank

String solenoidMessage; // Error message if solenoids are open

String FWpumpMessage; // Full text message for filling the FW tank

String SWpumpMessage; // Full text message for filling the SW tank

boolean FWsolenoidTextSent; // Solenoid text message status

boolean SWsolenoidTextSent; // Solenoid text message status

boolean FWpumpTextSent; // FW pump text message status

boolean SWpumpTextSent; // SW pump text message status

int text_interval = 1; // Set the interval for text monitoring messages in hours

// Declare brine tank fill switch variables

int fillBrineSwitchPin = 48;

boolean fillBrineSwitchState;

//----------------------------------------------------------------------------

void setup()

{

Serial.begin(9600);

Serial3.begin(9600);

Serial2.begin(9600);

Serial3.print("pre,m\r"); // Select medium flow meter size

Serial2.print("pre,m\r"); // Select medium flow meter size

Serial3.print("c,1\r"); // Set flow meter to continuous mode

Serial2.print("c,1\r"); // Set flow meter to continuous mode

Serial3.print("clear\r"); // Set the initial volume of the flow meter to zero

Serial2.print("clear\r"); // Set the initial volume of the flow meter to zero

// Set up the real time clock

Wire.begin();

RTC.begin();

RTC.adjust(DateTime(__DATE__, __TIME__)); // Sets the RTC to the date & time this sketch was compiled from the computer

DateTime now = RTC.now();

// Set ping sensor to be an input

pinMode(A0, INPUT); // AO refers to analog #0 pin

// Set the float switches to be inputs

pinMode(floatSwitchPin_fresh, INPUT);

pinMode(floatSwitchPin_salt, INPUT);

pinMode(floatSwitchPin_lowBrine, INPUT);

// Set the flags for the fresh and salt tanks to either filled or not filled

floatSwitchState_fresh = digitalRead(floatSwitchPin_fresh);

if(floatSwitchState_fresh == LOW){

freshTankFilled = false;}

else if(floatSwitchState_fresh == HIGH){

freshTankFilled = true;}

floatSwitchState_salt = digitalRead(floatSwitchPin_salt);

if(floatSwitchState_salt == LOW){

saltTankFilled = false;}

else if(floatSwitchState_salt == HIGH){

saltTankFilled = true;}

// Set brine tank fill switch to be an input

pinMode(fillBrineSwitchPin, INPUT);

// Set all relays to be off when the program starts (THIS MUST HAPPEN BEFORE THEY ARE DECLARED AS OUTPUTS BECAUSE THE RELAY IS AUTOMATICALLY ON)

digitalWrite(brinePumpRelayPin, HIGH); // Pumps

digitalWrite(freshPumpRelayPin, HIGH);

digitalWrite(saltPumpRelayPin, HIGH);

digitalWrite(brineMixerRelayPin, HIGH); // Mixers

digitalWrite(saltMixerRelayPin, HIGH);

digitalWrite(solenoidRelayPin1, HIGH); // Freshwater Solenoids

digitalWrite(solenoidRelayPin2, HIGH);

digitalWrite(solenoidRelayPin3, HIGH);

digitalWrite(solenoidRelayPin4, HIGH);

digitalWrite(solenoidRelayPin5, HIGH);

digitalWrite(solenoidRelayPin6, HIGH); // Saltwater Solenoids

digitalWrite(solenoidRelayPin7, HIGH);

digitalWrite(solenoidRelayPin8, HIGH);

digitalWrite(solenoidRelayPin9, HIGH);

digitalWrite(solenoidRelayPin10, HIGH);

// Set all relays to be outputs

pinMode(brinePumpRelayPin, OUTPUT); // Pumps

pinMode(freshPumpRelayPin, OUTPUT);

pinMode(saltPumpRelayPin, OUTPUT);

pinMode(brineMixerRelayPin, OUTPUT); // Mixers

pinMode(saltMixerRelayPin, OUTPUT);

pinMode(solenoidRelayPin1, OUTPUT); // Freshwater Solenoids

pinMode(solenoidRelayPin2, OUTPUT);

pinMode(solenoidRelayPin3, OUTPUT);

pinMode(solenoidRelayPin4, OUTPUT);

pinMode(solenoidRelayPin5, OUTPUT);

pinMode(solenoidRelayPin6, OUTPUT); // Saltwater Solenoids

pinMode(solenoidRelayPin7, OUTPUT);

pinMode(solenoidRelayPin8, OUTPUT);

pinMode(solenoidRelayPin9, OUTPUT);

pinMode(solenoidRelayPin10, OUTPUT);

// Track the status of the SIM card's connection to the network

notConnected = true;

while (notConnected){

if (gsmAccess.begin(PINNUMBER) == GSM_READY) // Connect to the network

notConnected = false;

else{

delay(2000);

}

}

sms.beginSMS(remoteNum);

sms.print("System Reset");

sms.endSMS();

// Set brine and relay text messages to false indicating message not sent

brineTextSent = false;

relayTextSent = false;

FWsolenoidTextSent = false;

SWsolenoidTextSent = false;

}

//----------------------------------------------------------------------------

// FUNCTIONS

// Read freshwater flow meter data

void serialEvent3(){

receivedFresh = Serial3.readBytesUntil(13, flowdataFresh, 20); // Read the data sent from the serial monitor until we see a <CR> and count how many characters have been received

flowdataFresh[receivedFresh] = 0; // Stop the buffer from transmitting leftovers

serial_event_fresh = 1; // Flag to indicate that we have received a string

}

// Read saltwater flow meter data

void serialEvent2(){

receivedSalt = Serial2.readBytesUntil(13, flowdataSalt, 20); // Read the data sent from the serial monitor until we see a <CR> and count how many characters have been received

flowdataSalt[receivedSalt] = 0; // Stop the buffer from transmitting leftovers

serial_event_salt = 1; // Flag to indicate that we have received a string

}

//----------------------------------------------------------------------------

void loop() // BEGIN LOOP

{

// ULTRASONIC SENSOR

// Ping the water in the creek every set interval to detect the water height

DateTime nowPing = RTC.now(); // Read the time and store in object "now"

currentHoursPing = nowPing.hour(); // Query the object "now" for the minutes

currentMinutesPing = nowPing.minute();

currentHoursMinutesPing = currentHoursPing + currentMinutesPing/60;

if ((currentHoursMinutesPing-initialHoursMinutesPing) < 0){

initialHoursMinutesPing = 0.0; // Set initial time back to 0 when going from midnight into the next morning

}

if ((currentHoursMinutesPing-initialHoursMinutesPing) >= ping_interval){

distance = analogRead(0); // Distance

mySmoother.pushValue(distance); // Insert distance value into smoothing function

avg_distance = mySmoother.pullValue(); // Average of the last 10 values

initialHoursMinutesPing = currentHoursMinutesPing;

}

//----------------------------------------------------------------------------

// FILL THE FRESHWATER AND SALTWATER TANKS

// When the water level is high enough and the tank is empty, fill the freshwater tank

floatSwitchState_fresh = digitalRead(floatSwitchPin_fresh); // Read the float switch state

FWstopLoop = false;

if ((floatSwitchState_fresh == LOW) && (avg_distance < pump1000gph_height)){ // When the distance from the ultrasonic sensor to the water is less than the depth of pump

// Record the time

DateTime nowTime = RTC.now(); // Read the time and store in object "now"

hoursTime = nowTime.hour();

minutesTime = nowTime.minute();

hoursMinutesTime = hoursTime + minutesTime/60;

// Fill the freshwater tank

timeElapsed = 0; // Reset elapsed time to zero

digitalWrite(freshPumpRelayPin, LOW); // Turn the freshwater pump on

while(floatSwitchState_fresh == LOW && FWstopLoop == false){ // While the float switch has not been triggered

floatSwitchState_fresh = digitalRead(floatSwitchPin_fresh); // Continue reading the state of the float switch

if (timeElapsed >= dispenseInterval){ // Monitor the time elapsed while the pump is running

digitalWrite(freshPumpRelayPin, HIGH); // If the pump runs too long and the tank hasn't filled, close the pump

// Send an error message indicating the malfunction

if(FWpumpTextSent == false){

FWpumpMessage = "FW pump off. Error.";

sms.beginSMS(remoteNum);

sms.print(FWpumpMessage);

sms.endSMS();

FWpumpTextSent = true;

}

FWstopLoop = true; // Exit the “while” loop

}

}

if(FWpumpTextSent == false){

digitalWrite(freshPumpRelayPin, HIGH); // When the float switch is triggered, exit the "while" loop and turn the freshwater pump off

freshTankFilled = true; // Set flag to true when the tank has filled

// Compile text message

textTime = "; Time: ";

textPing = "; Ping: ";

fillMessageFW = "Freshwater tank filled";

fillMessageFW = fillMessageFW + textPing + avg_distance + textTime + hoursMinutesTime;

// Send text message that the freshwater tank has filled

sms.beginSMS(remoteNum);

sms.print(fillMessageFW);

sms.endSMS();

}

}

// When the water level is high enough and the tank is empty, fill the saltwater tank

floatSwitchState_salt = digitalRead(floatSwitchPin_salt); // Read the float switch state

SWstopLoop = false;

if ((floatSwitchState_salt == LOW) && (avg_distance < pump1000gph_height)){ // When the distance from the ultrasonic sensor to the water is less than the depth of pump

// Record the time

DateTime nowTime = RTC.now(); // Read the time and store in object "now"

hoursTime = nowTime.hour();

minutesTime = nowTime.minute();

hoursMinutesTime = hoursTime + minutesTime/60;

// Creek Pump

timeElapsed = 0; // Reset elapsed time to zero

digitalWrite(saltPumpRelayPin, LOW); // Turn the saltwater pump on

while(floatSwitchState_salt == LOW && SWstopLoop == false){ // While the float switch has not been triggered

floatSwitchState_salt = digitalRead(floatSwitchPin_salt); // Continue reading the state of the float switch

if (timeElapsed >= dispenseInterval){ // Monitor the time elapsed while the pump is running

digitalWrite(saltPumpRelayPin, HIGH); // If the pump runs too long and the tank hasn't filled, close the pump

// Send an error message indicating the malfunction

if(SWpumpTextSent == false){

SWpumpMessage = "SW pump off. Error.";

sms.beginSMS(remoteNum);

sms.print(SWpumpMessage);

sms.endSMS();

SWpumpTextSent = true;

}

SWstopLoop = true; // Exit the “while” loop

}

}

if(SWpumpTextSent == false){

digitalWrite(saltPumpRelayPin, HIGH); // When the float switch is triggered, exit the "while" loop and turn the saltwater pump off

saltTankFilled = true; // Set flag to true when the tank has filled

// Mix saltwater tank

digitalWrite(saltMixerRelayPin, LOW); // Turn on mixer

delay(saltMixerTime); // Mix tank for 5 minutes

digitalWrite(saltMixerRelayPin, HIGH); // Turn off mixer

// Mix brine tank

floatSwitchState_lowBrine = digitalRead(floatSwitchPin_lowBrine); // Read the float switch state

if(floatSwitchState_lowBrine == HIGH){ // The tank has to have water in it because we don't want the pump to run dry

digitalWrite(brineMixerRelayPin, LOW); // Turn on mixing pump

delay(mixingTime); // Mix tank for 5 minutes

digitalWrite(brineMixerRelayPin, HIGH); // Turn off mixing pump

}

// Compile text message

textTime = "; Time: ";

textPing = "; Ping: ";

fillMessageSW = "Saltwater tank filled and mixed";

fillMessageSW = fillMessageSW + textPing + avg_distance + textTime + hoursMinutesTime;

// Send text message that the saltwater tank has filled

sms.beginSMS(remoteNum);

sms.print(fillMessageSW);

sms.endSMS();

}

}

//----------------------------------------------------------------------------

// DISPENSE FRESHWATER AND SALTWATER ONTO 10 PLOTS IN THE MARSH

// When the water level is low enough and the tank is full, dispense freshwater to the plots

if ((freshTankFilled == true) && (avg_distance > lowTideHeight)){ // If the tank is full and distance from the ultrasonic sensor to the water is greater than the height of the marsh

// Record the time

DateTime nowTime = RTC.now(); // Read the time and store in object "now"

hoursTime = nowTime.hour();

minutesTime = nowTime.minute();

hoursMinutesTime = hoursTime + minutesTime/60;

// Dispense water

Serial3.print("clear\r"); // Reset the flow meter to zero

timeElapsed = 0; // Reset elapsed time to zero

int i = 0;

for (i = 0; i < 5;){ // We will cycle through opening 5 solenoid valves

digitalWrite(freshSolenoids[i], LOW); // Open solenoid valve

serialEvent3(); // Read flow meter

if (serial_event_fresh == 1){ // If the serial_event flag is set we examine what data was sent