/*******************************************************************************************
 *******************************************************************************************
 **                                www.bastelgarage.ch                                    **
 ** Der Onlineshop mit Videoanleitungen und kompletten Bausätzen für Anfänger und Profis! **
 *******************************************************************************************
 *******************************************************************************************
 ** In diesem Beispiel zeigen wir dir wie du ein LCD Display mit dem ESP8266              **
 ** Mikrocontroller ansteuern kannst. für die Ansteuerung nutzen wir ein I2C Adapter der  **
 ** auf das Display montiert wird. Somit kann das Display über die I2c Adresse 0x27       **
 ** angesprochen werden. Da wir in diesem Beispiel ein 5V Display einsetzen und der       **
 ** ESP8266 auf 3.3V läuft ist noch ein Logic Level Converter notwendig der die           **
 ** 3.3V auf 5V konvertiert. Der SI7021 ist ein Luftfeuchtigkeitssensor der die Relative  **
 ** Luftfeuchtigkeit und Temperatur misst. Da es sich beim SI7021 um ein 3.3V Sensor      **
 ** handelt muss er auf der 3.3V Seite des Logic Level Converter angeschlossen werden.    **
 ** in diesem Beispiel wollen wir lernen wie man Benutzerdefinierte Symbole auf dem       **
 ** Display darstellen kann. Wir wollen auch die Luftfeuchtigkeit und Temperatur aus dem  **
 ** Si7021 auslesen und auf dem Display darstellen.                                       **
 ** Mittlers einem Ultraschal Distanzsensor HC-SR04 schalten wir die Beleuchtung des      **
 ** Displays ein sobald sich ein Objekt vor dem Raumsensor befindet.                      **
 ** Nun kommt noch zusätzlich der MQ135 Sensor dazu der die Luftqualität messen soll.     **
 ** Der MQ135 ist kein hoch genauer Sensor, jedoch reicht er für diese Applikation        **
 ** vollkommen aus. Wir werden in diesem Beispiel auch noch ein Kalibriertaster Auswerten **
 ** und die Kalibrierdaten in das EEPROM Speichern.                                       **
 ** Damit wir eine Mittelwertbildung durchführen können arbeiten wir mit der Median       **
 ** Library die uns diese Mittelungen übernimmt. Die Gemessenen Werte sollen nun noch in  **
 ** ein Thingspeak Kanal gelogt werden.                                                   **
 **                                                                                       **
 ** 14.02.2017 Version 2:                                                                 **                                                                                       
 ** ---------------------                                                                 **
 ** - Es kam vor das der Ultraschallsensor fehlerhafte Messungen hatte, die zum           **
 **   Einschalten der Hintergrundbeleuchtung führten. Damit diese Fehlmessungen gefiltert **
 **   werden, wurde nun eine Median Funktion hinzugefügt die über 5 Messwerte filtert.    **
 **                                                                                       **
 **                                                                                       **
 **                                                                                       **
 ** Autor: Philippe Keller                                                                **
 ** Datum: Februar 2017                                                                   **
 ** Version: 2.0                                                                          **
 *******************************************************************************************
*/

/************************( Importieren der genutzten Bibliotheken )************************/
#include <Wire.h>                   
#include <LiquidCrystal_I2C.h>                  
#include <SI7021.h>
#include <Ultrasonic.h>
#include <RunningMedian.h>
#include <EEPROM.h>
#include <Adafruit_ADS1015.h>
#include "ThingSpeak.h"
#include <ESP8266WiFi.h>

/********************( Definieren der Globale Variablen und Konstanten)********************/
float t;                                      // Variable zum Speichern der Temperatur
float h;                                      // Variable zum Speichern der Luftfeuchtigkeit
float ppm;                                    // Variable zum Speichern von Co2
float MQ135WiderstandZero;                    // Variable zum Speichern von Rz
long HCSR04Distanz;                           // Variable zum Speichern der gemessen Distanz
long lcdLastOnTime;                           // Letzte Zeit wo Beleuchtung eingeschaltet wurde
unsigned long ThingspeakLastLogTime = 0;
unsigned long SensorenLetzteAktualisierung;            // Hilfsvariable für Zeitmessung

int16_t adc0, adc1, adc2, adc3;

