//*******************************************************
//INCLUDES
//*******************************************************
#include <EQMon_ADS1115.h>       //https://forum.arduino.cc/index.php?topic=377370.0
#include <OneWire.h>             //https://github.com/PaulStoffregen/OneWire
#include <Wire.h>
#include <DallasTemperature.h>   //https://github.com/milesburton/Arduino-Temperature-Control-Library
#include <ESP8266WiFi.h>         //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>         //https://github.com/tzapu/WiFiManager
#include <Esp.h>                 //https://github.com/esp8266/Arduino
#include <stdint.h>
#include <ArduinoJson.h>         //https://github.com/bblanchon/ArduinoJson
#include <PubSubClient.h>        //https://github.com/knolleary/pubsubclient

extern "C" {
#include "user_interface.h"
}

//*******************************************************
//ADS1115 SETUP
//*******************************************************
Adafruit_ADS1115 ads;

//*******************************************************
//TIMER SETUP
//*******************************************************
os_timer_t myTimer;

//*******************************************************
//TEMPERATURE SENSOR SETUP
//*******************************************************
#define ONE_WIRE_BUS 13
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

//*******************************************************
//MQTT SETUP
//*******************************************************
const char* mqtt_server = "192.168.0.128";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

struct configuration{
    int qos = 1;
    int id = ESP.getChipId();
    String prefix = "MAT-SC-";
    char* intopic = "set/topic";
    char* outtopic = "esp/topic";
    char* lwttopic = "dead/topic";  
    char* mysqlprimar = "zasuvky_data";
    char* mysqlsecundar = "teplota";  
    char* mysqlconfig = "konfigurace";
    char* valnameprimar = "amps";  
    char* valnamesecundar = "temp"; 
    
};

typedef struct configuration Config;
Config pconfig;

//*******************************************************
//WIFI SETUP
//*******************************************************
WiFiManager wifiManager;


//*******************************************************
//GLOBAL VARIABLES
//*******************************************************
int rel_flag = 0;
int pwm = 0;
int led_interrupt = 2;
double Irms;
double Vrms;
float temp;
double cal_result = 0.0;
bool rel_state;
bool pulse;
bool dir = 0;

//*******************************************************
//DEFINES
//*******************************************************
#define REL 12
#define BUT 14
#define LED 2

//*******************************************************
//FUNCTIONS DECLARATION
//*******************************************************
extern void setup_wifi();
extern void reconnect();
extern void senddata(float temp, int reason);
extern void check_button();
extern void calibrate();
extern void measureI();
extern void measureV();  
extern void timerCallback(void *pArg);
extern void onmsg(char* topic, byte* payload, unsigned int length); 

//*******************************************************
//START OF THE PROGRAM
//*******************************************************
void setup() {
  ads.setGain(GAIN_ONE);
  ads.begin();
  pulse = 0;
  pinMode(REL, OUTPUT);
  pinMode(LED, OUTPUT);  
  pinMode(BUT, INPUT);
  rel_state = 0;
  digitalWrite(REL, rel_state);
  digitalWrite(LED, rel_state); 
  Serial.begin(115200);
  Serial.println("I waked up!");
  sensors.begin();
  calibrate();
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(onmsg);
  os_timer_disarm(&myTimer);
  os_timer_setfn(&myTimer, (os_timer_func_t *)timerCallback, NULL);
  os_timer_arm(&myTimer, led_interrupt, true);
}

void loop() {
  uint32_t start = millis();
  while((millis() - start) < 15000){
    if(digitalRead(BUT) == LOW){
      check_button();      
    }
    client.loop();    
    yield();
    delay(25);          
  }  
  sensors.requestTemperatures();
  if(rel_state == 1){
    measureI();
    measureV();
  }else{
    Irms = 0.0;
  }
  temp = sensors.getTempCByIndex(0);
  senddata(1);
    
}
//*******************************************************
//END OF THE PROGRAM
//*******************************************************

//*******************************************************
//START OF THE FUNCTIONS
//*******************************************************
void setup_wifi(){    
    String wifiname = "OUTLET: " + String(pconfig.id);
    char awifiname[wifiname.length()];
    wifiname.toCharArray(awifiname, wifiname.length());
    
    wifiManager.autoConnect(awifiname);             
}

void reconnect() {    
     
    if (WiFi.status() != WL_CONNECTED) {   
        setup_wifi();
    }
            
    int attempt = 0;
    while (!client.connected()) {
      Serial.print("Attempting MQTT connection...");
      String mqttname = pconfig.prefix + String(pconfig.id);
      String mqttlwt = mqttname + ": died";
      char clientname[mqttname.length()];
      char clientlwt[mqttlwt.length()];
      mqttname.toCharArray(clientname, mqttname.length());
      if (client.connect(clientname, "", "")) {
        Serial.println("connected");        
        client.subscribe(pconfig.intopic);
      } else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 5 seconds");
        delay(1000);
      }
      attempt = attempt + 1;
      if (attempt == 20){
        setup_wifi();
        break;  
      }
   }    
}

