/*!
 * @file DataLogger.ino
 * @brief This code is used to record the time information obtained from NB-IOT into Time.csv and save it in U disk.
 * @n It serves as a template to record battery voltage, sensor readings and other data to the  *.csv file in application.
 *
 * @copyright   Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @licence     The MIT License (MIT)
 * @author      [Felix](Felix.Fu@dfrobot.com)
 * @version  V1.0
 * @date  2019-12-05
 * @get from https://www.dfrobot.com
 */

#include "FS.h"
#include "FFat.h"

#include "DFRobot_BC20.h"
#include "DFRobot_Iot.h"

#include <Wire.h>
#include <OneWire.h>

#include <DFRobot_MAX17043.h>
#include <DFRobot_ADS1115.h>

#define DS18B20_PIN  D2   //定义温度传感器引脚
#define PhSensor_PIN 0    //定义ph传感器引脚
#define TdsSensor_PIN 1   //定义tds传感器引脚
#define samplingInterval 20
#define printInterval 800
#define ArrayLenth  40    //times of collection
#define Offset 0.00            //deviation compensate
#define VREF 3.3      // analog reference voltage(Volt) of the ADC
#define SCOUNT  20           // sum of sample point
#define DataReadTimes 20   //每次唤醒时的数据采样次数

#define uS_TO_S_FACTOR 1000000              /* Conversion factor for micro seconds to seconds */
#define uS_TO_MIN_FACTOR 60000000           /* Conversion factor for microseconds to minutes */
//#define TIME_TO_SLEEP_SECOND  10         /* Time ESP32 will go to sleep (in seconds) */
#define TIME_TO_SLEEP_MINUTE  5         /* Time ESP32 will go to sleep (in mimutes) */

//配置证书信息
String ProductKey = "Your_Product_Key";
String ClientId = "12345";
String DeviceName = "Your_Device_Name";
String DeviceSecret = "Your_Device_Secret";

//配置域名和端口号
String ALIYUN_SERVER = "iot-as-mqtt.cn-shanghai.aliyuncs.com";
uint16_t PORT = 1883;

//配置产品标识符
String TempIdentifier = "Your_Temp_Identifier";
String PHIdentifier = "Your_ph_Identifier";
String TDSIdentifier = "Your_TDS_Identifier";
String GaugeIdentifier = "Your_batGauge_Identifier";

//需要发布和订阅的TOPIC
const char * subTopic = "Your_sub_Topic";//****set
const char * pubTopic = "Your_pub_Topic";//******post

String filePath = "/log.csv";
String tempStr = "";
// Items of the log table
String tableItem = "Year,Month,Day,Hour,Minute,Second,TimeZone, Battery Voltage, Temperature (C), PH , TDS (ppm)";

DFRobot_Iot myDevice;
DFRobot_BC20 myBC20;
DFRobot_ADS1115 ads(&Wire);
OneWire ds(DS18B20_PIN);
int16_t ads_adc0;
DFRobot_MAX17043 batGauge;

RTC_DATA_ATTR uint32_t bootCount = 0;

int pHData[DataReadTimes];   //Store the average value of the sensor feedback
int tempData[DataReadTimes];
int tdsData[DataReadTimes];
int readIndex = 0;
int pHArrayIndex = 0;
int analogBuffer[SCOUNT];    // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0, copyIndex = 0;
int gaugeValue = 0;

float averageVoltage = 0, tdsValue = 0, temperature = 25, phVoltage = 0, phValue = 0;
float tempSensor;
float phSensor;

bool checkFile(fs::FS &fs, const char * path) {
  Serial.printf("Check file: %s\r\n", path);
  File file = fs.open(path);
  if (!file || file.isDirectory()) {
    return false;
  }
  return true;
}

void writeFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("- file written");
  }
  else {
    Serial.println("- frite failed");
  }
}

void appendFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Appending to file: %s\r\n", path);

  File file = fs.open(path, FILE_APPEND);
  if (!file) {
    Serial.println("- failed to open file for appending");
    return;
  }
  if (file.print(message)) {
    Serial.println("- message appended");
  }
  else {
    Serial.println("- append failed");
  }
}

float getTemp() {
  //returns the temperature from one DS18S20 in DEG Celsius

  byte data[12];
  byte addr[8];

  if (!ds.search(addr)) {
    //no more sensors on chain, reset search
    ds.reset_search();
    return -1000;
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!");
    return -1000;
  }

  if (addr[0] != 0x10 && addr[0] != 0x28) {
    Serial.print("Device is not recognized");
    return -1000;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1); // start conversion, with parasite power on at the end

  byte present = ds.reset();
  ds.select(addr);
  ds.write(0xBE); // Read Scratchpad


  for (int i = 0; i < 9; i++) { // we need 9 bytes
    data[i] = ds.read();
  }

  ds.reset_search();

  byte MSB = data[1];
  byte LSB = data[0];

  float tempRead = ((MSB << 8) | LSB); //using two's compliment
  float TemperatureSum = tempRead / 16;

  return TemperatureSum;

}

