123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- /*
- * 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 */
- // MQTT Callback: Gibt erhaltene Message aus
- void callback(char* topic, byte* payload, unsigned int length);
- // Liest serielle Daten ein, unterscheidet ob Daten von Teststand oder Stromzähler
- // kommen
- bool readTelegramm();
- // Versendet Zählstand per MQTT (TOPIC_LIVE)
- void read_smartmeter(int start, int komma, int ende);
- // Versendet Testdaten per MQTT (TOPIC_TEST)
- void read_teststation(int start);
- // Alarm Register und Control Bits setzen
- void RTC_Setup();
- // Ausschalten
- void RTC_Shutdown();
-
- /* 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%20
- // V1.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
- }
|