// *****************************************************
// *** Serrure de GN version 2.0
// *** Marcelin Delcour, 01-2017
// *** mathkuma (_@_) gmail (_._) com
// *****************************************************

#include <Keypad.h>
#include <EEPROM.h>

//on definit les etats possible de la serrure
#define FERMEE 0    // serrure fermée
#define OUVERTE 1   // serrure ouverte
#define BLOQUEE 2   // la serrure est bloquée pendant X secondes

#define TAILLEMAX 8

struct SERRURE_STRUCT
{
  char code[TAILLEMAX];
  int longCode;
  int erreurMax;
  int bloqueeDelai;
  int statutSerrure;
  int backup;
};


// *****************************************************
// *** 
// *** Paramètres modifiables via le clavier
// *** 
// *****************************************************

SERRURE_STRUCT mySerrure
{
  "12345678", // mot de passe par defaut : 12345678 , chiffre de 0 à 9 ou A, B, C, D
  4, // nombre de caracteres dans le code, !! entre 1 et 8 !!
  3,  // nombre d'erreur de code autorisées avant blocage
  10,  // delai pendant lequel la serrure sera bloquee, en secondes  
  FERMEE,  // état de la serrure, 0 fermée, 1 ouverte, 2 bloquée
  666  // marqueur de backup eeprom, pas touche !!
};

// *****************************************************
// *** Fonctions, serrure en mode OUVERTE
// *** appui long sur A -> changement du code, appuyer sur X characteres
// *** appui long sur B -> changement du nombre max d erreur, entrer le nouveau nombre + # pour valider
// *** appui long sur C -> changement du delai de blocage, entrer le delai + # pour valider 
// *** appui long sur D -> changement de la longueur du code, entre 1 et 8
// *****************************************************

// *****************************************************
// *** Fonction accessible pendant les 3 secondes qui suivent l'allumage
// *** appui simple sur # -> pour réinitialiser l'eeprom avec les parametres par defaut (voir SERRURE_STRUCT serrureDefaut) 
// *****************************************************





// on initialise une struct serrrue avec les valeurs par defaut
SERRURE_STRUCT serrureDefaut
{
  "12345678", // mot de passe par defaut : 123456789 , chiffre de 0 à 9 ou A, B, C, D
  4, // nombre de caracteres dans le code, !! entre 1 et 8 !!
  3,  // nombre d'erreur de code autorisées avant blocage
  10,  // delai pendant lequel la serrure sera bloquee, en secondes
  FERMEE,  // état de la serrure, 0 fermée, 1 ouverte, 2 bloquée
  666  // marqueur de backup eeprom, pas touche !!
};



// on definit les variables du clavier
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'#','0','#','D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 3, 2}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

// on definit les 2 LED
int ledVerte = 11;
int ledRouge = 12;
bool etatLed = false;

// on definit le buzzer
int buzzerPin = 10;

int statusSerrurePrecedent=FERMEE;


int mauvaisCodeCpt=0;
int paniqueCpt=0;
int nouveauCodeCpt=0;

bool codeOK = false;
bool nouveauCodeOK = false;
bool nouveauErreurMaxOK = false;
bool nouveauBloqueeDelaiOK = false;
bool nouveauLongCodeOK = false;

char codeTempo[TAILLEMAX]={'0','0','0','0','0','0','0','0'};

unsigned long int ul_PreviousMillisCP;

int ledCpt=0;
int ledStatut=HIGH;

