/*
  * WiFi Weather station with ESP-07 / ESP-12E and various sensor types
  *
  * 7.6.2017
  * Copyleft Lumir Vanek
  */

#include <ESP8266WiFi.h>
#include <OneWire.h>

// WiFi AP definitions
const char AP_SSID[] = "SSID";       // Case sensitive !
const char AP_PASSWORD[] = "secret";

// IOT service site information
const char http_ip[] = "10.5.73.x"; 
const int http_port = 8080;

/**
 * Allow turn some functionality ON/OFF - uncoment only one section with regards of which device programed !!!
 */

//************* Gargenhouse ************************** 
/*const char sensorCode_DallasTemp1[] = "LV_TEMP_01";
const char sensorCode_RSSI[] = "LV_RSSI_01";
#define _USE_DS18B20
#define ONE_WIRE_BUS 13  // DS18B20 pin*/
//****************************************************

//************* Workroom ****************************** 
/*
//const char sensorCode_DallasTemp1[] = "LV_TEMP_02";
const char sensorCode_Si7021Hum[] = "LV_HUM_03";
const char sensorCode_Si7021Temp[] = "LV_TEMP_12";
const char sensorCode_RSSI[] = "LV_RSSI_07";
//#define _USE_DS18B20
//#define ONE_WIRE_BUS 12  // DS18B20 pin
#define _USE_Si7021                 // I2C Si7021 Humidity and Temperature sensor */
//****************************************************

//************* Greenhouse 1 ************************* 
/*const char sensorCode_DallasTemp1[] = "LV_TEMP_03";  
const char sensorCode_BMP180Temp[] = "LV_TEMP_07";
const char sensorCode_BMP180Pres[] = "LV_PRES_02";
const char sensorCode_BMP180Atm[] = "LV_ATM_02";
const char sensorCode_BH1750Light[] = "LV_LIGH_01";
const char sensorCode_RSSI[] = "LV_RSSI_03";
#define _USE_DS18B20
#define ONE_WIRE_BUS 12  // DS18B20 pin
#define I2C_SDA      13
#define I2C_SCL      14
#define _USE_BH1750         // I2C Digital light sensor
#define _USE_BMP180         // I2C Digital pressure sensor*/
//****************************************************

//************* Greenhouse 2 ************************* 
/*const char sensorCode_Si7021Hum[] = "LV_HUM_02";
const char sensorCode_Si7021Temp[] = "LV_TEMP_10";
const char sensorCode_RSSI[] = "LV_RSSI_04";
#define I2C_SDA     12
#define I2C_SCL     13
#define _USE_Si7021                 // I2C Si7021 Humidity and Temperature sensor*/

//************* Basen ******************************** 
/*const char sensorCode_DallasTemp1[] = "LV_TEMP_11"; // Solar panel
const char sensorCode_DallasTemp2[] = "LV_TEMP_08"; // Water
const char sensorCode_DallasTemp3[] = "LV_TEMP_04"; // Air
const char sensorCode_RSSI[] = "LV_RSSI_05";
#define ONE_WIRE_BUS 13  // DS18B20 pin
#define _USE_DS18B20
#define SECND_DS18B20_PRESENT
#define THIRD_DS18B20_PRESENT*/
//****************************************************

//************* Air quality ************************** 
/*#define I2C_SDA     12
#define I2C_SCL     13
//#define _USE_CCS811                 // I2C CCS811 Air Quality sensor - Not works with ESP8266 for me */
//****************************************************

//************* Testboard **************************** 
const char sensorCode_DallasTemp1[] = "LV_TEMP_06";
const char sensorCode_BMP180Temp[] = "LV_TEMP_05";
const char sensorCode_BMP180Pres[] = "LV_PRES_01";
const char sensorCode_BMP180Atm[] = "LV_ATM_01";
const char sensorCode_RSSI[] = "LV_RSSI_06";
#define _USE_DS18B20
#define ONE_WIRE_BUS 13             // DS18B20 pin
#define I2C_SDA      12
#define I2C_SCL      14
#define _USE_BMP180                 // I2C Digital pressure sensor
//#define _USE_ADC                  // Read and send value from ADC
//****************************************************


