/*

   ChickenDoor - V0.7
    This is modified version of the original project (https://backyardpoultry.iamcountryside.com/coops/automating-a-chicken-coop-door/).
    The original version calculated the time in seconds, this version calculates in minutes.
    
    Motor s prevodovkou otvára a zatvára dvere podla tabuľky východu a západu slnka, ktoré sú súčasťou programu.
    Čas je v minútach.

    Používa knižnicu časových pásiem (webovú adresu nižšie) na automatické nastavenie času dvakrát ročne na denné svetlo,šetrí čas.
    

    

*/

   // Includes
   
   #include <Arduino.h>
   #include <DS1307RTC.h>   //http://www.arduino.cc/playground/Code/Time
   #include <Time.h>        //http://www.arduino.cc/playground/Code/Time
   #include <TimeLib.h>
   #include <Timezone.h>    //https://github.com/JChristensen/Timezone
   #include <Wire.h>        //http://arduino.cc/en/Reference/Wire (supplied with the Arduino IDE)

   #pragma GCC diagnostic ignored "-Wwrite-strings"

   // Defines
  
   #define DEBUG
   // #define OP    0
   // #define CLO   1
   #define SUNRISE_DELAY (0)  // x minutes delay to open
   #define SUNSET_DELAY  (15) // x minutes delay to close

   // Časy západu slnka v minutách po polnoci pre každý ordinálny deň v roku,
   // napr. 1. január = index poľa 0 atď. Časy sú CET a sú upravené o jednu
   // hodinu, keď je DST v platnosti.
   //
   // Polia sa ukladajú do programovej pamäte, pretože dynamická pamäť nie je
   // dosť veľká.
   //
static const int sunsetAr[] PROGMEM = {965,966,968,969,970,971,972,973,975,976,977,979,980,981,983,984,986,987,989,990,992,993,995,996,998,1000,1001,1003,1005,1006,1008,
1009,1011,1012,1014,1016,1017,1019,1021,1023,1024,1026,1028,1029,1031,1033,1034,1036,1038,1039,1041,1043,1044,1046,1048,1049,1051,1052,1054,1056,
1057,1059,1060,1062,1064,1065,1067,1068,1070,1072,1073,1075,1076,1078,1079,1081,1083,1084,1086,1087,1089,1090,1092,1093,1095,1096,1098,1099,1101,1102,1104,
1106,1107,1109,1110,1112,1113,1115,1116,1118,1119,1121,1122,1124,1125,1127,1128,1130,1131,1133,1134,1136,1137,1139,1140,1142,1143,1145,1146,1148,1149,
1151,1152,1154,1155,1157,1158,1159,1161,1163,1164,1165,1167,1168,1170,1171,1172,1174,1175,1176,1178,1179,1180,1181,1182,1184,1185,1186,1187,1188,1189,1190,
1191,1192,1193,1194,1195,1196,1196,1197,1198,1198,1199,1200,1200,1201,1201,1202,1202,1202,1203,1203,1203,1203,1203,1203,1203,1203,1203,1203,1203,1203,
1203,1202,1202,1201,1201,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,1200,
1200,1200,1200,1200,1200,1200,1200,1162,1159,1158,1157,1155,1153,1151,1150,1148,1146,1144,1142,1140,1138,1136,1134,1132,1130,1128,1110,1110,1110,1110,1110,
1110,1110,1110,1110,1090,1090,1090,1090,1090,1090,1090,1090,1090,1088,1086,1084,1082,1080,1078,1075,1073,1071,1069,1067,1065,1062,1060,1058,1056,1054,
1052,1050,1048,1045,1043,1041,1039,1037,1035,1033,1031,1029,1027,1025,1023,1021,1019,1017,1015,1013,1011,1009,1008,1006,1004,1002,1000,999,997,995,993,
992,990,989,987,985,984,982,981,980,978,977,975,974,973,972,970,969,968,967,966,965,964,963,962,962,961,960,959,959,958,
958,957,957,956,956,956,955,955,955,955,955,955,955,955,955,956,956,956,957,957,958,958,959,959,960,961,961,962,963,964,965};

