#include <Wire.h>
#include <U8x8lib.h>
#include <Adafruit_BME280.h>
#include <Adafruit_MLX90614.h>
#include "RTClib.h"
#include <ArduinoBLE.h>

#define VERSION "UmweltSensor Version 0.99"

// UUIDs für Nordic UART Service
BLEService uartService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
BLECharacteristic txCharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLERead | BLENotify, 512);
BLECharacteristic rxCharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse | BLEWrite, 512);
bool isBLE = false;

// Das Display ist optional und muss Standard I2C-Bus angeschlossen sein
#define SH1106_ADRESSE 0x3C      // usually 0x3C
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);  // 8 X 16 Zeichen
bool isSH1106 = false;

// Dieser Sensor muss vorhanden sein und am Standard I2C-Bus angeschlossen sein
#define BME280_ADRESSE 0x76      // usually 0x76, but might be 0x77
Adafruit_BME280 bme;
bool isBME280 = false;

// Dieser Sensor ist optional und muss am zweiten I2C-Bus angeschlossen sein
#define MLX90614_ADRESSE 0x5A    // usually 0x5A
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
bool isMLX90614 = false;
double dNewEmissivity = 0.95;

// Die Real Time Clock ist optional und muss Standard I2C-Bus angeschlossen sein
// Das Modul besteht aus einem DS3231 RTC-Chip und einem Atmel AT24C32 EEPROM-Chip
// Der AT24C32 hat eine Speicherkapazität von 4KByte
#define AT24C32_ADRESSE 0x57     // usually 0x57
#define EEPROM_ADDR     0x57     // I2C-Adresse des AT24C32 für wire.h
#define DS3231_ADRESSE 0x68      // usually 0x68
RTC_DS3231 rtc;
bool isDS3231 = false;

#define DELAY_NORMAL 60000ul
#define DELAY_FAST   6000ul
#define DELAY_LOOP   2 

unsigned long ulMeasurementTimestamp;
unsigned long ulMeasurementDelay = DELAY_NORMAL;

float fSimulation1 = 25.0;
float fSimulation2 = 50.0;

#define MODE_ASCOM 0
#define MODE_LOG   1
#define MODE_TERM  2
#define MODE_SIMU  3
int iSensorModus = MODE_ASCOM;

#define WARN_LEVEL_1 5.0  // Differenz Temperatur zu Taupunkt
#define WARN_LEVEL_2 3.0  // Differenz Temperatur zu Taupunkt

int iCount1 = 0;  // Count für Anzahl Messungen
int iCount2 = 0;  // Count für Ausgabe "." in Console
int iCount3 = 0;  // Count für Ausgabe "-" auf Display

// zweiter I2C-Bus
#define I2C_SDA 19
#define I2C_SCL 20
TwoWire I2CMLX = TwoWire(1);


float calculateDewPoint(float celsius, float humidity) {
  float a = 17.271;
  float b = 237.7;
  float temp = (a * celsius) / (b + celsius) + log(humidity / 100.0);
  float dewPoint = (b * temp) / (a - temp);
  return dewPoint;
}

/*
void writeEEPROM(unsigned int addr, char* data, int len) {
  for (int i = 0; i < len; i++) {
    Wire.beginTransmission(EEPROM_ADDR);
    Wire.write((addr + i) >> 8);     // High Byte der Adresse
    Wire.write((addr + i) & 0xFF);   // Low Byte der Adresse
    Wire.write(data[i]);
    Wire.endTransmission();
    delay(5); // Schreibzyklus (abhängig vom EEPROM)
  }
}

void readEEPROM(unsigned int addr, char* data, int len) {
  for (int i = 0; i < len; i++) {
    Wire.beginTransmission(EEPROM_ADDR);
    Wire.write((addr + i) >> 8);     // High Byte der Adresse
    Wire.write((addr + i) & 0xFF);   // Low Byte der Adresse
    Wire.endTransmission();
    Wire.requestFrom(EEPROM_ADDR, 1);
    if (Wire.available()) {
      data[i] = Wire.read();
    }
  }
}

void testEEPROM() {
  char writeBuffer[256];
  char readBuffer[256];

  // Beispiel: EEPROM mit Daten füllen
  for (int i = 0; i < 256; i++) {
    writeBuffer[i] = i;
  }

  // Schreiben
  writeEEPROM(0x0000, writeBuffer, 256);

  delay(1000); // Warte, bis EEPROM fertig ist

  // Lesen
  readEEPROM(0x0000, readBuffer, 256);

  // Ausgabe zum Test
  for (int i = 0; i < 16; i++) {
    for (int j = 0; j < 16; j++) {
      if (i==0) { 
        Serial.print("0"); 
      }
      Serial.print(readBuffer[i*16+j], HEX);
      Serial.print(" ");
    }
    Serial.println("");
  }
}
*/


