Repositories von Projektarbeit gebündelt

This commit is contained in:
Julian Rico
2021-10-07 11:18:07 +02:00
commit 6dc6f66e64
87 changed files with 132512 additions and 0 deletions
@@ -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
}
+109
View File
@@ -0,0 +1,109 @@
/*
* Stand: 25.08.2021
* Author: Julian Rico
*
* Teststand für Stromzähler-Empfänger.
*
* Diese Software bietet ein Interface, um den Empfänger zu testen.
* Der Benutzer kann Zahlen eingeben, welche als Zählstand interpretiert werden.
* Der Zählstand wird dann in Form eines SML Pakets verschickt.
*/
/* Defines */
#define BAUDRATE 9600
#define BUFFERSIZE 100
/* Globale Variablen */
int BUFFER[BUFFERSIZE]; // Buffer für Einlesen serieller Daten
float kWh; // Speichervariable für berechneten Input
int i, j; // Laufvariablen
/* Funktionsprototypen */
void readInput();
void sendSML(unsigned long mWh);
/* Setup: einmalig */
void setup() {
// Debug LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Serial Setup
Serial.begin(BAUDRATE);
Serial.println("¡Bitte geben Sie einen ganzzahligen Wert ein [kWh]!");
Serial.flush();
}
/* Hauptprogramm */
void loop() {
// Warten auf Eingabe von Bediener
if (Serial.available() > 0)
{
digitalWrite(LED_BUILTIN, HIGH);
readInput();
}
delay(50);
}
void readInput() {
// Einlesen
i = 0;
do {
if (i<BUFFERSIZE) {
BUFFER[i] = Serial.read();
i++;
}
} while (Serial.available());
i--; // Letzter Eintrag in Buffer ist LF
// Input umrechnen
kWh = 0;
for (j = i; j > 0; j--) {
kWh = kWh + (BUFFER[j-1]-48) * pow(10, (i-j));
}
// SML senden
sendSML(((unsigned long)(kWh+0.5)*10000)); // +0.5 für korrektes Auf- und Abrunden, cast rundet immer ab, *10.000 für kWh->mWh
// Buffer löschen
for (j = 0; j < i+2; j++)
BUFFER[j] = 0;
}
void sendSML(unsigned long mWh) {
// Hier wird ein Teil einer SML Nachricht verschickt
// Konkret handelt es sich um die Sequence mit dem OBIS Kenncode 1.8.0*255 (=Gesamtverbrauch)
Serial.write(0x77); /* 'M' ASCII = 77 - SML_Message.messageBody.SML_GetList_Reponse.valList.valListEntry (Sequence) */
Serial.write(0x07); /* 07 - objName (TL[1] + octet-string[6] */
Serial.write(0x01); /* 01 - objName Teil A */
Serial.write(0x00); /* 00 - objName Teil B */
Serial.write(0x01); /* 01 - objName Teil C */
Serial.write(0x08); /* 08 - objName Teil D */
Serial.write(0x00); /* 00 - objName Teil E */
Serial.write(0xFF); /* FF - objName Teil F */
Serial.write(0x01); /* 01 - Status (optional: 01 bedeutet "nicht versorgt") */
Serial.write(0x01); /* 01 - valTime (optional: 01 bedeutet "nicht versorgt") */
Serial.write(0x01); /* 01 - unit (optional: 01 bedeutet "nicht versorgt") */
Serial.write(0x01); /* 01 - scaler (optional: 01 bedeutet "nicht versorgt") */
Serial.write(0x59); /* 01 - value (TL[1] + Integer[8]) */
Serial.write(0x00); /* value[0] -- wir bekommen nur 4 Byte, daher die ersten 4 nullen */
Serial.write(0x00); /* value[1] -- wir bekommen nur 4 Byte, daher die ersten 4 nullen */
Serial.write(0x00); /* value[2] -- wir bekommen nur 4 Byte, daher die ersten 4 nullen */
Serial.write(0x00); /* value[3] -- wir bekommen nur 4 Byte, daher die ersten 4 nullen */
// Letzten 4 Bytes: mWh (unsigned long) konvertieren in 4 Bytes
unsigned long temp;
temp = (mWh & 0xFF000000) >> 24;
Serial.write(temp); /* value[4] */
temp = (mWh & 0x00FF0000) >> 16;
Serial.write(temp); /* value[5] */
temp = (mWh & 0x0000FF00) >> 8;
Serial.write(temp); /* value[6] */
temp = (mWh & 0x000000FF);
Serial.write(temp); /* value[7] */
Serial.println();
Serial.println("¡Bei Bedarf nächsten ganzzahligen Wert eingeben [kWh]!");
}