/*Power Analyzer
 *22.08.2018
 */

//Libraries
#include <Wire.h>
#include <TaskScheduler.h>
#include <LiquidCrystal_I2C.h>
#include <INA219.h>
#include <EEPROM.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h>
#include <SPIFFS.h>
#include <ESP8266FtpServer.h>

//Library Config
LiquidCrystal_I2C lcd(0x27,20,4);
INA219 ina219;
AsyncWebServer server(80);
DNSServer dns;
FtpServer ftpSrv;   

//Konstanten
#define R_SHUNT 0.001
#define V_SHUNT_MAX 0.060
#define V_BUS_MAX 26
#define I_MAX_EXPECTED 60
#define i2c_address 1
#define led_lowbat_pin 25
#define led_run_pin 26
#define button1_pin 14
#define button2_pin 17
#define button3_pin 16
#define adc_battery_pin A0

//Funktionsdeklarationen
void lcd_print();
void lcd_init();
void lcd_header();
void eeprom_save();
void i2c_receive();
void i2c_respond();
void readCurrent();
void read_bat();
void read_button();
void web_measuring_stop(AsyncWebServerRequest *request);
void web_measuring_run(AsyncWebServerRequest *request);
void web_measuring_reset(AsyncWebServerRequest *request);
void web_get_values(AsyncWebServerRequest *request);
void data_logging();

//Globale Variablen
float shuntvoltage_V = 0, shuntvoltage_mV = 0, busvoltage_V = 0, busvoltage_mV = 0, current_A = 0, current_mA = 0, power_W = 0, power_mW = 0;
float Ah = 0, mAh = 0, Wh = 0, mWh = 0;
float battery_voltage, battery_average;
int i_header = 0;
bool button1, button2, button3;
bool lcd_light = true;
bool measuring_run = false, reset_actual = false, reset_minmax =false, battery_low = false;
unsigned long lastread = 0;
unsigned long tick;
unsigned long previousMillisReadData = 0;
unsigned long previousMillisDisplay = 0;
unsigned long data_timestamp = 0;
byte i2c_MasterCommand = 0;

//Custom LCD Characters
byte grad[8] = {
  0b01100,
  0b10010,
  0b10010,
  0b01100,
  0b00000,
  0b00000,
  0b00000,
  0b00000
};

byte sollwert[8] = {
  0b00000,
  0b00000,
  0b01111,
  0b00011,
  0b00101,
  0b01001,
  0b10000,
  0b00000
};

byte batterie[8] = {
  0b01110,
  0b11011,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b11111
};

//Task Scheduler
Task t1(500, TASK_FOREVER, &lcd_print);
Task t2(1750, TASK_FOREVER, &lcd_header);
Task t3(10000, TASK_FOREVER, &read_bat);
Task t4(300, TASK_FOREVER, &read_button);
Task t5(30000, TASK_FOREVER, &data_logging);
//Task t3(60000, TASK_FOREVER, &eeprom_save);
Scheduler task;


void setup() {
  //Setup GPIO
  pinMode(led_lowbat_pin, OUTPUT);
  pinMode(led_run_pin, OUTPUT);
  pinMode(button1_pin, INPUT_PULLUP);
  pinMode(button2_pin, INPUT_PULLUP);
  pinMode(button3_pin, INPUT_PULLUP);
  battery_average = analogRead(A0);

  //Setup Debug Serial
  Serial.begin(115200);
  Serial.println("Power Analyzer by Carsten Schmiemann (C) 2018"); 

  //Init LCD and Custom Chars
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, grad);
  lcd.createChar(1, sollwert);
  lcd.createChar(2, batterie);
  digitalWrite(led_lowbat_pin, 255);
  digitalWrite(led_run_pin, 255);
  delay(1000);

  //Bootscreen
  lcd.clear();
  lcd.setCursor(4,0);
  lcd.print("Power Analyzer");
  lcd.setCursor(7,1);
  //lcd.print("Tracker");
  lcd.setCursor(5,2);
  lcd.print("V0.29 Beta");
  lcd.setCursor(7,3);
  lcd.print("CS,2018");
  lcd.write(1);

  //End Test Output PWM
  digitalWrite(led_lowbat_pin, 0);
  digitalWrite(led_run_pin, 0);
  delay(5000);
  lcd.clear();

  /*
  //Load Setpoint from EEPROM
  lcd.setCursor(0,0);
  lcd.print("Loading Setup from");
  lcd.setCursor(0,1);
  lcd.print("EEPROM...");
  EEPROM.get(0, ee_setpoint);
  setpoint = ee_setpoint;
  delay(800);
  lcd.setCursor(16,1);
  lcd.print("Done");
  delay(200);
*/

  //Init Wi-Fi
  lcd.setCursor(0,0);
  if (digitalRead(button3_pin)) {
    lcd.print("Setup Wi-Fi...");
    delay(300);
    AsyncWiFiManager wifiManager(&server,&dns);
    wifiManager.autoConnect("PowerAnalyzer WiFi");
    lcd.setCursor(16,0);
    lcd.print("Done");
  } else {
    lcd.print("Wi-Fi disabled");
  }

  //Init ina219s
  lcd.setCursor(0,1);
  lcd.print("Init Sensors...");  
  ina219.begin();
  ina219.configure(INA219::RANGE_16V, INA219::GAIN_2_80MV, INA219::ADC_16SAMP, INA219::ADC_16SAMP, INA219::CONT_SH_BUS);
  lastread = millis();
  ina219.calibrate(R_SHUNT, V_SHUNT_MAX, V_BUS_MAX, I_MAX_EXPECTED);
  delay(300);
  lcd.setCursor(16,1);
  lcd.print("Done");
  delay(200);

  //Init Task Scheduler
  lcd.setCursor(0,2);
  lcd.print("Init Threads...");
  task.init();
  task.addTask(t1);
  task.addTask(t2);
  task.addTask(t3);
  task.addTask(t4);
  task.addTask(t5);
  t1.enable();
  t2.enable();
  t3.enable();
  t4.enable();
  t5.enable();
  //Init Filesystem and Webserver
  SPIFFS.begin();
  server.on("/meas/run", HTTP_GET, web_measuring_run);
  server.on("/meas/stop", HTTP_GET, web_measuring_stop);
  server.on("/meas/reset", HTTP_GET, web_measuring_reset);
  server.on("/meas/values", HTTP_GET, web_get_values);
  server.on("/datalog.csv", HTTP_ANY, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/datalog.csv");
  });
  server.serveStatic("/img", SPIFFS, "/img");
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
  server.begin();
  ftpSrv.begin("power","analyzer"); 
  delay(500);
  lcd.setCursor(16,2);
  lcd.print("Done");
  delay(500);

  lcd.setCursor(0,3);
  lcd.print("Done. Starting up...");
  delay(1500);
  //Load Default Screen
  lcd_init();
}

