Projekt 23 Smarthome / Zugriff Stromzähler

Aus c3RE.de
Wechseln zu: Navigation, Suche

Diese Seite gehört zum Kontext Projekt 23 Smarthome und beschreibt die Umsetzung beim Zugriff auf die Daten des Stromzählers EMH Zweirichtungszähler EDL300L.

Stromzähler 'EMH Zweirichtungszähler EDL300L'

Dokumentation Zähler

Die Zählerdaten werden mittels SML-Protokoll (Smart Message Language) über die zwei im Zähler vorhandenen Datenschnittstellen (Kunden- und MSB-Schnittstelle) übertragen.'

Quelle: http://www.emh-metering.com/de/produkte/ed300l/

Datenschnittstellen

  • optische Datenschnittstelle auf der Zählervorderseite (unidirektional - Push Betrieb)
  • elektrische Datenschnittstelle unter dem Klemmendeckel (RS232), bidirektional;
  • Versorgung durch Kommunikationsgerät: + 5 V DC oder + 12 V DC mit Strombegrenzung bis 10 mA
  • Datenprotokoll Smart Message Language (SML); Sendung des Datensatzes lastabhängig alle 1...4 s, signierte Werte im EDL40-Modus3
  • Baudrate 9600 Baud
  • Auflösung des Zählerstandes 100 mWh
  • Datenkennzeichnung OBIS-Kennziffern

Mit einer normalen Videokamera kann die Aktivität der Infrarot(IR)-Schnittstelle beobachtet werden. Sie blinkt ca. alle 2 Sekunden.

Physischer Zugang

Der Infrarot-Lesekopf aus dem 'volkszaehler'-Projekt auf dem Zähler montiert.

Problem: Leider befindet sich der Zähler in einem verplombten Gehäuse, so dass ein handelsüblicher Lesekopf nicht einfach mit einer Magnethalterung befestigt werden kann. Entfernung zum Plexiglas-Sichtfenster 10cm, leicht verwinkelt.

Nach erster Recherche ist das tatsächlich ein rechtliches Hindernis, weil das Setzen von Plomben nur dem Verteilnetzbetreiber (VNB) als Eigentümer des Stromzählers oder von diesem konzessionierten Betrieben erlaubt ist. Bei zerstörten Plomben droht Beweislastumkehr bei Verdacht des Stromdiebstahls.

Lösung: Ein freundlicher Elektriker hat den Kasten geöffnet und der IR-Lesekopf wurde montiert. Der Kasten wurde an der Seite minimal ausgeschnitten, so dass dort das USB-Kabel herausgeführt werden konnte. Anschließend wurde der Kasten wieder verschlossen und neu plombiert. Auf diesem Wege sollte die Rechtssicherheit gewahrt bleiben.

Hardwareauswahl für optische Schnittstelle des Zählers

Kandidaten:

Entscheidung: Der USB-Lesekopf aus dem Projekt 'Volkszähler' von Udo

USB-Lesekopf aus dem Projekt 'Volkszähler'

Systemmeldungen beim Anschluss unter CentOS7:

