Dies ist eine alte Version des Dokuments!
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.
http://wiki.volkszaehler.org/hardware/channels/meters/power/edl-ehz/siemens_td3511_in_oberoesterreich
Siehe auch den Thread im Photovoltaikforum
Hardware
Gelesen werden kann mittels IR-Schreib-Lese-Kopf bei 300bd 7E1, das Zuleitungskabel geht nach unten weg.
Kommunikation
Der Zähler erwartet die Initialisierungssequenz
/?!\r\n
nach Startsequenz
\x06\x30\x35\x31\x0D\x0A \x06050\r\n
sendet er die Daten mit 9600bd.
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
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
Handbuch: AMIS_TD-351x_BHBK.pdf