/* * 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 #include /* https://github.com/tzapu/WiFiManager */ #include /* 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 }