#include "WiFi.h"
#include "esp_wps.h"
#include <Stepper.h>        //knihovna pro krokový motor
#include <OneWire.h>
#include <DallasTemperature.h>
#include <HTTPClient.h>
#include <Wire.h>
#include "ClosedCube_OPT3002.h"

ClosedCube_OPT3002 opt3002;

#define BUTTON_PIN_BITMASK 0xC00000000 // RTC piny GPIO_34 a GPIO_35     
#define ONE_WIRE_BUS 15
#define ESP_WPS_MODE      WPS_TYPE_PBC
#define ESP_MANUFACTURER  "ESPRESSIF"
#define ESP_MODEL_NUMBER  "ESP32"
#define ESP_MODEL_NAME    "ESPRESSIF IOT"
#define ESP_DEVICE_NAME   "ESP STATION"
#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  180        /* Time ESP32 will go to sleep (in seconds) */
#define STEPS 200   //počet kroků na jednu otáčku u krokového motoru

const char* serverName = "http://164273.9e.cz/zapis.php";     // URL pro odesílání dat
String apiKeyValue = "84k414r5k4pr4c3";                       // unikátní klíč k ověření zařízení
String sensorName = "OPT3002";   
String sensorLocation = "Kurník";

float intenzita;

OneWire oneWire(ONE_WIRE_BUS);
float tempC0;
float tempC1;
DallasTemperature sensors(&oneWire);
int numberOfDevices;
DeviceAddress tempDeviceAddress; 
unsigned long previousMillisWiFi = 0; // ukládá poslední aktualizaci události
long intervalWiFi = 6000; // Proměnná pro časový interval mezi jedním pokusem o připojení a dalším

Stepper stepper(STEPS, 16, 17, 18, 19);     // nastavení kroků a piny na ovládání driveru motoru
const int SLEEP = 4;                        // pin pro SLEEP vstup do driveru
boolean pin14;
boolean pin12;
boolean pin0;
int i;
const int FAULT = 5;                        // pin pro FAULT výstup z driveru
RTC_DATA_ATTR int prom1 = 0;  
RTC_DATA_ATTR int prom2 = 0;
boolean pin33;
boolean pin34;
boolean pin35;

static esp_wps_config_t config;

void wpsInitConfig(){
config.crypto_funcs = &g_wifi_default_wps_crypto_funcs;
config.wps_type = ESP_WPS_MODE;
strcpy(config.factory_info.manufacturer, ESP_MANUFACTURER);
strcpy(config.factory_info.model_number, ESP_MODEL_NUMBER);
strcpy(config.factory_info.model_name, ESP_MODEL_NAME);
strcpy(config.factory_info.device_name, ESP_DEVICE_NAME);
}

String wpspin2string(uint8_t a[]){
  char wps_pin[9];
  for(int i=0;i<8;i++){
    wps_pin[i] = a[i];
  }
  wps_pin[8] = '\0';
  return (String)wps_pin;
}

void WiFiEvent(WiFiEvent_t event, system_event_info_t info){
  switch(event){
    case SYSTEM_EVENT_STA_START:
      Serial.println("Station Mode Started");
      break;
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("Connected to :" + String(WiFi.SSID()));
      Serial.print("Got IP: ");
      Serial.println(WiFi.localIP());
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("Disconnected from station, attempting reconnection");
      WiFi.reconnect();
      break;
    case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
      Serial.println("WPS Successful, stopping WPS and connecting to: " + String(WiFi.SSID()));
      digitalWrite(27,HIGH);
      delay(3000);
      digitalWrite(27,LOW);
      delay(1000);
      esp_wifi_wps_disable();
      delay(10);
      WiFi.begin();
      break;
    case SYSTEM_EVENT_STA_WPS_ER_FAILED:
      Serial.println("WPS Failed, retrying");
      digitalWrite(25,HIGH);
      delay(2000);
      digitalWrite(25,LOW);
      delay(2000);
      esp_wifi_wps_disable();
      esp_wifi_wps_enable(&config);
      esp_wifi_wps_start(0);
      break;
    case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
      Serial.println("WPS Timeout, retrying");
      esp_wifi_wps_disable();
      esp_wifi_wps_enable(&config);
      esp_wifi_wps_start(0);
      break;
    case SYSTEM_EVENT_STA_WPS_ER_PIN:
      Serial.println("WPS_PIN = " + wpspin2string(info.sta_er_pin.pin_code));
      break;
    default:
      break;
  }
}

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL");       // důvod probuzení změnou na RTC pinech
pin34 = digitalRead(34);        //pin 34 nastaven na čtení
pin35 = digitalRead(35);        //pin 35 nastaven an čtení
if((pin34==HIGH)&&(pin35==HIGH)){     //pokud je na pinech 34 a 35 log. 1, modrá led dvakrát blikne a započne připojení k Wifi pomocí WPS
  WiFi.onEvent(WiFiEvent);
  WiFi.mode(WIFI_MODE_STA);
      digitalWrite(26,HIGH);
      delay(1000);
      digitalWrite(26,LOW);
      delay(1000);
      digitalWrite(26,HIGH);
      delay(1000);
      digitalWrite(26,LOW);
      delay(1000);
  Serial.println("Starting WPS");

  wpsInitConfig();
  esp_wifi_wps_enable(&config);
  esp_wifi_wps_start(0);
  delay(120000);
     
}
if((pin34==HIGH)&&(pin35==LOW)){
     otevreni();
 }