void callback(char * topic, byte * payload, unsigned int len) {
  Serial.print("Recevice [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < len; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void ConnectCloud() {
  while (!myBC20.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (myBC20.connect(myDevice._clientId, myDevice._username, myDevice._password)) {
      Serial.println("Connect Server OK");
    }
    else {
      myBC20.getQMTCONN();
    }
  }
}

double avergearray(int* arr, int number) {
  int i;
  int max, min;
  double avg;
  long amount = 0;
  if (number <= 0) {
    Serial.println("Error number for the array to avraging!/n");
    return 0;
  }
  if (number < 5) {   //less than 5, calculated directly statistics
    for (i = 0; i < number; i++) {
      amount += arr[i];
    }
    avg = amount / number;
    return avg;
  }
  else {
    if (arr[0] < arr[1]) {
      min = arr[0]; max = arr[1];
    }
    else {
      min = arr[1]; max = arr[0];
    }
    for (i = 2; i < number; i++) {
      if (arr[i] < min) {
        amount += min;        //arr<min
        min = arr[i];
      }
      else {
        if (arr[i] > max) {
          amount += max;    //arr>max
          max = arr[i];
        }
        else {
          amount += arr[i]; //min<=arr<=max
        }
      }//if
    }//for
    avg = (double)amount / (number - 2);
  }//if
  return avg;
}

int getMedianNum(int bArray[], int iFilterLen)
{
  int bTab[iFilterLen];
  for (byte i = 0; i < iFilterLen; i++)
    bTab[i] = bArray[i];
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++)
  {
    for (i = 0; i < iFilterLen - j - 1; i++)
    {
      if (bTab[i] > bTab[i + 1])
      {
        bTemp = bTab[i];
        bTab[i] = bTab[i + 1];
        bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0)
    bTemp = bTab[(iFilterLen - 1) / 2];
  else
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  return bTemp;
}

void setup() {
  Serial.begin(115200);

  //锂电池电量计模块使能
  while (batGauge.begin() != 0) {
    Serial.println("batGauge begin faild!");
    delay(2000);
  }
  delay(2);
  Serial.println("batGauge begin successful!");

  //IIC ADC模块使能
  ads.setAddr_ADS1115(ADS1115_IIC_ADDRESS0);   // 0x48
  ads.setGain(eGAIN_TWOTHIRDS);   // 2/3x gain
  ads.setMode(eMODE_SINGLE);       // single-shot mode
  ads.setRate(eRATE_128);          // 128SPS (default)
  ads.setOSMode(eOSMODE_SINGLE);   // Set to start a single-conversion
  ads.init();

  Serial.print("Starting the BC20.Please wait. . . ");
  while (!myBC20.powerOn()) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("BC20 started successfully !");
  while (!myBC20.checkNBCard()) {
    Serial.println("Please insert the NB card !");
    delay(1000);
  }
  Serial.println("Waitting for access ...");
  while (myBC20.getGATT() == 0) {
    Serial.print(".");
    delay(1000);
  }

  //Set T3412(TAU) timer. the difference of T3412-T3324 determines how long the NB-IoT module will stay at PSM
  myBC20.setTAUTime(4, eTAUunit_30S);  /* 4x30s = 120s  0<time < 2^5*/
  //Set T3324 timer. This determines how long the NB-IoT module will stay at DRX/eDRX (idle) before entering PSM
  myBC20.setActiveTime(5, eAcTunit_2S); /* 5x2s = 10s*/
  if (myBC20.setPSMMode(ePSM_ON)) {
    Serial.println("set psm OK");
  }
  //T3412 and T3324 timer are not always configurable, which are usually determined by telecom operators
  //Their actuall settings need to be confirmed by the network
  myBC20.setEREG(4);
  myBC20.getEREG();
  Serial.println("Network feedback");
  Serial.print("T3324 timer is finally set to:");
  Serial.print(sCEREG.ActiveTime);
  Serial.print(" * ");
  Serial.println(sCEREG.ActiveUint);
  Serial.print("T3412 timer is finally set to:");
  Serial.print(sCEREG.TAUTime);
  Serial.print(" * ");
  Serial.println(sCEREG.TAUUint);
  //BC20 serial print "QATWAKEUP" when it is woken up from PSM
  if (myBC20.setQATWAKEUP(ON)) {
    Serial.println("set QATWAKEUP\r\n");
  }

  //Enable entering PSM.
  //When PSM is entered, BC20 will not receive any commands or signal from the moblie station (i.e. not controllable)
  //until it is woken up by T3412(TAU) timer or by pressing buttom SET(equally, a falling edge to pin PSM_EINT of BC20).
  //However, when during DRX/eDRX, BC20 will still response to AT commands or NB signal.
  if (myBC20.ConfigSleepMode(eSleepMode_DeepSleep)) {
    Serial.println("enable BC20 sleep");
  }

  Serial.println("Waiting for NB time...");
  while (myBC20.getCLK()) {
    if (sCLK.Year > 2000) {
      break;
    }
    Serial.print(".");
    delay(1000);
  }
  FFat.format();
  if (!FFat.begin()) {
    Serial.println("FFat Mount Failed");
    return;
  }
  /*for (int i = 1; i < 10000; i++) {
    char fileNameNum[4];
    sprintf(fileNameNum, "%04d", i);
    String nowFileName = (char *)fileNameNum;
    filePath = "/log" + nowFileName + ".csv";*/
    // if (!checkFile(FFat, filePath.c_str())) {
  Serial.println("Create a log.csv file");
  writeFile(FFat, filePath.c_str(), tableItem.c_str());
  appendFile(FFat, filePath.c_str(), "\r\n");
  Serial.print(filePath.c_str());
  Serial.println(" cteated!");
  //  break;
   // }
 // }
  Serial.println("Log begin...");

  //Connect to cloud
  myDevice.init(ALIYUN_SERVER, ProductKey, ClientId, DeviceName, DeviceSecret);
  myBC20.setServer(myDevice._mqttServer, PORT);
  myBC20.setCallback(callback);
  ConnectCloud();
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP_MINUTE * uS_TO_MIN_FACTOR);
}

void loop() {

  //This section will be called every time ESP32 wakes up from deep sleep
  //You can add codes here for otaining data from peripherals.

  //Wake up BC20 from PSM (equally, a falling edge to pin PSM_EINT of BC20)
  if (bootCount > 0) {
    Serial.print("Wake up BC20...");
    while (!myBC20.BC20WakeUp()) {
      Serial.print(".");
      delay(100);
    }
    Serial.println();
  }
  ++bootCount;

  myBC20.getCLK();  // Get CLK from BC20

  //reset后读取前十次数据
  for (int i = 0; i < 10; i++) {
    getTemp();
    ads.readVoltage(PhSensor_PIN);
    ads.readVoltage(TdsSensor_PIN);
    delay(100);
  }

  //读取锂电池电量
  //gauge.readVoltage();    //读取锂电池电压
  gaugeValue = batGauge.readPercentage();   //读取锂电池电量百分比

  for (int i = 0; i < DataReadTimes; i++) {
    //将读取到的温度存入温度数组
    tempData[readIndex] = getTemp();
    //将读取到的ph存入ph数组
    pHData[readIndex] = ads.readVoltage(PhSensor_PIN);
    //将读取到的TDS存入TDS数组
    tdsData[readIndex] = ads.readVoltage(TdsSensor_PIN);
    readIndex++;
    delay(300);
  }

  //数据处理
  tempSensor = getMedianNum(tempData, DataReadTimes);
  phVoltage = avergearray(pHData, DataReadTimes) / 1000;
  phSensor = 3.5*phVoltage + Offset;
  averageVoltage = getMedianNum(tdsData, SCOUNT) ; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
  float compensationCoefficient = 1.0 + 0.02*(temperature - 25.0)* (float)VREF / 4096.0;    //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
  float compensationVolatge = averageVoltage / compensationCoefficient;  //temperature compensation
  tdsValue = (133.42*compensationVolatge*compensationVolatge*compensationVolatge - 255.86*compensationVolatge*compensationVolatge + 857.39*compensationVolatge)*0.5; //convert voltage value to tds value
  
  //串口输出数据
  Serial.print("Battery Power");
  Serial.print(gaugeValue);
  Serial.println("%");
  Serial.print("Temp Value:");
  Serial.print(tempSensor, 1);
  Serial.println("℃");
  Serial.print("PH Value:");
  Serial.println(phSensor, 2);
  Serial.print("TDS Value:");
  Serial.print(tdsValue, 2);
  Serial.println("ppm");

  // Add one line of record to the log
  // For more items (String type), add here...
  tempStr = "" + (String)sCLK.Year + ","
    + (String)sCLK.Month + ","
    + (String)sCLK.Day + ","
    + (String)sCLK.Hour + ","
    + (String)sCLK.Minute + ","
    + (String)sCLK.Second + ","
    + sCLK.Mode + ","
    + String(gaugeValue); +","
    + String(tempSensor); +","
    + String(phSensor); +","
    + String(tdsValue); +","
    "\r\n";
  Serial.println(tableItem.c_str());
  Serial.println(tempStr);
  appendFile(FFat, filePath.c_str(), tempStr.c_str());

  myBC20.loop();
  myBC20.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"" + TempIdentifier + "\":" + tempSensor + ",\"" + PHIdentifier + "\":" + phSensor + ",\"" + TDSIdentifier + "\":" + tdsValue + ",\"" + GaugeIdentifier + "\":" + gaugeValue + "},\"method\":\"thing.event.property.post\"}").c_str());
  Serial.println("Data is published to cloud.");
  delay(10000);

  //ESP32 enter deep sleep mode.
  Serial.println("ESP32 is going to sleep now.");
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP_MINUTE * uS_TO_MIN_FACTOR);
  esp_deep_sleep_start();
}