#if defined(_USE_BMP180) || defined(_USE_BH1750) || defined(_USE_Si7021) || defined(_USE_CCS811)
  #include <Wire.h>         // I2C
#endif

#ifdef _USE_BMP180
  #include <Adafruit_BMP085_U.h>
  #define I2C_BMP180        0x77      // GY-68, Digital pressure sensor  (0xEE >> 1) 
  #define SEA_LEVEL_PRESSURE 10100 // LKMT - Ostrava Mosnov airport QNH http://meteo.rlp.cz/LKMT_meteo.htm
  //#define ALTITUDE 360 // Zasova - sklenik
  #define ALTITUDE 366 // Zasova - doma
  //#define ALTITUDE 322 // Valmez
#endif

#ifdef _USE_BH1750
  #define I2C_BH1750         0x23      // GY-30, BH1750FVI 16-bit Light sensor. ADDR -> GND: Measurement with H-Resolution Mode2
#endif

#ifdef _USE_Si7021
  #include "SparkFun_Si7021_Breakout_Library.h"
#endif

#ifdef _USE_CCS811
  #include <SparkFunCCS811.h>
#endif

#define REPORT_INTERVAL       300   // in seconds

/**
 * Global variables
 */
#ifdef _USE_DS18B20 
  #include <DallasTemperature.h>
  OneWire oneWire(ONE_WIRE_BUS);
  DallasTemperature DS18B20(&oneWire);
  #define TEMPERATURE_PRECISION 12
#endif

#ifdef _USE_BH1750
  // Buffer for reading 16-bit value from BH1750 Light sensor
  byte buff[2];
  uint16_t old_valBH1750 = 0;
#endif

#ifdef _USE_BMP180
  Adafruit_BMP085_Unified bmp085 = Adafruit_BMP085_Unified(10085);
#endif

#ifdef _USE_Si7021
  //  The Si7021 has a default I2C address of 0x40 and cannot be changed!
  Weather si7021; //Create Instance of HTU21D or SI7021 temp and humidity sensor and MPL3115A2 barrometric sensor
#endif

#ifdef _USE_CCS811
  #define I2C_CCS811 0x5B //Default I2C Address
  CCS811 ccs811(I2C_CCS811);
#endif

long rssi = 0;

/**
 * Connect to WiFi AP
 * @return true if success, otherwise false
 */
boolean wifiConnect()
{
    Serial.print("Connecting to AP");
    
    // Set WiFi mode to station (client)
    WiFi.mode(WIFI_STA);
  
    // Initiate connection with SSID and PSK
    WiFi.begin(AP_SSID, AP_PASSWORD);

    int numberOfTries = 12;
    while (WiFi.status() != WL_CONNECTED) 
    {
      if(numberOfTries == 6) 
      {
         WiFi.forceSleepWake();
         WiFi.begin(AP_SSID, AP_PASSWORD);
         Serial.print("x");
      }
      
      if(numberOfTries-- <= 0) 
      {
        Serial.println("WiFi connect failed !");
        return false;
      }
      delay(1000);
      Serial.print(".");
    }
    
    Serial.println("");
    Serial.println("WiFi connected");

    rssi = WiFi.RSSI();
    Serial.print("RSSI:");
    Serial.println(rssi);
    
    return true;
}

/**
 * Send value to IOT service
 */
void sendValue(const char * sensorCode, float value)
{  
    WiFiClient client;

    int numberOfTries = 5; 
    while(!client.connect(http_ip, http_port)) 
    {
      Serial.println("Connection to IOT service failed !");
      if(numberOfTries-- <=0) return;
      
      wifiConnect();
    }
    Serial.println("Connected");
 
    String url = "/IotService/rest/insert-value/" + String(sensorCode) + "/" + String(value);

    Serial.print("GET data to URL: ");
    Serial.println(url);

    // Make an HTTP GET request
    client.print( "GET " + url + " HTTP/1.1\r\n");
    client.print("Host: " + String(http_ip) + "\r\n"); 
    client.print("Connection: close\r\n");
    client.println();
  
    delay(100);
    while(client.available())
    {
      String line = client.readStringUntil('\r');
      Serial.print(line);
    }
    
    Serial.println();
    Serial.println("Connection closed");
}

