/*********
  Adrian Zilly
  Tueroeffner-LoRa
  Hardware Lib: ESP8266 NodeMCU 1.0 (ESP12E-Module)
  v3.2
  Letzte Aenderung 2021-01-19
*********/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <WiFiUdp.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
#include <SPI.h>
#include <LoRa.h>

//--------------------------------------------------
//Define Variables
int Digits = 0;
int BattVolt = 0;
int BattPerc = 0;
int valA0 = 0;
int MVollgeladen = 0;
int StartMerker; //Startmerker Anzahl
int Start = 0;
int Start2 = 0;
int Start3 = 0;
unsigned long sleepMillis = 10, Sendertime = 10;
int SendeDelay = 5000;
int WLANTimeout = 30;
int WLANCounts = 0;
int SetSleep = 0;

//PINSETUP RFM95 LoRa Modul
#define ss 16 //NSS
#define rst 5 //RESET
#define dio0 15 //DIO0

//--------------------------------------------------
//PARAMETER EINSTELLUNGEN
//Parameter Analogwertauslesen (Digit Korrektur)
int DigitsCor = 0;

//Sleep Delay Parameter
unsigned long currentSleep, SenderSleep;
const long gosleepdelay = 120000; //Verzoegerung Schlafen gehen 1000ms = 1s - Set to 100000 (2min.)

//WLAN Setup
const char* SSID = "DEINWLANNETZWERK"; //WLAN Name
const char* PSK = "DEINWLANPASSWORT"; //Passwort WLAN

//Pin Setup
#define BattVoltIn A0 //Analog Input
int Laden = 2; //GPIO2 (D4) Input "wird geladen?"
int BattMess = 4; //GPIO4 (D2) Start Messung Batterie
int Tueroeffner = 0; //GPIO0 (D3) Tueroeffner --- HIGH
bool TueroeffnerRelais = HIGH; //Relaisboard HIGH or LOW Trigger
bool TueroeffnerRelaisOpen, TueroeffnerRelaisClose;

//Debug Mode and LED Mode
bool Debug = false; //zum abschalten/Led deaktivieren auf "false" setzen

//--------------------------------------------------
//---------------
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
int wifi_retry;

//---------------

void setup_LORA() {
  //setup LoRa transceiver module
  LoRa.setPins(ss, rst, dio0);

  //866E6 for Europe
  while (!LoRa.begin(868E6)) {
    Serial.println("Lora Serial Connecting..");
    delay(500);
  }

  while (!Serial);
  Serial.println("LoRa Sender");
  // The sync word assures you don't get LoRa messages from other LoRa transceiver
  //define the Code for Sending
  //ranges from 0x00-0xff (HEX)
  LoRa.setSyncWord(0xYY); //YY anpassen zu Codenummer
  Serial.println("LoRa Initializing OK!");

  //Send First Massage via LORA
  LoraSend();
  delay(500);
  LoraSend();
}

//---------------
/*Unterprogramm Akkulevel auslesen
  ggf. Kalibrieren mit Hilfe von "DigitsCor = Digits + 0;" hier hinter dem + entsprechende Korrektur eintragen
  Messgeraet an Akku direkt anschließen. Spannung messen und entsprechenden Korrekturwert ermitteln.
  Weitere Moeglichkeit, direkt die Punkte definieren.
  BattVolt = map(DigitsCor, 0, 282, 0, 4089); - map(Analogwert, LowDigit, HighDigit, LowValue, HighValue);
  Folglich dann die zwei Werte anpassen: map(DigitsCor, 0, !!hier Ausgegebener Digitwert bei gleichzeitiger Akkumessung eintragen!!, 0, !!hier gemessener Messwert eintragen!!);
  Nicht vergessen die gleichen Werte bei Battperc einzutragen.
*/

void AkkuStand() {
  //Start Messung Spannungsteiler einschalten
  digitalWrite(BattMess, HIGH);
  delay(500);
  //Volt auslesen
  Digits = analogRead(BattVoltIn);
  DigitsCor = Digits + 0;
  BattVolt = map(DigitsCor, 0, 282, 0, 4089);
  // 1d = 14,4mV / 230d = 3312mV / 284d = 4089mV
  BattPerc = map(DigitsCor, 230, 282, 0, 100);
  if (BattPerc == 0) {
    AkkuStand();
  }
  Serial.print("AkkuStand\nDigit: ");
  Serial.print(Digits);
  Serial.print("Batt Volt");
  Serial.print(BattVolt);
  //Ende Messung, Energiesparen, Spannungsteiler abschalten
  digitalWrite(BattMess, LOW);
}

//---------------