Dez 07 21:21:45 j3 kernel: usb 6-1: new full-speed USB device number 2 using uhci_hcd
Dez 07 21:21:46 j3 kernel: usb 6-1: New USB device found, idVendor=10c4, idProduct=ea60
Dez 07 21:21:46 j3 kernel: usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Dez 07 21:21:46 j3 kernel: usb 6-1: Product: CP2104 USB to UART Bridge Controller
Dez 07 21:21:46 j3 kernel: usb 6-1: Manufacturer: Silicon Labs
Dez 07 21:21:46 j3 kernel: usb 6-1: SerialNumber: <serial>
Dez 07 21:21:46 j3 mtp-probe[12525]: checking bus 6, device 2: "/sys/devices/pci0000:00/0000:00:1d.1/usb6/6-1"
Dez 07 21:21:46 j3 mtp-probe[12525]: bus: 6, device: 2 was not an MTP device
Dez 07 21:21:47 j3 kernel: usbcore: registered new interface driver cp210x
Dez 07 21:21:47 j3 kernel: usbserial: USB Serial support registered for cp210x                                                
Dez 07 21:21:47 j3 kernel: cp210x 6-1:1.0: cp210x converter detected                                                          
Dez 07 21:21:47 j3 kernel: usb 6-1: cp210x converter now attached to ttyUSB0
# ls -l /dev/serial/{by-path,by-id}/*
lrwxrwxrwx. 1 root root 13  7. Dez 21:21 /dev/serial/by-id/usb-Silicon_Labs_CP2104_USB_to_UART_Bridge_Controller_<serial>-if00-port0 -> ../../ttyUSB0
lrwxrwxrwx. 1 root root 13  7. Dez 21:21 /dev/serial/by-path/pci-0000:00:1d.1-usb-0:1:1.0-port0 -> ../../ttyUSB0

Softwareauswahl

Es steht eine ganze Reihe von quelloffenen SML-Implementierungen in unterschiedlichen Programmiersprachen zur Verfügung (s. Wikipedia-Artikel).

Ohne genauere Evaluierung wurden die ersten Tests auf Basis der Java-Implementierung jSML durchgeführt. Der Code entstammt dem Kontext des Fraunhofer Institute for Solar Energy Systems (Projekt openMUC) und ist sauber strukturiert. Eine Testapplication lässt sich hinsichtlich der verwendeten Hardware verändern und auf Basis von OpenJDK8 kompilieren und ausführen. Es fehlt lediglich die Funktionalität, die relevanten Daten zu selektieren und in die Datenbank zu loggen.

Weitere Motivationspunkte zur Auswahl dieser Software waren gute Kenntnisse des Autors im Bereich Java und die Option, später mit openHAB eine Java-basierte Engine für die eigentliche SmartHome-Implementierung zu wählen.

Implementierung

Installation

Weil das Projekt lediglich die Sourcen zur Verfügung stellt, ist neben der reinen Java Runtime Environment (JRE) auch der Compiler notwendig. Dieser befindet sich im Java Development Kit (JDK). Desweiteren wird benötigt: "RXTX - Java library for serial communication"

Installation der Version 8:

# yum install -y java-1.8.0-openjdk-devel.x86_64 rxtx-2.2-0.14.20100211.el7.4.x86_64

Es wird ein User angelegt, damit die Zugriffe nicht als root-User ausgeführt werden müssen:

# useradd jSML
# passwd jSML
# usermod --groups dialout jSML
# usermod --append --groups lock jSML

Hinweis: Die Gruppenzugehörigkeit 'dialout' wird für den Zugriff auf den USB-Port benötigt, 'lock' wird unter RedHat-Systemen benötigt, um Lock-Dateien schreiben zu dürfen.

Download und Auspacken der Sourcen des Projektes:

# su - jSML
$ wget https://www.openmuc.org/sml/files/releases/jsml-1.0.17.tgz
$ tar -xzf jsml-1.0.17.tgz

Neben dem eigentlichen Code für die Library sind die Sourcen als Sample sehr hilfreich:

$ ls -l jsml/src/sample/java/
insgesamt 32
-rw-r--r--. 1 jSML jSML 5112  2. Jun 2014  SampleSerialRead.java
-rw-r--r--. 1 jSML jSML 8172  2. Jun 2014  SampleSMLClient.java
-rw-r--r--. 1 jSML jSML 8714  2. Jun 2014  SampleSMLServer.java
-rw-r--r--. 1 jSML jSML 3014  2. Jun 2014  XTrustProvider.java

Nicht zwingend notwendig, aber aus Erfahrung sehr hilfreich: Ein lokales git-Repository initiieren, um jederzeit Änderungen verfolgen zu können:

