/////////////////////////////////////////////////////////////////////////////////////
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <Time.h> 
//Library Time.zip downloaded from http://www.pjrc.com/teensy/td_libs_Time.html
/////////////////////////////////////////////////////////////////////////////////////
// find your local ntp server http://www.pool.ntp.org/zone/europe or
// http://support.ntp.org/bin/view/Servers/StratumTwoTimeServers
// IPAddress timeServer(192, 43, 244, 18);  // time.nist.gov NTP server
   IPAddress timeServer(193, 79, 237, 14);  // ntp1.nl.net NTP server  
// IPAddress timeServer(132, 163, 4, 101);  // time-a.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 102);  // time-b.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 103);  // time-c.timefreq.bldrdoc.gov
/////////////////////////////////////////////////////////////////////////////////////
EthernetUDP Udp;
#define LOCALUDPPORT 8888
#define SDCARDPin 4
bool NTPworking = false;
#define TIME_SYSTEM_CLOCK       0b00000001
#define TIME_RTC_CLOCK          0b00000010
#define TIME_FORMAT_SMTP_DATE   0b00000100
#define TIME_FORMAT_SMTP_MSGID  0b00001000
#define TIME_FORMAT_HUMAN       0b00010000
#define TIME_FORMAT_HUMAN_SHORT 0b00100000
#define TIME_ZONE_INT 1
#define TIME_ZONE_NODST "+0100"
#define TIME_ZONE_DST "+0200"
String TIME_ZONE = TIME_ZONE_NODST;
#define DAYLIGHTSAVING_START_MONTH 3 // brezen
#define DAYLIGHTSAVING_START_HOUR  2 // 2:00:00 -> 3:00:00
#define DAYLIGHTSAVING_STOP_MONTH 10 // rijen
#define DAYLIGHTSAVING_STOP_HOUR   2 // 3:00:00 -> 2:00:00
#define DAYLIGHTSAVING_TIMESHIFT   1 // 1 hodina
volatile bool ntp_interrupt_in_progress = false;
#if defined(__AVR_ATmega2560__)
byte mac[] = { 
  0x90, 0xA2, 0xDA, 0x0D, 0x00, 0x01 };
#endif

#if defined(__AVR_ATmega328P__)
byte mac[] = { 
  0x90, 0xA2, 0xDA, 0x0D, 0x00, 0x02 };