void setup() {
  Wire.begin();
  Serial.begin(9600);
  I2CMLX.begin(I2C_SDA, I2C_SCL, 50000);
  delay(1000);

  if (iSensorModus == MODE_LOG) {
    Serial.println("UmweltSensor - www.RogerSteen.de - (C) 2025");
  } else if (iSensorModus == MODE_TERM || iSensorModus == MODE_SIMU) {
      Serial.println("\33\[1;1H\33\[2J\33\[1;32m UmweltSensor    www.RogerSteen.de     (C) 2025\33\[0;37m"); 
  }

  if (!mlx.begin(MLX90614_ADRESSE, &I2CMLX)) {
    if (iSensorModus == MODE_LOG) {
      Serial.println("Error connecting to MLX90614 sensor. Check wiring.");
    }
  } else {
    isMLX90614 = true;
    //mlx.writeEmissivity(dNewEmissivity);  // Das funktioniert nicht sicher, besser sein lassen
  };

  if (!bme.begin(BME280_ADRESSE)) {
    if (iSensorModus == MODE_LOG) {
      Serial.println("Error connecting to BME280 sensor. Check wiring.");
    }
  } else {
    isBME280 = true;
    // siehe https://www.lpomykal.cz/bme280-temperature-measurement/
    bme.setSampling(Adafruit_BME280::MODE_FORCED, // Force reading after delayTime
                    Adafruit_BME280::SAMPLING_X1, // Temperature sampling set to 1
                    Adafruit_BME280::SAMPLING_X1, // Pressure sampling set to 1
                    Adafruit_BME280::SAMPLING_X1, // Humidity sampling set to 1
                    Adafruit_BME280::FILTER_OFF   // Filter off - immediate 100% step response
                    );
  }

  if (!u8x8.begin()) {
    if (iSensorModus == MODE_LOG) {
      Serial.println("Error connecting to Display SH1106. Check wiring.");
    }
  } else {    
    u8x8.setPowerSave(0); 
    u8x8.setFont(u8x8_font_chroma48medium8_r);
    //                   0123456789012345
    u8x8.drawString(0,0,"  UmweltSensor  ");
    isSH1106 = true;
  }

  if (!rtc.begin()) {
    if (iSensorModus == MODE_LOG) {
      Serial.println("Error connecting to RTC DS3231. Check wiring.");
    }
  } else {
    isDS3231 = true;
  }

  if (!BLE.begin()) {
    if (iSensorModus == MODE_LOG) {
      Serial.println("Error starting BlueThooth LE.");
    }
  } else {
    isBLE = true;
    BLE.setLocalName("UmweltSensor UART");
    BLE.setAdvertisedService(uartService);
    uartService.addCharacteristic(txCharacteristic);
    uartService.addCharacteristic(rxCharacteristic);
    BLE.addService(uartService);
    BLE.advertise();
  }

  ulMeasurementTimestamp = millis() - ulMeasurementDelay;

  if (iSensorModus == MODE_TERM || iSensorModus == MODE_SIMU) {
    Serial.println("");
  }
}


