Archived
Repositories von Projektarbeit gebündelt
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user