void setup() 
{
    Serial.begin(115200);
    delay(10);
    Serial.println("Initializing ESP8266 module");

#ifdef _USE_DS18B20 
  DS18B20.begin();
#endif

#if defined(_USE_BMP180) || defined(_USE_BH1750) || defined(_USE_Si7021) || defined(_USE_CCS811)
    Serial.println("Initializing I2C bus");
    Wire.pins(I2C_SDA, I2C_SCL);   
    Wire.begin();             // join I2C bus
    Wire.pins(I2C_SDA, I2C_SCL);
#endif

#ifdef _USE_BH1750    
    /*
     * Init GY-30, BH1750FVI I2C 16 bit Light sensor.
     */
     bh1750Init(I2C_BH1750);
#endif

#ifdef _USE_BMP180
    /*
     * Init GY-68, BMP180 I2C Digital pressure sensor support.
     */
   
    if (!bmp085.begin())
    {
      Serial.print("Ooops, no BMP180 detected ... Check your wiring or I2C ADDR!");
      while (1);
    }
    else 
    {
      Serial.println("BMP180 ready.");
    }
#endif

#ifdef _USE_Si7021
    /*
     * Init Si7021 I2C temp and humidity sensor
     */
    si7021.begin();
#endif 

#ifdef _USE_CCS811
    /*
     * Init CCS811 I2C Air Quality sensor
     */
     delay(1000);
     
    //This begins the CCS811 sensor and prints error status of .begin()
    CCS811Core::status returnCode = ccs811.begin();
    Serial.print("CCS811 begin exited with: ");
    
    //Pass the error code to a function to print the results
    printCCS811DriverError(returnCode);
    Serial.println();
#endif

    pinMode(A0, INPUT);
}