void senddata(int reason){
   
  if(!client.connected()) {
    reconnect();
  }
   
  char msgbuffer[256];
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  
  switch(reason){  
    case 1:
            root["CHIP_ID"] = pconfig.id;
            root["mysql"] = pconfig.mysqlprimar;
            root["current"] = Irms; 
            root["voltage"] = (int)Vrms;     
            root["temp"] = temp;   
            root.printTo(msgbuffer, sizeof(msgbuffer));
            root.printTo(Serial);
            break;
    
    case 2:
           root["CHIP_ID"] = pconfig.id;
           root["user_set"] = rel_state;
           root["rel_flag"] = rel_flag;              
           root.printTo(msgbuffer, sizeof(msgbuffer));
           root.printTo(Serial);
           break;    
  }
  if(client.publish(pconfig.outtopic, msgbuffer)){
    Serial.print("data sent");
  }else {
    Serial.print("failed to sent data");    
  }         
}

void onmsg(char* topic, byte* payload, unsigned int length){
    char message[length];
    StaticJsonBuffer<200> jsonBuffer;
    
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    for (int i = 0; i < length; i++) {
      Serial.print((char)payload[i]);
    }
    Serial.println();
    
    for (int i = 0; i < length; i++){
        message[i] = (char)payload[i];
    }
    
    JsonObject& root = jsonBuffer.parseObject(message);
    
    if (!root.success()) {
        Serial.println("parseObject() failed");
        return;
    }
    else{
        Serial.println("parseObject() successed");
        if(root["CHIP_ID"] == pconfig.id){
          if(rel_flag == 1){      
              if(root["rele"] == 1){
                rel_state = 1;
                digitalWrite(REL, rel_state);
                pulse = 1;
              }else if(root["rele"] == 0){
                rel_state = 0;
                pulse = 0;
                analogWrite(LED, 0);
                digitalWrite(REL, rel_state);
              }
            }                            
         }    
    }
}


void calibrate(){
  double Itemp = 0;
  for(int i = 0; i < 10; i++){
    yield();
    measureI();    
    Itemp += Irms;
  }
  cal_result = Itemp / 10;
  Serial.print("calibrate result: ");
  Serial.println(cal_result);
}

void measureI(){
  int16_t proud;  
  int16_t maximum = 0;
  int16_t minimum = 1024; 
  int countmax = 1;
  int countmin = 1;
  
  uint32_t start = millis();
  while((millis() - start) < 600){
    proud = ads.readADC_SingleEnded(0);

    if(proud > maximum){      
      maximum += proud;
      countmax++;      
    }else if(proud < minimum){
      minimum += proud;
      countmin++;
    }     
  }
  maximum = maximum / countmax;
  minimum = minimum / countmin;
  proud = maximum - minimum;
  Irms = ((float)(proud/2.0)) * 0.707;
  Irms = Irms * 0.001875;  
}

void measureV(){
  int16_t napeti;  
  int16_t maximum = 0;
  int16_t minimum = 1024;
  int countmax = 1;
  int countmin = 1;
  
  uint32_t start = millis();
  while((millis() - start) < 600){
    napeti = ads.readADC_SingleEnded(1);

    if(proud > maximum){
      maximum += napeti;
      countmax++;
    }else if(proud < minimum){
      minimum += napeti;
      countmin++;
    }
  }
  maximum = maximum / countmax;
  minimum = minimum / countmin;
  napeti = maximum - minimum;
  Vrms = ((float)(napeti/2.0)) * 0.707;
  Vrms = Vrms * 0.03;  
}

void check_button(){
  int held = 0;
  while (digitalRead(BUT) == LOW && held < 10)
  {
    delay(100);
    held++;
  }
  if (held < 10){
    rel_state = !rel_state; 
    rel_flag = 1;
    digitalWrite(REL, rel_state);
    if(rel_state == 1){
      pulse = 1;
    }else{
      pulse = 0;
      analogWrite(LED, 0);
    }   
  }else{
    rel_state = !rel_state;
    digitalWrite(REL, rel_state);
    rel_flag = 0;
    pulse = 0;
    if(rel_state == 1){
      analogWrite(LED, 1023);
    }else{
      analogWrite(LED, 0);
    }   
  }
  senddata(2);  
  delay(500);
}

void timerCallback(void *pArg) {

  if(pulse == 1){
    if(dir == 1){
      pwm += 1;
      if(pwm >= 1022){
        pwm = 1022;
        dir = 0;
      }      
    }else{
      pwm -= 1;
      if(pwm <= 20){
        pwm = 20;
        dir = 1;
      }
    }
    analogWrite(LED, pwm);   
  }
}
//*******************************************************
//END OF THE FUNCTIONS
//******************************************************* 
