/* Code Written by Evan Cochran and Henok Techeste
 *  
 * Setup Info: 
 * 
 * COM window uses BAUD = 19200
 * 
 * 
 * Wiring Connections:
 * 
 * VCC is 5v
 * GND is GND
 * 
 * RTC:
 * SCL - A5
 * SDA - A4
 * 
 * SD Card:
 * MISO - PIN 12
 * MOSI - PIN 11
 * SCK - PIN 13
 * CS - (10)
 * 
 * Digital 2 is Emergency Open (active high)
 * Digital 7 is stop Emergency Open
 * A2 is plate usage (active high)
 * Ground these signals when not in use
 * 
 * Digital 3 and 4 are the output for the motor relays
 * Digital 5 is solenoid unlock signal (HIGH unlock)
 */


#include <Wire.h>
#include "RTClib.h"
#include <SD.h>
#include <SPI.h>


RTC_DS3231 rtc;


// global variables
const int debug = 0;        // debug mode
int triggerTimes[5][3];   // holds the times of when the pet door can be opened or if it is locked
int relayOFF, relayON;
const int relay1 = 3;       // pins for connection relay 1 and relay 2 to
const int relay2 = 4;
const int solenoid = 5;
const int triggerType = LOW;  // relay type
const int openTime = 12000;    // how long it takes the door to open in ms
const int closeTime = 11220;   // how long it takes the door to close in ms
const int petDelay = 5000;   // how long the door remains open for the pet in ms
int emergencyMode = 0;    // var for current state
const int pinCS = 10;       // pin definitions for sd setup
const int buttonPin = 2;    // pin for emergency open
const int buttonPin2 = 7;   // pin for stopping emergency open


// function prototypes
void time_setup();                 // acquires user timing windows
bool openability_check(int, int);  // for checking door openability given the current time
bool check_plate();                // for checking is a pet wants to use the door
void save_usage(int, int, int, int, int, int, int);      // for documenting pet usage
void door_open();                  // door actuation functions
void door_opened();
void door_close();
void door_closed();

  



void setup()    // set up code that is only run once
{ 
  // set baud to 19200
  Serial.begin(19200);
  while(!Serial)
  {
    ; //idle
  }
  Serial.print("\n\n\n");
  
  

  // RTC module setup
  if(rtc.begin())
  {
    Serial.println("RTC is good");
  }
  else
  {
    Serial.println("RTC error");
    while(1)
    {
      ;   // idle
    }
  }

  //the chip lost power and time needs to be updated
  if(rtc.lostPower())
  {
    //Serial.println("RTC lost power, setting the time!");

    //set to date and time of compilation
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

    //set to explicitly date and time
    //format is year, month, day, hour, minute, second
    //rtc.adjust(DateTime(2000, 4, 22, 12, 0, 0));
  }
  delay(500);



  // pin set up
  pinMode(pinCS, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buttonPin2, INPUT);
  pinMode(relay1, OUTPUT);
  pinMode(relay2, OUTPUT);
  pinMode(solenoid, OUTPUT);
  emergencyMode = 0;



  // Motor Relay Setup
  if(triggerType == LOW)
  {
    relayON = LOW;
    relayOFF = HIGH;
    digitalWrite(relay1, relayOFF);   // both relays are off
    digitalWrite(relay2, relayOFF);
  }
  else
  {
    relayON = HIGH;
    relayOFF = LOW;
    digitalWrite(relay1, relayOFF);    // keep both relays off
    digitalWrite(relay2, relayOFF);
  }
  digitalWrite(solenoid, LOW);
  Serial.println("Motor is good");
  delay(500);




  // SD card setup
  if(SD.begin())
  {
    Serial.println("SD card is good");
  }
  else
  {
    Serial.println("SD card error");
    while(1)
    {
      ;   // idle
    }
  }
  delay(500);



  // set up the timing windows for opening
  time_setup();
}







void loop()
{
  DateTime now = rtc.now(); // variable 'now' is used to access current time

  // variable to designate if the door can open when a pet wants
  bool canOpen = openability_check(now.hour(), now.minute());
  
  // variable to designate if the pet wants to open the door
  bool pressurePlate = check_plate();
  


  // Emergency open mode is activated by a HIGH on digital 2.
  // The door will remain open until a HIGH on digital 7
  if(digitalRead(buttonPin))
  {
    emergencyMode = 1;

    // record the date and time the door was opened as an emergency
    save_usage(now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(), 1);

    // open the door
    digitalWrite(solenoid, HIGH);
    delay(500);
    door_open();
    delay(openTime);
    door_opened();

    // remain open until a close signal sent
    while(emergencyMode)
    {
      if(digitalRead(buttonPin2))
      {
        emergencyMode = 0;
      }
      delay(10);
    }

    // close the door
    door_close();
    delay(closeTime);
    door_closed();
    delay(1500);
    digitalWrite(solenoid, LOW);
  }


  

  // check if a pet wants to open the door and it is able to be opened
  if(canOpen && pressurePlate)
  {
    // record the date and time the door was opened
    save_usage(now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(), 0);


    // actuate the door
    digitalWrite(solenoid, HIGH);
    delay(500);
    door_open();
    delay(openTime);
    door_opened();    // door is now open
    delay(petDelay);
    door_close();
    delay(closeTime);
    door_closed();    // door is now closed 
    delay(1500);
    digitalWrite(solenoid, LOW);   
  }
}