void loop() 
{
  float temperature;
  
#ifdef _USE_DS18B20 
    /*
     * Temperarure from Dallas DS18B20 chip's
     */
        
    do 
    {
        DS18B20.setResolution(TEMPERATURE_PRECISION);
        DS18B20.requestTemperatures(); 
      
        temperature = DS18B20.getTempCByIndex(0);
        Serial.print("Temperature 1: ");
        Serial.print(temperature);
        Serial.println(" deg C");
    } while (temperature == 85.0 || temperature == (-127.0));

    if (wifiConnect() == false)
    {
      goDeepSleep();
      return;
    }
    
    sendValue(sensorCode_DallasTemp1, temperature);
#endif

#ifdef SECND_DS18B20_PRESENT
    do 
    {
        temperature = DS18B20.getTempCByIndex(1);
        Serial.print("Temperature 2: ");
        Serial.print(temperature);
        Serial.println(" deg C");
    } while (temperature == 85.0 || temperature == (-127.0));
        
    sendValue(sensorCode_DallasTemp2, temperature);
#endif

#ifdef THIRD_DS18B20_PRESENT
    do 
    {
        temperature = DS18B20.getTempCByIndex(2);
        Serial.print("Temperature 3: ");
        Serial.print(temperature);
        Serial.println(" deg C");
    } while (temperature == 85.0 || temperature == (-127.0));
        
    sendValue(sensorCode_DallasTemp3, temperature);
#endif
    
    delay(200); 

#ifndef _USE_DS18B20 // If Dallas chpis not used, we're not connnected yet
    if (wifiConnect() == false)
    {
      goDeepSleep();
      return;
    }
#endif

#ifdef _USE_BMP180    
    /*
     *  BMP180 values
     */
     
    // Get a new sensor event
    sensors_event_t bmpEvent;
    bmp085.getEvent(&bmpEvent);
  
    if (bmpEvent.pressure)
    {
      bmp085.getTemperature(&temperature);
      Serial.print("BMP180 Temperature: ");
      Serial.print(temperature);
      Serial.println(" deg C");
      sendValue(sensorCode_BMP180Temp, temperature);
      delay(200);
      
      float pressure = bmpEvent.pressure;
      Serial.print("Atm pressure:    ");
      Serial.print(pressure);
      Serial.println(" hPa");
      sendValue(sensorCode_BMP180Atm, pressure); 

      float slPressure = calcSealevelPressure(ALTITUDE, pressure, temperature);
      Serial.print("Sea level pressure:    ");
      Serial.print(slPressure);
      Serial.println(" hPa");
      sendValue(sensorCode_BMP180Pres, slPressure);
    }
    else
    {
      Serial.println("BMP180 Sensor error");
    }
#endif

#ifdef _USE_BH1750
    uint16_t val = 0;

    if(2 == bh1750Read(I2C_BH1750))
      {
         val = ((buff[0] << 8) | buff[1]) / 1.2;
      
         Serial.print("Light: ");
         Serial.print(val);

         sendValue(sensorCode_BH1750Light, val);
      }
#endif

#ifdef _USE_Si7021
    float humidity7021 = si7021.getRH();
    float temperature7021 = si7021.getTemp();

    Serial.print("Si7021 Humidity: ");
    Serial.print(humidity7021);
    Serial.println(" %");
    sendValue(sensorCode_Si7021Hum, humidity7021); 

    Serial.print("Si7021 Temperature: ");
    Serial.print(temperature7021);
    Serial.println(" deg C");
    sendValue(sensorCode_Si7021Temp, temperature7021);

#endif

#ifdef _USE_CCS811
    //Check to see if data is available
    if (ccs811.dataAvailable())
    {
      //Calling this function updates the global tVOC and eCO2 variables
      ccs811.readAlgorithmResults();
 /*             
  #ifdef _USE_Si7021
      //This sends the temperature data to the CCS811
      ccs811.setEnvironmentalData(humidity7021, temperature7021);
  #else
      ccs811.setRefResistance( 9950 );
      ccs811.readNTC();
      Serial.print(" Measured resistance : ");
      //After .readNTC() is called, .getResistance() can be called to actually
      //get the resistor value.  This is not needed to get the temperature,
      //but can be useful information for debugging.
      //
      //Use the resistance value for custom thermistors, and calculate the
      //temperature yourself.
      Serial.print( ccs811.getResistance() );
      Serial.println(" ohms");
  
      //After .readNTC() is called, .getTemperature() can be called to get
      //a temperature value providing that part SEN-00250 is used in the
      //NTC terminals. (NTCLE100E3103JB0)
      Serial.print(" Converted temperature : ");
      float readTemperature = ccs811.getTemperature();
      Serial.print(readTemperature, 2);
      Serial.println(" deg C");
  
      //Pass the temperature back into the CCS811 to compensate
      myCCS811.setEnvironmentalData(50, readTemperature);
  #endif*/

      Serial.print(" CO2 concentration : ");
      float co2 = ccs811.getCO2();
      Serial.print(co2);
      Serial.println(" ppm");
      sendValue(sensorCode_CCS811CO2, co2);
    
      Serial.print(" TVOC concentration : ");
      float voc = ccs811.getTVOC();
      Serial.print(voc);
      Serial.println(" ppb");
      sendValue(sensorCode_CCS811VOC, voc);
    }
    else if (ccs811.checkForStatusError())
    {
      //If the CCS811 found an internal error, print it.
      printCCS811SensorError();
    }
#endif

#ifdef _USE_ADC

    int adcValue = analogRead(A0);
    sendValue("ADC_TESTBRD", adcValue);
#endif

     /* 
      *  Send WiFi signal strength
      */
    rssi = WiFi.RSSI();
    sendValue(sensorCode_RSSI, (float) rssi);
    
     /*
      * Standard sleep, it eats too many energy from baterry
      */
     /*int cnt = REPORT_INTERVAL;
        
     while(cnt--)
       delay(1000);*/
  
    goDeepSleep();
}

