Inhaltsverzeichnis
Siemens TD-3511
Beim TD-3511 von Siemens handelt sich um Zweirichtungszähler mit IR-Schnittstelle. Der Zähler besitzt auch noch einen frei zugänglichen Steckplatz für einen M-Bus Adapter.
Hardware
Gelesen werden kann mittels IR-Schreib-Lese-Kopf bei 300bd 7E1, das Zuleitungskabel geht nach unten weg.
Kommunikation
Die Datenübertragung erfolgt gemäß Hersteller-Anleitung per IEC 62056-21 in Mode „C“.
Das im vzlogger zu konfigurierende Protokoll ist „d0“.
Der Zähler arbeitet im Pull-Modus, d.h. er sendet die Daten erst nach Anforderung.
Der Zähler erwartet die Initialisierungssequenz
/?!\r\n (hex 2F 3F 21 0D 0A)
und antwortet mit seinem Identifikationstelegramm.
Nach der an den Zähler gesendeten Startsequenz („Optionsauswahltelegramm“) „ACK 0 Z 0 CR LF“
ACK0<Z><Y>\r\n (hex 06 30 zz yy 0D 0A)
sendet er bei Option „Y“ = 0 („Daten auslesen“) die Daten in der mit „Z“ ausgewählten Baudrate.
Beispiel: „ACK050\r\n“ (hex 06 30 35 30 0D 0A) –> Daten werden vom Zähler mit 9.600Bit/s gesendet.
Ausschnitt aus der Hersteller-Anleitung (s. Quellen - Benutzerhandbuch (Siemens AG Österreich)):
Folgende Bitraten in Abhängigkeit des Wertes des Zeichens „Z“ sind im Mode „C“ definiert:
Code Übertragungsrate 0 300 Bit/s 1 600 Bit/s 2 1.200 Bit/s 3 2.400 Bit/s 4 4.800 Bit/s 5 9.600 Bit/s 6 19.200 Bit/s 9 115.200 Bit/s
Der Zähler meldet sich aus Kompatibilitätsgründen mit der normkonformen Baudratenkennung „6“. Eine maximal mögliche Baudrate von 115.200 Bd wird unterstützt.
Wahrscheinlich funktionieren auch die Befehle des elster_AS1440. Die Umschaltung der Geschwindigkeit auf 19200bps funktioniert auf jeden Fall, dazu die Startsequenz verwenden:
\x06060\r\n
Falls die Kommunikation bei einer höheren Baudrate nicht funktioniert, sollte in der Startsequenz manuell die niedrigste Baudrate von 300Bit/s eingestellt werden (automatische Erkennung deaktivieren).
Beispielkonfiguration
- vzlogger.conf
{ "verbosity": 10, "log": "/var/log/vzlogger/vzlogger.log", "retry": 30, "local": { "enabled": false, "port": 8081, "index": true, "timeout": 0, "buffer": -1 }, "meters": [ { "enabled": true, "allowskip": false, "protocol": "d0", "device": "/dev/ttyAMA0", "aggtime": -1, "interval": 10, "pullseq": "2F3F210D0A", // "/?!\r\n" = 2F 3F 21 0D 0A "ackseq": "063034300D0A", //needs to be in accordance to parameter 'baudrate_read' //"auto" for auto detection "baudrate": 300, "baudrate_read": 4800, "baudrate_change_delay": 300, "parity": "7e1", "use_local_time": false, "read_timeout": 10, //?? 2200ms according to manual? "dump_file": "/home/pi/D0_pullSeq.txt", "channels": [ { "identifier": "1.7.0", //Positive active instantaneous power (A+) [kW] "uuid": "00000000-0000-0000-0000-000000000000", "api": "influxdb", "host": "http://localhost:8086", "database": "vzlogger", "measurement_name": "vz_measurement", "duplicates": 600, "username": "<username>", "password": "<password>" }, { "identifier": "1.8.1", //Positive active energy (A+) in tariff T1 [kWh] "uuid": "00000000-0000-0000-0000-000000000000", "api": "influxdb", "host": "http://localhost:8086", "database": "vzlogger", "measurement_name": "vz_measurement", "duplicates": 600, "username": "<username>", "password": "<password>" }, { "identifier": "2.8.1", //Negative active energy (A+) in tariff T1 [kWh] "uuid": "00000000-0000-0000-0000-000000000000", "api": "influxdb", "host": "http://localhost:8086", "database": "vzlogger", "measurement_name": "vz_measurement", "duplicates": 600, "username": "<username>", "password": "<password>" } ] } ] }
Auslesen mit anderen Methoden als vzlogger
Auslesen per PHP
Dieses Script übergibt die Daten ganz normal an die Middleware. Das Script verwendet als Geschwindigkeit 9600. Wer 19200 möchte muss die beiden Zeilen im Script einkommentieren.
<?php $urlBase='http://localhost/middleware.php/data/'; $device='/dev/serial/by-id/usb-bitteAnpassen'; $channels=array( '1.7.0(' => 'xxxxxxxx-yyyy-zzzz-aaaa-bbbbbbbbbbbb', '2.7.0(' => 'xxxxxxxx-yyyy-zzzz-aaaa-bbbbbbbbbbbb', '1.8.1(' => 'xxxxxxxx-yyyy-zzzz-aaaa-bbbbbbbbbbbb', '1.8.2(' => 'xxxxxxxx-yyyy-zzzz-aaaa-bbbbbbbbbbbb', '2.8.1(' => 'xxxxxxxx-yyyy-zzzz-aaaa-bbbbbbbbbbbb' ); error_reporting(E_ALL); function setSerial($device,$bps) { echo "setze Schnittstelle auf $bps bps\n"; $output=array(); $returnVar=0; $cmd="stty $bps -F $device"; exec($cmd, $output,$returnVar); echo "Ergebnis vom Setzen der seriellen Schnittstelle per stty:\n"; print_r($output); } // function initSerial function setSerialInital($device,$bps) { echo "setze Schnittstelle auf $bps bps\n"; $output=array(); $returnVar=0; $cmd="stty $bps -F $device 1:4:da7:a30:3:1c:7f:15:4:10:0:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"; exec($cmd, $output,$returnVar); echo "Ergebnis vom Setzen der seriellen Schnittstelle per stty:\n"; print_r($output); } // function initSerial function checkLine($line) { global $channels; global $urlBase; echo "Line: '$line'\n"; $line=trim($line); if ($line=='!') die("\n bin durchgelaufen\n"); foreach(array_keys($channels) as $orbis) { if(substr($line,0,strlen($orbis))==$orbis ) { echo "match für $orbis\n"; $part=(float)substr($line,6); // 2.8.0(885.259*kWh) $part=strstr(substr($line,6),'*',true); if (('1.7.0(' == $orbis) or ('2.7.0(' == $orbis)) $part=$part*1000; echo "ermittelter Wert: $part\n"; $url=$urlBase.$channels[$orbis].'.json?operation=add&value='.$part; echo "rufe $url auf.\n"; $dummy=curl_file_get_contents($url); } // if } // foreach } // function checkLine function curl_file_get_contents($URL) { $c = curl_init(); curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); curl_setopt($c, CURLOPT_URL, $URL); $contents = curl_exec($c); curl_close($c); if ($contents) return $contents; else return FALSE; } // function curl_get_file_contents function getTimestamp() { $seconds = microtime(true); // false = int, true = float return round( ($seconds * 1000) ); } setSerialInitial($device,300); $fp=fopen($device,'c+'); if (!$fp) { echo "Konnte Port nicht öffnen\n"; die; } else { echo "Port öffnen OK\n"; } // if echo "Request senden ..."; $out = "/?!\r\n"; fwrite($fp, $out); echo "Request OK.\n"; echo "Lese eine Zeile\n"; echo fgets($fp); echo "gelesen\n"; /* echo "Lese eine Zeile\n"; echo fgets($fp); echo "gelesen\n"; */ echo "sicherheitshalber etwas warten\n"; usleep(500 * 1000); echo "bps-Rate-Request senden ..."; $out = "\x06\x30\x35\x31\x0D\x0A"; $out = "\x06050\r\n"; // fuer 19200: // $out = "\x06060\r\n"; fwrite($fp, $out); echo "BPS Request OK.\n"; echo "warte bis Zeichen ausgegeben wurden...\n"; usleep(500 * 1000); //setSerial($device,9600); echo "schließe Port\n"; fclose($fp); setSerial($device,9600); // für 19200 // setSerial($device,19200); echo "öffne Port\n"; $fp=fopen($device,'c+'); if (!$fp) { echo "Konnte Port nicht öffnen\n"; die; } else { echo "Port 9600 OK\n"; } // if echo "lese Rest ein\n"; while (!feof($fp)) { echo $line=fgets($fp, 128); checkLine($line); } // while fclose($fp); ?>
Auslesen per C zu MySQL
Das folgende C Programm kann alle Daten mit dem USB-IR-Schreib-Lesekopf empfangen und in eine MySQL-Datenbank speichern.
Dabei wird die Übertragungsrate auf 9600bps erhöht.
Das Programm kann über PHP mit pclose(popen(„cmd.exe /c C:/Pfad/programm.exe“, 'r')); aufgerufen werden.
#include <windows.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <mysql.h> #include <math.h> /*http://www.freewebs.com/dragonstein/msql_c.html libmysql.dll von xampp-Ordner in Main.c-Ordner kopieren*/ // globale Variablen definieren // für COM-Ports DCB sDcb; HANDLE com; DWORD dwCount; COMMTIMEOUTS sTo; time_t rawtime; // -------------------------- int main (){ // Hauptprogramm int i=5,j=0,n=0,ci,error=1; unsigned char Data[999],Datar,Datas[9],TempBuffer[999]; //-------------------------- MYSQL initialisieren ------------------------------ MYSQL *conn; // pointer to connection handler MYSQL_RES *result; // holds the result set MYSQL_ROW row; // contains the data of the table row[n] conn = mysql_init(NULL); // initialize connection handler // connect to server time(&rawtime); strncpy(TempBuffer,ctime(&rawtime),strlen(ctime(&rawtime))-1); while (i>0) { if (mysql_real_connect (conn, "localhost", "user", "password", "home", 3306, NULL, 0)) {i=0; error=0;} else { conn = mysql_init(NULL); // initialize connection handler i--; Sleep(1000); } } //------------ENDE---------- MYSQL initialisieren ----------ENDE---------------- //-------------------------- RS232 initialisieren ------------------------------ if (error==0) { // COM-Port initialisieren // COM-Port des IR-Lesekopfs aus DB auslesen mysql_query(conn, "SELECT Value FROM infos WHERE Typ='Stromverbrauch_COM'"); result = mysql_store_result(conn); row=mysql_fetch_row(result); ci=atoi(row[0]); mysql_free_result(result); // SET Parameter sDcb.BaudRate = CBR_300; // Baudrate sDcb.ByteSize = 7; // DatenBits sDcb.Parity = EVENPARITY; // Kein ParityBit sDcb.StopBits = ONESTOPBIT; // ein StopBit sDcb.fBinary=TRUE; sDcb.fDsrSensitivity=FALSE; sDcb.fParity=FALSE; sDcb.fOutX=FALSE; sDcb.fInX=FALSE; sDcb.fNull=FALSE; sDcb.fAbortOnError=FALSE; sDcb.fOutxCtsFlow=FALSE; sDcb.fOutxDsrFlow=FALSE; sDcb.fDtrControl=DTR_CONTROL_DISABLE; sDcb.fDsrSensitivity=FALSE; sDcb.fRtsControl=RTS_CONTROL_ENABLE; sDcb.fOutxCtsFlow=FALSE; // SET timeouts sTo.ReadIntervalTimeout = MAXDWORD; sTo.ReadTotalTimeoutConstant = 50; // ms auf ein Byte warten sTo.ReadTotalTimeoutMultiplier = 0; sTo.WriteTotalTimeoutConstant = 0; sTo.WriteTotalTimeoutMultiplier= 0; wsprintf(TempBuffer, "//./com%d",ci); // COM Port initialisieren com=CreateFile(TempBuffer,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0); // kein COM Port => Abbruch if(com==-1) {error=2;} else { // Parameter setzen if(!SetCommState(com,&sDcb)) {printf("SetCommState-Error: %s\n",MB_OK+MB_ICONERROR);} // timeouts setzen if(!SetCommTimeouts(com,&sTo)) {printf("SetCommTimeouts-Error: %s\n",MB_OK+MB_ICONERROR);} } } //-----------ENDE----------- RS232 initialisieren -----------ENDE--------------- // ------------------------------------- Receive Data --------------------------------------------- if (error==0) { // No_Data Fehler setzen (wird bei Empfang > 500 Bytes 0 gesetzt) error=3; // Daten löschen und Zeit setzen wsprintf(TempBuffer, "UPDATE e_energie SET Data='0000\n', Time='%d', Value='1' WHERE Typ='Stromzaehler_Temp_Data'",time(NULL)); mysql_query(conn, TempBuffer); // Initialisierung senden // / ? ! \r \n Datas[0]=47; Datas[1]=63; Datas[2]=33; Datas[3]=13; Datas[4]=10; for (i=0;i<5;i++) {WriteFile(com,&Datas[i],1,&dwCount,0);} // Zählerantwort abwarten for (i=0;i<10;i++) { ReadFile(com,&Datar,1,&dwCount,0); if (dwCount!=0) {i=0;} } // Übertragungsgeschwindigkeit im Zähler auf 5=9600 Baud setzen // ACK 0 5 0 \r \n Datas[0]=6; Datas[1]=48; Datas[2]=53; Datas[3]=48; Datas[4]=13; Datas[5]=10; for (i=0;i<6;i++) {WriteFile(com,&Datas[i],1,&dwCount,0);} // warten bis Daten gesendet wurden Sleep(250); // Baudrate der COM-Schnittstelle auf 9600 ändern sDcb.BaudRate = CBR_9600; SetCommState(com,&sDcb); // Daten empfangen for (i=0;i<50;i++) { ReadFile(com,&Datar,1,&dwCount,0); if (dwCount!=0) { i=0; // ASCII Sonderzeichen ausfiltern, erlaubt sind nur: // \r \n . ( ) * 0-9 A-Z a-z if ((Datar==10)||(Datar==13)||(Datar==46)||((Datar>39)&&(Datar<43))||((Datar>47)&&(Datar<58))||((Datar>64)&&(Datar<91))||((Datar>96)&&(Datar<123))) { wsprintf(Data, "%s%c",Data,Datar); // append char to string //printf("%c",Datar); // Ausgabe anzeigen // jeweils nach 500 Zeichen Daten in MYSQL übertragen //(über 1000 Zeichen auf einmal sind nicht möglich!!!) if (j>500) { j=0; error=0; // Fehlercode zurücksetzen wsprintf(TempBuffer, "UPDATE e_energie SET Data=concat(Data,'%s') WHERE Typ='Stromzaehler_Temp_Data'",Data); mysql_query(conn, TempBuffer); wsprintf(Data, ""); // Data wieder leeren } j++; } } } } // ---------------ENDE----------------- Receive Data -----------------ENDE------------------------- // wenn keine Daten empfangen werden if (error!=0) {wsprintf(TempBuffer, "UPDATE e_energie SET Data='Read-Error %d (1=MYSQL;2=COM; 3=NO_DATA)', Time='%d', Value='0' WHERE Typ='Stromzaehler_Temp_Data'",error,time(NULL)); mysql_query(conn, TempBuffer);} mysql_close(conn); /* disconnect from server */ CloseHandle(com); /* disconnect from COM-Port */ return 0; }
Test mit HTerm
Zum Testen kann hterm verwendet werden.
Einzustellen sind:
Port: bei Windows:verwendeter COM-Anschluss des USB-Adapters, bei Linux z.B. /dev/ttyUSB0
Baud: 300
Data: 7
Stop: 1
Parity: Even
Bei „input control“: Send on Enter auf „CR-LF“
Auf „Connect“ klicken, dann im Eingabefeld „/?!“ (ohne Anführungszeichen) eingeben und Enter drücken. Dann sollte der Zähler sofort mit seiner Seriennummer antworten. Nach 2-3 Sekunden fängt der Zähler dann an, Daten auszugeben.
Test mit Perl-Script
#!/usr/bin/perl # # (m)ein Stromzähler mit IR-Schnittstelle blubbert nach einem "Aufforderung- # telegramm" Daten raus. Das Telegramm ist mit 300 Baud, 7 Bit, 1 Stoppbit # und gerader Parität zu senden. Das ist der Initialmodus von Geräten, # die das Protokoll IEC 62056-21 implementieren. # # Autor: Andreas Schulze # Bugfix: Eric Schanze # Datum: 20120302 # # 20171230: Andreas Schulze: speed :-) use warnings; use strict; use utf8; use Device::SerialPort; use Time::HiRes qw(usleep); # functions sub tty_init($); sub tty_baudrate($$); sub tty_close($); sub send_telegram($$); sub receive_line($); # global vars my $PORT='/dev/ttyUSB0'; my $aufforderung = "/?!"; my $speed = chr(6) . "090"; my $tty; my $answer; # main $tty = tty_init($PORT); send_telegram($tty, $aufforderung); print receive_line($tty); usleep(250*1000); send_telegram($tty, $speed); usleep(250*1000); tty_baudrate($tty, 115200); do { $answer = receive_line($tty); print $answer; } until $answer eq "!\r\n"; tty_close($tty); exit(0); # parameter: tty name # return : a tty handle # on error : die sub tty_init($) { my ($device) = @_; my $tty = new Device::SerialPort($device) || die "can't open $device: $!"; $tty->baudrate(300) || die 'fail setting baudrate'; $tty->databits(7) || die 'fail setting databits'; $tty->stopbits(1) || die 'fail setting stopbits'; $tty->parity("even") || die 'fail setting parity'; $tty->write_settings || die 'fail write settings'; $tty->read_const_time(10); #$tty->debug(1); $tty; } # parameter: a tty handle # on error : die # return : - sub tty_close($) { my ($tty) = @_; $tty->close || die "can't close tty: $!"; } # parameter: a tty handle # parameter: new baudrate # return : - # on error : die sub tty_baudrate($$) { my ($tty, $baudrate) = @_; $tty->baudrate($baudrate) || die 'fail setting baudrate'; $tty->write_settings || die 'fail write settings'; }; # parameter: a tty handle # parameter: string to send # return : - # on error : die sub send_telegram($$) { my ($tty,$telegram) = @_; print "send '$telegram' ...\n"; $telegram .= "\r\n"; my $num_out = $tty->write($telegram); die "write failed\n" unless ($num_out); die "write inclomplete\n" unless ($num_out == length($telegram)); print "$num_out Byte written ...\n"; } # parameter: a tty handle # return : received string including the final "\n" # on error : - sub receive_line($) { my ($tty) = @_; my ($line, $num_read, $c); do { ($num_read, $c) = $tty->read(1); $line .= $c; } until !defined($c) || $c eq "\n"; $line; }
Quellen
Benutzerhandbuch (Netze NGO)
Benutzerhandbuch (Siemens AG Österreich)