// Import des librairies
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

/******* PARAMÈTRES À MODIFIER SELON VOTRE CONFIG ********/

#define ssid            "XXXXXXX"             // votre ssid   
#define password        "YYYYYYYYYYYYYYYYYY"  // votre mdp
#define mqtt_server     "192.168.X.X"         // ip du serveur MQTT mosquitto
#define mqtt_username   "XXXX"                // votre login mqtt 
#define mqtt_password   "XXXXXXXX"            // votre mdp mqtt
#define mqtt_topic_in   "domoticz/in"
#define mqtt_topic_out  "domoticz/out"
#define bouton_IDX      2                     // Doit être le même que le capteur virtuel dans Domotocz */
#define buttonPin       0                     // D3
#define ledPin          2                     // D4

int       etat_led            = LOW;          // led éteinte au démarrage
boolean   etat_change         = true;         // flag à true pour forcer l'envoie de l'etat de la led au démarrage (led off) sur domoticz
int       etat_bouton         = 0;            
int       ancien_etat_bouton  = 0; 
String    etat_led_string     = "Off";  

WiFiClient nodemcuClient;                    // On crée l'objet WiFiClient 
PubSubClient client(nodemcuClient);          // On crée l'objet PubSubClient

// La fonction callback qui reçoit et traite les infos que domoticz a envoyé a MQTT
void callback(char* topic, byte* payload, unsigned int length) {  
  DynamicJsonDocument jsonBuffer(MQTT_MAX_PACKET_SIZE);                      // On déclare un buffer comme doc json
  String msgEntrant="";
  Serial.print("[callback()] Message entrant [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {                                         // on met ce qui arrive dans msgEntrant
    msgEntrant+=((char)payload[i]); 
  }  
  Serial.print(msgEntrant);                                                  // on l'affiche 
  if ( strcmp(topic, mqtt_topic_out) == 0 ) {                                // si c'est bien le topic de domoticz
    DeserializationError error = deserializeJson(jsonBuffer, msgEntrant);    // on récupère les champs du json    
    if (error) {
      Serial.print(F("[callback()] deserializeJson() --> echec : "));
      Serial.println(error.f_str());
      return;
    }
    String idx = String(jsonBuffer["idx"]);                                  // le champs idx
    String valeur = String(jsonBuffer["nvalue"]);                            // le champs nvalue
    if ( idx == String(bouton_IDX)) {                
      if( valeur == "0") {                                                   // si nvalue vaut 0  
        if( etat_led = HIGH ) {                                              // et que la led est allumé
          digitalWrite(ledPin, LOW);                                         // on éteint la led
          etat_led = LOW;                                                    
          etat_led_string = "Off";
        } 
      } else {                                                               // sinon
        digitalWrite(ledPin, HIGH);                                          // on allume la led
        etat_led = HIGH; 
        etat_led_string = "On";
      }           
      Serial.printf("[callback()] La LED est sur : %s dans domoticz.\n", etat_led_string);   
      Serial.printf("[callback()] La vrai LED est sur : %s .\n", etat_led_string);   
      Serial.println();
    }                     
  }   
  delay(20); 
} 

// fonction qui connecte ou reconnecte le nodemcu au serveur MQTT
void MQTTconnect() {     
  while (!client.connected()) {
    Serial.println("[MQTTconnect()] Attente de connexion ...");
    if ( client.connect( "nodemcu", mqtt_username, mqtt_password) ) {             // connexion au serveur
      Serial.println("[MQTTconnect()] Connexion OK");                 
      Serial.print("[MQTTconnect()] Abonnement au topic domoticz/out --> ");
      if ( client.subscribe(mqtt_topic_out, 0) ) Serial.println("OK");            // abonnement au topic de domoticz
      else Serial.println("KO"); 
      Serial.println();       
    } else {      
      Serial.print("[MQTTconnect()] echec de connection : ");          
      Serial.print(client.state());
      Serial.println(" nouvelle tentative dans 5 secondes");
      Serial.println();   
      Serial.print("[MQTTconnect()] vérification etat led et ajustement avec domoticz: "); 
      BoutonPoussoir();        
      delay(5000);
    } 
  } 
} 

// fonction qui écoute le bouton 
void BoutonPoussoir() {  
  etat_bouton = digitalRead(buttonPin);                                      // ecoute du bouton
  if (etat_bouton != ancien_etat_bouton) {                                   // si different de l'ancien état                                               
    ancien_etat_bouton = etat_bouton;      
    if (etat_bouton == LOW) {                             
      etat_led = !etat_led;
      etat_change = true;
      etat_led ? etat_led_string = "On" : etat_led_string = "Off";  
      Serial.println("[BoutonPoussoir()] Bouton activé"); 
    }
    digitalWrite(ledPin, etat_led);    
  } 
  if(etat_change) {      
    char msgSortant[MQTT_MAX_PACKET_SIZE+1]; 
    Serial.print("[loop()] Message à publier [domoticz/in] --> "); 
    sprintf(msgSortant, "{\"command\" : \"switchlight\", \"idx\" : %d, \"switchcmd\": \"%s\"}\n", bouton_IDX, etat_led_string);
    Serial.println(msgSortant);
    Serial.print("[loop()] Publication sur domoticz/in --> ");
    if ( client.publish(mqtt_topic_in, msgSortant) ) { 
      etat_change = false; 
      Serial.println("OK"); 
      Serial.println(); 
    } 
    else {
      Serial.println("KO");  
      Serial.println(); 
    }
    Serial.printf("[loop()] La vrai LED est sur %s \n", etat_led_string);       
  } 
}  

void setup() {       
  Serial.begin(115200);
  delay(1000);
  Serial.println();
  Serial.println("[setup()] Initialisation ...");   
  pinMode(ledPin,OUTPUT);   
  digitalWrite(ledPin, etat_led);      
  pinMode(buttonPin,INPUT);    
  Serial.print("[setup()] Connexion au WIFI -> ");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);    
  }  
  Serial.println("OK");
  Serial.print("[setup()] Adresse ip : ");
  Serial.println(WiFi.localIP());   
  client.setServer(mqtt_server, 1883);                              // on défini le serveur MQTT
  client.setCallback(callback);                                     // on défini la fonction de callback 
  Serial.printf("[setup()] La LED est %s\n", etat_led_string);   
  Serial.println("[setup()] Initialisation OK");
  Serial.println();
} 

void loop() {   
  client.loop();
  if (!client.connected()) { 
    MQTTconnect();
  }     
  BoutonPoussoir();    
}