void setup() 
{
  // on initialise la sortie serie pour debug
  Serial.begin(9600);

  // LED
  pinMode(ledVerte, OUTPUT);
  pinMode(ledRouge, OUTPUT);   

  majLed(ledVerte, LOW);
  majLed(ledRouge, HIGH);

  // buzzer
  pinMode(buzzerPin, OUTPUT); 

  // listener pour gerer les appuis longs
  keypad.addEventListener(keypadEvent); 
  keypad.setHoldTime(2000);

  // init time reference
  ul_PreviousMillisCP = millis();  

  // Check param
  mySerrure.longCode=max(min(mySerrure.longCode,8),1);
  
  

  // GESTION EEPROM
    
  // RESET
  // 3 secondes pour appuyer sur # et reset l'eeprom
  beep();
  int resetCpt=0;
  
  while(millis()-ul_PreviousMillisCP<3000)
  {
    char key = keypad.getKey();  
    if (key)
    {
      if (key == '#')
      {
        Serial.println("RESET EEPROM");
        // on ecrit les données de la serrure par defaut
        EEPROM.put(0, serrureDefaut);
        Serial.println("RESET EEPROM OK OK");
        beep();
        delay(150);
        beep();
        delay(150);
        
      }
    }

    resetCpt++;
    if (resetCpt>10000)
    {
      ledStatut=!ledStatut;
      majLed(ledVerte, ledStatut);
      majLed(ledRouge, ledStatut);
      resetCpt=0;
    }
  }    
   
  EEPROM.get(0, mySerrure);

  // 1ERE UTILISATION
  // premiere utilisation, on ecrit mySerrure dans l'eeprom
  if (mySerrure.backup!=666)
  {
    backupToEEPROM();
    Serial.println("1ERE UTILISATION, EEPROM OK");
  }

 
  // LECTURE DEPUIS EEPROM
  EEPROM.get(0, mySerrure);
  
  Serial.print("code: ");
  for (int i=0;i<mySerrure.longCode;i++)
  {
    Serial.print(mySerrure.code[i]);
  }
  Serial.println("");
  Serial.print("taille: ");
  Serial.print(mySerrure.longCode);
  Serial.print("  erreur: ");
  Serial.print(mySerrure.erreurMax);
  Serial.print("  delai: ");
  Serial.println(mySerrure.bloqueeDelai);

//  for (int i=0;i<8;i++)
//  {
//    code[i]=mySerrure.code[i];
//  }
//    
//  longCode=mySerrure.longCode;
//  erreurMax=mySerrure.erreurMax;
//  bloqueeDelai=mySerrure.bloqueeDelai;
   
  // le setup est termine, on commence !
  Serial.println("C'est parti !!");
  beep();  
}





void loop()
{
  // on traite selon l'etat de la serrure
  switch (mySerrure.statutSerrure)
    {
    case FERMEE:
    // serrure fermee
    fermee();
    break;
  
    case OUVERTE:
    // serrure ouverte
    ouverte();
    break;
  
    case BLOQUEE:
    // serrure en mode bloquee
    bloquee();
    break;
    
    default:
    // nothing
    break; 
    }

  char key = keypad.getKey();  

  // si une touche du clavier est pressee, on traite si la serrure nest pas deja bloquee
  if (key && mySerrure.statutSerrure!=BLOQUEE)
  {
    beep();
    
    // si autre chose qu'un # est presse, on met à jour le code temporaire
    if (key != '#')
    {
      for (int i=0;i<mySerrure.longCode-1;i++)
      {
        codeTempo[i] = codeTempo[i+1];
      }
      codeTempo[mySerrure.longCode-1] = key;
         
      Serial.print("nouveau code: ");
      for (int i=0;i<mySerrure.longCode;i++)
      {
        Serial.print(codeTempo[i]);
      }
    }
    else
    // un # a ete presse, on compare avec le code de la serrure
    {
      codeOK=true;

      for (int i=0;i<mySerrure.longCode;i++)
      {
         if (codeTempo[i] != mySerrure.code[i])
         {
          codeOK=false;
         }
      }

      // cas ou le code entre est le bon
      if (codeOK)
      {
        delay(100);
        beep();
        
        mySerrure.statutSerrure=!mySerrure.statutSerrure;
        mauvaisCodeCpt=0;

        // on affiche l etat de la serrure
        if (mySerrure.statutSerrure==0)
        {
          Serial.println("Serrure fermee");
        }
        else
        {
          Serial.println("Serrure ouverte");
        }
        
      }
      // cas ou le code entre est faux
      else
      {
        longBeep();
        mauvaisCodeCpt+=1;

        blinkLed();

        // si en plus il y a eu plus de erreurMax essais faux, on bloque la serrure
        if (mauvaisCodeCpt>=mySerrure.erreurMax)
        {
          statusSerrurePrecedent=mySerrure.statutSerrure;
          mySerrure.statutSerrure=BLOQUEE;
          paniqueCpt=0;
            
          Serial.println("Serrure en mode bloquee");
        }
      }

      for (int i=0;i<mySerrure.longCode;i++)
      {
        codeTempo[0] = '0';
      }
              
      // on indique si le code est vrai ou faux
      if (codeOK==0)
      {
        Serial.println("code FAUX");
      }
      else
      {
        Serial.println("code OK");
      }
    }
  
    Serial.println("");    
  }

  delay(5);
}