$ git init jsml
$ cd jsml && git add *
$ git commit -m "Initial"

Erster Testlauf mit SampleSerialRead.java

Der folgende Abschnitt beschreibt den Werdegang im Entwicklungsprozess und ist eventuell für das Verständnis hilfreich. Für die reine Anwendung ist dieser Abschnitt irrelevant...

Mit dem Beispielprogramm SampleSerialRead.java steht eine Ausgangsbasis für die ersten Tests und möglicherweise auch die weiteren Entwicklungsschritte zur Verfügung. Darin wird ein einmaliger Zugriff über den IR-Lesekopf auf die Daten des Zählers umgesetzt.

Eine Änderung ist am Code vorzunehmen, damit das passende Device gewählt wird:

$ grep 'receiver.setupComPort' src/sample/java/SampleSerialRead.java 
                // receiver.setupComPort("/dev/ttyS0");
                receiver.setupComPort("/dev/ttyUSB0");

Vor der ersten Ausführung müssen die Sourcen einmalig compiliert werden:

[jSML@j8 jsml]$ cd src/sample/java/
[jSML@j8 java]$ javac *.java -cp "../../../build/libsdeps/jsml-1.0.17.jar:../../../dependencies/rxtxcomm_api-2.2pre2-11_bundle.jar"
Note: XTrustProvider.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Man erkennt, dass neben den Source-Files (Extension '.java') aus dem Projekt nun ausführbare Compilate ('*.class') erzeugt wurden.

$ ls -ltar ~/jsml/src/sample/java/
insgesamt 80
-rw-r--r--. 1 jSML jSML 3014  2. Jun 2014  XTrustProvider.java
-rw-r--r--. 1 jSML jSML 8714  2. Jun 2014  SampleSMLServer.java
-rw-r--r--. 1 jSML jSML 8172  2. Jun 2014  SampleSMLClient.java
drwxr-xr-x. 3 jSML jSML 4096  2. Jun 2014  ..
-rw-r--r--. 1 jSML jSML 5170 11. Dez 21:04 SampleSerialRead.java
-rw-rw-r--. 1 jSML jSML 4316 11. Dez 21:12 SampleSerialRead.class
-rw-rw-r--. 1 jSML jSML 6265 11. Dez 21:12 SampleSMLClient.class
-rw-rw-r--. 1 jSML jSML 6726 11. Dez 21:12 SampleSMLServer.class
-rw-rw-r--. 1 jSML jSML  899 11. Dez 21:12 XTrustProvider$1.class
-rw-rw-r--. 1 jSML jSML  892 11. Dez 21:12 XTrustProvider$TrustManagerFactoryImpl$1.class
-rw-rw-r--. 1 jSML jSML 1064 11. Dez 21:12 XTrustProvider$TrustManagerFactoryImpl.class
drwxr-xr-x. 2 jSML jSML 4096 11. Dez 21:12 .
-rw-rw-r--. 1 jSML jSML 1218 11. Dez 21:12 XTrustProvider.class

Ein erster Auslesungsversuch wirft einen Haufen Daten raus, deren genaue Bedeutung im weiteren Projektverlauf noch erforscht werden muss. Jedoch zeigen sich darin auf den ersten Blick durchaus plausible Kandidaten für Zählerstände:

# cd /home/jSML/jsml/src/sample/java
# java -cp "../../../build/libsdeps/jsml-1.0.17.jar:../../../dependencies/rxtxcomm_api-2.2pre2-11_bundle.jar:./" -Djava.library.path=/usr/lib64/rxtx/ SampleSerialRead
Got SML_File
The SML_File contains 3 messages
Got SMLMessage with tag: 257
Got OpenResponse
Got SMLMessage with tag: 1793
Server-ID:      EMHE1�
[...]
String:�
Unit: 30
31295564
OctetString of length 6 hex: 0x01 0x00 0x01 0x08 0x02 0xff 