void LoraSend() {
  //Send LoRa packet to receiver
  LoRa.beginPacket();
  LoRa.print("Es hat geklingelt!");
  LoRa.endPacket();
  Serial.print("Lora sended");
  Start2++;
}

//---------------

void Close() {
  Serial.println("I close it for you!");
  client.publish("Haustuerstatuslaueft", "gesichert");
  //delay(500);
  client.publish("Haustuerstatusist", "gesichert");
  client.publish("Haustuer", "Sicherung durchgefuehrt!");
}

//---------------

void Open() {
  Serial.println("I open it for you!");
  client.publish("Haustuerstatuslaueft", "offen");
  client.publish("Haustuerstatuslaueft", "offen");
  //delay(500);
  digitalWrite(Tueroeffner, TueroeffnerRelaisOpen);
  client.publish("Haustuerstatusist", "offen");
  client.publish("Haustuerstatusist", "offen");
  Serial.println("Haustuer wurde geoeffnet!");
  delay(3000);
  digitalWrite(Tueroeffner, TueroeffnerRelaisClose);
  client.publish("Haustuerstatuslaueft", "gesichert");
  client.publish("Haustuerstatuslaueft", "gesichert");
  Serial.println("Haustuer wird geschlossen!");
  //delay(500);
  client.publish("Haustuerstatusist", "gesichert");
  client.publish("Haustuerstatusist", "gesichert");
  Serial.println("Haustuer wurde gesichert!");
  //delay(500);
}

void SendAkku() {
  client.publish("BatterieLevelTuer", String(BattPerc).c_str(), true);
  client.publish("HaustuerAkkuDigit", String(Digits).c_str(), true);
  String mV = ("mV: " + String(BattVolt));
  client.publish("HaustuerAkku", String(mV).c_str(), true);
  String Perc = ("Perc: " + String(BattPerc));
  client.publish("HaustuerAkku", String(Perc).c_str(), true);
  Serial.print("\nHaustuerAkku:");
  Serial.print(BattPerc);
  Start3++;
}

//---------------

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Received message [");
  Serial.print(topic);
  Serial.print("] ");
  char msg[length + 1];
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    msg[i] = (char)payload[i];
  }

  msg[length] = '\0';
  Serial.println(msg);

  client.publish("Haustuer", "I got an Order!");

  if (strcmp(msg, "gesichert") == 0) {
    Close();
  }
  else if (strcmp(msg, "offen") == 0) {
    Open();
  }
  else {
    Serial.print("Nichts kam an");
  }
}

//---------------

void setup() {
  //Cleanup
  ESP.eraseConfig();
  WiFi.disconnect();

  //Variable aus EEPROM lesen
  EEPROM.begin(512);
  delay(100);
  StartMerker = EEPROM.get(0, StartMerker);

  //PIN Setup
  pinMode(Tueroeffner, OUTPUT); //Relais Tueroeffner
  pinMode(BattMess, OUTPUT); //Batterie Messung start
  pinMode(Laden, INPUT); //Laden (Abgeschlossen = High)

  digitalWrite(BattMess, HIGH);
  digitalWrite(Tueroeffner, HIGH); //High = Zu - LOW = Oeffnen

  if (TueroeffnerRelais == HIGH) {
    TueroeffnerRelaisOpen = HIGH;
    TueroeffnerRelaisClose = LOW;
  }
  else if (TueroeffnerRelais == LOW) {
    TueroeffnerRelaisOpen = LOW;
    TueroeffnerRelaisClose = HIGH;
  }
  else {
    Serial.print("Error Set TueroeffnerRelais Output");
  }
  digitalWrite(Tueroeffner, TueroeffnerRelaisClose);

  //------- END Pin Setup ---------

  Serial.begin(115200);
  if (StartMerker < 1) {
    setup_LORA();
  }

  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PSK);

  StartMerker++;
  EEPROM.put(0, StartMerker);
  EEPROM.commit();
  delay(500);

  while (WiFi.status() != WL_CONNECTED && WLANTimeout > WLANCounts) {
    delay(500);
    WLANCounts++;
    Serial.print("\n Wifi Connecting..");
    Serial.print("\n Trys:");
    Serial.print(WLANCounts);
    Serial.print("/");
    Serial.print(WLANTimeout);
  }

  if (WLANCounts >= WLANTimeout) {
    ESP.restart();
  }
  else {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    WLANCounts = 0;
  }

  client.setServer("192.167.1.15", 1883);
  client.setCallback(callback);

  AkkuStand();

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");
    }
  });

  ArduinoOTA.setHostname("ESP-Haustuere");
  ArduinoOTA.setPassword("nimda");
  ArduinoOTA.begin();
}