void loop() {
  float fTemperature, fHumidity, fPressure, fDewPoint;
  float fObject, fAmbient, fEmissivity;

  char sBuffer[200]; 

  char sTemperatureASCOM[10];
  char sHumidityASCOM[10];
  char sPressureASCOM[10];
  char sDewPointASCOM[10];
  char sSkyTemperatureASCOM[10];

  bool isBeforeDevPoint = false;
  bool isDevPoint = false;

  if (millis() - ulMeasurementTimestamp > ulMeasurementDelay) {
    iCount1++;
    ulMeasurementTimestamp = millis();

    bme.takeForcedMeasurement();
    if (iSensorModus == MODE_SIMU) {
      fTemperature = fSimulation1; 
      fSimulation1 = fSimulation1 - 2.0;
      fHumidity = fSimulation2; 
      fSimulation2 = (fSimulation2 < 95.0 ? fSimulation2 + 5.0 : 95.0);
    } else {
      fTemperature = bme.readTemperature();
      fHumidity = bme.readHumidity();
    }
    fDewPoint = calculateDewPoint(fTemperature, fHumidity);
    fPressure = bme.readPressure() / 100.0;

    dtostrf(fTemperature, 4, 2, sTemperatureASCOM);
    dtostrf(fHumidity, 4, 2, sHumidityASCOM);
    dtostrf(fPressure, 4, 2, sPressureASCOM);
    dtostrf(fDewPoint, 4, 2, sDewPointASCOM);  
    
    if (isMLX90614) {
      fAmbient    = mlx.readAmbientTempC();
      fObject     = mlx.readObjectTempC();
      fEmissivity = mlx.readEmissivity();
      dtostrf(fObject, 4, 2, sSkyTemperatureASCOM);
    }

    if (isSH1106) {  // 0123456789012345
      sprintf(sBuffer, "Temp  %6.2f 'C", fTemperature);
      u8x8.drawString(0, 2, sBuffer);
      sprintf(sBuffer, "Hum    %5.2f %%", fHumidity);
      u8x8.drawString(0, 3, sBuffer);
      sprintf(sBuffer, "AirP %7.2f hPa", fPressure);
      u8x8.drawString(0, 4, sBuffer);
      sprintf(sBuffer, "DewP  %6.2f 'C", fDewPoint);
      u8x8.drawString(0, 5, sBuffer);
      if (isMLX90614) {
        sprintf(sBuffer, "Sky   %6.2f 'C", fObject);
        u8x8.drawString(0, 6, sBuffer);
        sprintf(sBuffer, "Temp  %6.2f 'C", fAmbient);
        u8x8.drawString(0, 7, sBuffer);
      }
    }

    isBeforeDevPoint = (fTemperature - WARN_LEVEL_1 <= fDewPoint ? true : false);
    isDevPoint = (fTemperature - WARN_LEVEL_2 <= fDewPoint ? true : false);

    if (iSensorModus == MODE_LOG || isBLE) {

      if (isDS3231) {
        DateTime now = rtc.now();
        sprintf(sBuffer, "%02d.%02d.%04d %02d:%02d:%02d - %6.2f °C - ", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second(), rtc.getTemperature());
        if (isBLE) { txCharacteristic.writeValue(sBuffer); }
        if (iSensorModus == MODE_LOG) { Serial.print(sBuffer); }
      }
      sprintf(sBuffer, "Temperature %6.2f °C - Humidity %5.2f %% - AirPressure %7.2f hPa - DewPoint %6.2f °C", fTemperature, fHumidity, fPressure, fDewPoint);
      if (isBLE) { txCharacteristic.writeValue(sBuffer); }
      if (iSensorModus == MODE_LOG) { Serial.print(sBuffer); }
      if (isMLX90614) {
        sprintf(sBuffer, " - Ambient %6.2f °C - Object %6.2f °C - Emissivity %4.2f", fAmbient, fObject, fEmissivity);
        if (isBLE) { txCharacteristic.writeValue(sBuffer); }
        if (iSensorModus == MODE_LOG) { Serial.print(sBuffer); }
      }
      if (isBLE) { sprintf(sBuffer, "\n"); txCharacteristic.writeValue(sBuffer); }
      if (iSensorModus == MODE_LOG) { Serial.println(); }
    
    } 
    if (iSensorModus == MODE_TERM || iSensorModus == MODE_SIMU) {

      Serial.print("\33\[3;1H");
      if (isDevPoint) {
        sprintf(sBuffer, " \33\[1;31mTemperature   %6.2f °C    DewPoint  %6.2f °C\33\[0;37m", fTemperature, fDewPoint);
      } else if (isBeforeDevPoint) {
        sprintf(sBuffer, " \33\[1;33mTemperature   %6.2f °C    DewPoint  %6.2f °C\33\[0;37m", fTemperature, fDewPoint);
      } else {
        sprintf(sBuffer, " Temperature   %6.2f °C    DewPoint  %6.2f °C", fTemperature, fDewPoint);
      }
      Serial.println(sBuffer);
      sprintf(sBuffer, " AirPressure  %7.2f hPa   Humidity  %6.2f %%", fPressure, fHumidity);
      Serial.println(sBuffer);
      if (isMLX90614) {
        sprintf(sBuffer, " Ambient       %6.2f °C    Object    %6.2f °C", fAmbient, fObject);
        Serial.println(sBuffer);
      }
      Serial.println("");  
      Serial.print(" ");
    }
  
    if (isSH1106) {
      u8x8.drawString(iCount3 / 2, 1, " ");
      iCount3 = 0;
      u8x8.drawString(iCount3 / 2, 1, "-");
    }

  } else {
  
    iCount2++;
    if (iCount2 >= (DELAY_NORMAL / DELAY_LOOP / 30)) {
      iCount2 = 0;
      if (iSensorModus == MODE_TERM || iSensorModus == MODE_SIMU) {
        Serial.print(".");
      }
      if (isSH1106) {
        u8x8.drawString(iCount3 / 2, 1, " ");
        iCount3++;
        u8x8.drawString(iCount3 / 2, 1, "-");
      }
    }  
  }
  
  while (Serial.available() > 0){
    String sCmd = Serial.readStringUntil('#');
    sCmd.toUpperCase();
    if (sCmd == "TEM")  { Serial.println(String(sTemperatureASCOM) + "#"); }
    if (sCmd == "HUM")  { Serial.println(String(sHumidityASCOM) + "#"); }
    if (sCmd == "DEW")  { Serial.println(String(sDewPointASCOM) + "#"); }
    if (sCmd == "PRE")  { Serial.println(String(sPressureASCOM) + "#"); }
    if (sCmd == "VER")  { Serial.println(String(VERSION) + "#"); }
    if (sCmd == "CNT")  { Serial.println(String(iCount1) + "#"); }
    if (sCmd == "SST1") { Serial.println(String(isBME280) + "#"); }

    if (sCmd == "SST2") { Serial.println(String(isMLX90614) + "#"); }
    if (sCmd == "SKYT") { Serial.println(String(sSkyTemperatureASCOM) + "#"); }

    if (sCmd == "MODEASCOM")  {
      iSensorModus = MODE_ASCOM; 
      Serial.println("");
      Serial.println("");
      ulMeasurementDelay = DELAY_NORMAL;
      ulMeasurementTimestamp = millis() - ulMeasurementDelay;
    }
    if (sCmd == "MODELOG")  {
      iSensorModus = MODE_LOG; 
      Serial.println("");
      Serial.println("");
      Serial.println("UmweltSensor by - www.RogerSteen.de - (C) 2025"); 
      ulMeasurementDelay = DELAY_NORMAL;
      ulMeasurementTimestamp = millis() - ulMeasurementDelay;
    }
    if (sCmd == "MODETERM") {
      iSensorModus = MODE_TERM; 
      Serial.println("\33\[1;1H\33\[2J\33\[1;32m UmweltSensor    www.RogerSteen.de     (C) 2025\33\[0;37m"); 
      ulMeasurementDelay = DELAY_NORMAL;
      ulMeasurementTimestamp = millis() - ulMeasurementDelay;
    }
    if (sCmd == "MODESIMU") {
      iSensorModus = MODE_SIMU; 
      fSimulation1 = 25.0;
      fSimulation2 = 50.0;
      Serial.println("\33\[1;1H\33\[2J\33\[1;32m UmweltSensor    www.RogerSteen.de     (C) 2025\33\[0;37m"); 
      ulMeasurementDelay = DELAY_FAST;
      ulMeasurementTimestamp = millis() - ulMeasurementDelay;
    }
  }

  BLE.poll(); // <-- GANZ WICHTIG!

  vTaskDelay(DELAY_LOOP / portTICK_PERIOD_MS); // <-- GANZ WICHTIG! 
}


void INT_PINisr(void){
  // ISR for INT_PIN = Detach Interrupt. 
  // Lets the serial communication run without any interrupts.
  detachInterrupt(0);
}