void loop() {
  //Exec TaskScheduler
  task.execute();
  ftpSrv.handleFTP(); 
  unsigned long currentMillis = millis();  

  if ((unsigned long)(currentMillis - previousMillisReadData) >= 250) {
    previousMillisReadData = millis();
    readCurrent();
  }
}

void lcd_print() {
  if (busvoltage_V > 10) {lcd.setCursor(1,3);} else if (busvoltage_V < 0) {lcd.setCursor(1,3);} else {lcd.setCursor(1,3); lcd.print(" ");}
  lcd.print(busvoltage_V);
  if (current_A > 10) {lcd.setCursor(1,1);} else if (current_A < 0) {lcd.setCursor(1,1);} else {lcd.setCursor(1,1); lcd.print(" ");}
  lcd.print(current_A);
  if (power_W > 10) {lcd.setCursor(1,2);} else if (power_W < 0){lcd.setCursor(1,2);} else {lcd.setCursor(1,2); lcd.print(" ");}
  lcd.print(power_W);
  if (Ah > 10) {lcd.setCursor(13,1);} else if (Ah < 0){lcd.setCursor(11,1);} else {lcd.setCursor(11,1); lcd.print(" ");}
  lcd.print(Ah);
  if (Wh > 10) {lcd.setCursor(13,2);} else if (Wh < 0){lcd.setCursor(11,2);} else {lcd.setCursor(11,2); lcd.print(" ");}
  lcd.print(Wh);
  lcd.setCursor(15,3);
  if (battery_voltage > 3.4) {lcd.print(battery_voltage);} else {lcd.print("LOW ");}
}

void lcd_init() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("===--->Power<---===");
  lcd.setCursor(7,1);
  lcd.print("A");
  lcd.setCursor(7,2);
  lcd.print("W");
  lcd.setCursor(7,3);
  lcd.print("V");
  lcd.setCursor(17,1);
  lcd.print("Ah");
  lcd.setCursor(17,2);
  lcd.print("Wh");
  lcd.setCursor(14,3);
  lcd.write((uint8_t)2);
  lcd.setCursor(19,3);
  lcd.print("V");
  delay(300);
}

void lcd_header() {
  if (measuring_run) {
  i_header++;
  if (i_header == 1) {
    lcd.setCursor(0,0);
    lcd.print("-===-->Power<--===-");  
    }
  if (i_header == 2) {
    lcd.setCursor(0,0);
    lcd.print("--===->Power<-===--");  
    }
  if (i_header == 3) {
    lcd.setCursor(0,0);
    lcd.print("---===>Power<===---");  
    }
  if (i_header == 4) {
    lcd.setCursor(0,0);
    lcd.print("=---==>Power<==---=");  
    }
  if (i_header == 5) {
    lcd.setCursor(0,0);
    lcd.print("==---=>Power<=---==");  
    }
  if (i_header == 6) {
    lcd.setCursor(0,0);
    lcd.print("===--->Power<---===");  
    i_header = 0;
    }
  }
  //Flash Battery Low LED
  if (battery_low && digitalRead(led_lowbat_pin)) {
    digitalWrite(led_lowbat_pin,LOW);
  } else if (battery_low && digitalRead(led_lowbat_pin) == false) { digitalWrite(led_lowbat_pin,HIGH); }
}