//---------------
//Unterprogramm Neu Verbinden MQTT
void reconnect() {
  while (!client.connected()) {
    Serial.println("\nReconnecting MQTT...");
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);

    if (!client.connect(clientId.c_str())) {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      delay(1000);
    }

    else {
      client.subscribe("Haustueroeffnen");
      client.publish("HaustuerStatus", "MQTT Connected");

      char buf[16];
      sprintf(buf, "%d", StartMerker);
      const char* SendMerker = buf;

      client.publish("HaustuerStatus", SendMerker);
      Serial.println("MQTT Connected...");
      Close();
    }
  }
}

//----------------------------------------

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("WLAN not connected, reconnecting...");
    wifi_retry = 0;
    while (WiFi.status() != WL_CONNECTED && wifi_retry < 30) {
      Serial.println(wifi_retry);
      wifi_retry++;
      Serial.println("WiFi not connected. Try to reconnect");
      delay(500);
    }
    if (wifi_retry >= 30) {
      Serial.println("\nReboot");
      ESP.restart();
    }
  }

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  if (digitalRead(Laden) == LOW) {
    MVollgeladen = 0;
    SetSleep = 0;
    sleepMillis = millis();
  }
  if (digitalRead(Laden) == HIGH && MVollgeladen == 0) {
    MVollgeladen = 1;
  }

  ArduinoOTA.handle();

  delay(100);
  //Serial.println((currentSleep - sleepMillis) / 1000);

  //Variable ablegen mit der aktuellen Zeit fuer Ueberpruefung erneutes Senden
  SenderSleep = millis();
  //Ueberpruefen ob zeit zum senden ist
  if (SenderSleep - Sendertime >= SendeDelay && client.connected()) {
    Sendertime = SenderSleep;
    String GPIO = ("LadeINPUT: " + String(digitalRead(Laden)) + "| 0 Start - 1 END");
    client.publish("Haustuer", String(GPIO).c_str(), true);
    String Uptime = ("SleepTimer: " + String((currentSleep - sleepMillis) / 1000) + "s/120s");
    client.publish("Haustuer", String(Uptime).c_str(), true);
    if (MVollgeladen == 0) {
      client.publish("Haustuer", "LiPo wird geladen..");
    }
    if (MVollgeladen == 1) {
      client.publish("Haustuer", "LiPo Vollgeladen");
    }
  }
  AkkuStand();
  SendAkku();
  //Variable ablegen mit der aktuellen Zeit fuer Ueberpruefung Schlafengehen
  currentSleep = millis();
  //Ueberpruefen ob zeit zum schlafen ist
  if (currentSleep - sleepMillis >= gosleepdelay && MVollgeladen == 1 && client.connected() && WiFi.status() == WL_CONNECTED) {
    //WLAN und MQTT noch verbunden? Andernfalls neu verbinden
    if (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("WLAN not connected, reconnecting...");
      while (WiFi.status() != WL_CONNECTED) {
        Serial.println("WiFi not connected. Try to reconnect");
        delay(500);
      }
    }
    if (!client.connected()) {
      reconnect();
    }
    client.publish("Haustuer", "Make ready for go Deep sleeping...");
    Serial.println("go sleep...");
    Serial.println("Set Start Merker = 0");
    //EEPROM Merken dass bei naechsten Neustart Klingelsignal ankam
    StartMerker = 0;
    EEPROM.put(0, StartMerker);
    delay(100);
    EEPROM.commit();
    delay(2000);
    Serial.println("END EEPROM \nEnding Pins and Connections");
    client.publish("Haustuer", "EEPROM Beendet...");

    sleepMillis = currentSleep;
    //Ausgaenge für Schlafen vorbereiten
    digitalWrite(Tueroeffner, TueroeffnerRelaisClose);
    digitalWrite(BattMess, LOW);

    //LoRa in Sleep Modus versetzen
    LoRa.end();
    SPI.end();
    pinMode(ss, INPUT_PULLUP);
    pinMode(rst, INPUT_PULLUP);
    pinMode(dio0, INPUT_PULLUP);
    delay(100);
    pinMode(ss, INPUT_PULLUP);
    pinMode(rst, INPUT_PULLUP);
    pinMode(dio0, INPUT_PULLUP);
    delay(100);
    pinMode(ss, INPUT_PULLUP);
    pinMode(rst, INPUT_PULLUP);
    pinMode(dio0, INPUT_PULLUP);
    client.publish("Haustuer", "END WIFI NOW and go Sleep...");
    client.publish("Haustuer", "go sleeping...");

    delay(1000);
    Serial.println("END WIFI");
    WiFi.mode(WIFI_OFF);
    delay(1000);
    Serial.println("Deepsleep Now..");
    delay(500);
    ESP.deepSleep(0);
    delay(200000);
  }
}
//--------------------------------------------------