static const int sunriseAr[] PROGMEM = {435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,
435,435,435,435,435,435,435,435,434,432,431,429,427,426,424,422,420,419,417,415,413,411,409,407,405,403,402,400,398,
396,394,392,390,387,385,383,381,379,377,375,373,371,369,367,364,362,360,358,356,354,352,350,347,345,343,341,339,360,300,300,
300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,298,296,294,292,290,288,286,284,282,281,279,277,275,273,
272,270,268,267,265,263,262,260,259,257,256,254,253,252,250,249,248,246,244,244,243,242,241,240,239,238,237,236,235,234,233,
233,232,232,231,230,230,229,229,229,228,228,228,228,228,228,228,228,228,228,228,228,228,229,229,229,230,230,231,231,232,
233,233,234,235,235,236,237,238,239,240,241,242,243,244,244,246,247,248,249,251,251,251,251,251,251,251,251,251,251,251,251,
251,251,251,251,251,251,251,251,251,251,270,270,270,270,270,270,270,270,270,270,294,296,297,299,300,300,300,300,300,300,300,
300,300,300,300,300,300,300,300,300,300,300,300,300,300,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,
330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,331,332,394,396,397,399,401,
402,404,405,407,409,410,412,413,415,417,418,420,421,423,425,426,428,429,431,432,434,435,435,435,435,435,435,435,435,435,
435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435,435};

    // Constants
    
    const int delayTime        = 1000;
    const int EnMotorPin       = 3;  //  Enable
    const int CloseDoorPin     = 4;  //  Výstup1 pre MotoDriv
    const int OpenDoorPin      = 5;  //  Výstup2 pre MotoDriv
    const int topSwitchPin     = A3; //  Horný spínač, 10K na GND
    const int bottomSwitchPin  = A2; //  Dolný spínač, 10K na GND
    const int minutesPerHour   = 60;

    int OP         = 0;              // OPEN
    int CLO        = 1;              // CLOSE
    int STOP       = 2;
    int UNKNOWN    = 2;
    int DoorState  = 2;              // predpokladajte, že nie sú otvorené ani zatvorené

    int current_day;
    int sunrise;                     // v minútach od polnoci 
    int sunset;                      // v minútach od polnoci
    unsigned long motorstarttime;

    // Days of months array for ordinal calculation
    static const int monthLen[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    // Globals
    
    //Central European Time (Frankfurt, Paris, Prague)
    
    TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};    // Central European Summer Time
    TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};      // Central European Standard Time
    Timezone myTZ(CEST, CET);

    TimeChangeRule *tcr;        // Ukazovateľ na pravidlo zmeny času, použite na získanie skratky TZ.
    time_t utc, local;

    // Init
void setup()
{
    int sunset  = 0;
    int sunrise = 0;
    int minutes = 0;

    // Initialize the relay pins
    pinMode(EnMotorPin,          OUTPUT);
    pinMode(CloseDoorPin,        OUTPUT);
    pinMode(OpenDoorPin,         OUTPUT);
    pinMode(topSwitchPin,         INPUT);
    pinMode(bottomSwitchPin,      INPUT); 
    digitalWrite(EnMotorPin,        LOW);
    digitalWrite(CloseDoorPin,      LOW);
    digitalWrite(OpenDoorPin,       LOW);  

    DoorState = GetDoorState();
    if (DoorState ==     UNKNOWN) 
    OpenDoor();
     
#ifdef DEBUG
    // Start the serial port
    Serial.begin(9600);
    while (!Serial);
    delay(200);
#endif

    // Set the time using the RTC's time
    setSyncProvider(RTC.get);           // Získajte čas od RTC.

#ifdef DEBUG
    if(timeStatus()!= timeSet)          // Kontrola stavu knižnice TZ
    Serial.println("Unable to sync with the RTC");
    else
    Serial.println("RTC has set the system time ");
#endif

    // Inicializácia dverí na základe času knižnice TZ.
    utc = now();
    local = myTZ.toLocal(utc, &tcr);

#ifdef DEBUG
    Serial.print("Start-up time: ");
    printTime(local, tcr -> abbrev);
#endif

    // Máme dátum a čas, prevádzame ich na minuty od polnoci.
    minutes = toMinutes(hour(local), minute(local));

    // Získajte ordinálny deň v roku, ktorý budete používať ako index do polí na zap. a vych.
    int ordinalDay = ordinalDayOfYear(tmYearToCalendar(year(local)), month(local), day(local));

    // Získajte časy upravené pre DST a oneskorenie otvorenia / zatvorenia.
    sunrise = getAdjustedSunriseTime(ordinalDay, local);
    sunset  = getAdjustedSunsetTime(ordinalDay, local);

    // Zatvorte alebo otvorte dvere, skontrolujú aktuálny stav.
    if ((minutes > sunrise) && (minutes < sunset))
{
    DoorState = CLO;
    OpenDoor();
}   else {
    DoorState = OP;
    CloseDoor();
}

    Serial.print(" VS    : ");
    Serial.println(sunrise);
    Serial.print(" ZS    : ");
    Serial.println(sunset);
    Serial.print(" Aktual: ");
    Serial.println(minutes);

}

