/*
 * RELOJ_LCD V2.0
 * Gabriel Tula - 11.10.21
 */

//#define _PROTEUS

//#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"

LiquidCrystal_I2C lcd(0x27,16,2);
RTC_DS1307 rtc;

 byte BTN_PIN = 6;
 byte INT_PIN = 2;
 byte SPK_PIN = 10;
 byte BATT_PIN = A2;
 byte SAMPLES = 1; // 30;
 byte BATT_ICON = 0;
 byte DCHGT_ICON = 7;
 uint32_t DEBOUNCETIME = 20;
 float VOLTS_PCT[21] = {3.31,3.68,3.69,3.71,3.74,3.76,3.77,3.78,
  3.79,3.81,3.82,3.84,3.88,3.92,3.95,3.98,4.00,4.05,4.09,4.13,4.18};
 float VREF = 5.0;
 float K_FACTOR = VREF / 1024.0;
 const byte BATT_CHRS[11][8] = {
  {0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01001,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01011,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01001,0b01011,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01001,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01011,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01001,0b01111,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01011,0b01111,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01001,0b01111,0b01111,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01011,0b01111,0b01111,0b01111,0b01111,0b01111},
  {0b00110,0b01111,0b01111,0b01111,0b01111,0b01111,0b01111,0b01111}
  };
byte DOWN_CHR[8] = {0b00000,0b00100,0b00100,0b00100,0b10101,0b01110,0b00100,0b00000};
// char daysOfWeek[7][4] = {"Dom","Lun","Mar","Mie","Jue","Vie","Sab"};
// char months[12][4] = {"Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Set","Oct","Nov","Dic"};
//char strBuff[17];
//String strBuff;
String strTime;
bool rfshDate = false;
bool rfshBatt = false;
bool shwVolts = false;
bool bkLight = true;
bool oldBtn = true;
bool debounce = false;
bool holdBtn = false;
bool beep = false;
bool getTemp = true;
bool ledSt = false;
bool battOK = true;
volatile bool alarm = false;
uint32_t newTime, oldTime, oldTimeA2;
uint32_t oldTimeD, oldTimeBtn, oldTimeLed;
uint32_t oldTimeBat, pressTime;
uint32_t ledDelay = 4500;
int auxTemp;
int battAcc = 0;
byte battCnt = 0;
byte battPct;
byte battLvl = 0;
byte oldBattLvl = 0;
float volts = 0.0;

bool get_button(){
  static bool oldbtn = true;
  uint32_t newtime = millis();
  if(debounce){
    if(newtime - oldTimeBtn >= DEBOUNCETIME) debounce = false;
  }
  if(!debounce) {
    bool newbtn = !digitalRead(BTN_PIN);
    if(newbtn != oldbtn){
      debounce = true;
      oldTimeBtn = newtime;
      oldbtn = newbtn;
    }
  }
  return oldbtn;
}

void format_data(String &buff, int d1, int d2, int d3, char sep){
  buff = "";
  if(d1 < 10) buff = "0";
  buff += d1;
  buff += sep ;
  if(d2 < 10) buff += "0";
  buff += d2;
  buff += sep;
  if(d3 < 10) buff += "0";
  buff += d3;
}
void show_data(){
  const char DEG_CHR = 0xDF;  // simbolo Grados
  //Serial.println(freeRam());
  String strBuff;
  DateTime actualTime = rtc.now();
//  sprintf(strBuff,"%02d:%02d:%02d",actualTime.hour(),actualTime.minute(),actualTime.second());
  format_data(strBuff,actualTime.hour(),actualTime.minute(),actualTime.second(),':');
  lcd.setCursor(0,0);
  lcd.print(strBuff);
  //Serial.println(freeRam());
  if(getTemp){
    getTemp = false;
//    auxTemp = (rtc.getTemperature() * 10 + 4) / 10;
    auxTemp = 24;
//    sprintf(strBuff,"%2d%cC",auxTemp,0xDF);
    strBuff = "";
    if(auxTemp < 10) strBuff = " ";
    strBuff += auxTemp;
    strBuff += DEG_CHR;
    strBuff += "C";
//    Serial.println(strBuff);
    lcd.setCursor(12,0);
    lcd.print(strBuff);
    //Serial.println(freeRam());
  }
  if(rfshDate){
//    sprintf(strBuff,"%02d.%02d.%02d",actualTime.day(),actualTime.month(),actualTime.year() % 100);
    format_data(strBuff,actualTime.day(),actualTime.month(),actualTime.year() % 100,'.');
    lcd.setCursor(0,1);
    lcd.print(strBuff);
    //Serial.println(freeRam());
    rfshDate = false;
  }
  if(rfshBatt){
    lcd.setCursor(10, 1);
    if(shwVolts){
      lcd.print(volts,2);
      lcd.print('V');
    }
    else {
      lcd.write(32);
      if(!battOK){
        lcd.write(32);
        lcd.write(DCHGT_ICON);
        lcd.write(DCHGT_ICON);
        lcd.write(DCHGT_ICON);
      }
      else {
//        sprintf(strBuff,"%3d%%",battPct);
        strBuff = "";
        if(battPct < 100) strBuff += ' ';
        if(battPct < 10) strBuff += ' ';
        strBuff += battPct;
        strBuff += '%';
        lcd.print(strBuff);
        //Serial.println(freeRam());
      }
    }
    lcd.write(BATT_ICON);
    rfshBatt = false;
  }
}

