#include "main.h"
//Include libraries for project
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <SPI.h>
#include <EEPROM.h>
#include <Ethernet.h>

#include "EmonDIY.h"  
// Creating instance of emonlib
EnergyMonitor emon1;             

// Initialize Arduino Ethernet Client
EthernetClient client;


//initialise display  with the I2C addr 0x3C
#define i2c_Address 0x3c

#define SCREEN_WIDTH 128 // OLED display width
#define SCREEN_HEIGHT 64 // OLED display height
#define OLED_RESET -1

Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define PinA 3
#define PinB 2
#define PinSW 4
#define PinV 0
#define PinI 1
#define PinRele 5

//Variables for load controll
int loadSend = 0;
int loadMenu = 0;
int loadSelector = 0;

//Variables for encoder
boolean interuptCoder = false;
int pos = 0;
int storePos = 0;
int direct = 0;

int lastMSB = 0;
int lastLSB = 0;
volatile int lastEncoded = 0;

//Variables for menu of OLED display
boolean interuptSW = false;
volatile int menu = 1;
volatile int menuLevel = 1; 
unsigned long printPeriod = 500; //Refresh rate of display
unsigned long previousMillis = 0;

//Variebles for mesuring
float testFrequency = 50;                     // Mains voltage frequency (Hz)
float windowLength = 40.0 / testFrequency;   // Time for averiging values

float lastSendVoltage;
float lastSendCurrent;
float lastSendPower;
float lastSendPowerConsumption;

float consumedPower= 0;
float readingTime= 0;

// Local Network Settings
boolean internetConnection = 0; 
int internetMenu = 0;
byte mac[] = { 0xD4, 0x28, 0xB2, 0xFF, 0xA0, 0xA1 }; 

//Sending data Settings
const char* apiKeyValue = "tPmAT5Ab3j7F9"; 
const char* sensorName = "Sensor1";
const char* serverAdress = "192.168.100.123"; //b04-715a.kn.vutbr.cz
char tsPower [8];
char tsVoltage [8];
char tsCurrent [8];

// Variable Setup
int waitConnectionPeriod = 30;
int waitConnection = 0;
int failedCounter = 0;

//Encoder interupt
void interupt ()  {
  boolean MSB = digitalRead(PinA); //MSB = most significant bit
  boolean LSB = digitalRead(PinB); //LSB = least significant bit

  byte encoded = (MSB << 1) | LSB; //converting the 2 pin value from inputs to single number
  byte sum  = (lastEncoded << 2) | encoded; //adding converted value to previous encoded value

  if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
  {
    pos++;
    if ((pos % 2 == 0) && (pos != storePos)) 
    {
      storePos = pos;
      if (direct < 1)
        direct++;
      interuptCoder = true;
    }
  }
  if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
  {
    pos--;
    if ((pos % 2 == 0) && (pos != storePos))
    {
      storePos = pos;
      if (direct > 0)
        direct--;
      interuptCoder = true;
    }
  }
  lastEncoded = encoded;
}

void setup() {

  Serial.begin(9600);
  
  //W5100.setRetransmissionCount(2);

  //Imputs of encoder
  pinMode(PinA, INPUT_PULLUP);
  pinMode(PinB, INPUT_PULLUP);

  //Analog imputs for sensing
  pinMode(PinV, INPUT);
  pinMode(PinI, INPUT);
  pinMode(PinRele, OUTPUT);

  //Switch on rotary encoder
  pinMode(PinSW, INPUT_PULLUP);

  digitalWrite(PinA, HIGH); 
  digitalWrite(PinB, HIGH); 
  digitalWrite(PinSW, HIGH); 
  digitalWrite(PinRele, LOW); 

  //Turn on interupt pins
  attachInterrupt(digitalPinToInterrupt(PinA), interupt, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PinB), interupt, CHANGE);

  display.begin(i2c_Address, true);
  display.display();
  delay(20);
  display.setTextColor(SH110X_WHITE);
  display.clearDisplay();

  menuSelector();

  emon1.voltage(PinV, 268.0, 2.0);  // Voltage calibration
  emon1.current(PinI, 5.95);        // Current calibration

  if (EEPROM.read(0) == true)
  {
    internetMenu = 2;
    startEthernet();
  }

  if (EEPROM.read(1) == true)
  {
    loadMenu = 2;
    startLoad();
  }

  Serial.println("Start");
}

