#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h> // La bibliothèque pour l'application de Telegram'
#include <ArduinoJson.h>
#include <Keypad.h> // La bibliothèque pour le clavier 4x4
#include <LiquidCrystal_I2C.h> // La bibliothèque pour l’écran LCD I2C 1602
#include <ESP32Servo.h> // La bibliothèque pour contrôler le servomoteur

// Les informations d’identification réseau
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// Initialiser Telegram BOT
String chatId = "5961859310";
String BOTtoken = "5985906933:AAHNpX_11PsNrpkhbPwSM9SCevMH4WF7Kdg";
WiFiClientSecure clientTCP;

UniversalTelegramBot bot(BOTtoken, clientTCP);

LiquidCrystal_I2C lcd(0x27, 16, 2); // Obtient l’écran LCD
Servo servo;
int buzz=19;                    // choisir la broche d’entrée (pour Buzzer)
int inputPin = 4;               // choisir la broche d’entrée (pour le capteur PIR sensor)
int pirState = LOW;             // Nous commençons, en supposant qu’aucun mouvement n’a été détecté
int val = 0;                    // Variable de lecture de l’état du code pin

#define Password_Length 5 // la longueur du mot de passe, si le mot de passe comporte 4 chiffres, définissez-la sur 5
int Position = 0; // Position du servo
char Particular[Password_Length]; // la longueur du mot de passe 
char Specific[Password_Length] = "2023"; // Le mot de passe
byte Particular_Count = 0, Specific_Count = 0; // compte le nombre de chiffres et vérifie si le mot de passe est correct
char Key; 
const byte ROWS = 4; // Le nombre de lignes sur le clavier
const byte COLS = 4; // Le nombre de colonnes sur le clavier
char keys[ROWS][COLS] = { // Définit les lignes et les colonnes
  // Définit les chiffres du clavier
  {'1','2','3','A'}, 

  {'4','5','6','B'},

  {'7','8','9','C'},

  {'*','0','#','D'}
};
bool SmartDoor = true; // Le servo 
// les broches dans lesquelles brancher le clavier
byte rowPins[ROWS] = {13, 12, 14, 27};
byte colPins[COLS] = {26,25, 33, 32};
Keypad myKeypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS); // Obtient les données du clavier

// Caractères verrouillés
byte Locked[8] = { 
  B01110,
  B10001,
  B10001,
  B11111,
  B11011,
  B11011,
  B11011,
  B11111
};
// caractère ouvert
byte Opened[8] = {
  B01110,
  B00001,
  B00001,
  B11111,
  B11011,
  B11011,
  B11011,
  B11111
};
int pinLedVert=15;
int pinLedRouge=2;
int lockState = 0;
String r_msg = "";
const unsigned long BOT_MTBS = 1000; // Temps moyen entre les messages d’analyse
unsigned long bot_lasttime; // Dernière analyse des messages