void loop()
{
    int sunset  = 0;
    int sunrise = 0;
    int minutes = 0;

    // Počkajte na aktuálny čas oneskorenia.
    delay(delayTime);

    // Získať aktuálny dátum a čas.
    utc = now();
    local = myTZ.toLocal(utc, &tcr);

    //  Serial.print("Aktual time: ");     // 
    //  printTime(local, tcr -> abbrev);   //

#ifdef DEBUG
    //  printTime(local, tcr -> abbrev);   //
#endif

    // Máme dátum a čas.
    minutes = toMinutes(hour(local),minute(local));

    // Teraz získajte ordinálny deň v roku.
    int ordinalDay = ordinalDayOfYear(tmYearToCalendar(year(local)), month(local), day(local));

    // Získajte časy upravené pre DST a oneskorenie otvorenia / zatvorenia.
    sunrise = getAdjustedSunriseTime(ordinalDay, local);
    sunset  = getAdjustedSunsetTime(ordinalDay, local);

    // Zatvorte alebo otvorte dvere,skontroluje aktuálny stav dverí.
    if ((minutes > sunrise) && (minutes < sunset))
{
    OpenDoor();
}   else {
    CloseDoor();
}

}

void OpenDoor()
{
  
#ifdef DEBUG
    Serial.print("OpenDoor: ");
    utc = now();
    local = myTZ.toLocal(utc, &tcr);
    printTime(local, tcr -> abbrev);        
#endif

    motorstarttime = millis();
    DoorState = GetDoorState();
    while (DoorState != OP) {
    MotorControl(OP);
    DoorState = GetDoorState();
    if (millis() - motorstarttime > 10000) { break; } // Po uplynutí 10 sekúnd zastavte motor
}
    delay(50);
    MotorControl(STOP);
}

void CloseDoor()
{
      
#ifdef DEBUG
    Serial.print("CloseDoor: ");
    utc = now();
    local = myTZ.toLocal(utc, &tcr);
    printTime(local, tcr -> abbrev);
#endif

    motorstarttime = millis();
    DoorState = GetDoorState();
    while (DoorState != CLO) {
    MotorControl(CLO);
    DoorState = GetDoorState();
    if (millis() - motorstarttime > 10000) { break; } // Po uplynutí 10 sekúnd zastavte motor
}
    delay(50);
    MotorControl(STOP);
}

void MotorControl(int Direction) {
 
    if (Direction        == OP) {
    digitalWrite(CloseDoorPin,  LOW);
    digitalWrite(OpenDoorPin,  HIGH);
    digitalWrite(EnMotorPin,   HIGH);
}   else if (Direction == CLO) {
    digitalWrite(CloseDoorPin, HIGH);
    digitalWrite(OpenDoorPin,   LOW);
    digitalWrite(EnMotorPin,   HIGH);
}   else if (Direction == STOP) {
    digitalWrite(CloseDoorPin,  LOW);
    digitalWrite(OpenDoorPin,   LOW);
    digitalWrite(EnMotorPin,    LOW);
}  
}