void loop() {
  if (interuptCoder == true)
  {
    menuSelector();
    interuptCoder = false;
  }

  if (!digitalRead(PinSW)) {
    while (!digitalRead(PinSW));
    if (menuLevel == 1)
      menuLevel--;
    else
      menuLevel++;
    interuptSW = true;
    menuSelector();
    delay(100);
  }

  //Calculating power and voltage and current
  emon1.calcVI(10, 200);

  //For dysplay ubdate and ethernet sending data
  if ((unsigned long)(millis() - previousMillis) >= printPeriod) {
    consumedPower += emon1.realPower * ((float)(millis() - previousMillis) /3600000);
    readingTime += ((float)(millis() - previousMillis) /60000);
    previousMillis = millis();   

    executeAction();

    if (EEPROM.read(0) == true)
    {
      if (waitConnection == waitConnectionPeriod)
      {
        waitConnection = 0;
        updateLocalServer();
      }

      if (((lastSendVoltage - emon1.Vrms) >= 5) || ((lastSendVoltage - emon1.Vrms) <= -5) || ((lastSendCurrent - emon1.Irms) >= 0.5) || ((lastSendCurrent - emon1.Irms) <= -0.5) || ((lastSendPower - emon1.realPower) >= 3) || ((lastSendPower - emon1.realPower) <= -3))
        updateLocalServer();


      if (failedCounter > 20 ) {
        startEthernet();
        failedCounter = 0;
      }
      waitConnection++;
    }

  }
}

//For selecting between menu and sub-menu
void menuSelector()
{
  switch (menuLevel) {
    case 0:
      updateMenu();
      interuptSW = false;
      break;
    case 1:
      executeAction();
      interuptSW = false;
      break;
  }
}

//Settings for Power Meter
void updateMenu() {

  if ((direct > 0) && (interuptCoder == true))
    menu++;
  else if ((direct == 0) && (interuptCoder == true))
    menu--;
  else;

  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0, 0);
  display.println("Settings");
  display.setTextSize(1);
  display.println("  Mesurement");
  display.setTextSize(1);
  display.println("  Advanced mesurement");
  display.setTextSize(1);
  display.println("  Network info");
  display.setTextSize(1);
  display.println("  Network connection");
  display.setTextSize(1);
  display.println("  Load control");
  display.setTextSize(1);
  display.println("  Totalizer");

  switch (menu) {
    case 0:
      menu = 1;
      break;
    case 1:
      display.setCursor(0, 16);
      display.setTextSize(1);
      display.print(">");
      display.display();
      break;
    case 2:
      display.setCursor(0, 24);
      display.setTextSize(1);
      display.print(">");
      display.display();
      break;
    case 3:
      display.setCursor(0, 32);
      display.setTextSize(1);
      display.print(">");
      display.display();

      break;
    case 4:
      display.setCursor(0, 40);
      display.setTextSize(1);
      display.print(">");
      display.display();

      break;
    case 5:
      display.setCursor(0, 48);
      display.setTextSize(1);
      display.print(">");
      display.display();

      break;
    case 6:
      display.setCursor(0, 56);
      display.setTextSize(1);
      display.print(">");
      display.display();

      break;
    case 7:
      menu = 6;
      break;
  }
}

//Sub-menu for Power Meter
void executeAction() {
  if (menuLevel == 1)
  {
    if (interuptSW == true)
      display.clearDisplay();
    else;

    switch (menu) {
      case 0:
        menu = 1;
        break;
      case 1:
        display.clearDisplay();
        display.setCursor(0, 0);

        display.setTextSize(1);
        display.print("Power:  ");
        display.setTextSize(2);
        display.print((int)emon1.realPower);
        display.println("W");
        display.setTextSize(1);
        display.println("");
        display.print("Voltage:");
        display.setTextSize(2);
        display.print((int)emon1.Vrms);
        display.println("V");
        display.setTextSize(1);
        display.println("");
        display.print("Curent: ");
        display.setTextSize(2);
        display.print(emon1.Irms);
        display.println("A");

        display.display();
        break;
      case 2:
        display.clearDisplay();
        display.setCursor(0, 0);

        display.setTextSize(1);
        display.print("Apparent Power: ");
        display.setCursor(0, 12);
        display.setTextSize(2);
        display.print((int)emon1.apparentPower);
        display.println("Wh");
        display.setCursor(0, 32);
        display.setTextSize(1);
        display.print("Power factor: ");
        display.setCursor(0, 44);
        display.setTextSize(2);
        display.print(emon1.powerFactor);
        display.display();
        break;
      case 3:
        display.clearDisplay();
        display.setCursor(0, 0);
        display.setTextSize(2);
        display.print("Network");
        display.setTextSize(1);
        display.print("");
        display.setTextSize(2);
        display.print("inf");
        display.println("");

        if (EEPROM.read(0) == true)
        {
          display.setCursor(0, 20);
          display.setTextSize(1);
          display.print("IP adres:");
          display.setCursor(0, 30);
          display.println(Ethernet.localIP());
          display.setCursor(0, 39);
          display.print("Gateway:");
          display.setCursor(0, 48);
          display.println(Ethernet.gatewayIP());

        }
        else
        {
          display.setTextSize(1);
          display.println("");
          display.println("Network connection not allowed");
        }
        display.display();
        break;
      case 4:
        internetChoose();
        break;
      case 5:
        loadChoose();
        break;
      case 6:
        display.clearDisplay();
        display.setCursor(0, 0);

        display.setTextSize(1);
        display.print("Totalizer:  ");
        display.setCursor(0, 12);
        display.setTextSize(2);
        display.print(consumedPower);
        display.println("Wh");
        display.setCursor(0, 32);
        display.setTextSize(1);
        display.print("Reading time:  ");
        display.setCursor(0, 44);
        display.setTextSize(2);
        display.print((int)readingTime);
        display.println("min");
        display.display();
        break;
      case 7:
        menu = 6;
        break;
    }
  }
}

