/*
 Version 0.1 - septembre 2019
*/ 

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WebSocketsClient.h>   //  https://github.com/kakopappa/sinric/wiki/How-to-add-dependency-libraries
#include <ArduinoJson.h>        // https://github.com/kakopappa/sinric/wiki/How-to-add-dependency-libraries
#include <StreamString.h>
#include "DHTesp.h"             // https://github.com/beegee-tokyo/DHTesp
#include <AceButton.h>          // https://github.com/bxparks/AceButton
using namespace ace_button;

#define DEBUG_WEBSOCKETS true

ESP8266WiFiMulti WiFiMulti;
WebSocketsClient webSocket;
DHTesp dht;

#define MyApiKey "xxxxxxxxxxxxxxxxxxxx"   // Changez votre cle API. votre cle API est affiché dans votre compte sur sinric.com
#define MySSID "xxxxxxxx"                 // Changez votre reseau Wifi
#define MyWifiPassword "xxxxxxxx"         // Changez votre mot de passe reseau Wifi 
#define Switch1 "xxxxxxxxx"               // Changez l'ID par celuis de votre switch crée sur sinric.com

#define HEARTBEAT_INTERVAL  300000 // 3 Minutes 
#define TEMPRATURE_INTERVAL 100000 // 1 Minutes 

#define SERVER_URL "iot.sinric.com" //"iot.sinric.com"
#define SERVER_PORT 80 // 80

const int LED_PIN = 13;     
const int BUTTON_PIN = 0;
const int RELAY_PIN = 12;
float temp;
float setpoint = 18.0;
uint64_t heartbeatTimestamp = 0;
uint64_t tempratureUpdateTimestamp = 0;
bool isConnected, flag = false;

AceButton button(BUTTON_PIN);

void handleEvent(AceButton*, uint8_t, uint8_t);
void setPowerStateOnServer(String deviceId, String value);
void setSetTemperatureSettingOnServer(String deviceId, String value, String scale);
void readTempature();

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_DISCONNECTED:
      isConnected = false;    
      Serial.printf("[WSc] Webservice disconnected from sinric.com!\n");
      break;
    case WStype_CONNECTED: {
      isConnected = true;
      Serial.printf("[WSc] Service connected to sinric.com at url: %s\n", payload);
      Serial.printf("Waiting for commands from sinric.com ...\n");        
      }
      break;
    case WStype_TEXT: {
        Serial.printf("[WSc] get text: %s\n", payload);
        // Example payloads For Thermostat
        // {"deviceId": xxxx, "action": "setPowerState", value: "ON"} // https://developer.amazon.com/docs/device-apis/alexa-thermostatcontroller.html
        // {"deviceId": xxxx, "action": "SetTargetTemperature", value: "targetSetpoint": { "value": 20.0, "scale": "CELSIUS"}} // https://developer.amazon.com/docs/device-apis/alexa-thermostatcontroller.html#settargettemperature
        // {"deviceId": xxxx, "action": "AdjustTargetTemperature", value: "targetSetpointDelta": { "value": 2.0, "scale": "FAHRENHEIT" }} // https://developer.amazon.com/docs/device-apis/alexa-thermostatcontroller.html#adjusttargettemperature
                   
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject((char*)payload); 
        String deviceId = json ["deviceId"];     
        String action = json ["action"];

        if (deviceId == Switch1) {// Device ID of first device
                
          if(action == "setPowerState") { // On or Off
            String value = json ["value"];
            Serial.println("[WSc] setPowerState" + value);
            if (value == "ON") {
              flag = true;
              digitalWrite(LED_PIN, LOW);
              digitalWrite(RELAY_PIN, HIGH);
            } else {
              flag = false;
              digitalWrite(LED_PIN, HIGH); 
              digitalWrite(RELAY_PIN, LOW);
            }
          }
          else if(action == "SetTargetTemperature") { 
            // Alexa, set thermostat to 20      
            //String value = json ["value"];
            String value = json["value"]["targetSetpoint"]["value"];
            String scale = json["value"]["targetSetpoint"]["scale"];
  
            Serial.println("[WSc] SetTargetTemperature value: " + value);
            Serial.println("[WSc] SetTargetTemperature scale: " + scale);
            setpoint = value.toFloat();
          }
          else if(action == "AdjustTargetTemperature") {
            //Alexa, make it warmer in here
            //Alexa, make it cooler in here
            String value = json["value"]["targetSetpointDelta"]["value"];
            String scale = json["value"]["targetSetpointDelta"]["scale"];  
  
            Serial.println("[WSc] AdjustTargetTemperature value: " + value);
            Serial.println("[WSc] AdjustTargetTemperature scale: " + scale);      
            setpoint = setpoint + value.toFloat();     
          }          
          else if (action == "test") {
                  Serial.println("[WSc] received test command from sinric.com");
          }
      }
    }
    
    break;
    case WStype_BIN:
      Serial.printf("[WSc] get binary length: %u\n", length);
      break;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  WiFiMulti.addAP(MySSID, MyWifiPassword);
  Serial.println();
  Serial.print("Connecting to Wifi: ");
  Serial.println(MySSID);  

  // Waiting for Wifi connect
  while(WiFiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  if(WiFiMulti.run() == WL_CONNECTED) {
    Serial.println("");
    Serial.print("WiFi connected. ");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }

  dht.setup(3, DHTesp::DHT22); // Connect DHT sensor to GPIO 3
  button.init(BUTTON_PIN);
  button.setEventHandler(handleEvent);
  digitalWrite(LED_PIN, HIGH); 
  digitalWrite(RELAY_PIN, LOW);
  // server address, port and URL
  webSocket.begin(SERVER_URL, SERVER_PORT, "/");

  // event handler
  webSocket.onEvent(webSocketEvent);
  webSocket.setAuthorization("apikey", MyApiKey);
  
  // try again every 5000ms if connection has failed
  webSocket.setReconnectInterval(5000);   // If you see 'class WebSocketsClient' has no member named 'setReconnectInterval' error update arduinoWebSockets
}

