#include <Arduino.h>
#include "WizFi360.h"
#include <Wire.h>
#include <OLED.h>
#include <PZEM004Tv30.h>

float voltage;
float current;
float power;
float energy;
float frequency;
float pf;

namespace {
  char ssid[] = "XXXXXXXXXX"; // Assign your Network SSID
  char pass[] = "XXXXXXXXXX";  // Assign your Network password
  char SERVER[] = "industrial.api.ubidots.com"; // Ubidots Server
  const char * TOKEN = "XXXXXXXXXXXXXXXXXXXXX"; // Assign your Ubidots TOKEN
  const char * DEVICE_LABEL = "Energy-Monitor"; // Ubidots Device Label
  const char * VARIABLE_LABEL = "lamp"; // Assign the variable label to get the last value
  const char * USER_AGENT = "wiznet";
  const char * VERSION = "1.0";
  const int PORT = 80;
  int  status = WL_IDLE_STATUS; // the Wifi radio's status  
  int LAMP = 12;          // gpio 12 of the wizfi360 pico board
}

//prototype
float getData(const char * variable_label);
void sendValue(const char * variable_label, float value);
int dataLen(char* variable);
void read_energy();
void update_display();

WiFiClient client;
PZEM004Tv30 pzem(Serial2);

void setup() {
  Wire1.setSDA(2);
  Wire1.setSCL(3);
  Wire1.begin();
  delay(500);
  Oled.begin();  //initialze OLED display
  Oled.clearDisplay();            //clear the screen and set start position to top left corner
  Oled.setNormalDisplay();        //Set display to normal mode (i.e non-inverse mode)
  Oled.setPageMode();             //Set addressing mode to Page Mode

  Serial.begin(115200); 
  Serial2.begin(115200);  
  delay(6000);
  Serial.print("Starting...");
  Serial.println("WiFi initiallize");
  WiFi.init(&Serial2);
  
  if (WiFi.status() == 0) { 
    Serial.println("Failed to configure WiFi");
    // don't continue
    while(true)
    ;
  }
  //attempt to connect to WiFi network
  while (status != WL_CONNECTED) {
    Serial.print("[WiFi] Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    //Connect to WPA/WPA2 network
    status = WiFi.begin(ssid, pass);
    Serial.println("Connected!");
  }

  pinMode(LAMP, OUTPUT);
  delay(1000);
}

void loop() {
  read_energy();
  update_display();
  
  sendValue("Voltage", voltage);
  sendValue("Current", current);
  sendValue("Power", power);
  sendValue("Energy", energy);
  sendValue("Frequency", frequency);
  sendValue("Power Factor", pf);

  delay(500);

  float value = getData(VARIABLE_LABEL);
  Serial.print("The value received form Ubidots is: ");
  Serial.println(value);
  if ( value == 1.0) {
    digitalWrite(LAMP, HIGH);   
  } else {
    digitalWrite(LAMP, LOW);
  }

  delay(1000);
}

void sendValue(const char * variable_label, float value) {
  
  /* Build the body to be POST */
  char* body = (char *) malloc(sizeof(char) * 100);
  sprintf(body, "{\"%s\":%.2f}", variable_label, value);

  /* Builds the HTTP request to be POST */
  char* data = (char *) malloc(sizeof(char) * 300);
  sprintf(data, "POST /api/v1.6/devices/%s", DEVICE_LABEL);
  sprintf(data, "%s HTTP/1.1\r\n", data);
  sprintf(data, "%sHost: industrial.api.ubidots.com\r\n", data);
  sprintf(data, "%sUser-Agent: %s/%s\r\n", data, USER_AGENT, VERSION);
  sprintf(data, "%sX-Auth-Token: %s\r\n", data, TOKEN);
  sprintf(data, "%sConnection: close\r\n", data);
  sprintf(data, "%sContent-Type: application/json\r\n", data);
  sprintf(data, "%sContent-Length: %d\r\n\r\n", data, dataLen(body)); 
  sprintf(data, "%s%s\r\n\r\n", data, body);

  if (client.connect(SERVER, PORT)) {
    client.print(data);
  } else {
    Serial.println("connection failed");
  }
  
  while (!client.available());
    //Serial1.println("Reading..");
  while (client.available()) {
    char c = client.read();
    Serial.print(c); // Response Monitoring
  }
  client.flush();
  client.stop();
  free(body);
  free(data);
}

int dataLen(char* variable) {
  uint8_t dataLen = 0;
  for (int i = 0; i <= 250; i++) {
    if (variable[i] != '\0') {
      dataLen++;
    } else {
      break;
    }
  }
  return dataLen;
}

float getData(const char * variable_label) {
  /* Assigns the constans as global on the function */
  char* response; // Array to store parsed data
  char* serverResponse; // Array to store values
  float num;
  char resp_str[700]; // Array to store raw data from the server
  uint8_t j = 0;
  uint8_t timeout = 0; // Max timeout to retrieve data
  uint8_t max_retries = 0; // Max retries to make attempt connection

  /* Builds the request GET - Please reference this link to know all the request's structures https://ubidots.com/docs/api/ */
  char* data = (char *) malloc(sizeof(char) * 220);
  sprintf(data, "GET /api/v1.6/devices/%s/%s/lv", DEVICE_LABEL, variable_label);
  sprintf(data, "%s HTTP/1.1\r\n", data);
  sprintf(data, "%sHost: things.ubidots.com\r\n", data);
  sprintf(data, "%sUser-Agent: %s/%s\r\n", data, USER_AGENT, VERSION);
  sprintf(data, "%sX-Auth-Token: %s\r\n", data, TOKEN);
  sprintf(data, "%sConnection: close\r\n\r\n", data);

  /* Initial connection */
  client.connect(SERVER, PORT);
  
  /* Reconnect the client when is disconnected */
  while (!client.connected()) {
    Serial.println("Attemping to connect");
    if (client.connect(SERVER, PORT)) {
      break;
    }

    // Tries to connect five times as max
    max_retries++;
    if (max_retries > 5) {
      Serial.println("Could not connect to server");
      free(data);
      return NULL;
    }
    delay(5000);
  }

  /* Make the HTTP request to the server*/
  client.print(data);

  /* Reach timeout when the server is unavailable */
  while (!client.available() && timeout < 2000) {
    timeout++;
    delay(1);
    if (timeout >= 2000) {
      Serial.println(F("Error, max timeout reached"));
      client.stop();
      free(data);
      return NULL;
    }
  }

  /* Reads the response from the server */
  int i = 0;
  while (client.available()) {
    char c = client.read();
    //Serial.write(c); // Uncomment this line to visualize the response from the server
    if (c == -1) {
      Serial.println(F("Error reading data from server"));
      client.stop();
      free(data);
      return NULL;
    }
    resp_str[i++] = c;
  }

  /* Parses the response to get just the last value received */
  response = strtok(resp_str, "\r\n");
  while(response!=NULL) {
    j++;
    //printf("%s", response);
    response = strtok(NULL, "\r\n");
    if (j == 10) {
      if (response != NULL) {
        serverResponse = response;
      }
      j = 0;
    }
  }

  /* Converts the value obtained to a float */
  num = atof(serverResponse);
  free(data);
  /* Removes any buffered incoming serial data */
  client.flush();
  /* Disconnects the client */
  client.stop();
  /* Returns de last value of the variable */
  return num;
}

void update_display(){
  Oled.clearDisplay();           
  Oled.setTextXY(0, 0);           
  Oled.putString("Voltage: "); 
  Oled.setTextXY(0, 200);           
  Oled.putFloat(voltage);          
  
  Oled.setTextXY(2, 0);           
  Oled.putString("Current: "); 
  Oled.setTextXY(2, 200);          
  Oled.putFloat(current);         
  
  Oled.setTextXY(4, 0);           
  Oled.putString("Power  : "); 
  Oled.setTextXY(4, 200);           
  Oled.putFloat(power);          

  Oled.setTextXY(6, 0);           
  Oled.putString("Energy : "); 
  Oled.setTextXY(6, 200);           
  Oled.putFloat(energy);          
}

void read_energy(){
  Serial.print("Custom Address:");
  Serial.println(pzem.readAddress(), HEX);
  // Read the data from the sensor
  voltage = pzem.voltage();
  current = pzem.current();
  power = pzem.power();
  energy = pzem.energy();
  frequency = pzem.frequency();
  pf = pzem.pf();
}