//Sub-menu for choosing load
void loadChoose() {
  if (!digitalRead(PinSW)) {
    while (!digitalRead(PinSW));
    menuLevel = 0;
    menuSelector();

    if (loadMenu == 2)
    {
      EEPROM.update(1, true);
      if (EEPROM.read(0) == true)
        {
          loadSend = 1;
          updateLocalServer();
          startLoad();
        }
        else
        {
          startLoad();
        }
    }
    else
    {
      EEPROM.update(1, false);
        if (EEPROM.read(0) == true)
        {
          loadSend = 1;
          updateLocalServer();
          startLoad();
        }
        else
        {
          startLoad();
        }
    }
    delay(100);

  }
  else
  {
    if ((direct > 0) && (interuptCoder == true))
    {
      loadMenu++;
      interuptCoder = false;
    }
    else if ((direct == 0) && (interuptCoder == true))
    {
      loadMenu--;
      interuptCoder = false;
    }

    display.clearDisplay();
    display.setCursor(0, 0);

    display.setTextSize(2);
    display.println("Load");
    display.setCursor(20, 32);
    display.setTextSize(2);
    display.print("OFF");
    display.setCursor(78, 32);
    display.setTextSize(2);
    display.println("ON");

    switch (loadMenu) {
      case 0:
        loadMenu = 1;
        break;
      case 1:
        display.fillRoundRect(16, 28, 42, 22, 4, SH110X_WHITE);  // Draw rounded rectangle (x,y,width,height,radius,color)
        display.setCursor(20, 32);
        display.setTextColor(SH110X_BLACK);
        display.setTextSize(2);
        display.print("OFF");
        display.setTextColor(SH110X_WHITE);
        display.display();
        break;
      case 2:
        display.fillRoundRect(68, 28, 42, 22, 4, SH110X_WHITE);  // Draw rounded rectangle (x,y,width,height,radius,color)
        display.setCursor(78, 32);
        display.setTextColor(SH110X_BLACK);
        display.setTextSize(2);
        display.println("ON");
        display.setTextColor(SH110X_WHITE);
        display.display();
        break;
      case 3:
        loadMenu = 2;
        break;
    }
    loadChoose();
  }
}

//Setting load controll to HIGH or LOW
void startLoad()
{
  if (EEPROM.read(1) == true)
    digitalWrite(PinRele, HIGH);
  else
    digitalWrite(PinRele, LOW);
  menuSelector();
}

//Sub-menu for choosing internet conection
void internetChoose()
{
  if (!digitalRead(PinSW)) {
    while (!digitalRead(PinSW));
    menuLevel = 0;
    menuSelector();
    if (internetConnection == true)
    {
      EEPROM.update(0, true);
      startEthernet();
    }
    else
    {
      EEPROM.update(0, false);
      startEthernet();
    }
    delay(100);

  }
  else
  {
    if ((direct > 0) && (interuptCoder == true))
    {
      internetMenu++;
      interuptCoder = false;
    }
    else if ((direct == 0) && (interuptCoder == true))
    {
      internetMenu--;
      interuptCoder = false;
    }

    display.clearDisplay();
    display.setCursor(0, 0);

    display.setTextSize(2);
    display.println("Network");
    display.setCursor(20, 32);
    display.setTextSize(2);
    display.print("OFF");
    display.setCursor(78, 32);
    display.setTextSize(2);
    display.println("ON");

    switch (internetMenu) {
      case 0:
        internetMenu = 1;
        break;
      case 1:
        display.fillRoundRect(16, 28, 42, 22, 4, SH110X_WHITE);  // Draw rounded rectangle (x,y,width,height,radius,color)
        display.setCursor(20, 32);
        display.setTextColor(SH110X_BLACK);
        display.setTextSize(2);
        display.print("OFF");
        display.setTextColor(SH110X_WHITE);
        display.display();
        internetConnection = false;
        break;
      case 2:
        display.fillRoundRect(68, 28, 42, 22, 4, SH110X_WHITE);  // Draw rounded rectangle (x,y,width,height,radius,color)
        display.setCursor(78, 32);
        display.setTextColor(SH110X_BLACK);
        display.setTextSize(2);
        display.println("ON");
        display.setTextColor(SH110X_WHITE);
        display.display();
        internetConnection = true;
        break;
      case 3:
        internetMenu = 2;
        break;
    }
    internetChoose();
  }
}