/****************************************************************************
 * void time_setup
 * 
 * sets up the timing windows for when the door can be opened by a pet
 * 
 * Under normal usage, the time data is read in from 'TIMES.txt' which
 * is located on the SD card
 * 
 * The times are stored in array triggerTimes[A][B]. A can be a number from 0-4,
 * and designates a distinct time for pet usability of the door.
 * B = 0 designates the hour in 24 hour format, so accepts the values of 0-23.
 * B = 1 designates the minute, so accepts the values of 0-59 
 * B = 2 designates whether a pet can open the door at the given time, with 1 
 * meaning openable and 0 meaning locked.
 * 
 * When the current time hits one of these trigger thresholds, the openability
 * of the door is updated until the next trigger. The first time must be midnight,
 * which is 00:00.
 *****************************************************************************/
void time_setup()
{
  // read user input from the file 'TIMES.txt'
  
  File timeFile = SD.open("TIMES.TXT");


  if(!timeFile)    // failed to open TIMES
  {
    Serial.println("TIMES.TXT needed");
    while(1)
    {
      ;   //stall out since no file
    }
  }
  

  char c[5][12];    // character array for times data

  // copy data into the array
  for(int i = 0; i < 5; i++)
  {
    for(int j = 0; j < 12; j++)
    {
      c[i][j] = timeFile.read();
    }
  }

  timeFile.close();
  


  // translate char array into number data
  for(int i = 0; i < 5; i++)
  {
    // make temp into a length 2 string to be converted into
    // integers for the hour and minue portion
    char temp[3];
    temp[2] = '\0';

    temp[0] = c[i][0];
    temp[1] = c[i][1];
    triggerTimes[i][0] = atoi(temp);

    temp[0] = c[i][3];
    temp[1] = c[i][4];
    triggerTimes[i][1] = atoi(temp);

    // if the next part starts with an 'L' the door is locked
    // otherwise it is open
    if(c[i][6] == 'L')
    {
      triggerTimes[i][2] = 0;
    }
    else
    {
      triggerTimes[i][2] = 1;
    }
  }


  // serial output of data retrieved
  Serial.println("Time data found:");
  for(int i = 0; i < 5; i++)
  {
    Serial.print(triggerTimes[i][0]);
    Serial.print("  ");
    Serial.print(triggerTimes[i][1]);
    Serial.print("  ");
    Serial.println(triggerTimes[i][2]);
  }
}






/**********************************************************************************
 * bool openability_check
 * 
 * this function takes the current hour and minute, and determines whether the door
 * is able to be opened by a pet
 * 
 * A value of true is returned if the door is able to be opened, else a value of 
 * false is returned
***********************************************************************************/
bool openability_check(int hr, int mins)
{
  // result to be returned
  bool res;
  
  for(int i = 0; i < 5; i++)
  {
    // if the real life time is later that the triggerTimes entry update result
    // result will end up with the state of the most recent trigger time
    if( (hr>triggerTimes[i][0]) || ((hr==triggerTimes[i][0])&&(mins>triggerTimes[i][1])) )
    {
      if(triggerTimes[i][2])
      {
        res = true;
      }
      else
      {
        res = false;
      }
    }
  }
  return res;  
}





/**********************************************************************
 * bool check_plate
 * 
 * this function checks if a pet wants to use the pet door
 * 
 * The function takes 50ms to execute
 * 
 * it takes 5 samples of analog pin 2 over a 50ms interval, averaging
 * them out. If the average is found to be above 4v, a pet wants to use 
 * the door and the function returns a value of true. Else it returns
 * false.
 **********************************************************************/
bool check_plate()
{
  int v = 0;
  
  // take 5 samples over a 50 ms period to find an average
  for(int i = 0; i < 5; i++)
  {
    v += analogRead(A2);
    delay(10);
  }
  v = v / 5;

  // returning values based on measured input
  if(v > 800)
  {
    return true;
  }
  else
  {
    return false;
  }
}





/*************************************************************
 * void save_usage
 * 
 * this function records the date and time for each time the
 * door is used. The record is stored on an external SD card
 * in the file 'Pet-data.txt'
 *************************************************************/
void save_usage(int yr, int mon, int d, int hr, int m, int s, int emg)
{
  File dataFile = SD.open("Pet-data.txt", FILE_WRITE);    // open or create a file

  // if it was opened by emergency signal, write a specific message
  if(emg)
  {
    Serial.println("Emergency door usage recorded");
    dataFile.print("Door opened by emergency signal on  ");
  }
  else
  {
    // it was opened by the pet, write a different message
    Serial.println("Regular door usage recorded");
    dataFile.print("Door accessed by pet on - - - - - - ");
  }

  // write time data to the file
  dataFile.print(yr, DEC);
  dataFile.print(" / ");
  dataFile.print(mon, DEC);
  dataFile.print(" / ");
  dataFile.print(d, DEC);
  dataFile.print("  at  ");
  dataFile.print(hr, DEC);
  dataFile.print(':');
  if(m < 10)    // add a buffer 0 for readability
  {
    dataFile.print('0');
  }
  dataFile.print(m, DEC);
  dataFile.print(':');
  if(s < 10)    // add a buffer 0 for readability
  {
    dataFile.print('0');
  }
  dataFile.println(s, DEC);

  dataFile.close();
}





/******************************************************************
 * Door actuating functions
 * 
 * they control the opening and closing of the pet door
 * through the use of relays
 *******************************************************************/
void door_open()
{
  digitalWrite(relay1, relayON);
  digitalWrite(relay2, relayOFF);
  Serial.println("Door is opening");
}
void door_opened()
{
  digitalWrite(relay1, relayOFF);
  digitalWrite(relay2, relayOFF);
  Serial.println("Door is now open");
}
void door_close()
{
  digitalWrite(relay1, relayOFF);
  digitalWrite(relay2, relayON);
  Serial.println("Door is closing");
}
void door_closed()
{
  digitalWrite(relay1, relayOFF);
  digitalWrite(relay2, relayOFF);
  Serial.println("Door is now closed");
}