/*void eeprom_save() {
  if (ee_setpoint != setpoint) {
    EEPROM.put(0, setpoint);
    ee_setpoint = setpoint;
    lcd.setCursor(0,2);
    lcd.print("                    ");
    lcd.setCursor(2,2);
    lcd.print("EEProm written.");
  }
}*/

void i2c_receive(int howMany){
  i2c_MasterCommand = Wire.read();
}

void i2c_respond(){
 
  int returnValue = 0;
 
  switch(i2c_MasterCommand){
    case 0:   // No new command was received
      Wire.write("Invalid Request");
    break;
    
    case 10:
      returnValue = busvoltage_V;
    break;
 
    case 20:
      returnValue = current_A;
    break;

    case 30:
      returnValue = power_W;
    
  }
 
  byte buffer[2]; 
  buffer[0] = returnValue >> 8;
  buffer[1] = returnValue & 255;
  Wire.write(buffer, 2);
  i2c_MasterCommand = 0;
}

void readCurrent() {
  uint32_t count = 0;
  unsigned long newtime;
  busvoltage_V = ina219.busVoltage();
  while(!ina219.ready() && count < 500) {
    count++;
    delay(1);
    busvoltage_V = ina219.busVoltage(); 
    busvoltage_mV = ina219.busVoltage() * 1000; 
  }
  shuntvoltage_V = ina219.shuntVoltage() * 1000;
  shuntvoltage_mV = ina219.shuntVoltage();
  current_A = ina219.shuntCurrent();
  current_mA = current_A * 1000;
  power_W = ina219.busPower();
  power_mW = power_W * 1000;
  newtime = millis();
  tick = newtime - lastread;
  if (measuring_run) {
      Ah += (current_A * tick)/3600000.0;
      mAh = Ah * 1000;
      Wh += (power_W * tick)/3600000.0;
      mWh = Wh * 1000;
  }
  if (reset_actual) {
    Ah = 0;
    Wh = 0;
    data_timestamp = 0;
    reset_actual = false;
  }
  lastread = newtime;
  ina219.recalibrate();
  ina219.reconfig();
}

void read_bat() {
  battery_average += (analogRead(adc_battery_pin) - battery_average) * 0.1;
  battery_voltage = map(battery_average,0,4096,0,450)/100.0;
  if (battery_voltage < 3.3 && battery_low == false) {
    battery_low = true;
    lcd.noBacklight();
  }
  if (battery_voltage > 3.6 && battery_low) {
    battery_low = false;
    digitalWrite(led_lowbat_pin,LOW);
    lcd.backlight();
  }
}

void read_button() {
  if (digitalRead(button1_pin)) {
    button1 = false;
  }
    else {
      button1 = true;
    }
  if (digitalRead(button2_pin)) {
    button2 = false;
    }
    else {
      button2 = true;
    }
  if (digitalRead(button3_pin)) {
    button3 = false;
    }
    else {
      button3 = true;
    }
  if (button1) {
    reset_actual = true;
  }
  if (button2 && measuring_run) {
    measuring_run = false;
      } else if (button2 && measuring_run == false) {
    measuring_run = true;
  }
  if (measuring_run) {
    digitalWrite(led_run_pin,true);
  } else {
    digitalWrite(led_run_pin,false);
  }
  if (button3 && lcd_light) {
    lcd_light = false;
    lcd.noBacklight();
      } else if (button3 && lcd_light == false) {
    lcd_light = true;
    lcd.backlight();
  }
}

void web_measuring_stop(AsyncWebServerRequest *request) {
  measuring_run = false;
  request->send(200);
}

void web_measuring_run(AsyncWebServerRequest *request) {
  measuring_run = true;
  request->send(200);
}

void web_measuring_reset(AsyncWebServerRequest *request) {
  reset_actual = true;
  request->send(200);
}

void web_get_values(AsyncWebServerRequest *request) {
    request->send( 200, "application/json", "{\"voltage\":" + String(busvoltage_V) + ", \"current\":" + String(current_A) + ", \"power\":" + String(power_W) + ", \"Ah\":" + String(Ah) + ", \"Wh\":" + String(Wh) + ", \"battery\":" + String(battery_voltage) + ", \"run\":" + String(measuring_run) + "}");
}

void data_logging() {
  if (measuring_run) {
  File tempLog = SPIFFS.open("/datalog.csv", "a");
      tempLog.print(data_timestamp); tempLog.print(',');
      tempLog.print(busvoltage_V); tempLog.print(',');
      tempLog.print(current_A); tempLog.print(',');
      tempLog.println(power_W); 
      tempLog.close();
      data_timestamp++;
  }
}