#endif
/////////////////////////////////////////////////////////////////////////////////////
#include <Wire.h>
#define DS3231_ADDRESS 104
int rtc_second; //00-59;
int rtc_minute; //00-59;
int rtc_hour;//1-12 - 00-23;
int rtc_weekday;//1-7
int rtc_day;//01-31
int rtc_month;//01-12
int rtc_year;//0-99 + 2000;
#define TEMPERATURE_SENSOR_DS3231 0     // RTC - zde muze byt cokoli krome A2 a A3, coz je 16 a 17
#define TEMPERATURE_SENSOR_TMP36_1 A2   // TMP36 inside
#define TEMPERATURE_SENSOR_TMP36_2 A3   // TMP36 outside
#define MAX_TEMPERATURE_EXCEED 111.11   // Celsius, bad sensor if exceed
bool RTCworking = false;
/////////////////////////////////////////////////////////////////////////////////////
#define BAUD_RATE 115200
/////////////////////////////////////////////////////////////////////////////////////
#define BUFFERSIZE 85
static char buffer[BUFFERSIZE+1];
char TextInfo0[]  PROGMEM = "\r\nGet NTP time and update RTC chip\r\n================================";
char TextInfo1[]  PROGMEM = "\r\nUninitialized RTC time and temperature:";
char TextInfo2[]  PROGMEM = "\r\nWaiting for DHCP lease ...";
char TextInfo3[]  PROGMEM = "Failed to configure IPv4 for using Dynamic Host Configuration Protocol!";
char TextInfo4[]  PROGMEM = "Dynamic Host Configuration Protocol passed";
char TextInfo5[]  PROGMEM = "Board IP address = ";
char TextInfo6[]  PROGMEM = "Network subnet mask = ";
char TextInfo7[]  PROGMEM = "Network gateway IP address = ";
char TextInfo8[]  PROGMEM = "DNS IP address = ";
char TextInfo9[]  PROGMEM = "MAC = ";
char TextInfo10[] PROGMEM = "\r\nWaiting to obtain the accurate time from NTP server ";
char TextInfo11[] PROGMEM = "Sending NTP request...";
char TextInfo12[] PROGMEM = "... and NTP responses\r\nNTP time: ";
char TextInfo13[] PROGMEM = "No NTP Response";
char TextInfo14[] PROGMEM = "RTC time: ";
char TextInfo15[] PROGMEM = "RTC chip do not need to be updated";
char TextInfo16[] PROGMEM = "RTC chip updated";
char TextInfo17[] PROGMEM = "Error setting time to RTC";
char TextInfo18[] PROGMEM = "Error setting date to RTC";
char TextInfo19[] PROGMEM = "Error getting time";
char TextInfo20[] PROGMEM = "Error calling function: function ComposeTimeStamp requires time source (CPU or RTC)";
char TextInfo21[] PROGMEM = "Current time failed to get either the NTP server or the RTC chip";
char TextInfo22[] PROGMEM = "no data (temperature sensor error)";
char TextInfo23[] PROGMEM = "RTC time and temperature: ";
char TextInfo24[] PROGMEM = " TMP36: ";
char* TextItemPointers[] PROGMEM = {
  TextInfo0,TextInfo1,TextInfo2,TextInfo3,TextInfo4,TextInfo5,TextInfo6,TextInfo7,TextInfo8,TextInfo9,TextInfo10,TextInfo11,TextInfo12,TextInfo13,TextInfo14,TextInfo15,TextInfo16,TextInfo17,TextInfo18,TextInfo19,TextInfo20,TextInfo21,TextInfo22,TextInfo23,TextInfo24
};
#define WELCOME_MESSAGE              0
#define UNINITIALIZED_RTC_MESSAGE    1
#define DHCP_WAIT_MESSAGE            2
#define DHCP_FAIL_MESSAGE            3
#define DHCP_PASS_MESSAGE            4
#define IP_MESSAGE                   5
#define MASK_MESSAGE                 6
#define GATEWAY_MESSAGE              7
#define DNS_MESSAGE                  8
#define MAC_MESSAGE                  9
#define NTP_WAIT_MESSAGE            10
#define NTP_SEND_MESSAGE            11
#define NTP_RESPONSES_MESSAGE       12
#define NTP_NO_RESPONSE_MESSAGE     13
#define RTC_TIME_MESSAGE            14
#define RTC_OK_MESSAGE              15
#define RTC_SYNC_MESSAGE            16
#define RTC_TF_MESSAGE              17
#define RTC_DF_MESSAGE              18
#define ERROR_MESSAGE               19
#define TIMESOURCE_ERRROR_MESSAGE   20
#define TIME_ERRROR_MESSAGE         21
#define TEMPERATURE_ERRROR_MESSAGE  22
#define INFO_MESSAGE                23
#define SENSORS_MESSAGE             24
/////////////////////////////////////////////////////////////////////////////////////
/*-------- Time subroutines ----------*/
/////////////////////////////////////////////////////////////////////////////////////
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
};
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
};
/////////////////////////////////////////////////////////////////////////////////////
bool set_rtc_datum()
{
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write(3);//set register to day
  Wire.write(decToBcd(rtc_weekday));
  Wire.write(decToBcd(rtc_day));
  Wire.write(decToBcd(rtc_month));
  Wire.write(decToBcd(rtc_year-2000));
  if (Wire.endTransmission() == 0) {
    return true; 
  }
  else{
    return false;  
  };
};
bool set_rtc_time()
{
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write(0);//set register to time
  Wire.write(decToBcd(rtc_second));
  Wire.write(decToBcd(rtc_minute));
  Wire.write(decToBcd(rtc_hour));
  if (Wire.endTransmission() == 0) {
    return true;  
  }
  else{
    return false;  
  };
};
bool get_rtc_datum()
{
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write(3);//set register to day
  Wire.endTransmission();
  if (Wire.requestFrom(DS3231_ADDRESS, 4) == 4) { //get ?5? bytes(day,date,month,year);
    rtc_weekday = bcdToDec(Wire.read());
    rtc_day     = bcdToDec(Wire.read());
    rtc_month   = bcdToDec(Wire.read());
    rtc_year    = bcdToDec(Wire.read())+2000;
    RTCworking = true;
    return true;
  }
  else{
    RTCworking = false;
    return false;
  };
};
bool get_rtc_time()
{
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write(0);//set register to time
  Wire.endTransmission();
  if (Wire.requestFrom(DS3231_ADDRESS, 3) == 3) { //get 3 bytes (seconds,minutes,hours);
    rtc_second = bcdToDec(Wire.read() & 0x7f);
    rtc_minute = bcdToDec(Wire.read());
    rtc_hour   = bcdToDec(Wire.read() & 0x3f);
    return true;
  }
  else{
    return false;
  };
};
float get_rtc_temperature()
{
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write(17);//set register to DS3132 internal temperature sensor
  Wire.endTransmission();
  if (Wire.requestFrom(DS3231_ADDRESS, 2) == 2) {
    float ttc = (float)(int)Wire.read();
    byte portion = Wire.read();
    if(portion == 0b01000000) ttc += 0.25;
    if(portion == 0b10000000) ttc += 0.5;
    if(portion == 0b11000000) ttc += 0.75;
    return ttc;
  }
  else{
    return MAX_TEMPERATURE_EXCEED;
  };
};
/////////////////////////////////////////////////////////////////////////////////////
/*-------- NTP subroutines ----------*/
/////////////////////////////////////////////////////////////////////////////////////
#define NTP_PACKET_SIZE 48 // NTP time is in the first 48 bytes of message
/////////////////////////////////////////////////////////////////////////////////////
time_t getNtpTime(){
  if (ntp_interrupt_in_progress == false){
    ntp_interrupt_in_progress = true;
    Serial.println(GetTextFromFlashMemory(NTP_SEND_MESSAGE));
    while (Udp.parsePacket() > 0); // discard any previously received packets
    byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets 
    memset(packetBuffer, 0, NTP_PACKET_SIZE);
    packetBuffer[0]  = 0b11100011;   // LI, Version, Mode
    packetBuffer[1]  = 0;     // Stratum, or type of clock
    packetBuffer[2]  = 6;     // Polling Interval
    packetBuffer[3]  = 0xEC;  // Peer Clock Precision
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;
    Udp.beginPacket(timeServer, 123); //NTP requests are to port 123
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
    delay(1500);
    volatile time_t result = 0;
    volatile int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      volatile unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      result = secsSince1900 - 2208988800UL + TIME_ZONE_INT * SECS_PER_HOUR;
      if (
      (
      (                                                                  // plati od
      (month(result) == DAYLIGHTSAVING_START_MONTH)                      // breznove
      and (month(nextSunday(result)) == (DAYLIGHTSAVING_START_MONTH+1))  // posledni nedele
      and 
        (
      (weekday(result) != 1)
        or
        (hour(result) >= DAYLIGHTSAVING_START_HOUR)                      // od dvou hodin rano
      )
        )
        or  (month(result) > DAYLIGHTSAVING_START_MONTH)                 // a v nasledujich mesicich
      )
        and
        (
      (                                                                  // plati do
      (month(result) == DAYLIGHTSAVING_STOP_MONTH)                       // rijnove
      and (month(nextSunday(result)) == DAYLIGHTSAVING_STOP_MONTH)       // posledni nedele
      )
        or
        (
      (month(result) == DAYLIGHTSAVING_STOP_MONTH)
        and (month(nextSunday(result)) == (DAYLIGHTSAVING_STOP_MONTH+1))
        and (weekday(result) == 1)
        and (hour(result) < DAYLIGHTSAVING_STOP_HOUR)
        )
        or  (month(result) < DAYLIGHTSAVING_STOP_MONTH)                  // a v predchozich mesicich
      ) 
        ){
        result = result + DAYLIGHTSAVING_TIMESHIFT * SECS_PER_HOUR;
        TIME_ZONE = TIME_ZONE_DST;
      }
      else{
        TIME_ZONE = TIME_ZONE_NODST;
      };
      NTPworking = true;
      Serial.print(GetTextFromFlashMemory(NTP_RESPONSES_MESSAGE));
      Serial.println(ComposeTimeStamp(TIME_SYSTEM_CLOCK bitor TIME_FORMAT_HUMAN, result));
      Serial.print(GetTextFromFlashMemory(RTC_TIME_MESSAGE));
      Serial.println(ComposeTimeStamp(TIME_RTC_CLOCK bitor TIME_FORMAT_HUMAN, 0));
    }
    else{
      NTPworking = false;
      Serial.println(GetTextFromFlashMemory(NTP_NO_RESPONSE_MESSAGE));
    }
    ntp_interrupt_in_progress = false;
    return result;
  };
};
/////////////////////////////////////////////////////////////////////////////////////
bool checkntp()
{
  Serial.print(GetTextFromFlashMemory(NTP_WAIT_MESSAGE));
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    Serial.print(timeServer[thisByte], DEC);
    if (thisByte < 3) {
      Serial.print(F("."));
    };
  };
  Serial.println(F(" ..."));
  setTime(getNtpTime());
  get_rtc_datum();
  get_rtc_time();
  time_t avr_now = now();
  setTime(rtc_hour, rtc_minute, rtc_second, rtc_day, rtc_month, rtc_year);
  time_t rtc_now = now();
  setTime(avr_now);
  if (rtc_now == avr_now){
    Serial.println(GetTextFromFlashMemory(RTC_OK_MESSAGE));
  }
  else{
    if ( NTPworking ) {
      rtc_second = second();
      rtc_minute = minute();
      rtc_hour   = hour();
      rtc_weekday = weekday();
      rtc_day     = day();
      rtc_month   = month();
      rtc_year    = year();
      if (!set_rtc_time()){
        Serial.println(GetTextFromFlashMemory(RTC_TF_MESSAGE));
      }
      else{
        if (!set_rtc_datum()){
          Serial.println(GetTextFromFlashMemory(RTC_DF_MESSAGE));
        }
        else{
          Serial.println(GetTextFromFlashMemory(RTC_SYNC_MESSAGE));
        };
      };
      return true;
    }
    else{
      Serial.println(GetTextFromFlashMemory(ERROR_MESSAGE));
      return false;
    };
  };
};
/////////////////////////////////////////////////////////////////////////////////////
/*-------- DHCP subroutine ----------*/
/////////////////////////////////////////////////////////////////////////////////////
bool checkdhcp(){
  Serial.println(GetTextFromFlashMemory(DHCP_WAIT_MESSAGE));
  if (Ethernet.begin(mac) == 0) 
  {
    Serial.println(GetTextFromFlashMemory(DHCP_FAIL_MESSAGE));
    return false;
  } 
  else {
    Serial.println(GetTextFromFlashMemory(DHCP_PASS_MESSAGE));
    Serial.print(GetTextFromFlashMemory(IP_MESSAGE));
    Serial.println(Ethernet.localIP());
    Serial.print(GetTextFromFlashMemory(MASK_MESSAGE));
    Serial.println(Ethernet.subnetMask());
    Serial.print(GetTextFromFlashMemory(GATEWAY_MESSAGE));
    Serial.println(Ethernet.gatewayIP());
    Serial.print(GetTextFromFlashMemory(DNS_MESSAGE));
    Serial.println(Ethernet.dnsServerIP());
    Serial.print(GetTextFromFlashMemory(MAC_MESSAGE));
    for (byte thisByte = 0; thisByte < 6; thisByte++) {
      if (mac[thisByte] < 16){
        Serial.print(F("0"));
      };
      Serial.print(mac[thisByte], HEX);
      if (thisByte < 5) {
        Serial.print(F(":"));
      };
    };
    Serial.println();
    Udp.begin(LOCALUDPPORT); 
    return true;
  };
};
/////////////////////////////////////////////////////////////////////////////////////
/*-------- FLASH ROM subroutine ----------*/
/////////////////////////////////////////////////////////////////////////////////////
String GetTextFromFlashMemory(int ItemIndex) //function to return string by index
{
  int i = 0;
  char c;
  while ((c != '\0') and (i < BUFFERSIZE))
  {
    c = pgm_read_byte(pgm_read_word(&TextItemPointers[ItemIndex]) + i);
    buffer[i] = c;
    i++;
  }
  buffer[i] = '\0';
  return String(buffer);
}
/////////////////////////////////////////////////////////////////////////////////////
/*-------- PrintTime subroutines ----------*/
/////////////////////////////////////////////////////////////////////////////////////
String ComposeZerosLeadedNumber(unsigned long int MyNumber, byte NumberOfCharacters){
  String TempString = "";
  for(byte index=1;index<=NumberOfCharacters;index++){
    TempString = "0" + TempString;
  };
  TempString = TempString + String(MyNumber);
  int ifrom = TempString.length() - NumberOfCharacters;
  int ito = TempString.length();
  return TempString.substring(ifrom,ito);
}; 
String ComposeTimeStamp(byte details, time_t datum){
  String strday;
  String strmonth;
  String stryear;
  String strhour;
  String strminute;
  String strsecond;
  String strweekday;
  int myvalue;
  if (((details bitand TIME_RTC_CLOCK) == TIME_RTC_CLOCK) and (get_rtc_datum())){
    time_t savesystime = now();
    get_rtc_time();
    setTime(rtc_hour, rtc_minute, rtc_second, rtc_day, rtc_month, rtc_year);
    datum = now();
    setTime(savesystime);
  }
  else{
    if ((details bitand TIME_SYSTEM_CLOCK) == TIME_SYSTEM_CLOCK){
      if (datum <= 0){
        datum=now();
      };
    }
    else{
      strday = GetTextFromFlashMemory(TIMESOURCE_ERRROR_MESSAGE);
      return strday;
    };
  };
  if (!RTCworking and !NTPworking){
    strday = GetTextFromFlashMemory(TIME_ERRROR_MESSAGE);
    return strday;
  };
  strday = ComposeZerosLeadedNumber(day(datum),2);
  myvalue = month(datum);
  if ((details bitand TIME_FORMAT_SMTP_DATE) == TIME_FORMAT_SMTP_DATE){
    strmonth = monthShortStr(myvalue);
  }
  else{
    strmonth = ComposeZerosLeadedNumber(myvalue,2);
  };
  stryear = String(year(datum));
  strhour = ComposeZerosLeadedNumber(hour(datum),2);
  strminute = ComposeZerosLeadedNumber(minute(datum),2);
  strsecond = ComposeZerosLeadedNumber(second(datum),2);
  int tweekday;
  if (details bitand TIME_RTC_CLOCK){
    tweekday = rtc_weekday;
  }
  else{
    tweekday = weekday(datum);
  };
  if ((details bitand TIME_FORMAT_HUMAN) == TIME_FORMAT_HUMAN){
    strweekday = dayStr(tweekday);
  }
  else{
    strweekday = dayShortStr(tweekday);
  };
  if ((details bitand TIME_FORMAT_HUMAN) == TIME_FORMAT_HUMAN){
    return String(strday + "." + strmonth + "." + stryear + " " + strhour + ":" + strminute + ":" + strsecond + " " + strweekday);
  }
  else{
    if ((details bitand TIME_FORMAT_HUMAN_SHORT) == TIME_FORMAT_HUMAN_SHORT){
      return String(strday + "." + strmonth + "." + stryear + " " + strhour + ":" + strminute + ":" + strsecond + " ");//20 characters for LCD
    }
    else{
      if ((details bitand TIME_FORMAT_SMTP_MSGID) == TIME_FORMAT_SMTP_MSGID){
        return String(stryear+strmonth+strday+strhour+strminute+strsecond+".")+String(millis());
      }
      else{
        return String(strweekday + ", " + strday + " " + strmonth + " " + stryear + " " + strhour + ":" + strminute + ":" + strsecond + " " + TIME_ZONE);
      };
    };

  };
};
/////////////////////////////////////////////////////////////////////////////////////
float ComposeTemperatureValue(byte sensor){
  String TempStr = "";
  // Convert Analog value to temperature
  // 10 mV na 1 stupen Celsia/Kevina
  float TemperatureC1, TemperatureC2;
  if (sensor == TEMPERATURE_SENSOR_DS3231){
    TemperatureC1 = get_rtc_temperature();
  }
  else{
    do
    {
      TemperatureC1 = (((analogRead(sensor) * (5000.0/1024)) - 750) / 10) + 25;
      delay(10);
      TemperatureC2 = (((analogRead(sensor) * (5000.0/1024)) - 750) / 10) + 25;
    }
    while( TemperatureC1 != TemperatureC2 );
  };
  return TemperatureC1;
};
/////////////////////////////////////////////////////////////////////////////////////
String ComposeTemperature(byte sensor){
  String TempStr = "";
  // Convert Analog value to temperature
  // 10 mV na 1 stupen Celsia/Kevina
  float TemperatureC = ComposeTemperatureValue(sensor);
  if (TemperatureC < MAX_TEMPERATURE_EXCEED) {
    char buffer[16];
    TempStr = dtostrf(TemperatureC,5,2,buffer);
    TempStr = TempStr + " \xB0\x43";
  }
  else{
    //error message
    TempStr = GetTextFromFlashMemory(TEMPERATURE_ERRROR_MESSAGE);
  };
  return TempStr;
};
/////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(BAUD_RATE);
  Wire.begin();
  pinMode(TEMPERATURE_SENSOR_TMP36_1, INPUT);
  pinMode(TEMPERATURE_SENSOR_TMP36_2, INPUT);
  pinMode(SDCARDPin, OUTPUT);
  digitalWrite(SDCARDPin, HIGH);
  for(byte index = 10;index > 0; index--){
    delay(1000);
    Serial.println(index);
  };
  Serial.println(GetTextFromFlashMemory(WELCOME_MESSAGE));
};
/////////////////////////////////////////////////////////////////////////////////////
void loop() {
  Serial.println(GetTextFromFlashMemory(UNINITIALIZED_RTC_MESSAGE));
  Serial.print(ComposeTimeStamp(TIME_RTC_CLOCK bitor TIME_FORMAT_HUMAN, 0)); 
  Serial.print(" ");
  Serial.println(ComposeTemperature(TEMPERATURE_SENSOR_DS3231));        
  if (checkdhcp()){
    while(true){    
      if (checkntp()){
        Serial.println();
        while(true){
          Serial.print(GetTextFromFlashMemory(INFO_MESSAGE));
          Serial.print(ComposeTimeStamp(TIME_RTC_CLOCK bitor TIME_FORMAT_HUMAN, 0)); 
          Serial.print(" ");
          Serial.print(ComposeTemperature(TEMPERATURE_SENSOR_DS3231));        
          Serial.print(GetTextFromFlashMemory(SENSORS_MESSAGE));
          Serial.print(ComposeTemperature(TEMPERATURE_SENSOR_TMP36_1));
          Serial.print(" ");
          Serial.println(ComposeTemperature(TEMPERATURE_SENSOR_TMP36_2));
          delay(9530);
        };
      }
      else{
        delay(60000);
      };
    };
  }
  else{
    delay(60000);
  };
};
/////////////////////////////////////////////////////////////////////////////////////
