

#include <ESP8266WiFi.h>          //ESP8266 Core WiFi Library (you most likely already have this in your sketch)
#include <PubSubClient.h>

#include <Wire.h>
#include "LiquidCrystal_I2C.h"

//#include <DNSServer.h>            //Local DNS Server used for redirecting all requests to the configuration portal
//#include <ESP8266WebServer.h>     //Local WebServer used to serve the configuration portal
//#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager WiFi Configuration Magic

#include <AS_BH1750.h> // BH1750 lux sensor
#include "DHT.h"

#define DHTTYPE DHT22
#define LCDADDR 0x27
#define LUXSENSORADDR 0x23

const int DHTPin = 13;
const int RelaisPin = 16;
const int PlusButtonPin = 0;
const int MinusButtonPin = 2;
const int ModeButtonPin = 14;
const int PIRPin = 12;

const bool RelaisOnState = LOW;
const bool ButtonOnState = LOW;

float setTemp;
float setTempDay = 21.5;
float setTempNight = 18.0;

float setTempMin = 14.0;
float setTempMax = 29.0;
float setTempMinLow = 10.0;
float setTempMaxLow = 19.0;
float hysteresis = 0.5;

long heatingLastOnMillis;
long heatingLastOffMillis;
long minOffTime = 10; // s
//uint16_t lux;
//int lux;
//float lux;

int measureinterval = 60000; // ms

bool heatingEnabled = true;
byte heatingMode = 1;
///// 0 = off
// 1 = default/day
// 2 = night/reduction

bool turnHeatingOn = false;

float humidity, temperature;

const char* ssid = "KS61T5SH";  // WiFi SSID
const char* password = "Gurken651salat";  // WiFi PWD

const char* mqtt_server = "10.1.1.11";
const int mqtt_port = 1883;

//const char* mqtt_topic_prefix = "Test/Thermostat/";
const char* mqtt_topic_temp = "Test/Thermostat/Temp";
const char* mqtt_topic_settemp = "Test/Thermostat/SetTemp";
//const char* mqtt_topic_setmode = "Test/Thermostat/SetMode";
const char* mqtt_topic_hum = "Test/Thermostat/Hum";
const char* mqtt_topic_lux = "Test/Thermostat/Lux";
const char* mqtt_topic_heating = "Test/Thermostat/Heating";
const char* mqtt_topic_heating_lastofftime = "Test/Thermostat/HeatingLastOffTime";
const char* mqtt_topic_heating_lastontime = "Test/Thermostat/HeatingLastOnTime";

// subscribe topic
const char* mqtt_topic_in = "Test/Thermostat/in";

DHT dht(DHTPin, DHTTYPE);

//AS_BH1750 lightMeter; // Lichtstärkemessung mit BH1750

LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display

WiFiClient espClient;
PubSubClient client(espClient);

long lastMsg = 0;
char msg[50];

char topic[50];

// LCD
byte customCharSun[8] = {0b00100, 0b10001, 0b01110, 0b11111, 0b11111, 0b01110, 0b10001, 0b00100};
byte customCharMoon[8] = {0b11000, 0b01110, 0b00110, 0b00111, 0b00111, 0b00110, 0b01110, 0b11000};
byte customCharArrowRight[8] = {0b00000, 0b00100, 0b00110, 0b11111, 0b11111, 0b00110, 0b00100, 0b00000};
bool displayActive = false;
unsigned long displayLastOnMillis = 0;
int displayTimeout = 10000;