int GetDoorState() {
 
    // Read switch states
    int topSwStatus    = digitalRead   (topSwitchPin);
    int bottomSwStatus = digitalRead(bottomSwitchPin);
    int returnValue    = UNKNOWN;
 
    // skontrolujte, či sú dvere medzi stavmi
    if ((topSwStatus   == 0) and (bottomSwStatus == 0)) { returnValue = UNKNOWN; }
    // skontrolujte, či sú dvere otvorené
    if (topSwStatus    == 1) { returnValue = OP; }
    // skontrolujte, či sú dvere zatvorené
    if (bottomSwStatus == 1) { returnValue = CLO; }
    return(returnValue);
}

    int toMinutes(int hours, int minutes)
{
    int mins = (hours*minutesPerHour)+minutes;
    return mins;
}

    int ordinalDayOfYear(int year, int month, int day)
{
    int ordinal = 0;

    // Je to priestupný rok?
    int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);

    // Sčítajte dni v predchádzajúcich mesiacoch k aktuálnemu mesiacu
    for (int i = 0; i < month - 1; i++)
{
    ordinal += monthLen[i];
}

    // Skontrolujte, či nie je priestupný rok.
    if (leap && month > 2)
{
    ordinal++;
}

    // Zahrňte dni v aktuálnom mesiaci.
    ordinal += day;

    return ordinal;
}

    int getAdjustedSunriseTime(int ordinalDay, time_t local)
{
    //  Získajte čas východu z tabuľky - upravte index pre nulové pole.
    int sunrise = pgm_read_word_near(sunriseAr + (ordinalDay - 1));

    // Skontrolujte DST a podľa potreby ho upravte.
    if (myTZ.locIsDST(local))
{
    // Pridajte hodinu do východu slnka.
    sunrise += minutesPerHour;
}
    // Nakoniec pridajte oneskorenie otvorenia (svitanie).
    sunrise += SUNRISE_DELAY;

    return sunrise;
}

    int getAdjustedSunsetTime(int ordinalDay, time_t local)
{
    // Získajte čas západu slnka z tabuľky - upravte index pre nulové pole.
    int sunset = pgm_read_word_near(sunsetAr + (ordinalDay - 1));

    // Skontrolujte DST a podľa potreby ho upravte.
    if (myTZ.locIsDST(local))
{
    // Pridajte hodinu do západu slnka
    sunset += minutesPerHour;
}

    // Nakoniec pridajte oneskorenie otvorenia (svitanie).
    sunset += SUNSET_DELAY;

    return sunset;
}

#ifdef DEBUG

    //Funkcia na tlač času s časovým pásmom
void printTime(time_t t, char *tz)
{
    sPrintI00(hour(t));
    sPrintDigits(minute(t));
    sPrintDigits(second(t));
    Serial.print(' ');
    Serial.print(dayShortStr(weekday(t)));
    Serial.print(' ');
    sPrintI00(day(t));
    Serial.print(' ');
    Serial.print(monthShortStr(month(t)));
    Serial.print(' ');
    Serial.print(year(t));
    Serial.print(' ');
    Serial.print(tz);
    Serial.println();
    // delay(5000);
}

    // Vytlačte celé číslo vo formáte „: 00“ (s nulou na začiatku).
    // Predpokladá sa, že vstupná hodnota je medzi 0 a 99.
void sPrintI00(int val)
{
    if (val < 10) Serial.print('0');
    Serial.print(val, DEC);
    return;
}

    // Vytlačte celé číslo vo formáte „: 00“ (s nulou na začiatku).
    // Predpokladá sa, že vstupná hodnota je medzi 0 a 99.
void sPrintDigits(int val)
{
    Serial.print(':');
    if(val < 10) Serial.print('0');
    Serial.print(val, DEC);
}

#endif