bool chk_serial(){
  String strBuff;
  char inChar;
  bool isValid = false;
  
  while(Serial.available() > 0){
    inChar = Serial.read();
    if(inChar == '\n') break;  // si es LF sale
    if(isDigit(inChar)) strTime += inChar;
    if(inChar == 'H'){
      DateTime actualTime = rtc.now();
//      sprintf(strBuff,"%02d:%02d:%02d - ",actualTime.hour(),actualTime.minute(),actualTime.second());
      format_data(strBuff,actualTime.hour(),actualTime.minute(),actualTime.second(),':');
      strBuff += " - ";
      Serial.print(strBuff);
//      sprintf(strBuff,"%02d.%02d.%02d\r\n",actualTime.day(),actualTime.month(),actualTime.year() % 100);
      format_data(strBuff,actualTime.day(),actualTime.month(),actualTime.year() % 100,'.');
      strBuff += "\r\n";
      Serial.print(strBuff);
      Serial.flush();
    }
  }
  isValid = (strTime.length() == 12);
  if(!isValid && (inChar == '\n')) strTime = "";
  return isValid;
}

byte to_decimal(char dd, char uu){
  return (dd & 0x0F) * 10 + (uu & 0x0F);
}

void adjust_time(){
  int hh, mm, ss, dd, mo, yy;
  hh = to_decimal(strTime[0], strTime[1]);
  mm = to_decimal(strTime[2], strTime[3]);
  ss = to_decimal(strTime[4], strTime[5]);
  dd = to_decimal(strTime[6], strTime[7]);
  mo = to_decimal(strTime[8], strTime[9]);
  yy = to_decimal(strTime[10], strTime[11]) + 2000;
  rtc.adjust(DateTime(yy, mo, dd, hh, mm, ss));
}

void display_test(){
  for(byte j = 0; j < 2; j++){
    for(byte i = 0; i < 16; i++){
      byte chr = 255 - j;
      lcd.setCursor(i, 0);
      lcd.write(chr);
      lcd.setCursor(i, 1);
      lcd.write(chr);
      delay(100);
    }
  }
}

void calc_volts(){
  
  battOK = false;
  battPct = 0;
  float vBatt = (battAcc / float(SAMPLES)) * K_FACTOR;
  for(int8_t i = 20; i >= 0; i--){
    if(vBatt >= VOLTS_PCT[i]){
      battPct = i * 5;
      battOK = true;
      break;
    }
  }
  battLvl = battPct / 10;
  if(battLvl != oldBattLvl){
    oldBattLvl = battLvl;
    byte tmpChr[8];
    memcpy(tmpChr,BATT_CHRS[battLvl],sizeof(BATT_CHRS[battLvl]));
    lcd.createChar(BATT_ICON,tmpChr);
  }
  volts = vBatt * 2.0;
}      

void onAlarm(){  // ** ISR para alarma **
  alarm = true;
}

void chime_alarm(){
// ** Chime **
  if(alarm){
//    if(rtc.alarmFired(2)){
//      rtc.clearAlarm(2);
//      beep = true;
//      rfshDate = true;
//    }
//    tone(SPK_PIN,8100,120);
//    oldTimeA2 = newTime;
    alarm = false;
  }  
  if(beep){
    if(newTime - oldTimeA2 >= 180UL){
      tone(SPK_PIN,8100,120);
      beep = false;
    }
  }
}

void read_batt_init(){  // ** Lectura inicial de nivel de batería **
  for(byte i = 0; i < SAMPLES; i++){
    battAcc += analogRead(BATT_PIN);
    delay(1);
  }
  calc_volts();
  battAcc = 0;
}

void read_batt() {
// ** Leer batería cada segundo **
  if(newTime - oldTimeBat >= 1000UL){
    oldTimeBat = newTime;
    battAcc += analogRead(BATT_PIN);
    battCnt++;
// ** actualizar nivel de bateria cada x samples
    if(battCnt == SAMPLES){
      calc_volts();
      battCnt = 0;
      battAcc = 0;
      rfshBatt = true;
    }
    //Serial.println(freeRam());
  }  
}

