SML auf Ethernet (UDP)
Bei meinen Basteleien zum Auslesen eines Smartmeters (Smartmeter D0, SML) habe ich anfangs noch nicht mit Tasmota und Co arbeiten können. In der Zeit hatte ich auch noch keinen MQTT-Server o.ä. und dachte, dass ich einfach ein Serial2UDP-Gateway mit einem ESP8266 schreibe. Der ESP sollte seriell die Daten vom Smartmeter ablesen und per UDP-Paket ins Netzwerk "Broadcasten". Damit spare ich mir die Konfiguration von Servern und quasi jedes Endgerät könnte den Zählerstand selbst auswerten.
Mittlerweile ist dieser Code nicht mehr aktiv und nur noch als Muster hier veröffentlicht.
Bei Net at Work haben wir nicht nur
Aufgaben für Office 365 Consultants und Windows Supporter.
Auch pfiffige Entwickler dürfen sich bewerben. Unsere
Mitarbeiter sind auch Bienenzüchter (IoT Sensoren),
Modellflugbauer, Hausautomatisierer u.a.
https://www.netatwork.de/unternehmen/karriere/
Hardware
Meine Überlegung war folgende.
- ESP8266 als Basis
Versorgung mit Batterie oder 5V Netzadapter, IR-Fototransistor an RxD. - System bucht sich ins WiFi ein, liest die Messwerte und sendet diese per UDP ins LAN oder per HTTPS an einen hinterlegten Server. Ich erspare mir so einen permanent aktiven Client auf dem ein Server läuft.
- Erfassung
Mein eh vorhandenes Monitoring-System (Siehe PRTG) startet regelmäßig ein Skript, welches genau diese UDP-Pakete empfängt und an PRTG übergibt
Zuerst musste ich natürlich die Hardware entsprechend vorbereiten. Ich habe mich für ein "fertiges" Board entschiede, welches schon einen USB-Anschluss inkl. Programmierung hat, von der Arduino-IDE unterstützt wird und sehr klein ist. Meine Wahl fiel auf das Wemos D1 und eine BPW40, die ich einfach direkt angeschlossen habe:
Der Emitter ist auf "G" = (GND) verbunden und der Kollektor an "RxD" angeschlossen. Zum "Programmieren" muss ich den Fototransistor allerdings "dunkel" abdecken oder abziehen. Ich habe vorher mit einem Amperemeter den Kurzschlussstrom gemessen (5 mA). Selbst bei 5 Volt wären das also maximal 25mW. Laut Datenblatt soll die BPW40 aber bis zu 150mW bzw. einen Kollektorstrom von bis zu 100mA aushalten. Das ist im grünen Bereich. Später wird die Schaltung natürlich in ein Gehäuse eingebaut und die LED mit einem Ringmagnet und schwarzen Abdeckungen montiert. Aber auch so ist die Funktion schon sehr zuverlässig.
Der nächste Schritt ist die Software, die folgende Schritte durchläuft. Knifflig war etwas, dass die serielle Kommunikation nur einen Puffer von 64bytes hat, während ein Datagramm knapp 400Bytes sind. Also darf ich beim Lesen nicht trödeln. Insbesondere Ausgaben zur Fehlersuche bremsen bei 9600 Baud den Ablauf. Da der Stromzähler aber immer wieder sendet, macht es hier mal nichts aus, ein Paket zu verlieren. Ich habe meinen Code dann einfach den Buffer leert, bis ca. keine Daten mehr kommen. Dann kann ich davon ausgehen, dass es bald "losgeht" und dann stumpf lesen, bis wieder eine Pause kommt. Um den Code einfach zu halten, findet im Code keine umfangreiche Validierung, Normalisierung oder Konvertierung statt. Das überlasse ich dem empfangenden Programm. So ist der Code mit der Hardware quasi universell mit vielen Systemen einsetzbar, die spontan Daten senden.
LED als Status
Da der ESP ja keine Bildschirmausgabe hat und ich nicht immer am USB-Anschluss einen Notebook mit Terminal anschliessen will, habe ich die eingebaute LED als Statusanzeige programmiert.
Blink (Anzahl, Einschaltzeit, Ausschaltzeit, Pause am Ende) | Phase | Beschreibung |
---|---|---|
blink(1,5000,500,1000) |
INIT Startet |
|
blink(4,100,500,2000) |
Serial ist initialisiert |
|
blink(5,50,100,2000) |
WiFi Verbindung wird hergestellt |
|
blink(6,100,500,2000) |
WiFi Connected |
|
blink(7,100,500,2000) |
INIT abgeschlossen |
|
blink(2,400,100,2000) |
LOOP Startet |
|
Sek Aus, 0,5 Sek an |
Warte auf Serial Bytes |
|
blink(5,100,100,0) |
Sende UDP Paket |
|
blink(10,100,100,2000) |
Fehler beim Einlesen |
|
blink(100,30,70,0) |
10 Sek "warten" |
|
Zum Testen konnte ich das System erst mal direkt am PC seriell ansteuern.
UDP auf dem Kabel
Ehe sie weiter unten den Code finden, zeige ich ihnen hier erst einmal, dass es wirklich funktioniert hat. Jede Information, die der ESP8266 seriell bekommen hat, wurde per UDP ins Netz gesendet.
Die Ausgabe ist im Wireshark gut zu sehen.
Code
Hier der aktuelle Code. Die Zugangsdaten des WiFi habe ich aber absichtlich "falsch" eingetragen. Es lohnt sich also nicht nach Hövelhof zu fahren um auf "Free Internet" zu hoffen.
/* * ESP8266 and D=-Smart Metering * * Simple Code to collect the SML-Data from a IR-LED and send it using UDP to the local subnet * Configuration : Enter the ssid and password of your Wifi AP. Enter the port number your server is listening on. * *Serial Port RxD is used to read data from Smartmeter. Phototransistor is pulling down the RxD-Line. Should work even with PC connected *Serial Port TxD is used to send debug output * * 20160501 frank@carius.de initial Version * Portions from http://www.esp8266.com/viewtopic.php?f=29&t=2222 */ // Every serial.write or serial.writeln will take long at 9600 baud. So remove them in production #include <ESP8266WiFi.h> #include <WiFiUDP.h> extern "C" { //required for system_get_chip_id() #include "User_interface.h" // uint16 readvdd33(void); } // Global constants for WiFi connections int status = WL_IDLE_STATUS; const char* ssid = "FCHAUS"; // your network SSID (name) Case sensible ! const char* pass = "wifi4haus"; // your network password IPAddress udpip(192,168,178,255); // specify target IP unsigned int udpport = 12345; // Specify Source and Target Port // Buffer for serial reading int serIn; // var that will hold the bytes-in read from the serialBuffer byte datagram[1000]; // array that will hold the different bytes int serindex = 0; // index of serInString[] in which to insert the next incoming byte int serialbyte; // Byte to store serial input data // Create a UDP instance to send and receive packets over UDP WiFiUDP Udp; // Instantiate UDP-Class // // Blink Function for Feedback of Status, takes about 1000ms // void blink(int count, int durationon, int durationoff, int delayafter) { for (int i=0; i < count; i++) { digitalWrite(LED_BUILTIN, LOW); // Turn the LED on delay(durationon); digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH delay(durationoff); } delay(delayafter); } // ------------------------------------------------ // SETUP running once at the beginning // ------------------------------------------------ // Initialize Serial, WIFi and UDP void setup() { pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output blink(1,5000,500,1000); // Signal startup Serial.begin(9600); // Open serial communications and wait for port to open: while (!Serial) { ; // wait for serial port to connect. } Serial.println("ESP8266-DO-Logger init"); blink(4,100,500,2000); // Signal Serial OK // // Wait for connect to AP // Serial.print("Start WiFi to SSID: "); Serial.println(ssid); while (WiFi.status() != WL_CONNECTED) { blink(5,50,100,2000); WiFi.begin(ssid, pass); } // Report Data to Serial Port. Serial.print("Connect with IP:"); Serial.println(WiFi.localIP()); blink(6,100,500,2000); // Start UDP Object Serial.print("Start UDP-Service on port:"); Serial.println(udpport); Udp.begin(udpport); // Serial.setTimeout(1000); // Set Timeout for Serial.readBytesUntil() blink(7,100,500,2000); Serial.print("INIT Done"); } // ------------------------------------------------ // MAIN LOOP RUNNING all the time // ------------------------------------------------ void loop() { Serial.print("LOOP: "); // Send some startup data to the console Serial.print("SSID: "); // SSID of Network Serial.print(WiFi.SSID()); // SSID ausgeben Serial.print(" IP Address: "); // assigned IP-Address IPAddress ip = WiFi.localIP(); // IP Adresse auslesen Serial.println(ip); // IP Adresse ausgeben blink(2,400,100,2000); // Show status // // Clear Serial Data // Serial.println("C"); while (Serial.available()) { while (Serial.available()) { serialbyte = Serial.read(); //Read Buffer to clear } //Serial.print("F"); delay(10); // wait approx 10 bytes at 9600 Baud to clear bytes in transmission } // // assume, that there is now a pause. Wait for start of transmission // Serial.println("W"); int flashcount = 0; while (!Serial.available()){ flashcount++; // Serial.println(flashcount); if (flashcount == 400) { digitalWrite(LED_BUILTIN, LOW); // Turn the LED on } else if (flashcount > 500) { digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off flashcount=0; } else { delay(5); // wait 5 ms for new packets } } // We got some bytes. read until next pause Serial.println("R"); // Serial.println("Reading serial data"); Serial.setTimeout(500); // Set Timeout to 500ms. serindex = Serial.readBytes(datagram,1000); // serindex = Serial.readBytesUntil('',datagram,1000); // read all serial data and end with timeout.. How to read without looking for stop character //serindex = 0; //while (Serial.available() && (serindex < 1000)){ // while (Serial.available() && (serindex < 1000)){ // serialbyte = Serial.read(); // Read Data with a 1000ms timeout // datagram[serindex] = serialbyte; // serindex++; // } // delay(10); // wait 10ms for more bytes //} if (serindex < 1000) { Serial.println("D"); blink(5,100,100,0); Serial.print("Datagram received. Total Bytes:");Serial.println(serindex); //Serial.println("Sending UDP-Paket XML"); //Udp.beginPacket(udpip, udpport); // Start new paket //Udp.write("<SMLR eader>"); //Udp.write(" <chipid>"); //Udp.print(system_get_chip_id()); // Udp.write("#IP of ESP8266#"); //Udp.write(" </chipid>"); //Udp.write(" <ipaddress>"); //Udp.println(WiFi.localIP()); //Udp.write(" </ipaddress>"); //Udp.write(" <datagram>"); //for (int i=0; i < serindex ; i++) { //char zeichen = String(datagram[i], HEX)[0]; //Udp.write(zeichen); // Udp.write(datagram[i]); // } //Udp.write(" </datagram>"); //Udp.write("</SMLReader>"); //Udp.endPacket(); // Send paket Serial.println("Sending UDP-Paket RAW"); Udp.beginPacket(udpip, udpport); // Start new paket Udp.write(datagram,serindex); Udp.endPacket(); // Send paket } else { // Error out of bounds during reading bytes from serial. blink(10,100,100,2000); } // Serial.println("Sleep 10 Seconds"); blink(100,30,70,0); }
Der Code ist sicher nicht "schön" aber funktionierte und vielleicht brauche ich ihn ja noch mal für andere Dinge, z.B. könnte das Gerät tief schlafen und durch einen Schaltkontakt (Briefkasten, Wassermelder, Leckage etc.) aufwachen und eine Meldung absetzen.
PRTG Probe
Nun fehlt nur noch die PRTG-Probe, die ab uns an mal gestartet wird und die eingehenden Daten einfängt, verarbeitet und zur Auswertung weiter gibt. Auch hier ist natürlich wieder PowerShell meine erste Wahl. Es ist damit viel einfacher einen UDP-Port zum "Lesen" zu starten als in VBScript o.ä. und eine EXE wollte ich nun doch mal nicht schreiben.
Hinweis
Achten Sie darauf, dass Sie den UDP-Port auch in der
Firewall freischalten, sonst wartet ihre Probe vergeblich
auf eingehende Pakete
Auf der Seite PS UDP habe ich schon entsprechende Vorarbeiten geleistet. Allerdings muss ich hier natürlich noch die Daten auflösen und konvertieren um sie dann z.B. an PRTG zu übergeben.
# Smartmeter2prtg param ( [string]$localip = "0.0.0.0", [string]$udplistenport="12345" ) $udpClient = New-Object system.Net.Sockets.Udpclient($udplistenport) $RemoteIpEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse($localip) , $udplistenport); Write-host "Receive-UDP:Wait für Data on Port: $udplistenport" $data=$udpclient.receive([ref]$RemoteIpEndPoint) write-host "Received packet from IP " $RemoteIpEndPoint.address ":" $RemoteIpEndPoint.Port # Parse Content write-host "Content" ([string]::join("",([System.Text.Encoding]::ASCII.GetChars($data)))) # Generate PRTG Output
Das Skript ist noch nicht fertig und wird es wohl auch nicht mehr, denn mittlerweile kann ich die Daten per Tasmota lesen und auswerten.
Mittelfristig wäre es natürlich auch eine Idee, einen Dienst zu schreiben, der von mehreren Sensoren die Daten annimmt und dann an PRTG z.B. als HTTPPush-Sensor an PRTG zu senden. Noch direkter wäre, wenn die Aufbereitung der SML-Daten in dem Wemos selbst passiert und dieser dann gleich als HTTPPushSensor die Daten an PRTG übermittelt. Das macht aber erst Sinn, wenn ich mal mehrere SML-Zähler als Beispiele ausgelesen habe. Schon mein Zähler konnte ich nur eingeschränkt decodieren..Ich vermute ja nicht, dass PRTG selbst irgendwann selbst SML-Datensttöme direkt lesen kann.
Zwischenstand
Mit viel Elan gestartet, immer wieder verzögert und mittlerweile durch freie Software wie Tasmota überholt. Anders kann ich dieses nicht abgeschlossene Projekt nicht beschreiben. Aber ich habe viel dabei gelernt und Spaß hat es auch gemacht. Vielleicht können Sie von dem ein oder anderen Code noch etwas mitnehmen aber produktiv werden ich ihn wohl nicht mehr nehmen.