//bool lightMeter_present;

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

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

  if ((char)payload[0] == 's' && \
      (char)payload[1] == 'e' && \
      (char)payload[2] == 't' && \
      (char)payload[3] == ' ' ) {
    if ((char)payload[4] == 't' && \
        (char)payload[5] == 'e' && \
        (char)payload[6] == 'm' && \
        (char)payload[7] == 'p' && \
        (char)payload[8] == ' ' ) {
      char inValue[length - 9 + 1];
      for (int i = 0; i < length; i++) {
        inValue[i] = (char)payload[i + 9];
      }
      inValue[sizeof(inValue) - 1] = '\0';
      Serial.print(inValue);
      Serial.println();
      float invalueFloat = round(atof(inValue) * 2.0) / 2.0;

      Serial.print(invalueFloat);
      if (invalueFloat >= setTempMin && invalueFloat <= setTempMax) {
        setTempDay = invalueFloat;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if (invalueFloat > setTempMax) {
        setTempDay = setTempMax;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if (invalueFloat < setTempMin) {
        setTempDay = setTempMin;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
    }
    else if ((char)payload[4] == 't' && \
             (char)payload[5] == 'l' && \
             (char)payload[6] == 'o' && \
             (char)payload[7] == 'w' && \
             (char)payload[8] == ' ' ) {
      char inValue[length - 9 + 1];
      for (int i = 0; i < length; i++) {
        inValue[i] = (char)payload[i + 9];
      }
      inValue[sizeof(inValue) - 1] = '\0';
      Serial.print(inValue);
      Serial.println();
      float invalueFloat = round(atof(inValue) * 2.0) / 2.0;

      Serial.print(invalueFloat);
      if (invalueFloat >= setTempMinLow && invalueFloat <= setTempMaxLow) {
        setTempNight = invalueFloat;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if (invalueFloat > setTempMaxLow) {
        setTempNight = setTempMaxLow;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if (invalueFloat < setTempMinLow) {
        setTempNight = setTempMinLow;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
    }
    else if ((char)payload[4] == 'm' && \
             (char)payload[5] == 'o' && \
             (char)payload[6] == 'd' && \
             (char)payload[7] == 'e' && \
             (char)payload[8] == ' ' ) {
      if ((char)payload[9] == '1') {
        // switch to mode default/day
        heatingMode = 1;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if ((char)payload[9] == '2') {
        // switch to mode night/reduction
        heatingMode = 2;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
    }

    else if ((char)payload[4] == 'e' && \
             (char)payload[5] == 'n' && \
             (char)payload[6] == 'a' && \
             (char)payload[7] == 'b' && \
             (char)payload[8] == ' ' ) {
      if ((char)payload[9] == '0') {
        // switch heating off
        heatingEnabled = false;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
      else if ((char)payload[9] == '1') {
        // switch heating on
        heatingEnabled = true;
        lastMsg = 0; // reset var lastMsg to speed up lcd update
      }
    }

  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      //client.publish("TestoutTopic", "hello world");
      // ... and resubscribe
      client.subscribe(mqtt_topic_in);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  //WiFiManager wifiManager;
  //wifiManager.autoConnect("Thermostat", "configesp");
  //wifiManager.setConfigPortalTimeout(180);
  Serial.begin(115200);
  delay(300);

  pinMode(RelaisPin, OUTPUT);
  digitalWrite(RelaisPin, HIGH);
  pinMode(PlusButtonPin, INPUT_PULLUP);
  pinMode(MinusButtonPin, INPUT_PULLUP);
  pinMode(ModeButtonPin, INPUT_PULLUP);

  // DHT11/22 Temp/Hum-Sensor initialisieren
  dht.begin();

  //  // lightMeter BH1750
  //  //if (! lightMeter.begin(BH1750_CONTINUOUS_HIGH_RES_MODE_2)) {
  //  if (! lightMeter.begin(RESOLUTION_AUTO_HIGH)) {
  //    lightMeter_present = false;
  //    delay(50);
  //    Serial.println(F("ERROR: no BH1750 light meter found"));
  //  }
  //  else {
  //    lightMeter_present = true;
  //    delay(50);
  //    Serial.println(F("BH1750 light meter found"));
  //  }

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  lcd.init();
  lcd.createChar(0, customCharMoon);
  lcd.createChar(1, customCharSun);
  lcd.createChar(2, customCharArrowRight);

  lcd.setCursor(0, 0);

  lcd.print("WiFi-Thermostat ");
  lcd.setCursor(0, 1);
  lcd.print("      by Flo Kra");

  //  for ( int i = 0; i < 3; i++) {
  //    lcd.backlight();
  //    delay(200);
  //    lcd.noBacklight();
  //    delay(200);
  //  }
  lcd.backlight();
  delay(1000);
  lcd.clear();
  displayActive = true;
  displayLastOnMillis = millis();
}


bool plusButtonCurrentState = HIGH;
bool plusButtonLastState = HIGH;
unsigned long plusButtonDownMillis = 0;
unsigned long plusButtonRepeatMillis = 0;
unsigned long plusButtonHoldTime = 0;
bool plusButtonFired = false;

bool minusButtonCurrentState = HIGH;
bool minusButtonLastState = HIGH;
unsigned long minusButtonDownMillis = 0;
unsigned long minusButtonRepeatMillis = 0;
unsigned long minusButtonHoldTime = 0;
bool minusButtonFired = false;

bool modeButtonCurrentState = HIGH;
bool modeButtonLastState = HIGH;
unsigned long modeButtonDownMillis = 0;
unsigned long modeButtonHoldTime = 0;
bool modeButtonFired = false;
bool modeButtonHoldFired = false;

void enableDisplay() {
  lcd.backlight();
  displayActive = true;
  displayLastOnMillis = millis();
}

void disableDisplay() {
  lcd.noBacklight();
  displayActive = false;
}

void extendDisplayTimeout() {
  displayLastOnMillis = millis();
}



void plusButtonAction() {
  if(heatingMode == 1 && heatingEnabled) {
    Serial.println("+");
    if ( setTempDay <= (setTempMax - 0.5)) setTempDay += 0.5;
  }
  extendDisplayTimeout();
  lastMsg = 0; // to enforce immediate display update
}

void minusButtonAction() {
  if(heatingMode == 1 && heatingEnabled) {
    Serial.println("-");
    if ( setTempDay >= (setTempMin + 0.5)) setTempDay -= 0.5;
  }
  extendDisplayTimeout();
  lastMsg = 0; // to enforce immediate display update
}

void modeButtonAction() {
  extendDisplayTimeout();
  if (heatingEnabled) {
    Serial.print("switch mode to ");
    if (heatingMode == 1) heatingMode = 2;
    else if (heatingMode == 2) heatingMode = 1;
    Serial.println(heatingMode);
    //lastMsg = lastMsg + measureinterval; // force update
    lastMsg = 0; // to enforce immediate display update
  }
}

void modeButtonHoldAction() {
  Serial.println("switch off/on");
  extendDisplayTimeout();
  if (heatingEnabled) heatingEnabled = false;
  else heatingEnabled = true;
  //lastMsg = lastMsg + measureinterval; // force update
  lastMsg = 0; // to enforce immediate display update
}



void loop() {

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


  // Buttons einlesen

  // plus button
  plusButtonCurrentState = digitalRead(PlusButtonPin);
  if (plusButtonCurrentState == LOW && plusButtonLastState == HIGH) { // button was unpressed and is now pressed
    plusButtonDownMillis = millis();
    plusButtonLastState = plusButtonCurrentState;
  }
  else if (plusButtonCurrentState == LOW && plusButtonLastState == LOW) { // button is held
    plusButtonHoldTime = millis() - plusButtonDownMillis;
    if ( plusButtonHoldTime > 50 ) {
      plusButtonLastState = plusButtonCurrentState;
            if (!displayActive && !plusButtonFired ) {
              enableDisplay();
              plusButtonFired = true;
            }
      //      else if (displayActive && !plusButtonFired ) {
      //        plusButtonAction();
      //        plusButtonFired = true;
      //      }
    }
    if ( plusButtonHoldTime > 1250 ) {
      // button is held longer - repeating
      if ( (millis() - plusButtonRepeatMillis) > 300 ) {
        plusButtonAction();
        plusButtonRepeatMillis = millis();
      }
    }
  }
  else if (plusButtonCurrentState == HIGH && plusButtonLastState == LOW) { // button is released again
    plusButtonLastState = plusButtonCurrentState;
    
    if (displayActive && !plusButtonFired) {
      plusButtonAction();
    }
    plusButtonFired = false;
  }




  // minus button
  minusButtonCurrentState = digitalRead(MinusButtonPin);
  if (minusButtonCurrentState == LOW && minusButtonLastState == HIGH) { // button was unpressed and is now pressed
    minusButtonDownMillis = millis();
    minusButtonLastState = minusButtonCurrentState;
  }
  else if (minusButtonCurrentState == LOW && minusButtonLastState == LOW) { // button is held
    minusButtonHoldTime = millis() - minusButtonDownMillis;
    if ( minusButtonHoldTime > 50 ) {
      minusButtonLastState = minusButtonCurrentState;
            if (!displayActive && !minusButtonFired ) {
              enableDisplay();
              minusButtonFired = true;
            }
      //      else if (displayActive && !minusButtonFired ) {
      //        minusButtonAction();
      //        minusButtonFired = true;
      //      }
    }
    if ( minusButtonHoldTime > 1250 ) {
      // button is held longer - repeating
      if ( (millis() - minusButtonRepeatMillis) > 300 ) {
        minusButtonAction();
        minusButtonRepeatMillis = millis();
      }
    }
  }
  else if (minusButtonCurrentState == HIGH && minusButtonLastState == LOW) { // button is released again
    minusButtonLastState = minusButtonCurrentState;
    
    if (displayActive && !minusButtonFired) {
      minusButtonAction();
    }
    minusButtonFired = false;
  }




  // mode button
  modeButtonCurrentState = digitalRead(ModeButtonPin);
  if (modeButtonCurrentState == LOW && modeButtonLastState == HIGH) { // button was unpressed and is now pressed
    modeButtonDownMillis = millis();
    modeButtonLastState = modeButtonCurrentState;
  }
  else if (modeButtonCurrentState == LOW && modeButtonLastState == LOW) { // button is held
    modeButtonHoldTime = millis() - modeButtonDownMillis;
    if ( modeButtonHoldTime > 50 ) {
      modeButtonLastState = modeButtonCurrentState;
            if (!displayActive && !modeButtonFired ) {
              enableDisplay();
              modeButtonFired = true;
            }
      //      else if (displayActive && !modeButtonFired ) {
      //        modeButtonAction();
      //        modeButtonFired = true;
      //      }
    }
    if ( modeButtonHoldTime > 2000 && !modeButtonHoldFired ) {
      // button is held longer
      modeButtonHoldFired = true;
      modeButtonHoldAction();
    }
  }
  else if (modeButtonCurrentState == HIGH && modeButtonLastState == LOW) { // button is released again
    modeButtonLastState = modeButtonCurrentState;
    if (displayActive && !modeButtonFired) {
      modeButtonAction();
    }
    modeButtonFired = false;
    modeButtonHoldFired = false;
  }



  unsigned long now = millis();

  if (displayActive && (now - displayLastOnMillis) > displayTimeout) {
    disableDisplay();
  }


  if ( (now - lastMsg) > measureinterval) {
    lastMsg = now;

    humidity = dht.readHumidity();
    temperature = dht.readTemperature();  // Read temperature as Celsius (the default)

    unsigned long heatingOnTime, heatingOffTime;

    if (heatingEnabled && turnHeatingOn) {
      heatingOnTime = (now - heatingLastOffMillis) / 1000;
      Serial.print("heatingOnTime: ");
      Serial.print(heatingOnTime);
      Serial.println();
    }
    else if (heatingEnabled && !turnHeatingOn) {
      heatingOffTime = (now - heatingLastOnMillis) / 1000;
      Serial.print("heatingOffTime: ");
      Serial.print(heatingOffTime);
      Serial.println();
    }

    if ( temperature >= setTemp ) {
      turnHeatingOn = false;
      heatingLastOnMillis = now;
      digitalWrite(RelaisPin, !RelaisOnState);
      Serial.println("heating off");

      Serial.print("last onTime: ");
      Serial.print(heatingOnTime);
      Serial.println();

      client.publish(mqtt_topic_heating, "off");

      //client.publish(mqtt_topic_heating_lastontime, heatingOnTime);
    }
    else if ( heatingEnabled && ( temperature < (setTemp - hysteresis) ) && ( heatingOffTime > minOffTime ) ) {
      turnHeatingOn = true;
      digitalWrite(RelaisPin, RelaisOnState);
      Serial.println("heating on");

      Serial.print("last offTime: ");
      Serial.print(heatingOffTime);
      Serial.println();

      client.publish(mqtt_topic_heating, "on");

      //client.publish(mqtt_topic_heating_lastofftime, heatingOffTime);
    }

    // set target temp for heating mode
    if (heatingMode == 1) { // heating on - default/day mode
      setTemp = setTempDay;
    }
    else if (heatingMode == 2) { // heating of - night/reduction mode
      setTemp = setTempNight;
    }



    //    if (lightMeter_present) {
    //      delay(50);
    //      lux = lightMeter.readLightLevel();
    //
    //      String lux_str = String(lux, 1); //converting humidity (the float variable above) to a string with 0 decimals
    //      char lux_chararr[lux_str.length() + 1];
    //      lux_str.toCharArray(lux_chararr, lux_str.length() + 1); //packaging up the data to publish to mqtt whoa...
    //
    //      Serial.print("Lux: ");
    //      Serial.println(lux_str);
    //      client.publish(mqtt_topic_lux, lux_chararr);
    //    }

    String hum_str = String(humidity, 0); //converting humidity (the float variable above) to a string with 0 decimals
    char hum_chararr[hum_str.length() + 1];
    hum_str.toCharArray(hum_chararr, hum_str.length() + 1); //packaging up the data to publish to mqtt whoa...

    String temp_str = String(temperature, 1); //converting Temperature (the float variable above) to a string with 1 decimal
    char temp_chararr[temp_str.length() + 1];
    temp_str.toCharArray(temp_chararr, temp_str.length() + 1); //packaging up the data to publish to mqtt whoa...

    char setTemp_chararr[5];     //result string 4 positions + \0 at the end
    // convert float to fprintf type string format 2 positions with 1 decimal place
    dtostrf(setTemp, 2, 1, setTemp_chararr );

    Serial.println("Publish messages temp/hum ");

    client.publish(mqtt_topic_temp, temp_chararr);
    client.publish(mqtt_topic_hum, hum_chararr);
    client.publish(mqtt_topic_settemp, setTemp_chararr);


    Serial.print("set temp: ");
    Serial.println(setTemp_chararr);
    Serial.print("current temp: ");
    Serial.println(temp_str);

    // print is-temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars
    lcd.setCursor(0, 0);
    lcd.write(0x3D); // = Zeichen
    lcd.print(" ");
    //if (temperature < -9) lcd.print(" ");
    //else if (temperature < 0) lcd.print("");
    //else if (temperature < 10) lcd.print("  ");
    //else lcd.print(" ");
    //lcd.print(tempstr2);
    lcd.print(temp_str); // inside temperature should hopefully always be 2.1 chars, so no special formatting necessary
    lcd.write(0xDF); // degree symbol
    lcd.print(" ");

    //lcd.print(humstr2);
    lcd.print(hum_str); // always 2 chars
    lcd.print("%");


    // display current mode on LCD
    lcd.setCursor(13, 0); // to char 15, line 1
    if (heatingEnabled) {
      if ( heatingMode == 1 ) { // day/normal mode
        lcd.print(" ");
        lcd.write((uint8_t)1); // sun symbol if mode is day/normal
        lcd.print(" ");
      }
      else if ( heatingMode == 2 ) { // night/reduction mode
        lcd.print(" ");
        lcd.write((uint8_t)0); // moon symbol if mode is night/reduction
        lcd.print(" ");
      }
    }
    else lcd.print("   "); // mode is heating off


    // display target temperature to line 2, 8 chars length incl space at the end
    lcd.setCursor(0, 1);

    // when the heating mode is OFF, do not display target temp - instead show "Heating off" info in line 2
    if ( !heatingEnabled ) {
      //         1234567890123456
      lcd.print("  Heizung aus   ");
    }
    else {
      lcd.write((uint8_t)2); // Pfeil rechts
      lcd.print(" ");
      lcd.print(setTemp_chararr);
      lcd.write(0xDF); // degree symbol
      lcd.print(" ");

      // display status info to line 2 from char 9 -> 8 chars length
      lcd.setCursor(8, 1);
      if (turnHeatingOn) {
        //         12345678
        lcd.print("heizen..");
      }
      else if ( heatingMode == 1 ) { // day/normal mode
        //         12345678
        lcd.print("        ");
      }
      else if ( heatingMode == 2 ) { // night/reduction mode
        //         12345678
        lcd.print("N-Absenk");
      }
      else lcd.print("        ");
    }


  }


  // Check if any reads failed and exit early (to try again).
  //if (isnan(hum) || isnan(temp)) {
  //  Serial.println("Failed to read from DHT sensor!");
  //  return;
  //}

}