int status = WL_IDLE_STATUS;
/**********************************(Thingspeak Eistellungen)*******************************/
unsigned long myChannelNumber = 0;         // Deine ThingSpeak Kanal Nummer anstelle von 0
const char * myWriteAPIKey = "0"; // Dein ThingSpeak API Key anstelle von 0

/************************************(Wifi Einstellungen)**********************************/
char ssid[] = "0";               // Deine Wlan SSID (name) anstelle des 0
char pass[] = "0";               // Dein Wlan Passwort anstelle des 0

/*******************( Definieren von Variable Zeiten die änderbar sind )********************/
int LcdBeleuchtungsDauer = 30;                // Einschaltzeit LCD Beleuchtung in Sekunden                                                  
int SensorenSampleTime = 1;                   // Zeit in s für Aktualisierung der
                                              // Sensordaten und Anzeige
                                              
int ThingspeakSampleTime = 60000;             // Die schnellste sampletime ist 15s ich empfehle 
                                              // 60s zu verwenden oder 300 Sekunden                               
                                              
/******************************(Ultraschall Sensor Einstellungen)***************************/
#define TRIGGER_PIN       14  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN          12  // Arduino pin tied to echo pin on the ultrasonic sensor.
unsigned int MAX_TimeOut = 80 * 58;       // Timeout Max.distanz(cm) * 58   Hier also 80cm 
                                          // Beispiel: 400cm*58 = 23200 

/********************(Einstellungen für MQ135)********************/

#define MQ135_DEFAULTPPM 392 //default ppm of CO2 for calibration
#define MQ135_SCALINGFACTOR 116.6020682 //CO2 gas value
#define MQ135_EXPONENT -2.769034857 //CO2 gas value
#define MQ135_PULLDOWNRES 20000
long MQ135_DEFAULTRO = 41000; //default Ro for MQ135_DEFAULTPPM ppm of CO2


/********************************( Definieren der Objekte )********************************/
// Einstellen der I2C Adresse des Displays auf 0x27 für
// Character Display mit zwei Linien und 16 Zeichen (PCF8574T I2C Adapter)

//LiquidCrystal_I2C lcd(0x3F, 16, 2); //I2c Adapter mit PCF8574AT(ohne Brücken auf 0,A1,A2)
LiquidCrystal_I2C lcd(0x27, 16, 2); //I2c Adapter mit PCF8574T(ohne Brücken auf 0,A1,A2)

Ultrasonic ultrasonic(TRIGGER_PIN, ECHO_PIN, MAX_TimeOut);  //Library für Distanzmessung initialisieren

SI7021 SI7021_Sensor;                                // Si7021 initialisieren 
Adafruit_ADS1115 ads;                                // ADS1115 initialisieren

RunningMedian MedianPPM = RunningMedian(40);         // Mittelung aktivieren über 40 Messwerte
RunningMedian MedianMQ135WiderstandZero =  RunningMedian(40);
RunningMedian Mediant = RunningMedian(10);
RunningMedian Medianh = RunningMedian(10);
RunningMedian MedianUltrasonic = RunningMedian(5);

WiFiClient  client;

/******************************( Definieren der LCD Symbolen )*****************************/
// ein Super Tool um Symbole zu erzeugen findest du unter folgendem Link:
// https://omerk.github.io/lcdchargen/

byte thermometer[8] = //Icon für Thermometer Symbol
{ 
  B00100,
  B01010,
  B01010,
  B01110,
  B01110,
  B11111,
  B11111,
  B01110
};

byte tropfen[8] = //Icon für Wassertropf Symbol
{
  B00100,
  B00100,
  B01010,
  B01010,
  B10001,
  B10001,
  B10001,
  B01110,
};

byte CO2[8] = //Icon für CO2 Symbol
{
  0b00000,
  0b01100,
  0b10010,
  0b00100,
  0b01000,
  0b11110,
  0b00000,
  0b00000
};