void fermee()
{
  majLed(ledVerte, LOW);
  majLed(ledRouge, HIGH);
}

void ouverte()
{
  majLed(ledVerte, HIGH);
  majLed(ledRouge, LOW);
}

void bloquee()
{
  etatLed=!etatLed; 
  majLed(ledVerte, etatLed);
  majLed(ledRouge, !etatLed);

  paniqueCpt+=1;
  delay(100);

  if (paniqueCpt>(mySerrure.bloqueeDelai*10))
  {
    mySerrure.statutSerrure=statusSerrurePrecedent;
    mauvaisCodeCpt=0;
    paniqueCpt=0;
  }
}

void blinkLed()
{
  for (int i=0;i<5;i++)
  {
    majLed(ledVerte, HIGH);
    majLed(ledRouge, LOW);
    delay(150);
  
    majLed(ledVerte, LOW);
    majLed(ledRouge, HIGH);
    delay(150);    
  }
}

void majLed(int led, int etat)
{
  digitalWrite(led, etat);
}

void beep() 
{
  tone(buzzerPin, 1000, 50);
}

void longBeep() 
{
  // 1 seconde
  tone(buzzerPin, 500, 1000);
}


// Taking care of some special events.
void keypadEvent(KeypadEvent key)
{
    switch (keypad.getState())
    {             
    case HOLD:
      // si la serrure est a l'etat ouvert
      if (mySerrure.statutSerrure == OUVERTE)
      {
      // appui long sur A, on change le code
      if (key == 'A') 
        {
            beep();
            nouveauCodeCpt=0;
            nouveauCodeOK=false;
            nouveauCode();
            backupToEEPROM();
        }

      // appui long sur B, on change le nombre max d'erreur
      if (key == 'B') 
        {
            beep();
            nouveauErreurMaxOK=false;
            mySerrure.erreurMax=0;
            nouveauErreurMax();
            backupToEEPROM();
        }

      // appui long sur C, on change le delai de blocage
      if (key == 'C') 
        {
            beep();
            nouveauBloqueeDelaiOK=false;
            mySerrure.bloqueeDelai=0;
            nouveauBloqueeDelai();
            backupToEEPROM();
        }

      // appui long sur D, on change la taille du code
      if (key == 'D') 
        {
            beep();
            nouveauLongCodeOK=false;
            mySerrure.longCode=0;
            nouveauLongCode();
            backupToEEPROM();
        }
      }

    break;
    }
}



// changement de code
void nouveauCode()
{
  Serial.println("A - changement de code");
  majLed(ledVerte, HIGH);
  majLed(ledRouge, HIGH);

  
  while(!nouveauCodeOK)
  {
    char key = keypad.getKey();
    if (key)
    {  
      if (key != '#')
      {
        mySerrure.code[nouveauCodeCpt]=key;
        nouveauCodeCpt+=1;
        beep();
      }
    }

    if (nouveauCodeCpt==mySerrure.longCode)
    {
      nouveauCodeOK=true;
      Serial.print("code change: ");
      for (int i=0;i<mySerrure.longCode;i++)
      {
        Serial.print(mySerrure.code[i]);
      }
      Serial.println("");
      beep();
      delay(100);
      beep();
    }
  }
}