void goDeepSleep()
{
    /*
     * Connect GPIO16 to RST to deep sleep functionality
     * ESP07-12E eats 6.4 mA while deep sleep
     */
    Serial.print("Going to deep sleep");
    delay(200);
    //sleep and try again
    ESP.deepSleep(REPORT_INTERVAL * 1000 * 1000, WAKE_RF_DEFAULT); // x min. update interval
    delay(1000);
}

///////////////////////////////////////////////////////////////////////////
//
// BH1750 Light sensor support
//
///////////////////////////////////////////////////////////////////////////

#ifdef _USE_BH1750 

int bh1750Read(int address) 
{
    int i = 0;
    Wire.beginTransmission(address);
    Wire.requestFrom(address, 2);
  
    while(Wire.available())
    {
      buff[i++] = Wire.read();  // receive one byte
    }
  
    Wire.endTransmission();  
    return i;
}

void bh1750Init(int address) 
{
    Wire.beginTransmission(address);
    Wire.write(0x10); //1lx resolution 120ms
    Wire.endTransmission();
}

#endif

///////////////////////////////////////////////////////////////////////////
//
// BMP180 Digital pressure sensor support
//
///////////////////////////////////////////////////////////////////////////

#ifdef _USE_BMP180 

float calcAltitude(float pressure)
{
    return 44330.0 * (1 - pow(pressure / SEA_LEVEL_PRESSURE, (1 / 5.25588)));
}

float calcMeanPressure(float altitude)
{
    return pow(1.0 - (altitude / 44330.0), 5.25588) * SEA_LEVEL_PRESSURE;
}

/*
 * http://keisan.casio.com/exec/system/1224575267
 */
float calcSealevelPressure(float altitude, float atm, float temperature)
{
    float alt = (0.0065 * altitude);
    return atm * pow(1.0 - (alt / (temperature + alt + 273.15)), -5.25588);
}
#endif


#ifdef _USE_CCS811
///////////////////////////////////////////////////////////////////////////
//
// CCS811 Air Quality sensor support
//
///////////////////////////////////////////////////////////////////////////


/*
 * Decode the CCS811Core::status type and prints the type of error to the serial terminal.
 *
 * Save the return value of any function of type CCS811Core::status, then pass
 * to this function to see what the output was.
 */
void printCCS811DriverError(CCS811Core::status errorCode)
{
    switch (errorCode)
    {
      case CCS811Core::SENSOR_SUCCESS:
        Serial.print("CCS811 SUCCESS");
        break;
        
      case CCS811Core::SENSOR_ID_ERROR:
        Serial.print("CCS811 ID ERROR");
        break;
        
      case CCS811Core::SENSOR_I2C_ERROR:
        Serial.print("CCS811 I2C ERROR");
        break;
        
      case CCS811Core::SENSOR_INTERNAL_ERROR:
        Serial.print("CCS811 INTERNAL ERROR");
        break;
        
      case CCS811Core::SENSOR_GENERIC_ERROR:
        Serial.print("CCS811 GENERIC ERROR");
        break;
        
      default:
        Serial.print("Unspecified error.");
    }
}

/*
 * Gets, clears, then prints the errors saved within the error register.
 */
void printCCS811SensorError()
{
    uint8_t error = ccs811.getErrorRegister();
  
    if (error == 0xFF) //comm error
    {
      Serial.println("Failed to get ERROR_ID register.");
    }
    else
    {
      Serial.print("Error: ");
      if (error & 1 << 5) Serial.print("HeaterSupply");
      if (error & 1 << 4) Serial.print("HeaterFault");
      if (error & 1 << 3) Serial.print("MaxResistance");
      if (error & 1 << 2) Serial.print("MeasModeInvalid");
      if (error & 1 << 1) Serial.print("ReadRegInvalid");
      if (error & 1 << 0) Serial.print("MsgInvalid");
      Serial.println();
    }
}
#endif