/*****************************************( Setup )****************************************/
void setup() {
  Wire.begin(D2, D1);         // Festlegen der I2c Pins und initialisieren (4,5 oder D2,D1)
  lcd.begin();                // LCD Initialisieren
  lcd.backlight();            // LCD Hintergrundbeleuchtung ein
  lcd.clear();                // LCD Anzeige Löschen

  lcd.setCursor(0, 0);           // Cursor auf das erste Zeichen auf der ersten Zeile setzen.
  lcd.print("Bastelgarage.ch");  // Text schreiben maximal 16 Zeichen lang
  delay(3000);                   // Wartezeit in ms (3 Sekunden warten)
  lcd.setCursor(0, 0);           // Cursor auf das erste Zeichen auf der ersten Zeile setzen.
  lcd.print("Raumsensor     ");  // Text schreiben maximal 16 Zeichen lang
  lcd.setCursor(0, 1);           // Cursor auf das erste Zeichen auf der zweiten Zeile setzen.
  lcd.print("Version 2.0");      // Text schreiben maximal 16 Zeichen lan
  delay(3000);                   // Wartezeit in ms (3 Sekunden warten)
  lcd.clear();

  lcd.createChar(1, thermometer); //Benutzerdefiniertes Thermometer Symbol auf LCD Speichern
  lcd.createChar(2, tropfen);     //Benutzerdefiniertes Wassertropfen Symbol auf LCD Speichern
  lcd.createChar(3, CO2);         //Benutzerdefiniertes CO2 Symbol auf LCD Speichern

  ads.setGain(GAIN_TWOTHIRDS);    // ADS auf +/- 6.144V einstellen
  ads.begin();                    // ADS Starten

  
  pinMode(D4, INPUT);             // Pin D4 für Kalibriertaster auf Input Stellen
  digitalWrite(D4, HIGH);         // Pin D4 interner Pull up Wiederstand aktivieren

  EEPROM.begin(512);              
  EEPROM.get(0, MQ135_DEFAULTRO); // Wert aus EEPROM Lesen
  EEPROM.end();

  WiFi.begin(ssid, pass);         // Wlan Aktivieren und mit definiertem Netzwerk verbinden

  ThingSpeak.begin(client);       // ThingSpeak Client starten
}


/***********************(Funktion um Daten auf das LCD zu Schreiben)***********************/
void lcdSchreiben() {

  // Zuerst wird der Cursor auf dem LCD Display da gesetzt wo etwas geschrieben werden soll.
  // Dazu wird der Befehl lcd.SetCursor(0,0) genutzt.
  // lcd.SetCursor(X,Y)
  // Bei X wird die Position in der Zeile gewählt in unserem Beispiel mit 0 ist es die erste Position
  // ganz der Zeile also das erste Zeichen.
  // Bei Y wird die Zeile angewählt. in unserem Beispiel ist es eine 0 also die erste Zeile.
  
  lcd.setCursor(2, 0);       // Cursor auf das drittes Zeichen auf der ersten Zeile setzen.
  lcd.print(ppm, 0);         // Aktueller Co2 Wert schreiben
  lcd.print(" CO");
  lcd.write(3);              // Benutzerdefiniertes CO2 auf LCD schreiben
  lcd.print(" PPM        ");

  lcd.setCursor(0, 1);           // Cursor auf die zweite Zeile setzen
  lcd.write(1);                  // Benutzerdefiniertes Thermometersymbol auf LCD schreiben
  lcd.setCursor(2, 1);
  lcd.print(t, 1);               // Temperatur aus Variable t auf LCD schreiben
                                 // mit einer nachkomastelle
                                 
  lcd.print((char)223);          // Gradzeichen auf LCD schreiben
  lcd.print("C");
  lcd.setCursor(10, 1);
  lcd.write(2);                  // Benutzerdefiniertes Wassertropfensymbol auf LCD schreiben
  lcd.setCursor(12, 1);
  lcd.print(h, 0);               // Relative Luftfeuchtigkeit aus Variabel h auf LCD schreiben
  lcd.print("%");

}

void beleuchtung(int zeit) {
  if (HCSR04Distanz < 60 and HCSR04Distanz > 10) { // Überprüfen ob ein Objekt zwischen 10cm bis 60cm ist
    lcd.backlight();                            // Display Beleuchtung einschalten
    lcdLastOnTime = millis();                   // Einschalt Zeitpunkt abspeichern
  }
  else {
    if (millis() - lcdLastOnTime > zeit*1000) { // Überprüfen wie lange die Beleuchtung ein ist
      lcd.noBacklight();                        // Display Beleuchtung ausschalten
    }
  }
}