void loop() {
  webSocket.loop();  
  button.check();
  if(isConnected) {
      uint64_t now = millis();
      
      // Send heartbeat in order to avoid disconnections during ISP resetting IPs over night. Thanks @MacSass
      if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) {
          heartbeatTimestamp = now;
          webSocket.sendTXT("H");          
      }

      // Send the tempature settings to server
      if((now - tempratureUpdateTimestamp) > TEMPRATURE_INTERVAL) {
          tempratureUpdateTimestamp = now;
          readTempature();
          Serial.println(setpoint);
          Serial.println(temp);
          Serial.println(flag);
          if (temp < setpoint && flag == true) {
            Serial.println("en chauffe");
          } else {
            Serial.println("chauffage coupe");
          }
      }
  }   
}


// Read tempratre from DHT Sensor

void readTempature() { 
  //delay(dht.getMinimumSamplingPeriod());
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();
  float temperaturefh = dht.toFahrenheit(temperature);
  temp = temperature;
  Serial.print(dht.getStatusString());
  Serial.print("\t");
  Serial.print(humidity, 1);
  Serial.print("\t\t");
  Serial.print(temperature, 1);
  Serial.print("\t\t");
  Serial.print(dht.toFahrenheit(temperature), 1);
  Serial.print("\t\t");
  Serial.print(dht.computeHeatIndex(temperature, humidity, false), 1);
  Serial.print("\t\t");
  Serial.println(dht.computeHeatIndex(dht.toFahrenheit(temperature), humidity, true), 1);

  setSetTemperatureSettingOnServer("<device id>", temperature, "FAHRENHEIT", temperaturefh, humidity);   
  
}

// If you are going to use a push button to on/off the switch manually, use this function to update the status on the server
// so it will reflect on Alexa app.
// eg: setPowerStateOnServer("deviceid", "ON")

// Call ONLY If status changed. DO NOT CALL THIS IN loop() and overload the server. 

void setPowerStateOnServer(String deviceId, String value) {
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["deviceId"] = deviceId;
  root["action"] = "setPowerState";
  root["value"] = value;
  StreamString databuf;
  root.printTo(databuf);
  
  webSocket.sendTXT(databuf);
}

// Call ONLY If status changed. DO NOT CALL THIS IN loop() and overload the server. 

//eg: setSetTemperatureSettingOnServer("deviceid", 25.0, "CELSIUS" or "FAHRENHEIT", 23.0, 45.3)
// setPoint: Indicates the target temperature to set on the termostat.
void setSetTemperatureSettingOnServer(String deviceId, float setPoint, String scale, float ambientTemperature, float ambientHumidity) {
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["action"] = "SetTemperatureSetting";
  root["deviceId"] = deviceId;
  
  JsonObject& valueObj = root.createNestedObject("value");
  JsonObject& temperatureSetting = valueObj.createNestedObject("temperatureSetting");
  temperatureSetting["setPoint"] = setPoint;
  temperatureSetting["scale"] = scale;
  temperatureSetting["ambientTemperature"] = ambientTemperature;
  temperatureSetting["ambientHumidity"] = ambientHumidity;
   
  StreamString databuf;
  root.printTo(databuf);
  
  webSocket.sendTXT(databuf);
  
}
// Call ONLY If status changed. DO NOT CALL THIS IN loop() and overload the server. 

void setThermostatModeOnServer(String deviceId, String thermostatMode) {
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["deviceId"] = deviceId;
  root["action"] = "SetThermostatMode";
  root["value"] = thermostatMode;
  StreamString databuf;
  root.printTo(databuf);
  
  webSocket.sendTXT(databuf);
}

void handleEvent(AceButton*, uint8_t eventType, uint8_t) {
  switch (eventType) {
    case AceButton::kEventPressed:
      Serial.println("kEventPressed");
      if (flag == false) {
        setPowerStateOnServer(Switch1, "ON");
        Serial.println("ON par le bouton");       
        digitalWrite(LED_PIN, LOW);        
        flag = true;              
        break; 
      } else {
        setPowerStateOnServer(Switch1, "OFF");        
        Serial.println("OFF par le bouton");          
        digitalWrite(LED_PIN, HIGH); 
        digitalWrite(RELAY_PIN, LOW);
        flag = false;                
        break;   
      }
    case AceButton::kEventReleased:
          Serial.println("kEventReleased");
          break;
  }
}