//librairies
#include <LiquidCrystal_I2C.h>        // Ecran LCD sur le bus I2C
#include <DS3231.h>                   // Horloge RTC 
#include <math.h>
#include <Stepper.h>                  // Driver du moteur pas à pas

// Constantes
const int ledPin[8] = {2, 3, 4, 5, 6, 7, 8, 9};
const String nom_phase[8] = {"NL", "PC", "PQ", "GC", "PL", "GD", "DQ", "DC"};
const int UpButtonPin = A1;
const int DwButtonPin = A2;
const int RsButtonPin = A3;
const float REFLUNE = 9227.70903;       // NOUVELLE LUNE    date julienne : 13 janvier 2021 à 05h01m UT
const int stepsPerRevolution = 32 * 64;   // nombre de pas par revolution du moteur : 2048

//  Variables globales
int numero_phase = 0;         // de 0 à 8 les 8 phases principales
float JD;                     // date julienne calculée
float k;                      // nombre décimal de lunaisons depuis la date de référence
bool mode_auto = true;        // mode automatique grâce à l'horloge RTC ou choix de la date
char sdate[12];               // String formatée dans la procédure JJMMAAA(JD) pour convertir la date julienne en date grégorienne
float Mposition = 0;          // Position du moteur (il faut l'initialiser au départ)
int long_press = 0;           // permet de compter la durée sur le bouton OK/RESET
bool goRAZ = true ;           // booleen qui lance la procédure d'initialisation s'il est à valeur "true".

// Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);     // écran LCD sur le port I2C
DS3231 clock;                           // I2C
RTCDateTime dt;
Stepper myStepper(stepsPerRevolution, 10, 11, 12, 13); // initialise la stepper library sur les pins 10 à 13

//############ INITIALISATION ###########################
void setup() {
  Serial.begin(9600);                     // option : port série ordi pour débogage, permet de faire des Serial.print dans la console
  clock.begin();                          // démarrer l'horloge
  //clock.setDateTime(2021, 01, 04, 10, 56, 00);   // pour configurer (une fois) l'horloge à l'heure UT puis on commente cette ligne par la suite
  //clock.setDateTime(__DATE__, __TIME__);  // ou alors  : Send sketch compiling time to Arduino mais attention on est alors plus en heure UT
  lcd.init();                             // initialise le lcd
  lcd.backlight();                        // rétroéclairage de l'écran LCD
  lcd.clear();                            // effacer l'écran des glitches au démarrage
  pinMode(UpButtonPin, INPUT_PULLUP);     // bouton J+1 sur le pin A1 en pull-up : état bas quand on appuie
  pinMode(DwButtonPin, INPUT_PULLUP);     // bouton J-1 sur le pin A2 en pull-up : état bas quand on appuie
  pinMode(RsButtonPin, INPUT_PULLUP);     // bouton ok  sur le pin A3 en pull-up : état bas quand on appuie
  for (int i = 0 ; i < 9; i++) {          // définition des pin pour les 8 LED des phases sur les pin 2 à 9
    pinMode(ledPin[i], OUTPUT);
  }
  myStepper.setSpeed(10);                 // défini la vitesse du moteur à 60 tour/mn:
}

//############ PROGRAMME PRINCIPAL ###########################
void loop() {
  dt = clock.getDateTime();                     // Quelle heure est-il? quel jour? donné par RTC
  // Calcul du jour julien à partir de ces données si on est en mode auto
  if (mode_auto) JD = Julien(dt.day, dt.month, dt.year, dt.hour, dt.minute);

  k = (JD - REFLUNE) / 29.53;                   // calcul de k en comptant le nombre de jours depuis ou avant la date de reférence
  while (k < 0) {                               // puis en divisant par une lunaison moyenne est de 29,53j.
    k = k + 1;                                  // on ramene le résultat dans un intervalle [0,1]
  }                                             // Bien sûr il y a plus simple mais c'était pour utiliser pédagogiquement un while 
  while (k > 1) {
    k = k - 1;
  }

  numero_phase = round(k * 8) % 8;              // on fait correspondre une phase principale dans l'intervalle [0,7]
  affiche_heure();                              // gestion de l'affichage sur le LCD  (date, heure, fraction de lunaison, phase... )
  phase_led();                                  // allume une LED correspondant à la phase

  if (digitalRead(UpButtonPin) == LOW) {              // bouton J+1
    while (digitalRead(UpButtonPin) == LOW) {}        // debounce : halt tant qu'il reste appuyé
    JD = int(JD) + 0.5;                               // se placer à midi.
    JD++;                                             // incrément JD de 1j
    mode_auto = false;                                // mode auto désactivé : on fige la valeur de JD
  }
  if (digitalRead(DwButtonPin) == LOW) {              // bouton J-1
    while (digitalRead(DwButtonPin) == LOW) {}
    JD = int(JD) + 0.5;                               // se placer à midi.
    JD--;                                             // désincrément JD de 1j
    mode_auto = false;
  }
  if (digitalRead(RsButtonPin) == LOW) {
    lcd.clear();
    while (digitalRead(RsButtonPin) == LOW) {        // tant que le bouton Ok/RESET est appuyé on incrémente long_press
      long_press++;
      lcd.setCursor(0, 0);
      lcd.print((long_press < 100) ? "RAZ : Non" : "RAZ : Oui");
      goRAZ = (long_press > 100) ? true : false;
    }
    long_press = 0;
    mode_auto = true;                                 // retour en mode auto
  }

  if (goRAZ) {                                        // appel de la procédure RAZ si besoin
    RAZ();
    goRAZ = false;
  }

  if (abs(k - Mposition) > .001) {       // sur un changement de k, changer la position du moteur et enregistrer la nouvelle position
    int sens = ((k - Mposition) > 0) ? - 2048 : 2048;   // évite de faire plus 1/2 tour par modulo 2048
    myStepper.step(sens / 30);       // optionnel : petit effet pour précéder un changement de position.
    delay (500);
    myStepper.step((abs(k - Mposition) < 0.5) ? (int)(sens * abs(k - Mposition)) - sens / 30 : (int)(sens * (abs(k - Mposition) - 1)) - sens / 30);
    Mposition = k;
  }
}