String unlockDoor(){  
  if (lockState == 0) {
    ServoOpen();
    digitalWrite(pinLedVert, HIGH);
    lcd.clear();
    lcd.setCursor(1,0); 
    lcd.print("Door Opened");
    lockState = 1;
    delay(100);
    return "Door Unlocked. /lock";
 }
  else{
    return "Door Already Unlocked. /lock";
 }  
}
String lockDoor(){
  if (lockState == 1) {
    ServoClose();
    digitalWrite(pinLedVert, LOW);
    lcd.clear();
    lcd.setCursor(1,0); 
    lcd.print("Door Closed");
    lockState = 0;
    delay(100);
    return "Door Locked. /unlock";
 }
  else{
    return "Door Already Locked. /unlock";
 }
}
void handleNewMessages(int numNewMessages){
  Serial.print("Handle New Messages: ");
  Serial.println(numNewMessages);
  for (int i = 0; i < numNewMessages; i++){
    // ID de chat du demandeur
    String chat_id = String(bot.messages[i].chat_id);
    if (chat_id != chatId){
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }
    // Imprimer le message reçu
    String text = bot.messages[i].text;
    Serial.println(text);

    String fromName = bot.messages[i].from_name;
    if (text == "/lock"){
      String r_msg = lockDoor();
      bot.sendMessage(chatId, r_msg, "");
    }
    if (text == "/unlock"){
      String r_msg = unlockDoor();
      bot.sendMessage(chatId, r_msg, "");
    }
    if (text == "/start"){
      String welcome = "Welcome to the ESP32 Telegram Smart Lock.\n";
      welcome += "/unlock : Unlock the Door\n\n";
      welcome += "/lock : Lock the Door\n";
      bot.sendMessage(chatId, welcome, "Markdown");
    }
  }
}
void setup()
{
  pinMode(pinLedVert, OUTPUT);  //Déclarer la LED vert comme sortie
  pinMode(pinLedRouge, OUTPUT); // Déclarer la LED rouge comme sortie
  pinMode(inputPin, INPUT);   // Déclarer le capteur PIR sensor comme entrée
  Serial.begin(115200);
  pinMode(buzz, OUTPUT);      // Déclarer le capteur Buzzer comme entrée
  servo.attach(18); // Attache le servo à la broche 18
  ServoClose(); // Ferme le servo lorsque vous dites cette fonction
  lcd.init(); // Initialise l’écran LCD 
  lcd.backlight(); // allume leLCD

  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);  
  clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  bot.sendMessage(chatId, "Bot started up", "");
}
void loop()
{
  if (SmartDoor == 0) // Ouvre la porte intelligente
  {
    Key = myKeypad.getKey(); // Le mot clé = myKeypad qui obtient la valeur
    if (Key == '#') // lorsque la touche '#' est enfoncée
    {
      lcd.clear(); // Efface l’écran LCD
      ServoClose(); // Ferme le servomoteur
      lcd.setCursor(2,0); // Définit le curseur sur l’écran LCD
      lcd.print("Door Closed"); // Imprime le texte sur l’écran LCD
      lcd.createChar(0, Locked); // Imprime le caractère verrouillé
      lcd.setCursor(14,0); // Définit le curseur sur l’écran LCD
      //digitalWrite(pinLedVert, LOW);
      lcd.write(0); // Imprime le premier caractère lorsque vous êtes sur la page fermée de la porte
      delay(3000); // Attend 3 secondes
      SmartDoor = 1; // ferme la porte
    }
  }  
  else Open(); // keeps the door open 
  if (millis() - bot_lasttime > BOT_MTBS)
  {
     int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

     while (numNewMessages)
     {
      Serial.println("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
     }
     bot_lasttime = millis();
  }
    
}
void clearData() // efface les données
{
  while (Particular_Count != 0) // compte les chiffres enfoncés
  {
    Particular[Particular_Count--] = 0; // compte le nombre de chiffres
  }
  return; // Renvoie les données
}
void ServoOpen() // Ouvre le servo
{
  for (Position = 180; Position >= 0; Position -= 5) { // se déplace de 0 à 180 degrés
    servo.write(Position); // se déplace vers la position
    delay(15); // Attend 15 millisecondes
  }
}
void ServoClose() // Ferme le servo
{
  for (Position = 0; Position <= 180; Position += 5) { // se déplace de la position 0 à 180 degrés
    servo.write(Position); // se déplace vers la position
    delay(15); // Attend 15 millisecondes
  }
}
void Open() // déclarations de fonction
{
  val = digitalRead(inputPin);  // Lire la valeur d’entrée
  if (val == HIGH) {            // vérifiez si l’entrée est HIGH
    if (pirState == LOW) {
      // nous venons de tourner ON
      Serial.println("Motion detected!");
      // Nous voulons seulement imprimer sur le changement de sortie, pas l’état
      pirState = HIGH;
      lcd.setCursor(1,0); // Définit le curseur sur l’écran LCD
      lcd.print("Enter Password:"); // Imprime le texte
    }
  } else {
    if (pirState == HIGH) {
      // nous venons de tourner OF
      Serial.println("Motion ended!");
      // Nous voulons seulement imprimer sur le changement de sortie, pas l’état
      pirState = LOW;
    }
  }
  Key = myKeypad.getKey(); // Obtient les touches sur lesquelles vous appuyez à partir du clavier

  if (Key)
  {
    Particular[Particular_Count] = Key; 
    lcd.setCursor(Particular_Count, 1); // Définit le curseur sur l’écran LCD
    lcd.print("*"); // imprime '*' au lieu du mot de passe
    Particular_Count++; // compte la longueur du mot de passe
    digitalWrite(pinLedVert, LOW);
    digitalWrite(pinLedRouge, LOW);
  }
  if (Particular_Count == Password_Length - 1) // Obtient la longueur du mot de passe 
  {
    if (!strcmp(Particular, Specific)) // compte la longueur et vérifie si le mot de passe est correct
    {
      lcd.clear();
      lcd.setCursor(2,0); // Définit le curseur sur l’écran LCD
      digitalWrite(buzz, LOW);
      lcd.print("Door Opened");
      bot.sendMessage(chatId, "Door Opened");
      Serial.println("Accès autorisé");
      digitalWrite(pinLedVert, HIGH);
      delay(3000);
      ServoOpen(); // Déplace le servo à 180 degrés
      lcd.createChar(1, Opened);
      lcd.setCursor(14,0); // Définit le curseur sur l’écran LCD
      lcd.write(1);
      lcd.setCursor(0,1); // Définit le curseur sur l’écran LCD
      lcd.print("Press # to Close");
      digitalWrite(pinLedVert, LOW);
      SmartDoor = 0;
    }
    else
    {
      lcd.clear();
      tone(19, 262, 250);
      lcd.setCursor(0,0); // Définit le curseur sur l’écran LCD
      lcd.print("Wrong Password"); // Imprime le texte/caractère
      lcd.setCursor(0,1);
      Serial.println("Accès refusé");
      digitalWrite(pinLedRouge, HIGH);
      lcd.print("Try Again");
      lcd.setCursor(13,1);
      delay(3000);
      noTone(19);
      lcd.clear();
      lcd.print("Enter Password:");
      digitalWrite(pinLedRouge, LOW);
      SmartDoor = 1; // Ferme la porte intelligente
    }
    clearData(); // efface les données
  }
}