//Connecting to DHCP server and starting connection 
void startEthernet()
{
  client.stop();

  if (EEPROM.read(0) == true)
  {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(2);
    display.println("Connection");
    display.println("STARTING");
    display.println("...");
    display.display();

    if (Ethernet.begin(mac) == 0)
    {
      Serial.println("DHCP Failed, reset Arduino to try again");
      Serial.println();
      display.clearDisplay();
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.println("Connection");
      display.println("FAILED");
      display.println("...");
      display.display();
      delay(1000);
      menuSelector();
    }
    else
    {
      Serial.println("Arduino connected to network using DHCP");
      Serial.println();
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.clearDisplay();
      display.println("Connection");
      display.println("SUCCESS");
      display.println("...");
      display.display();
      loadSend = 1;
      updateLocalServer();
      delay(1000);
      menuSelector();
    }
  }
  menuSelector();
}
//Sending data to server
void updateLocalServer()
{
  int inChar;
  char tsData[strlen(apiKeyValue) + strlen(sensorName) + 200];
  client.setConnectionTimeout(100);
  Serial.println("T1");
  if (client.connect(serverAdress, 80))
  {
    Serial.println("T2");
    lastSendVoltage = emon1.Vrms;
    lastSendCurrent = emon1.Irms;
    lastSendPower = emon1.realPower;
    float sendConsumption = consumedPower - lastSendPowerConsumption;

    char tsPower [8];
    char tsConsumption [16];
    char tsVoltage [8];
    char tsCurrent [8];
    char tsloadSend [8];
    char tsRelay [8];

    dtostrf(lastSendVoltage, 1, 2, tsVoltage);
    dtostrf(lastSendCurrent, 1, 2, tsCurrent);
    dtostrf(lastSendPower, 1, 2, tsPower);
    dtostrf(sendConsumption, 1, 2, tsConsumption);
    dtostrf(loadSend , 1, 2, tsloadSend);
    dtostrf((int) EEPROM.read(1) , 1, 2, tsRelay);

    strcpy(tsData, "");
    strcat(tsData, "api_key=");
    strcat(tsData, apiKeyValue);
    strcat(tsData, "&sensor=");
    strcat(tsData, sensorName);
    strcat(tsData, "&value1=");
    strcat(tsData, tsVoltage);
    strcat(tsData, "&value2=");
    strcat(tsData, tsCurrent);
    strcat(tsData, "&value3=");
    strcat(tsData, tsPower);
    strcat(tsData, "&value4=");
    strcat(tsData, tsConsumption);
    strcat(tsData, "&loadSend=");
    strcat(tsData, tsloadSend);
    strcat(tsData, "&relay=");
    strcat(tsData, tsRelay);

    client.println(F("POST /post-data.php HTTP/1.1"));
    client.println(F("Host: 192.168.0.110"));
    client.println(F("User-Agent: Arduino/1.0"));
    client.println(F("Connection: close"));
    client.println(F("Content-Type: application/x-www-form-urlencoded;"));
    client.print(F("Content-Length: "));
    client.println(strlen(tsData));
    client.println();
    client.println(tsData);

    char c;
    if(loadSend == 0)
    {
    while (client.connected()) {
      while (client.available()) {
        // read an incoming byte from the server and print it to serial monitor:
        c = (char) client.read();
        //Serial.println(c);
        if (c == 36) {
          c = client.read();
          Serial.println(c);
          switch (c) {
            case 48:
              if (EEPROM.read(1) == true)
                  EEPROM.update(1, false);
                  loadMenu = 1;
                  startLoad();
              break;
            case 49:
              if (EEPROM.read(1) == false)
                EEPROM.update(1, true);
              loadMenu = 2;
              startLoad();
              break;
            default:
              break;
          }
          //delay(100);
        }
        }
        failedCounter = 0;
      }
      }
      loadSend = 0;
      failedCounter = 0;
      
      if (failedCounter == 0)
        lastSendPowerConsumption = consumedPower;

    if (!client.connected())
    {
      failedCounter++;
    }
  }
  else
  {
    failedCounter++;
    Serial.println("T3");
  }
}