//############## AFFIHAGE SUR LE LCD     #####################
void affiche_heure() {
  lcd.setCursor(0, 0);
  lcd.print(JJMMAAAA(JD));                            // appel de la fonction JJMMAAAA pour convertir JD en string jj/mm/aaaa
  lcd.print(" ");
  if (mode_auto) {
    if (dt.hour < 10) lcd.print("0");                   // on prévoit de mettre un 0 si le nombre est plus petit que 10 : ex 08 au lieu de 8
    lcd.print(dt.hour);
    if (dt.second % 2 == 0) lcd.print(":"); else lcd.print(" ");   // astuce pour faire clignoter les ":" sur les secondes paires
    if (dt.minute < 10) lcd.print("0");                 // on prévoit de mettre un 0 si le nombre est plus petit que 10 : ex 08 au lieu de 8
    lcd.print(dt.minute);
  }
  else lcd.print("midi ");
  lcd.setCursor(0, 1);
  lcd.print(JD, 2);                                   // affiche le jour julien (modifié) avec 2 décimales
  lcd.print(" ");
  lcd.print(nom_phase[numero_phase]);                 // affiche le nom de la phase correspondant à numéro_phase dans la liste de noms de phase
  lcd.print(" ");
  if (k * 29.53 < 10) lcd.print(" ");
  lcd.print(k * 29.53, 1);                            // age de la lune en j avec 1 décimale
  lcd.print("j");
}

//############## LED PHASES DE LA LUNE    #####################
void phase_led() {                                   // allume la Led qui correspond à la phase et éteint les autres
  for (int i = 0; i < 9; i++) {                      // pour chaque i on allume la LED i si elle correspond  : sinon on l'éteint
    digitalWrite(ledPin[i], (numero_phase == i) ? HIGH : LOW);
  }
}

//############## INITIALISATION DU MOTEUR #####################
void RAZ() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" Remise A Zero  ");
  lcd.setCursor(0, 1);
  lcd.print("avec +/- puis Ok");
  while (digitalRead(RsButtonPin) == HIGH) {
    if (digitalRead(UpButtonPin) == LOW) myStepper.step(-1);
    if (digitalRead(DwButtonPin) == LOW) myStepper.step(1);
  }
  Mposition = 0;
  lcd.clear();
}

//############## CALCUL DU JOUR JULIEN   ########################
// fonction de conversion d'une date en jour julien décimal à partir de DD/MM/YYYY HH:MN
// jour julien tronqué (-2450000) pour éviter les problèmes de chiffres significatifs des float (ou double) sur arduino
float Julien (int DD, int MM, int YY, int HH, int MN) {
  float JJ = floor(365.25 * (((MM < 3) ? YY - 1 : YY) + 4716)) 
              + floor(30.6001 * (((MM < 3) ? MM + 12 : MM) + 1)) 
              + DD + 2 - floor(((MM < 3) ? YY - 1 : YY) / 100.0) 
              + floor((floor(((MM < 3) ? YY - 1 : YY) / 100.0)) / 4.0) - 1524.5 - 2450000;
  JJ = JJ + (HH + MN / 60.0) / 24.0;
  return JJ;
}

//############## CONVERSION DU JOUR JULIEN  en DATE JJ/MM/AAAA  ###
// fonction de conversion d'un jour julien en date JJ/MM/AAAA  cf J.Meeus
String JJMMAAAA (float jj) {
  float A, B, C, D, E, Z, M, F, alpha;
  int J, AA;
  Z = floor(jj + 0.5);
  F = jj - (int)(jj);             // on garde la partie décimale hors du calcul
  if (Z + 2450000 < 2299161) {    // pour en arriver là on remonte beaucoup en arrière !!
    A = Z + 2450000;
  }
  else {
    alpha = floor((Z + 2450000 - 1867216.25) / 36524.25);
    A = Z + 2450000 + 1 + alpha - floor(alpha / 4.0);
  }
  B = A + 1524;
  C = floor((B - 122.1) / 365.25);
  D = floor(365.25 * C);
  E = floor((B - D) / 30.6001);
  J = (int)(B - D - floor(30.6001 * E) + F );  // calcul du jour (entier)
  M = (E < 14) ? E - 1 : E - 13;               // calcul du mois
  AA = (M > 2) ? C - 4716 : C - 4715 ;         // calcul de l'année
  sprintf(sdate, "%02d/%02d/%04d", J, (int)M, (int)AA);
  return sdate;
}