// changement du nombre max d erreur
void nouveauErreurMax()
{
  Serial.println("B - changement du nombre max d erreur");
  majLed(ledVerte, HIGH);
  majLed(ledRouge, HIGH);

  ledCpt=0;
  ledStatut=HIGH;
      
  while(!nouveauErreurMaxOK)
  {
    char key = keypad.getKey();
    
    if (key)
    {  
      // ascii code, 0=48, 9=57
      if (key>=48 && key<=57)
      {
        // nouveau nombre d erreur
        mySerrure.erreurMax*=10;
        mySerrure.erreurMax+=(key-48);
        beep();
      }
      else
      {
        if (key == '#')
        {
          nouveauErreurMaxOK=true;
          beep();
          delay(100);
          beep();
          mauvaisCodeCpt=0;

          if (mySerrure.erreurMax == 0)
          {
            mySerrure.erreurMax=1;
          }
          Serial.print("nouveau nb d erreur: ");
          Serial.println(mySerrure.erreurMax);
        }
      }
    }

    ledCpt++;
    if (ledCpt>10000)
    {
      ledStatut=!ledStatut;
      majLed(ledRouge, ledStatut);
      ledCpt=0;
    }
  }
}



// changement du delai de blocage
void nouveauBloqueeDelai()
{
  Serial.println("C - changement du delai de blocage");
  majLed(ledVerte, HIGH);
  majLed(ledRouge, HIGH);

  int ledCpt=0;
  int ledStatut=HIGH;
  
  while(!nouveauBloqueeDelaiOK)
  {
    char key = keypad.getKey();
    
    if (key)
    {  
      // ascii code, 0=48, 9=57
      if (key>=48 && key<=57)
      {
        // nouveau nombre d erreur
        mySerrure.bloqueeDelai*=10;
        mySerrure.bloqueeDelai+=(key-48);
        beep();
      }
      else
      {
        if (key == '#')
        {
          nouveauBloqueeDelaiOK=true;
          beep();
          delay(100);
          beep();
          paniqueCpt=0;
          Serial.print("nouveau delai: ");
          Serial.println(mySerrure.bloqueeDelai);
        }
      }
    }

    ledCpt++;
    if (ledCpt>10000)
    {
      ledStatut=!ledStatut;
      majLed(ledVerte, ledStatut);
      ledCpt=0;
    }    
  }
}



// changement de la taille du code
void nouveauLongCode()
{
  Serial.println("D - changement de la taille du code");
  majLed(ledVerte, HIGH);
  majLed(ledRouge, HIGH);

  int ledCpt=0;
  int ledStatut=HIGH;
  
  while(!nouveauLongCodeOK)
  {
    char key = keypad.getKey();
    
    if (key)
    {  
      // taille min=1, taille max=8
      // ascii code, 1=49, 8=56
      if (key>=49 && key<=56)
      {
        // nouvelle taille        
        mySerrure.longCode=(key-48);
        nouveauLongCodeOK=true;
        beep();
        delay(100);
        beep();
        paniqueCpt=0;
        Serial.print("nouvelle taille du code: ");
        Serial.println(mySerrure.longCode); 

        // on reinitialise le code à "12345678"
        for (int i=0;i<TAILLEMAX;i++)
        {
          mySerrure.code[i]=i+48+1;
        }
      }
    }

    ledCpt++;
    if (ledCpt>10000)
    {
      ledStatut=!ledStatut;
      majLed(ledVerte, ledStatut);
      majLed(ledRouge, ledStatut);
      ledCpt=0;
    }    
  }
}



void backupToEEPROM()
{
//  for (int i=0;i<8;i++)
//  {
//    mySerrure.code[i]=code[i];
//  }
//    
//  mySerrure.longCode=longCode;
//  mySerrure.erreurMax=erreurMax;
//  mySerrure.bloqueeDelai=bloqueeDelai;
//  mySerrure.statutSerrure=statutSerrure;
//  mySerrure.backup=666;
  
  EEPROM.put(0, mySerrure);
}