/*
 Arduino Sketch zum Messen der Luftgüte und Signalisierenung zum Lüften über eine RGB LED.
 Die Messung geschieht als virtuelle CO2-Konzentrationsmessung unter der Annahme, dass nur CO2
 als Spurengas vorhanden ist.
 Die ppm-Werte sind nur als sehr grobe Abschätzung zu sehen, da sie einerseits von der Kalibrierung
 frischer Luft abhängen und andererseits die Annahme, dass nur CO2 in der Luft ist in den meisten Fällen
 nicht zutrifft.
 Als Anzeiger, wann es Zeit ist zum Lüften kann es dennoch nützlich sein.

 Based on:
 https://github.com/NEOE-IO/NEOE-IOT-Kit-1
 https://github.com/GeorgK/MQ135

 PPM Calculations done using power regression:
 https://davidegironi.blogspot.com/2014/01/cheap-co2-meter-using-mq135-sensor-with.html
 
 More Infofmation to the Setup about to come.
 */

/* Ideen:
 *  - Sensor liest nach dem Einbrennen und langer Kalibrierungszeit den Mittelwert des Widerstands aus
 *    und schreibt den als RZERO in den EEPROM
 *  - Buzzer implementieren
 */


/***********************************************************
Globale Parameter
***********************************************************/
//// Grenzwerte
// immer untere Grenze des Bereichs
float mittlereLuft = 900;
float schlechteLuft = 1500;

//// Verwendete Pins

// Analog PIN - Messung des Widerstands am MQ-135
uint8_t _pin = A0;
int pinWert;

// Parameter für die Indikator-LED
int pinBlau = 9;
int pinGruen = 10;
int pinRot = 11; 
int ledBlau; int ledGruen; int ledRot;

//// Weitere Parameter, die keiner Anpassung bedürfen

// Parameter zur Berechnung der Durchschnittswerte
const int anzahlMessungen = 10;
int messungen[anzahlMessungen]; // Array zur Berechnung der Durchschnittswerte
int messungIndex = 0; // Index eines einzelnen Wertes
float gesamt = 0; // Summe aller Werte
float durchschnitt = 0; // Durchschnittswert

// Lastwiderstand des MQ-135
// Bei Aufbau mit Platine: Wert des mittleren SMD-Elements oben auf der Platine
// Bei Aufbau mit Sensor ohne Platine: Wert des mit dem Sensor in Reihe geschalteten Widerstands.
#define RLAST 1.0

// Parameter zur CO2 Berechnung in ppm auf Basis des Sensor-Widerstands
// Aus dem Datenblatt zum MQ135 entnehmen "Fig.2 sensitivity characteristics of the MQ-135"
#define PARA 116.6020682 
#define PARB 2.769034857 

// CO2-Wert in der Atmosphäre, als Basis für die Kalibrierung
#define ATMOCO2 397.13

// Alle 1 Sekunden eine Messung durchführen (ausser beim Kalibrieren)
int zeitAbstand = 1000;

// Für die Kalibrierung erforderliche Parameter
float widerstandMomentan;
float widerstandMaximal;


/***********************************************************
Funktionen
***********************************************************/
// Widerstand des Sensors (in Kiloohm) berechnen
float berechneWiderstand() {
  float wert = durchschnitt;
  return ((1023. / wert) - 1.) * RLAST;
}

// CO2 Wert in ppm berechnen. Auf Basis der Vereinfachung, dass sich nur CO2 in der Luft befindet.
// Fig.2 sensitivity characteristics of the MQ-135
float berechnePPM() {
  return PARA * pow((berechneWiderstand() / widerstandMaximal), -PARB);
}

// RZero Widerstand des Sensors (in Kiloohm) für die Kalibrierung berechnen
float berechneWiderstandAtmo() {
  return berechneWiderstand() * pow((ATMOCO2 / PARA), (1. / PARB));
}

//Sensor auslesen und Mittelwert ueber die letzten 10 Messwerte berechnen
float berechneDurchschnittlichenSensorWert(){
  gesamt = gesamt - messungen[messungIndex]; 
  messungen[messungIndex] = analogRead(_pin); 
  gesamt = gesamt + messungen[messungIndex]; 
  messungIndex = messungIndex + 1; 
  if (messungIndex >= anzahlMessungen) {
    messungIndex = 0;
  }
  return gesamt / anzahlMessungen;
}

// Sensor Kalibrierung
// Volatiler Sensor, daher sanfte Kalibrierung, RCurrent nur mit 0,001%
void kalibriereSensor(){
  float widerstandMomentan = berechneWiderstandAtmo();
  if ((((9999 * widerstandMaximal) + widerstandMomentan) / 10000) > widerstandMaximal) {
    widerstandMaximal = (((9999 * widerstandMaximal) + widerstandMomentan) / 10000);
  }
}

//LED-Farben
void setzeGruen(){ledGruen=255; ledRot=0; ledBlau=0; analogWrite(pinGruen, ledGruen); analogWrite(pinRot, ledRot); analogWrite(pinBlau, ledBlau);}
void setzeBlau(){ledGruen=0; ledRot=0; ledBlau=255; analogWrite(pinGruen, ledGruen); analogWrite(pinRot, ledRot); analogWrite(pinBlau, ledBlau);}
void setzeRot(){ledGruen=0; ledRot=255; ledBlau=0; analogWrite(pinGruen, ledGruen); analogWrite(pinRot, ledRot); analogWrite(pinBlau, ledBlau);}
void setzeOrange(){ledGruen=165; ledRot=255; ledBlau=0; analogWrite(pinGruen, ledGruen); analogWrite(pinRot, ledRot); analogWrite(pinBlau, ledBlau);}


/***********************************************************
Setup: wird einmal zu Beginn ausgeführt
***********************************************************/

void setup() {
  // Array zur Berechnung der Durchschnittswerte aufbauen
  for (int dieseMessung = 0; dieseMessung < anzahlMessungen; dieseMessung++) {
    messungen[dieseMessung] = 0;  }
  Serial.begin(9600);
}

/***********************************************************
Loop: Wird immer wieder ausgeführt, wenn am Ende angelangt.
***********************************************************/

void loop() {

  // Sensor auslesen und Durchschnittswerte berechnen
  durchschnitt = berechneDurchschnittlichenSensorWert();

  // Volatiler Sensor, daher sanfte Kalibrierung, RCurrent nur mit 0,001%
  kalibriereSensor();

  // ppm berechnen
  float ppm = berechnePPM();
  Serial.println(ppm);

  // Indikator-LED
  if (ppm < ATMOCO2) setzeBlau();
  else if (( ppm >= ATMOCO2 ) && (ppm < mittlereLuft)) setzeGruen();
  else if (( ppm >= mittlereLuft ) && (ppm < schlechteLuft)) setzeOrange();
  else setzeRot();
  
  // Nach erfolgter Kalibrierung Anzahl der Messungen reduzieren, da sonst zu viele MQTT-Meldungen versendet werden
  if (ppm > ATMOCO2) delay(zeitAbstand);

}