Möglicherweise Zählerstand 3129 KWh für Tarif 2.8.0 (in das öffentlichen Netz eingespeister Strom)

String:�
Unit: 30
19990951
OctetString of length 6 hex: 0x01 0x00 0x02 0x08 0x01 0xff 
[...]

Möglicherweise Zählerstand 1999 KWh für Tarif 1.8.0 (aus dem öffentlichen Netz bezogener Strom)


Die gelieferten Datenströme wurden analysiert und gefiltert. Nun ist es möglich, die notwendigen Informationen zu selektieren, um sie im nächsten Schritt weiterverarbeiten zu können.

Der Zähler liefert die folgenden Messwerte (Datentyp long):

  • Tarif 1.8.0: Wert in KWh * 10000
  • Tarif 1.8.1: <entspricht Tarif 1.8.0>
  • Tarif 1.8.2: 0 (konstant)
  • Tarif 2.8.0: Wert in KWh * 10000
  • Tarif 2.8.1: <entspricht Tarif 2.8.0>
  • Tarif 2.8.2: 0 (konstant)

Jeder Aufruf liefert 1 Datensatz der Tarif-Typen 1.8.0 und 2.8.0.

EDL300Lreader.java

Einige Refactorings, Erweiterungen und Tests später lautet der Name des Programmes nun 'EDL300Lreader' und wurde auf GitHub freigegeben (s. #GitHub).

Datenbank-Logging

Eine funktionale Erweiterung ist das Logging der abgefragten Daten in einer Datenbank. Im Projektkontext ist das mariadb.

Im ersten Schritt wird der Datenbank-Treiber für Java als Lib installiert:

# yum install mysql-connector-java

Lage der Datei:

279670  864 -rw-r--r--   1 root     root       883898 Jun 10  2014 /usr/share/java/mysql-connector-java.jar

Die Datenbank wird für kontinuierliches Logging vorbereitet:

MariaDB [(none)]> create database sml; use sml;

In einem ersten (naiven?) Ansatz wird eine Tabelle angelegt, welche Platz für die Daten beider Tarife (1.8.0 und 2.8.0) bietet:

MariaDB [sml]> create table meter ( id INTEGER(20) UNSIGNED primary key AUTO_INCREMENT, TIMESTAMP timestamp, value_180 INTEGER(12) UNSIGNED, value_280 INTEGER(12) UNSIGNED);
Query OK, 0 rows affected (0.02 sec)

MariaDB [sml]> desc meter;
+-----------+------------------+------+-----+-------------------+-----------------------------+
| Field     | Type             | Null | Key | Default           | Extra                       |
+-----------+------------------+------+-----+-------------------+-----------------------------+
| id        | int(20) unsigned | NO   | PRI | NULL              | auto_increment              |
| TIMESTAMP | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| value_180 | int(12) unsigned | YES  |     | NULL              |                             |
| value_280 | int(12) unsigned | YES  |     | NULL              |                             |
+-----------+------------------+------+-----+-------------------+-----------------------------+

Es wird ein User angelegt, welcher mit möglichst minimalen Rechten ausgestattet ist:

MariaDB [sml]> CREATE USER jsml@localhost IDENTIFIED BY '<secret>';
MariaDB [sml]> GRANT INSERT ON sml.meter TO jsml@localhost;
MariaDB [sml]> FLUSH PRIVILEGES;

Der einmalige Aufruf des Programmes schreibt die aktuellen Zählerstände in diese Tabelle:

MariaDB [sml]> select * from meter;
+----+---------------------+-----------+-----------+
| id | TIMESTAMP           | value_180 | value_280 |
+----+---------------------+-----------+-----------+
|  1 | 2017-01-29 21:21:54 |  35158890 |  34688905 |
+----+---------------------+-----------+-----------+
1 row in set (0.00 sec)

Eine etwas leichter zu erfassende Abfrage:

MariaDB [sml]> select TIMESTAMP, value_180/10000 as 'Tarif 1.8.0 [KWh]', value_280/10000 as 'Tarif 2.8.0 [KWh]' from meter order by id desc limit 3;
+---------------------+-------------------+-------------------+
| TIMESTAMP           | Tarif 1.8.0 [KWh] | Tarif 2.8.0 [KWh] |
+---------------------+-------------------+-------------------+
| 2017-01-29 22:00:15 |         3516.9214 |         3468.8905 |
| 2017-01-29 21:55:15 |         3516.6873 |         3468.8905 |
| 2017-01-29 21:50:16 |         3516.5149 |         3468.8905 |
+---------------------+-------------------+-------------------+
3 rows in set (0.00 sec)

Konfiguration

Die Konfiguration erfolgt wie in Java üblich über eine einfache properties-Datei. Diese enthält key-value-Paare, welche beim Programmstart ausgewertet werden. Die Datei config.properties liegt im selben Verzeichnis wie die ausführbare Datei (.class).

# cat config.properties 
database.mysql.connection=jdbc:mysql://localhost/sml?user=jsml&password=<secret>
ir-device=/dev/ttyUSB0
loglevel=WARNING    # http://docs.oracle.com/javase/7/docs/api/index.html?java/util/logging/Level.html

Bedeutung:

  • database.mysql.connection: Zugriffspfad für den mysql-Connector inklusive Credentials für den Zugriff auf die Datenbank. Andere RDBMS sind prinzipiell möglich, derzeit aber nicht unterstützt.
  • ir-device: Der Pfad, unter dem das Betriebssystem den Infrarot-Lesekopf anspricht.
  • loglevel: Über den Log-Level kann die Menge an Log-Ausgaben gesteuert werden (Default: WARNING).

Parametrisierung

Der Aufruf ohne zusätzliche Parameter führt zu einer einfachen Textausgabe der Zählerstände.

Darüber hinaus können beim Programmstart die folgenden Parameter zur Steuerung mitgegeben werden:

  • database: Die abgelesenen Zählerstände werden in die konfigurierte Datenbank geschrieben.
  • json: Gibt die Werte im JSON-Format aus. Beispiel:
{ "timestamp": 1487358981323 , "1.8.0": 40191282 , "2.8.0": 36516004 }

Zeitsteuerung

Das Programm wird im gewünschten Rythmus per cron aufgerufen (hier ein Shell-Script als Wrapper):

# crontab -l -u jSML
*/5 * * * * sleep 10 && /home/jSML/collectMeterData.sh database

Datenbank-Abfragen

Sammlung interessanter Queries...

Tagesbezogener Zustand für eine Woche:

MariaDB [sml]> select date_format( TimeStamp, '%Y-%m-%d') as Datum, max( value_180/10000) - min( value_180/10000) as 'Tarif 1.8.0 [KWh]', max( value_280/10000) - min( value_280/10000) as 'Tarif 2.8.0 [KWh]' from meter group by Datum order by Datum desc limit 7;
+------------+-------------------+-------------------+
| Datum      | Tarif 1.8.0 [KWh] | Tarif 2.8.0 [KWh] |
+------------+-------------------+-------------------+
| 2017-01-30 |           16.3754 |            0.0523 |
| 2017-01-29 |            2.8436 |            0.0000 |
+------------+-------------------+-------------------+

Diese Zählerstandsdaten lassen sich im Zusammenhang mit den Daten anderer Quellen analysieren und ergeben in der Kombination das gewünschte Gesamtbild. Diese übergreifenden Fragestellungen werden auf einer eigenen Seite betrachtet: Projekt 23 Smarthome / Datenanalyse.

Lizenzierung

Das Programm steht unter dieser Lizenz: GNU General Public License v3.0

Have a lot of fun...

GitHub

Veröffentlichung der Source-Files:

Anwendungsbeispiele

Neben dem eigentlichen Verwendungszweck (lokale Datenhaltung) sind natürlich auch ein paar schnelle Hacks zum Spaß möglich...

Einfache Visualisierung über externe Web-Services

Mit etwas bash-Voodoo und dem json-Parser jp lassen sich die Ergebnisse auswerten und an hilfreiche Dienste im Web schicken:

  • data.sparkfun.com: Datensammelstelle
  • analog.io: Daten-Visualisierung

Der Aufrug erfolgt minütlich über cron:

# crontab -l -u jSML
*/1 * * * * sleep 25 && /home/jSML/collectMeterData.sh json | /home/jSML/jq-linux64 --join-output '"https://data.sparkfun.com/input/MGXv1Jbm7DiJ51vOvnE6?private_key=<secret>&180=", ."1.8.0"/10000, "&280=", ."2.8.0"/10000' | xargs wget --quiet --output-file /dev/null

Die Idee dazu ergab sich nach Lektüre dieses Artikels: 'Feinstaubmessung mit dem Raspi' von Charly Kühnast (Make: IoT SPECIAL S.26).

Hinweise:

  • Es wäre leicht gewesen, die Funktionalität der Datenübergabe an einen externen Service innerhalb des Java-Codes selbst zu implementieren. Es war aber eine bewusste Designentscheidung, stattdessen getreu dem alten Unix-Motto "One job, one tool!" nur die Daten in einem gut zu parsenden Format (JSON) auszugeben. Die weiteren Schritte werden flexibel von anderen Tools umgesetzt und passend kombiniert.
  • Unter den Aspekten Datenschutz und Privatsphäre ist eine solcher Umgang mit den eigenen Daten wenig empfehlenswert. Der Export von Verbrauchsdaten an einen fremden Hoster bedingt, dass man selbst keine Kontrolle mehr über die eigenen Daten hat. Bei genauerer Betrachtung der entstandenen Kurven lassen sich relativ einfach Verhaltensmuster ablesen (Stichwort Lastprofile), beispielsweise wann Stromverbraucher in welchem Umfang aktiv waren und daraus Rückschlüsse auf das Nutzungsverhalten der Bewohner ziehen. Eine eigene Analyse der Daten eines Vormittages zeigen beispielsweise klar, wann die Familienaktivitäten begonnen haben, wann geduscht wurde usw. Es ist auch sehr eindeutig abzulesen, ob und in welchen Zeiträumen das Haus bewohnt ist.
Hinzu kommt, dass der Anbieter beim Anlegen der Datenquelle eine URL mit unverschlüsseltem Protokoll verkündet. Zwar lässt sich diese auch mit HTTPS aufrufen, aber die TLS-Konfiguration scheint erhebliche Mängel zu haben. Am 18.02.2017 diagnostiziert der Rating-Dientst SSLLabs eine Anfälligkeit gegen ' OpenSSL Padding Oracle vulnerability (CVE-2016-2107)' und vergibt die schlechtest mögliche Note 'F'.
Aktuelle Bewertung: https://www.ssllabs.com/ssltest/analyze.html?d=data.sparkfun.com
Aus diesen Gründen ist die hier dargestellte Vorgehensweise mit Vorsicht zu sehen und soll nur dem Test und der Dokumentation der Möglichkeiten dienen. Für die Visualisierung der Daten sind eigene Dienste vorzuziehen. Die Datenlieferung an die externen Dienste wurde nach einer Testphase von einer Woche beendet.
Diagramm zur Auswertung Tarif 2.8.0 (ins öffentliche Netz eingespeister Strom) über eine Woche. (analog.io)
Die feingranulare Auswertung für Tarif 1.8.0 (aus dem Netz bezogen) zeigt neben den zyklischen Kompressionsphasen der Heizung eindeutige Hinweise auf morgendliche Benutzeraktivitäten. Solche Informationen sollte man zur Wahrung der Privatsphäre nicht in die Welt hinausposaunen... (analog.io)

Links