// Use 150R resistors + 9V battery

#include <AFMotor.h>
#include "Wire.h"
#include <Bounce2.h>
#define DS3231_I2C_ADDRESS 0x68

#define OPEN_DELAY_TIME 2000//260
#define CLOSE_DELAY_TIME 70

#define OPEN 0
#define CLOSE 1
#define DO_NOTHING -1



short start_hour[] = {22,22,22};
short start_minute[] = {30,34,37};
short open_time[] = {4,3,3};

#define N_VALVES 3

#define DEBUG

AF_DCMotor valve0(2);
AF_DCMotor valve1(1);
AF_DCMotor valve2(3);

Bounce debouncer0 = Bounce(); 
Bounce debouncer1 = Bounce(); 
Bounce debouncer2 = Bounce(); 

byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;

bool opened[N_VALVES];

bool btnOpened[N_VALVES];
bool buttons[N_VALVES];
byte btnPin[N_VALVES];
byte modePin;

void setup() {
  #ifdef DEBUG
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("Sprinklers test!");
  #endif
  btnPin[0] = A0;
  btnPin[1] = A1;
  btnPin[2] = A2;
  modePin = A3;
  for (int i=0;i<N_VALVES;i++) {
    pinMode(btnPin[i],INPUT_PULLUP);    
  }
  
  pinMode(modePin,INPUT_PULLUP);
  

  debouncer0.attach(btnPin[0]);
  debouncer0.interval(5); // interval in ms
  debouncer1.attach(btnPin[1]);
  debouncer1.interval(5); // interval in ms
  debouncer2.attach(btnPin[2]);
  debouncer2.interval(5); // interval in ms
  
  Wire.begin();

  for (int i=0;i<N_VALVES;i++) {
    opened[i] = false;
    btnOpened[i] = false;
  }
  
  valve0.setSpeed(255); 
  valve0.run(RELEASE);
  valve1.setSpeed(255); 
  valve1.run(RELEASE);
  valve2.setSpeed(255); 
  valve2.run(RELEASE);
  closeValve(valve0);
  closeValve(valve1);
  closeValve(valve2);
  
}

void loop() {
  int mode;
  mode = digitalRead(modePin);
  if (mode == LOW) {
     // PROGRAMMED MODE
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

  switch (checkValve(0)) {
    case OPEN: if (!opened[0]) {
              openValve(valve0);
              opened[0] = true;
              #ifdef DEBUG
              Serial.println("OPENING VALVE 0");
              #endif
            }
            break;
    case CLOSE: if (opened[0]) {
              closeValve(valve0);
              opened[0] = false;
              #ifdef DEBUG
              Serial.println("CLOSING VALVE 0");
              #endif
            }
            break;
  }

  switch (checkValve(1)) {
    case OPEN: if (!opened[1]) {
              openValve(valve1);
              opened[1] = true;
              #ifdef DEBUG
              Serial.println("OPENING VALVE 1");
              #endif
            }
            break;
    case CLOSE: if (opened[1]) {
              closeValve(valve1);
              opened[1] = false;
              #ifdef DEBUG
              Serial.println("CLOSING VALVE 1");
              #endif
            }
            break;
  }

  switch (checkValve(2)) {
    case OPEN: if (!opened[2]) {
              openValve(valve2);
              opened[2] = true;
              #ifdef DEBUG
              Serial.println("OPENING VALVE 2");
              #endif
            }
            break;
    case CLOSE: if (opened[2]) {
              closeValve(valve2);
              opened[2] = false;
              #ifdef DEBUG
              Serial.println("CLOSING VALVE 2");
              #endif
            }
            break;
  }
   } else {
   // MANUAL MODE
     checkButtons();
   }
  
}

void checkButtons() {
  debouncer0.update();
  debouncer1.update();
  debouncer2.update();

  buttons[0] = debouncer0.fell();
  buttons[1] = debouncer1.fell();
  buttons[2] = debouncer2.fell();

  for (int i=0;i<N_VALVES;i++) {
    if (buttons[i] && !btnOpened[i]) {
      switch (i) {
        case 0: openValve(valve0);
                break;
        case 1: openValve(valve1);
                break;
        case 2: openValve(valve2);
                break;
      } 
      btnOpened[i] = true;
      #ifdef DEBUG
        char buffer[64];
        sprintf(buffer,"OPENING WITH BUTTON %d\n",i);
        Serial.println(buffer);
      #endif
    }
    else if (buttons[i] && btnOpened[i]) {
      switch (i) {
        case 0: closeValve(valve0);
                break;
        case 1: closeValve(valve1);
                break;
        case 2: closeValve(valve2);
                break;
      }
      btnOpened[i] = false;
      #ifdef DEBUG
        char buffer[64];
        sprintf(buffer,"OPENING WITH BUTTON %d\n",i);
        Serial.println(buffer);
      #endif
    }
  }

}

int checkValve(int idx) {
  if (!opened[idx]) {
    if ((minute >= start_minute[idx]) && (minute < start_minute[idx] + open_time[idx]) && (hour == start_hour[idx]))
      return OPEN;
    else
      return DO_NOTHING;
  }
  else {
    if (minute >= start_minute[idx] + open_time[idx])
      return CLOSE;
    else
      return DO_NOTHING;
  }
}


void openValve(AF_DCMotor &valve) {
  valve.run(FORWARD);
  delay(OPEN_DELAY_TIME);
  valve.run(RELEASE);
}

void closeValve(AF_DCMotor &valve) {
  valve.run(BACKWARD);
  delay(CLOSE_DELAY_TIME);
  valve.run(RELEASE);
}

void readDS3231time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}

byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}

void displayTime()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
  &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10)
  {
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10)
  {
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(" Day of week: ");
  switch(dayOfWeek){
  case 1:
    Serial.println("Sunday");
    break;
  case 2:
    Serial.println("Monday");
    break;
  case 3:
    Serial.println("Tuesday");
    break;
  case 4:
    Serial.println("Wednesday");
    break;
  case 5:
    Serial.println("Thursday");
    break;
  case 6:
    Serial.println("Friday");
    break;
  case 7:
    Serial.println("Saturday");
    break;
  }
}