if((pin34==LOW)&&(pin35==HIGH)){
     zavreni();
 
}
    break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); 

    WiFi.begin();
    Serial.print("Připojení k poslednímu AP");
      while (WiFi.status() != WL_CONNECTED) {
    if (previousMillisWiFi < intervalWiFi)
    {
      previousMillisWiFi = millis();
      Serial.print(".");

      delay(500);
    }
    else break;

  }
  if (WiFi.status() != WL_CONNECTED) {

    WiFi.onEvent(WiFiEvent);
    WiFi.mode(WIFI_MODE_STA);

    Serial.println("Starting WPS");

    wpsInitConfig();
    esp_wifi_wps_enable(&config);
    esp_wifi_wps_start(0);
  }
  else {
    Serial.println();
    Serial.print("Připojeno k síti: ");
    Serial.println(WiFi.SSID());
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
  }

    
    delay(10000);
    break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}
  
void setup(){
  Serial.begin(115200);
  pinMode(25, OUTPUT);
  pinMode(26, OUTPUT);
  pinMode(27, OUTPUT);
  pinMode(SLEEP, OUTPUT);
  pinMode(FAULT, INPUT);
  snimani();
  jakje();
  teplota();
  sql();
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  print_wakeup_reason();

  print_GPIO_wake_up();

  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
  " Seconds"); 
  Serial.println("Going to sleep now");
  delay(1000);
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This is not going to be called
}

void print_GPIO_wake_up(){
  uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
  Serial.print("GPIO that triggered the wake up: GPIO ");
  Serial.println((log(GPIO_reason))/log(2), 0);
}