void show_batt_low(){
  if(!battOK){
    if(newTime - oldTimeLed >= ledDelay){
      oldTimeLed = newTime;
      ledSt = !ledSt;
      digitalWrite(LED_BUILTIN,ledSt);
      ledDelay = ledSt ? 500 : 4500;
    }
  }
  else {
    if(ledSt){
      ledSt = false;
      digitalWrite(LED_BUILTIN,LOW);
    }
  }  
}

void refresh_display(){
// ** Actualizar fecha y temperatura cada minuto **
  if(newTime - oldTimeD >= 60000UL){
    oldTimeD = newTime;
    rfshDate = true;
    getTemp = true;
  }
// ** Actualizar hora cada 200 ms **
  if(newTime - oldTime >= 200UL){
    oldTime = newTime;
    show_data();
  }
}

void time_adjust(){
  adjust_time();
  tone(SPK_PIN,1000,50);
  strTime = "";
  rfshDate = true;
  show_data();
}

void display_init(){
  lcd.init();
//  lcd.createChar(BATT_ICON,BATT_CHRS[battLvl]);
  byte tmpChr[8];
  memcpy(tmpChr,BATT_CHRS[battLvl],sizeof(BATT_CHRS[battLvl]));
  lcd.createChar(BATT_ICON,tmpChr);
  memcpy(tmpChr,DOWN_CHR,sizeof(DOWN_CHR));
  lcd.createChar(DCHGT_ICON,tmpChr);
//  lcd.createChar(DCHGT_ICON,DOWN_CHR);
  lcd.clear();
  delay(150);
  lcd.backlight();
  delay(150);
  display_test();
  delay(150);
  lcd.clear();
}

void rtc_init(){
  if (!rtc.begin()){
    lcd.setCursor(5,0);
    lcd.print(F("RELOJ"));
    lcd.setCursor(1,1);
    lcd.print(F("NO DISPONIBLE!"));
    abort();
  }
//  rtc.writeSqwPinMode(DS3231_OFF);
//  rtc.disable32K();
//  rtc.clearAlarm(1);
//  rtc.clearAlarm(2);
//  rtc.disableAlarm(1);
//  rtc.disableAlarm(2);
//  attachInterrupt(digitalPinToInterrupt(INT_PIN), onAlarm, FALLING);
//  rtc.setAlarm2(DateTime(2022,1,1,0,0,0),DS3231_A2_Minute); // alarma cada hora
 }

void button_actions(){
  bool newBtn = get_button();
  if(newBtn){
    pressTime = millis();
    while(get_button()){
      if(millis() - pressTime >= 500UL){
        if(bkLight) holdBtn = true;  // indica pulsación larga solo si el display encendido
        break;
      }
    }
  }  
  if(newBtn != oldBtn){
    oldBtn = newBtn;
    if(newBtn){
      tone(SPK_PIN,880,50);
      if(holdBtn){
        shwVolts = !shwVolts;//true;
        oldTimeBat = millis();
        battCnt = 0;
        battAcc = 0;
        rfshBatt = true;
      }
      else {
        bkLight = !bkLight;
        if(bkLight){
          rfshDate = true;
          show_data();
          lcd.display();
          delay(100);
          lcd.backlight();
        }
        else{
          lcd.noDisplay();
          lcd.noBacklight();
        }
      }
    }
    else {
      holdBtn = false;
    }
  }
}
//int freeRam() {
  
//  extern int __heap_start, *__brkval;
//  int v;
 // return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
//}

void setup(){
  pinMode(BTN_PIN,INPUT_PULLUP);
  pinMode(INT_PIN,INPUT_PULLUP);
  pinMode(SPK_PIN,OUTPUT);
  pinMode(LED_BUILTIN,OUTPUT);
  digitalWrite(LED_BUILTIN,LOW);
  strTime.reserve(15);
#ifdef _PROTEUS
  Serial.begin(57600);  // *** Proteus
#else
  Serial.begin(115200);
#endif
//  Wire.setClock(400000);
  display_init(); // iniciar display y presentación
  rtc_init(); // iniciar y verificar reloj
  delay(1000);
  show_data();
  delay(300);
  rfshDate = true;
  read_batt_init();  // primera lectura de batería
  rfshBatt = true;
  oldTime = millis();
  oldTimeD = oldTime;
  oldTimeBat = oldTime;
  Serial.println(F("LISTO!")); // mensaje para soft de PC
  Serial.flush();
}

void loop (){
  newTime = millis(); 
  chime_alarm();  // ** Chime **
  if(!rfshBatt){
    read_batt();  // ** Leer batería cada segundo **
  }
  show_batt_low();  // ** Chequear si batería baja y señalizarlo **
  if(bkLight){
    refresh_display();  // ** Refrescar display **
  }
  if(chk_serial()){  // ** Chequear puerto serie *
    time_adjust();  // ** Ajustar fecha y hora **
  }
  button_actions();  // ** Acciones desencadenadas por el botón
  delay(1);  // wokwi
}
GND5VSDASCLSQWRTCDS1307+