/*
 *   Stand: 25.08.2021
 *   Author: Julian Rico
 *   
 *   Software für Stromzähler-Empfänger
 *   
 *   Diese Software beinhaltet die Logik, um einen EMH Gen. K Stromzähler per Infrarot-Schnittstelle auszulesen 
 *   und den Gesamtverbrauch des Zählers per MQTT an einen MQTT Broker zu schicken.
 */
 
/* Includes */
#include <Wire.h>
#include <WiFiManager.h>    /* https://github.com/tzapu/WiFiManager */
#include <PubSubClient.h>   /* https://github.com/knolleary/pubsubclient */

/* Defines */
#define BAUDRATE      9600
#define BUFFERSIZE    1000
#define RTC_I2C_ADDR  0x68

/* Globale Variablen */
int BUFFER[BUFFERSIZE];                       // Buffer für Einlesen serieller Daten
int i, j, error;                              // Laufvariablen
int shutdown = 0;                             // Wird gesetzt nach erfolgreichem Einlesen des Zählstands
WiFiClient espClient;                         // WiFi
PubSubClient client(espClient);               // MQTT Client
const char* MQTT_BROKER = "192.168.178.102";  // MQTT Broker IP -> TODO anpassen
const unsigned short MQTT_PORT = 1883;        // MQTT Broker Port -> TODO prüfen 
char* MQTT_TOPIC_LIVE = "smartmeter";         // MQTT Topic für Stromzählerwerte
char* MQTT_TOPIC_TEST = "ESP";                // MQTT Topic für Eingaben von Teststand
char* MQTT_MSG   = "ESP ONLINE.";             // Message von ESP wenn Setup erfolgreich

/* Functionsprototypen */
void callback(char* topic, byte* payload, unsigned int length);     // MQTT Callback: Gibt erhaltene Message aus
bool readTelegramm();                                               // Liest serielle Daten ein, unterscheidet ob Daten von Teststand oder Stromzähler kommen
void read_smartmeter(int start, int komma, int ende);               // Versendet Zählstand per MQTT (TOPIC_LIVE)
void read_teststation(int start);                                   // Versendet Testdaten per MQTT (TOPIC_TEST)
void RTC_Setup();                                                   // Alarm Register und Control Bits setzen
void RTC_Shutdown();                                                // Ausschalten

/* Setup: einmalig */
void setup() {

  // Debug LED
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  // Serial Setup
  Serial.begin(BAUDRATE);
  Serial.flush();
  Serial.println("Serial init done.");

  // Wifi Setup
  WiFi.mode(WIFI_STA);
  WiFiManager wifiManager;  
  bool res; 
  res = wifiManager.autoConnect("ESP32-STROMZÄHLER", "PASSWORD");
  if(!res) {
    Serial.println("Failed to connect");
  } 
  else {
    //if you get here you have connected to the WiFi    
    Serial.println("connected...yeey :)");
  }
  
  // MQTT Setup
  error = 0;
  client.setServer(MQTT_BROKER, MQTT_PORT);
  while (!client.connected()) {
    client.connect("ESP8266Client");
    if (client.connected()) {
      Serial.println("MQTT connected");
    }
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("try again in 5 seconds");
      delay(5000);
      error++;
      if (error == 12) { // Nach 60 Sekunden Shutdown
        RTC_Shutdown();
      }
    }
  }

  // Das kann dann später raus, ist nur zum testen der MQTT Verbindung per Serial Monitor
  client.subscribe(MQTT_TOPIC_TEST);
  client.setCallback(callback);
  client.publish(MQTT_TOPIC_TEST, MQTT_MSG);

  // RTC 
  RTC_Setup();
  Serial.println("RTC Setup done");
}

/* Hauptprogramm */
void loop() {
  // Benötigt für Empfangen von MQTT Messages -> kann später raus
  client.loop();

  // ESP kommst sonst seriell nicht hinterher
  delay(75);
  
  // Serielle Daten einlesen falls vorhanden
  if (Serial.available() > 0) {
    if (readTelegramm())
      shutdown = 1;    
  }

  // Buffer für nächsten Datensatz leeren (benötigt, wenn Zählstand nicht dabei war)
  if (i > 0) {
    for (int k = 0; k < i; k++)
      BUFFER[k] = 0;
    i = 0;    
  }

  // Shutdown durch RTC vorbereiten
  if (shutdown)
    RTC_Shutdown();
}

/* MQTT Subscriber Callback */
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("New message in topic: ");
  Serial.println(topic);

  Serial.print("Message: ");
  for (i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  Serial.println("-----------------------");
  Serial.println();
}