void zavreni(){
  stepper.setSpeed(400);
     pinMode(SLEEP, OUTPUT);
     digitalWrite(SLEEP, HIGH);
     pin0 = digitalRead(0);
     for ( i=0; i<20; i++ ) { // inicializátor; podmínka; inkrement
        if((pin0==HIGH)&&(pin12==LOW){
        pin0 = digitalRead(0);
        Serial.println(pin0);
        Serial.println("zavíráme");
        stepper.step(STEPS);
        delay(1);
    }
    else {
      Serial.println("nejde zavřít");
    }
}
 digitalWrite(SLEEP, LOW);
}

void jakje(){
if((intenzita>30000)&&(prom1==1)){ 
     prom2++;
     }
     else if ((prom2==3)&&(prom1==1)){
     otevreni();
     prom2=0;
     }
if((intenzita<2000)&&(prom0==0)){
     prom2++;
     }
     else if ((prom2==3)&&(prom1==0)){
     zavreni();
     prom2=0;
     }
}

void otevreni(){
  stepper.setSpeed(400);
     pinMode(SLEEP, OUTPUT);
     digitalWrite(SLEEP, HIGH);
     pin14 = digitalRead(14);
     for ( i=0; i<20; i++ ) { // inicializátor; podmínka; inkrement
        if(pin14==HIGH){
        pin14 = digitalRead(14);
        Serial.println(pin14);
        Serial.println("otvíráme");
        stepper.step(STEPS);
        delay(1);
        prom1=0;    // proměnná určující, že jsou dvířka otevřená
    }
    else {
      Serial.println("nejde otevřít");
    }
}
 digitalWrite(SLEEP, LOW);
}

void teplota(){
  digitalWrite(33,HIGH);
  delay(2000);
  sensors.begin();
  
  numberOfDevices = sensors.getDeviceCount();
  Serial.print("Locating devices...");
  Serial.print("Found ");
  Serial.print(numberOfDevices, DEC);
  Serial.println(" devices.");
  
  for(int i=0;i<numberOfDevices; i++){
    if(sensors.getAddress(tempDeviceAddress, i)){
      Serial.print("Found device ");
      Serial.print(i, DEC);
      Serial.print(" with address: ");
      printAddress(tempDeviceAddress);
      Serial.println();
    } else {
      Serial.print("Found ghost device at ");
      Serial.print(i, DEC);
      Serial.print(" but could not detect address. Check power and cabling");
    }
  }
  sensors.requestTemperatures();
  
  for(int i=0;i<numberOfDevices; i++){
    if(sensors.getAddress(tempDeviceAddress, i)){
      Serial.print("Temperature for device: ");
      Serial.println(i,DEC);
      float tempC = sensors.getTempC(tempDeviceAddress);
      if(i==0){
      tempC0 = tempC;
      }
      else{
      tempC1 = tempC;
      }
      Serial.print("Temp C: ");
      Serial.print(tempC);
      Serial.print(" Temp F: ");
      Serial.println(DallasTemperature::toFahrenheit(tempC)); // Converts tempC to Fahrenheit
    }
  }
  Serial.print("Naměřené teploty: ");
  Serial.println(tempC0);
  Serial.println(tempC1);
  delay(5000);
}
void snimani(){
    digitalWrite(33,HIGH);
    delay(2000);
    Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Sken");
  int adresa = 0;
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      Serial.println(address,DEC);
      adresa = address;
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  delay(5000); 
  if (adresa == 69) {
    Serial.println("externí senzor\n");
#define OPT3002_ADDRESS 0x45
Serial.println("ClosedCube OPT3002 Arduino Test");

  opt3002.begin(OPT3002_ADDRESS);
  Serial.print("OPT3002 Manufacturer ID:");
  Serial.print(opt3002.readManufacturerID());
  Serial.print(" Device ID:");
  Serial.println(opt3002.readDeviceID());

  configureSensor();
  printResult("High-Limit", opt3002.readHighLimit());
  printResult("Low-Limit", opt3002.readLowLimit());
  Serial.println("----");
    
  }
   else {
    Serial.println("interní senzor\n");
#define OPT3002_ADDRESS 0x44
Serial.println("ClosedCube OPT3002 Arduino Test");

  opt3002.begin(OPT3002_ADDRESS);
  Serial.print("OPT3002 Manufacturer ID:");
  Serial.print(opt3002.readManufacturerID());
  Serial.print(" Device ID:");
  Serial.println(opt3002.readDeviceID());

  configureSensor();
  printResult("High-Limit", opt3002.readHighLimit());
  printResult("Low-Limit", opt3002.readLowLimit());
  Serial.println("----");
  }
  OPT3002 result = opt3002.readResult();
  printResult("OPT3002", result);
  delay(500);
}
void printAddress(DeviceAddress deviceAddress) {
  for (uint8_t i = 0; i < 8; i++){
    if (deviceAddress[i] < 16) Serial.print("0");
      Serial.print(deviceAddress[i], HEX);
  }
}
void sql(){
  while(WiFi.status() != WL_CONNECTED) { 
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  //Check WiFi connection status
  if(WiFi.status()== WL_CONNECTED){
    WiFiClient client;
    HTTPClient http;
    

    http.begin(client, serverName);   //jméno domény
    
    // Specify content-type header
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    
    // Prepare your HTTP POST request data
    String httpRequestData = "api_key=" + apiKeyValue + 
                             "&sensor=" + sensorName  + 
                             "&location=" + sensorLocation 
                             + "&value1=" + float(intenzita)
                             + "&value2=" + float(tempC0) 
                             + "&value3=" + float(tempC1) + "";
    Serial.print("HTTP data: ");
    Serial.println(httpRequestData);
    
    int httpResponseCode = http.POST(httpRequestData);
       
    if (httpResponseCode>0) {
      Serial.print("HTTP odpověď: ");
      Serial.println(httpResponseCode);
    }
    else {
      Serial.print("Chybový kód: ");
      Serial.println(httpResponseCode);
    }
    http.end();
  }
  else {
    Serial.println("WiFi Odpojeno");
  }
  delay(30000);
}

void printResult(String text, OPT3002 result) {
  if (result.error == NO_ERROR) {
    Serial.print(text);
    Serial.print(": ");
    Serial.print(result.lux);
    intenzita = result.lux;
    Serial.println(" nW/cm2");
  }
  else {
    printError(text, result.error);
  }
}

void printError(String text, OPT3002_ErrorCode error) {
  Serial.print(text);
  Serial.print(": [ERROR] Code #");
  Serial.println(error);
}

void configureSensor() {
  OPT3002_Config newConfig;

  newConfig.RangeNumber = B1100;
  newConfig.ConvertionTime = B0;
  newConfig.Latch = B1;
  newConfig.ModeOfConversionOperation = B11;

  OPT3002_ErrorCode errorConfig = opt3002.writeConfig(newConfig);
  if (errorConfig != NO_ERROR)
    printError("OPT3002 configuration", errorConfig);
  else {
    OPT3002_Config sensorConfig = opt3002.readConfig();
  }

}