/********************************(Funktionen für MQ135)************************************/
// Die Berechnungen wurden anhand von Beispielen von folgender Seite umgesetzt:              
// http://davidegironi.blogspot.ch/2014/01/cheap-co2-meter-using-mq135-sensor-with.html#.V7mPG5iLS0o
// Es gibt auf Github auch noch eine Library mit Temperatur Kompensation jedoch funktioniert
// die Library für den ESP8266 nicht richtig.

float MQ135getResistance() {

  int16_t val = ads.readADC_SingleEnded(0);     // Analogwert auslesen und in val speichern
  return ((26666. / (float)val) - 1.) * MQ135_PULLDOWNRES; // Widerstand berechnen
}

long mq135_getro(long resvalue, double ppm) {
  return (long)(resvalue * exp( log(MQ135_SCALINGFACTOR / ppm) / MQ135_EXPONENT ));
}


double mq135_getppm(long resvalue, long ro) {
  return (double)MQ135_SCALINGFACTOR * pow( ((double)resvalue / ro), MQ135_EXPONENT);
}

void MQ135Kalibrieren(long ro) {
  lcd.clear();
  EEPROM.begin(512);
  EEPROM.put(0, ro);
  EEPROM.end();
  EEPROM.begin(512);
  EEPROM.get(0, MQ135_DEFAULTRO);
  EEPROM.end();

  lcd.setCursor(0, 0);
  lcd.print("Bastelgarage.ch");
  lcd.setCursor(0, 1);
  lcd.print(MQ135_DEFAULTRO / 1000.0, 1);
  lcd.print(" kOhm Rzero");
  
  delay(6000);
  lcd.clear();
}

/*****************  **(Funktion um Daten an Thingspeak zu senden)**************************/
void MesswerteSenden() {
  
  if (millis() - ThingspeakLastLogTime > ThingspeakSampleTime) {
    ThingSpeak.setField(1, t);
    ThingSpeak.setField(2, h);
    ThingSpeak.setField(3, ppm );
    ThingSpeak.setField(4, MQ135WiderstandZero);
    ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
    ThingspeakLastLogTime = millis();
  }
}



/*************************************(Hauptprogramm)**************************************/
void loop() {

// Mit der Funktion millis() wird die vergangene Zeit in ms zurückgegeben. Wir vergleichen diesen
// Wert mit der letzten Sensor Aktualisierung ist mehr Zeit vergangen als in der Variable 
// SensorenSampleTime steht wird die if Verzweigung ausgeführt und sonst nicht. Wir könnten das auch
// mit einer delay() Funktion lösen jedoch möchten wir das der Mikrocontroller nicht einfach
// stehen bleibt, sondern weiterarbeiten kann.
  
  if (millis() - SensorenLetzteAktualisierung > (SensorenSampleTime*1000)) {
    Mediant.add(SI7021_Sensor.getCelsiusHundredths() / 100.0); // Messwert hinzufügen
    t = Mediant.getAverage();                                  // Mittelwert auslesen
    
    Medianh.add(SI7021_Sensor.getHumidityPercent());
    h = Medianh.getAverage();
    
// Berechnen des CO2 Wertes PPM    
    MedianPPM.add(mq135_getppm(MQ135getResistance(), MQ135_DEFAULTRO));
    ppm = MedianPPM.getAverage();

// Berechnen des Rzero bei 392 PPM
    MedianMQ135WiderstandZero.add(mq135_getro(MQ135getResistance(), MQ135_DEFAULTPPM));
    MQ135WiderstandZero = MedianMQ135WiderstandZero.getAverage();
    
    lcdSchreiben();                         // LCD Aktualisieren
    
    SensorenLetzteAktualisierung=millis();  // Letzte Aktualisierung speichern
  }


  MedianUltrasonic.add(ultrasonic.Ranging(CM));// Durchführen einer Distanzmessung mit dem Ultraschallsensor
    HCSR04Distanz = MedianUltrasonic.getMedian();
  
  beleuchtung(LcdBeleuchtungsDauer);        // Funktion für Beleuchtung aufrufen

if (myChannelNumber != 0){
    MesswerteSenden();                        // Messdaten an Thingspeak senden
}


// Wenn der Kalibriertaster betätigt wird soll die Funktion Kalibrieren aufgerufen werden
    if (digitalRead(D4) == false) {
    MQ135Kalibrieren(MQ135WiderstandZero);
  }                                  
 
}
