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.
Gelesen werden kann mittels IR-Schreib-Lese-Kopf bei 300bd 7E1, das Zuleitungskabel geht nach unten weg.
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
{ "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>" } ] } ] }
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); ?>
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; }
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.
#!/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; }
Benutzerhandbuch (Netze NGO)
Benutzerhandbuch (Siemens AG Österreich)