bool readTelegramm() {
  // Read D0 Telegram
  i = 0;
  do {
    if(i < BUFFERSIZE) {
      BUFFER[i] = Serial.read();
      
      /* Debug */
      if (BUFFER[i] < 0xF) 
        Serial.print("0"); // Führende Null erzeugen
      Serial.print(BUFFER[i], HEX);
      Serial.print(" ");
      if (BUFFER[i] == 0x0A) { // 0A (hex) = LF (ASCII) => Neue Zeile
        Serial.println();
      }
      /* Debug Ende */
      i++;
    }  
  } while (Serial.available());

  // Buffer nach Daten für Zählerstand absuchen
  for (j = 0; j < i; j++) { // Buffer nach Zeichen absuchen
      
    // Daten von Teststand: "¡Bitte geben Sie ... !");
    if (BUFFER[j] == 0xC2 && BUFFER[j+1]) { /* C2 A1 = '¡' */
      return false; // Shutdown für RTC nur schicken wenn Daten von Smartmeter kommen
    }

    // http://itrona.ch/stuff/F2-2_PJM_5_Beschreibung%20SML%20Datenprotokoll%20V1.0_28.02.2011.pdf
    // Daten von Stromzähler: Gesamtverbrauch herausfiltern 
    if (                        /* OBIS Kennung: 1-0.1.8.0*255 = 01 00 01 08 00 FF */
      BUFFER[j]   == 0x77 &&    /* 77 - SML_Message.messageBody.SML_GetList_Reponse.valList.valListEntry (Sequence) */
      BUFFER[j+1] == 0x07 &&    /* 07 - objName (TL[1] + octet-string[6] */
      BUFFER[j+2] == 0x01 &&    /* 01 - objName Teil A */
      BUFFER[j+3] == 0x00 &&    /* 00 - objName Teil B */
      BUFFER[j+4] == 0x01 &&    /* 01 - objName Teil C */
      BUFFER[j+5] == 0x08 &&    /* 08 - objName Teil D */
      BUFFER[j+6] == 0x00 &&    /* 00 - objName Teil E */
      BUFFER[j+7] == 0xFF)      /* FF - objName Teil F */
                                /* xx - status          */
                                /* xx - valTime         */
                                /* xx - unit            */
                                /* xx - scaler          */
    {
      j = j+8;
      
      // status, valTime, unit und scaler überspringen
      while (BUFFER[j] != 0x59) { j++; }  /* 59 - value (TL[1] + 64 Bit Integer */
                                
      // Zahl aus SML in Variable überführen

      // 64 Bit: 2 x 32 Bit Variablen -> mWh
      long long mWh = ((long long)BUFFER[j+1]) << 56 | ((long long)BUFFER[j+2]) << 48 | ((long long)BUFFER[j+3]) << 40 | ((long long)BUFFER[j+4]) << 32 |
                      ((long long)BUFFER[j+5]) << 24 | ((long long)BUFFER[j+6]) << 16 | ((long long)BUFFER[j+7]) <<  8 | ((long long)BUFFER[j+8]);         

      // Debug
      Serial.println();
      Serial.print("mWh:"); Serial.println(mWh);
      
      mWh = mWh / 10000;    // mWh -> kWh
      int kWh = (int) mWh;     
      
      // Debug
      Serial.print("Gesamtverbrauch: ");Serial.println(kWh);  
      
      // Zählstand an MQTT Broker schicken
      send_MQTT(kWh);

      // Wenn Gesamtverbrauch in SML gefunden: Signal für Shutdown geben
      return true;
       
    } // Ende if (OBIS Kennung)
    j++;
  } // Ende for-Schleife

  // Hier return falls keine gültige SML Nachricht erkannt wurde => ESP nicht ausschalten, sondern auf nächste warten
  return false;
}

void send_MQTT(int kWh) {
  String temp = String(kWh);  
  client.publish(MQTT_TOPIC_LIVE, temp.c_str());
}

void RTC_Setup() {
  /* Set Alarm 1 on seconds = 0, minutes = 0, hours = 0 */
  Wire.beginTransmission(RTC_I2C_ADDR);
  Wire.write(0x07);                     // Address of A1M1
  Wire.write(0x00);                     // A1M1 = 0, alarm value seconds = 0
  Wire.write(0x80);                     // A1M2 = 0, alarm value minutes = 0
  Wire.write(0x80);                     // A1M3 = 0, alarm value hours   = 0
  Wire.write(0x80);                     // A1M4 = 1, rest X
  Wire.endTransmission();

  /* Set alarm 2 on minutes = 0, hours = 12 */
  Wire.beginTransmission(RTC_I2C_ADDR);
  Wire.write(0x0B);                     // Address of A2M2
  Wire.write(0x00);                     // A2M2 = 0, alarm value minutes = 0
  Wire.write(0x12);                     // A2M3 = 0, alarm value hours = 12
  Wire.endTransmission();
 
  /* Set A1E & A2E control bits */
  Wire.beginTransmission(RTC_I2C_ADDR);
  Wire.write(0x0e);                     // Control byte
  Wire.write(0x1C | 3);                 // Default | A1E | A2E
  Wire.endTransmission();
}

void RTC_Shutdown() {
  Wire.beginTransmission(RTC_I2C_ADDR);
  Wire.write(0x0F);                     // Address of control/status register
  Wire.endTransmission();
 
  Wire.requestFrom(RTC_I2C_ADDR, 1);    // Read the current value of the register
  unsigned char reg_val = Wire.read();
 
  Wire.beginTransmission(RTC_I2C_ADDR); 
  Wire.write(0x0F);                     // Address of control/status register
  Wire.write(reg_val & ~0x03);          // Write the old value with A1F&A2F flags cleared
  Wire.endTransmission();               // -> this resets the latching ~INT Pin
}
