Skip to content

Instantly share code, notes, and snippets.

@tanmaykm
Created August 29, 2011 04:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tanmaykm/1177762 to your computer and use it in GitHub Desktop.
Save tanmaykm/1177762 to your computer and use it in GitHub Desktop.
Arduino Sprinkler Controller
// Arduino Sprinkler
// Version: 0.4 (beta)
// Updated: 03 Oct 2011
#include <LiquidCrystal.h>
#include <EEPROM.h>
// SWITCHES
// Pin 1 (Learn/Auto)
// Pin 2 (Manual trigger)
// EEPROM USE
// 1 : Mode: Learn (resets readings)/Auto
// 2 : Switch on interval (minutes)
// 3 : Trigger threshold (ADC reading)
// digital pins
// interSWITCH_PIN_1rupts (switches)
#define SWITCH_PIN_1 2
#define SWITCH_PIN_2 3
// leds
#define ATTN_LED_PIN 11
// Sprinkler Pin Constants
#define SP_VALVE_PIN 12
#define SP_SENSOR_POW_PIN 13
// analog pin5
#define SP_SENSOR_PIN A5
#define DEBUG_ON false
// Sprinkler Modes
#define SP_MODE_AUTO 0
#define SP_MODE_LEARN 1
#define MAX_VALVE_ON_MINS 15
boolean justStarted = true;
int spTriggerThreshold = 0; // stored average of learn mode data (value in EEPROM if in auto mode)
long spLastRunTime = 0;
long spIntervalTime = 0;
long spLastSensorReadTime = -9999;
long spLastFlushTime = 0;
boolean spValveOn = false; //whether valve is on (initialized on setup)
boolean spValveFlushing = false; // whether valve is on for flushing
boolean spLearnMode;
long switchOnInterval = 0; // minutes
int lastSensorReading = 0;
int sensorPin = 0;
int powerPin = 2;
int programSwitchPin = 3;
#define LOOP_ACTION_SWITCH_1 1
#define LOOP_ACTION_SWITCH_2 2
volatile int spISRAction = 0;
#define SECS_IN_MS 1000
#define MINS_IN_MS 60000L
#define HR_IN_MS 3600000L
// EEPROM Stuff
#define EE_SIG_1_POS 0
#define EE_SIG_2_POS 1
#define EE_SIG_3_POS 2
#define EE_SIG_4_POS 3
#define EE_LRN_MODE_POS 4
#define EE_ON_INTV_POS 5
#define EE_TRIG_THRES_POS 6 // and 7
#define EE_SIG_1_VAL 84
#define EE_SIG_2_VAL 85
#define EE_SIG_3_VAL 84
#define EE_SIG_4_VAL 85
// LCD DISPLAY
//
// 1234567890123456
// METS V9.9 LEARN / AUTO
// HUMIDITY: 1234
//
// TRIGGER < 1234
// OPEN MINS: 15
//
// CHECK IN: 1234 MINS / 24 HR
// FLUSH IN: 1234 MINS / 24 HR
String lcdlines[8] = {
"METS V0.4 ",
"Humidity: ",
"Trigger < ",
"Open Mins: ",
"Check In: ",
"Flush In: ",
"Last Message:",
""
};
int lcdStartLine = 0;
String attn = "All is well";
boolean blinkOn = true;
// initialize the library with the numbers of the interface pins
//LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// yellow, blue, green, black, yellow, blue
LiquidCrystal lcd(10, 9, 8, 7, 6, 5);
//////////////////////////////////////////////////////////////////////////
// Sprinkler
//////////////////////////////////////////////////////////////////////////
void flushValve(long minsNow) {
if(spValveFlushing) {
// handling time overflow
if(minsNow < spLastFlushTime) spLastFlushTime = 0;
// if flushing is on for more than 2 mins
if((minsNow - spLastFlushTime) > 2) {
// set flush off and close valve
switchOnValve(false, minsNow);
spValveFlushing = false;
}
}
else if(!spValveOn) {
// else if valve is not on and valve has not been switched on since last 24hrs
if((minsNow - spLastFlushTime) > (24*60)) {
// set flush on and open valve
spValveFlushing = true;
switchOnValve(true, minsNow);
}
}
}
void switchOnValve(boolean on, long minsNow) {
digitalWrite(SP_VALVE_PIN, on ? HIGH : LOW);
spValveOn = on;
if(!spValveFlushing) spLastRunTime = minsNow;
spLastFlushTime = minsNow;
if(spValveFlushing) {
attn = (spValveOn ? "Flushing on" : "Flushing done");
}
else {
attn = (spValveOn ? "Tap switched on" : "Tap switched off");
}
if(DEBUG_ON) {
Serial.print("Valve set to ");
Serial.println(spValveOn ? "on" : "off");
if(spValveFlushing) Serial.println("For flushing");
}
blinkOn = false;
}
int readSensor(long minsNow) {
// handling time overflow
if(minsNow < spLastSensorReadTime) spLastSensorReadTime = 0;
// read sensor only once every 5 minutes
if((minsNow - spLastSensorReadTime) < 5) return lastSensorReading;
digitalWrite(SP_SENSOR_POW_PIN, HIGH);
delay(100);
lastSensorReading = analogRead(SP_SENSOR_PIN);
digitalWrite(SP_SENSOR_POW_PIN, LOW);
delay(100);
if(DEBUG_ON) {
Serial.print("Sensor reading: ");
Serial.println(lastSensorReading);
}
spLastSensorReadTime = minsNow;
return lastSensorReading;
}
void setup() {
if(DEBUG_ON == true) Serial.begin(9600); // debug
pinMode(ATTN_LED_PIN, OUTPUT);
pinMode(SWITCH_PIN_1, INPUT);
pinMode(SWITCH_PIN_2, INPUT);
pinMode(SP_VALVE_PIN, OUTPUT);
pinMode(SP_SENSOR_POW_PIN, OUTPUT);
digitalWrite(SP_VALVE_PIN, LOW);
digitalWrite(SP_SENSOR_POW_PIN, LOW);
spValveOn = false;
// read signature from EEPROM to find whether it was initialized by us
if(!verifyEEPROM()) writeEEPROMSignature();
spLearnMode = (EEPROM.read(EE_LRN_MODE_POS) > 0) ? true : false;
switchOnInterval = constrain(EEPROM.read(EE_ON_INTV_POS), 1, MAX_VALVE_ON_MINS);
spTriggerThreshold = constrain(readEEPROM_Int(EE_TRIG_THRES_POS), 200, 800);
attachInterrupt(0, switch1ISR, FALLING);
attachInterrupt(1, switch2ISR, FALLING);
lcd.begin(16,2);
}
void loop() {
long secsNow = millis()/1000;
long minsNow = secsNow/60;
long elapsedTime = (minsNow - spLastRunTime);
if(elapsedTime < 0) { // handling overflow. don't bother about the loss in time
spLastRunTime = 0;
return;
}
if(blinkOn) digitalWrite(ATTN_LED_PIN, ((secsNow % 2) == 0) ? HIGH : LOW);
else digitalWrite(ATTN_LED_PIN, LOW);
// if flush mode do only flush mode operations
flushValve(minsNow);
// display LCD
int loopsForDisplay = DEBUG_ON ? 8 : 40;
if((lcdStartLine % loopsForDisplay) == 0) {
if(lcdStartLine == (loopsForDisplay*4)) lcdStartLine = 0;
displayLCD(lcdStartLine / loopsForDisplay, minsNow, elapsedTime);
}
lcdStartLine++;
if(spValveFlushing) return;
doISRActions(secsNow, minsNow, elapsedTime);
//dbgPrintConfig(secsNow);
if(spLearnMode) learnModeCheckInLoop(secsNow, minsNow, elapsedTime);
else autoModeCheckInLoop(secsNow, minsNow, elapsedTime);
if(justStarted) resetIntervalTime(minsNow);
delay(DEBUG_ON ? 500 : 100);
}
void autoModeCheckInLoop(long secsNow, long minsNow, long elapsedTime) {
// if valve on for more than switchOnInterval, switch it off
if(spValveOn) {
if (elapsedTime > switchOnInterval) {
dbgPrintConfig(secsNow);
switchOnValve(false, minsNow);
}
}
else if (elapsedTime > spIntervalTime) {
dbgPrintConfig(secsNow);
int reading = readSensor(minsNow);
if(reading < spTriggerThreshold) switchOnValve(true, minsNow);
else spLastRunTime = minsNow;
}
}
void learnModeCheckInLoop(long secsNow, long minsNow, long elapsedTime) {
// don't allow more than 30 mins valve open in any case
if(spValveOn && (elapsedTime > MAX_VALVE_ON_MINS)) {
dbgPrintConfig(secsNow);
switchOnValve(false, minsNow);
attn = "Auto Off Done";
}
// blink for suggestions
if(spValveOn && (elapsedTime > switchOnInterval)) {
dbgPrintConfig(secsNow);
blinkOn = true;
attn = "Switch Off";
}
else if(!spValveOn && ((elapsedTime > spIntervalTime) || blinkOn)) {
dbgPrintConfig(secsNow);
int reading = readSensor(minsNow);
if(reading < spTriggerThreshold) {
blinkOn = true;
attn = "Dry. Switch On";
}
else {
blinkOn = false;
attn = "All is well";
}
spLastRunTime = minsNow;
}
else {
blinkOn = false;
}
}
void resetIntervalTime(long minsNow) {
if(minsNow < 10) spIntervalTime = 1;
else if(minsNow < 60) spIntervalTime = 30;
else if(minsNow < (2*60)) spIntervalTime = 60;
else if(spIntervalTime != (6*60)) {
spIntervalTime = 6*60;
justStarted = false;
}
}
//////////////////////////////////////////////////////////////////////////
// LCD DISPLAY
//////////////////////////////////////////////////////////////////////////
void displayLCD(int line, long minsNow, long elapsedTime) {
lcd.clear();
lcd.setCursor(0, 0);
switch(line) {
case 0:
lcd.print(lcdlines[0] + (spLearnMode ? "LEARN" : "AUTO"));
lcd.setCursor(0, 1);
lcd.print(lcdlines[1] + readSensor(minsNow));
break;
case 1:
lcd.print(lcdlines[2] + spTriggerThreshold);
lcd.setCursor(0, 1);
lcd.print(lcdlines[3] + switchOnInterval);
break;
case 2:
lcd.print(lcdlines[4] + max(spIntervalTime - elapsedTime, 0));
lcd.setCursor(0, 1);
lcd.print(lcdlines[5] + max(24*60 - (minsNow - spLastFlushTime), 0));
break;
case 3:
lcd.print(lcdlines[6]);
lcd.setCursor(0, 1);
lcd.print(attn);
break;
}
}
//////////////////////////////////////////////////////////////////////////
// Misc
//////////////////////////////////////////////////////////////////////////
boolean verifyEEPROM() {
int sig[] = {0,0,0,0};
if(EE_SIG_1_VAL != EEPROM.read(EE_SIG_1_POS)) return false;
if(EE_SIG_2_VAL != EEPROM.read(EE_SIG_2_POS)) return false;
if(EE_SIG_3_VAL != EEPROM.read(EE_SIG_3_POS)) return false;
if(EE_SIG_4_VAL != EEPROM.read(EE_SIG_4_POS)) return false;
return true;
}
void writeEEPROMSignature() {
EEPROM.write(EE_SIG_1_POS, EE_SIG_1_VAL);
EEPROM.write(EE_SIG_2_POS, EE_SIG_2_VAL);
EEPROM.write(EE_SIG_3_POS, EE_SIG_3_VAL);
EEPROM.write(EE_SIG_4_POS, EE_SIG_4_VAL);
// write the default values as well
EEPROM.write(EE_LRN_MODE_POS, 1);
EEPROM.write(EE_ON_INTV_POS, MAX_VALVE_ON_MINS);
writeEEPROM_Int(EE_TRIG_THRES_POS, 390);
if(DEBUG_ON) Serial.println("Wrote fresh EEPROM signature");
}
void dbgPrintConfig(long secsNow) {
if(DEBUG_ON) {
Serial.println("Loop ");
Serial.print(blinkOn ? "Y" : "N");
Serial.print(" Now: ");
Serial.print(secsNow);
Serial.print(" Last Run: ");
Serial.print(spLastRunTime);
Serial.print(" Learn:");
Serial.print(spLearnMode ? "Y" : "N");
Serial.print(" ValveOn:");
Serial.print(spValveOn ? "Y" : "N");
Serial.print(" switchOnInterval:");
Serial.print(switchOnInterval);
Serial.print(" spIntervalTime:");
Serial.print(spIntervalTime);
Serial.print(" spTriggerThreshold:");
Serial.println(spTriggerThreshold);
}
}
void writeEEPROM_Int(int p_address, int p_value) {
byte lowByte = ((p_value >> 0) & 0xFF);
byte highByte = ((p_value >> 8) & 0xFF);
EEPROM.write(p_address, lowByte);
EEPROM.write(p_address + 1, highByte);
}
//This function will read a 2 byte integer from the eeprom at the specified address and address + 1
unsigned int readEEPROM_Int(int p_address) {
byte lowByte = EEPROM.read(p_address);
byte highByte = EEPROM.read(p_address + 1);
return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}
//////////////////////////////////////////////////////////////////////////
// Switch Handlers
//////////////////////////////////////////////////////////////////////////
void doISRActions(long secsNow, long minsNow, long elapsedTime) {
if(spISRAction == LOOP_ACTION_SWITCH_1) switchLearnAutoModeFn(secsNow, minsNow, elapsedTime);
else if(spISRAction == LOOP_ACTION_SWITCH_2) switchValveOnOffFn(secsNow, minsNow, elapsedTime);
spISRAction = 0;
}
// switch between learn and auto modes
void switch1ISR() {
spISRAction = LOOP_ACTION_SWITCH_1;
}
void switchLearnAutoModeFn(long secsNow, long minsNow, long elapsedTime) {
if(DEBUG_ON) Serial.print("In switch 1 fn. New mode: ");
spLearnMode = !spLearnMode;
EEPROM.write(EE_LRN_MODE_POS, spLearnMode ? 1 : 0);
if(DEBUG_ON) Serial.println(spLearnMode ? "learn" : "auto");
lcdStartLine = 0;
}
// toggle valve manually
void switch2ISR() {
spISRAction = LOOP_ACTION_SWITCH_2;
}
void switchValveOnOffFn(long secsNow, long minsNow, long elapsedTime) {
if(DEBUG_ON) Serial.println("In switch 2 fn");
switchOnValve(!spValveOn, minsNow);
// if learn mode, measure time and do stuff
if(spLearnMode) {
if(!spValveOn) { // valve was on before and was switched off now
switchOnInterval = constrain((switchOnInterval + elapsedTime)/2, 1, MAX_VALVE_ON_MINS);
EEPROM.write(EE_ON_INTV_POS, switchOnInterval);
}
else { // valve was off before and was switched on now
spTriggerThreshold = (spTriggerThreshold + readSensor(minsNow)) / 2;
writeEEPROM_Int(EE_TRIG_THRES_POS, spTriggerThreshold);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment