Ziemlich zusammengewürfelt aber funktioniert.
Auf das senden auf den Bus hab ich vorerst mal verzichtet, wird wieder eingebaut. Socat hab ich noch nicht implementiert, das warte ich auch erst mal ab wie weit vzlogger auf dem WireGate unterstützt werden soll.
Zur Konfiguration:
device = serieller/usb-port
conf4800 - ggf Pfad anpassen
endsign = muss eigentlich nicht geändert werden
rrdpath = Pfad zum RRD-Verzeichnis
counterid = Gibt den ersten Teil der RRD-Datei-Namen an
Die RRD werden nach dem Schema counterid_obiscode benannt, für OBIS 1.8.0 -> z.B. Zaehler_1-8-1.rrd .
obis = auszulesender Obis-Wert und Länge des Datensatzes
countermode = hier nochmal die Obiscodes eintragen die Zählerstände liefern, es werden Tagesverbräuche erstellt
Das erste Auslesen sollte in der Konsole mit dem zweiten Script hier erfolgen, da wird dann einmal der Zähler ausgelesen und angezeigt welche Codes er liefert und wieviele Stellen der einzelne Obiscode hat. Das Komma wird mitgezählt.
Das Script als *.pl ablegen und via crontab aufrufen lassen - Zyklus 5 Minuten.
Code 1 - Hauptprogramm:
Code:
#!/usr/bin/perl
# Zaehlerabfrage fuer Zaehler nach Protokoll IEC 62056-21 / OBIS
# Ein Anfrage-Telegramm ist mit 300 Baud, 7 Bit, 1 Stoppbit
# und gerader Paritaet zu senden. Das ist der Initialmodus von Geraeten,
# die das Protokoll IEC 62056-21 implementieren.
# Ein Wechsel der Geschwindigkeit ist möglich, in diesem Script aber noch nicht umgesetzt.
# !!! Wiederholung nur alle 3 Minuten da der Zaehler nach Ende des Scriptes weiter sendet !!!
# Basis des Scripts von volkszaehler.org / Autor: Andreas Schulze & Bugfix: Eric Schanze
# DPT9 sub: makki / www.knx-user-forum.de
# In dieser Version keine Anbindung an den KNX-Bus
# Erweiterung um RRD,KNX-Anbindung und gezielte Wertsuche auf Wiregate:
# JuMi2006 / www.knx-user-forum.de
# Version: 0.1.3
# Datum: 23.04.2012
use warnings;
use strict;
use Device::SerialPort;
use RRDs;
### KONFIGURATION ###
my $device = "/dev/ttyUSB-1-4.1"; #Port
my $conf4800 = "/tmp/conf4800"; #temporäre Konfigurationsdatei für Baudwechsel
my $endsign = "!"; #Letztes Zeichen im Protokoll vom Zaehler
my $rrdpath = "/var/www/rrd"; #Pfad für RRDs
my $counterid = "Zaehler_HZ"; #Grundname für RRDs
my %obis=( "16.7"=>6, #Obis-Zahl => Anzahl der Stellen z.B.: 6 = 123.45
"32.7"=>3,
"52.7"=>3,
"31.7"=>6,
"51.7"=>6,
"71.7"=>6,
"72.7"=>3,
"1.8.1"=>10); #!!!COUNTER!!!
my %countermode=("1.8.1"=>24); #Obiscode für Zaehlerstaende
#RRD gibt Verbrauch pro Tag aus (beta)
### ENDE KONFIGURATION ###
### Seriellen Port initialisieren
####4800baud
my $port = new Device::SerialPort($device) || die "can't open $device: $!";
$port->baudrate(300) || die 'fail setting baudrate';
$port->databits(7) || die 'fail setting databits';
$port->stopbits(1) || die 'fail setting stopbits';
$port->parity("even") || die 'fail setting parity';
$port->rts_active(1);
$port->dtr_active(1);
$port->read_char_time(500); # 0.5 seconds for each character
$port->read_const_time(1000); # 1 second per unfulfilled "read" call
$port->write_settings || die 'fail write settings';
$port->save($conf4800) || warn "Can't save $conf4800: $!\n";
####START 300baud
$port->baudrate(300) || die 'fail setting baudrate';
$port->databits(7) || die 'fail setting databits';
$port->stopbits(1) || die 'fail setting stopbits';
$port->parity("even") || die 'fail setting parity';
$port->dtr_active(1);
$port->rts_active(1);
$port->read_char_time(500); # 0.5 seconds for each character
$port->read_const_time(1000); # 1 second per unfulfilled "read" call
$port->write_settings || die 'fail write settings';
### Anforderungstelegramm senden
my $data="2f3f210d0a"; #Anfrage als HEX "/?!<CR><LF>"
my $request = pack('H*',$data);
my $num_out = $port->write($request);
print $request;
die "write failed\n" unless ($num_out);
die "write inclomplete\n" unless ($num_out == length($request));
print "$num_out Bytes written\n";
### Warte auf Zaehlerkennung
select(undef, undef, undef, 1.5); # 1.5 Sekunden warten
### Telegramm mit ACK und neuer Geschwindigkeit senden
my $data2="063030300d0a"; #ACK und neue Geschwindigkeit in HEX "<ACK>040<CR><LF>"
my $baudwechsel = pack('H*',$data2); # 000 = 300baud, 040 = 4800baud
my $num_out2 = $port->write($baudwechsel);
print $baudwechsel;
die "write failed\n" unless ($num_out2);
die "write inclomplete\n" unless ($num_out2 == length($baudwechsel));
print "$num_out2 Bytes written\n";
### Port mit neuer Geschwindigkeit intialisieren
$port->restart($conf4800) || warn "Can't restart $conf4800: $!\n"; ;
#$port->baudrate(300);
#$port->write_settings;
### AUSLESEN
my $STALL_DEFAULT=5; # how many seconds to wait for new input
my $timeout=$STALL_DEFAULT;
my $chars=0;
my $buffer="";
while ($timeout>0) {
my ($count,$saw)=$port->read(25); # Liest 25 Zeichen je Durchlauf aus
if ($count > 0) {
$chars+=$count;
$buffer.=$saw;
#print ($buffer,"\n"); # Ausgabe der eingelesenen Daten
### FILTER FOR END
if ($buffer =~ /\Q$endsign\E/) # \Q \E entwertet alle Sonderzeichen dazwischen
{
$port->close || die "can't close $device: $!"; # Port schlieen
last; # Schleife verlassen
}
### ENDE FILTER FOR END
}
else {
$timeout--;
}
}
if ($timeout<=0) {
$port->close || die "can't close $device: $!";
print "Waited $STALL_DEFAULT seconds and never saw what I wanted\n";
}
print $buffer; #Nur zur Kontrolle
### FILTER FOR DATA
while (my($key, $value) = each %obis)
{
my $obiskey = $key."\(";
if ($buffer =~ /\Q$obiskey\E/) # \Q \E entwertet alle Sonderzeichen dazwischen
{
my $pos=index($buffer,$obiskey); # Anfangsposition des OBIS-Key finden
#print ("Obiskey: $obiskey","\n"); # Kontrolldruck
#print ("Position: $pos","\n"); # Kontrolldruck
#print (length($obiskey),"\n"); # Kontrolldruck
my $obisvalue=substr($buffer,($pos+length($key)+1),$obis{$key}); # Wert extrahieren
print ($key,": ",$obisvalue,"\n"); # Kontrolldruck
### RRD-Erstellen
my $obisname = "$key";
$obisname =~ tr/./-/;
my $rrdname = $counterid."_".$obisname."\.rrd";
print ($rrdname,"\n");
my $rrdfile = $rrdpath."\/".$rrdname;
### RRD check COUNTER/GAUGE
if (exists $countermode{$key})
{
print ("COUNTER","\n");
### COUNTER schreiben
unless (-e $rrdfile)
{
RRDs::create ($rrdfile,"DS:value:COUNTER:86500:0:10000000000","RRA:AVERAGE:0.5:1:365","RRA:AVERAGE:0.5:7:300","-s 86400");
}
### RRD-Füllen
RRDs::update("$rrdfile", "N:$obisvalue*=86400");
}
else
{
print ("GAUGE","\n");
###GAUGE schreiben
unless (-e $rrdfile)
{
RRDs::create ($rrdfile,"DS:value:GAUGE:900:0:10000000000","RRA:AVERAGE:0.5:1:2160","RRA:AVERAGE:0.5:5:2016","RRA:AVERAGE:0.5:15:2880","RRA:AVERAGE:0.5:60:8760");
}
### RRD-Füllen
RRDs::update("$rrdfile", "N:$obisvalue");
}
}
}
### ENDE FILTER FOR DATA
### Log der Zaehlerausgabe
#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
#my $timestamp = printf "%4d-%02d-%02d %02d:%02d:%02d\n",$year+1900,$mon+1,$mday,$hour,$min,$sec;
#open(LOG,'>>',$file) || die "Fehler $!";
#print LOG ("\n",$year+1900,"-",$mon+1,"-",$mday," ",$hour,":",$min,":",$sec," \; ",$value);
#close LOG;
Hier der Auslesecode fürs erste auslesen. Etwas Geduld, kann je nach Zähler bis zu 3 Minuten dauern.
Am besten als init.pl speichern.
Code:
#!/usr/bin/perl
use warnings;
use strict;
use Device::SerialPort;
### KONFIGURATION ###
my $device = "/dev/ttyUSB-1-4.1"; #Port
my $endsign = "!"; #Letztes Zeichen im Protokoll vom Zaehler
### ENDE KONFIGURATION ###
### Seriellen Port initialisieren
####START 300baud
my $port = new Device::SerialPort($device) || die "can't open $device: $!";
$port->baudrate(300) || die 'fail setting baudrate';
$port->databits(7) || die 'fail setting databits';
$port->stopbits(1) || die 'fail setting stopbits';
$port->parity("even") || die 'fail setting parity';
$port->dtr_active(1);
$port->rts_active(1);
$port->read_char_time(500); # 0.5 seconds for each character
$port->read_const_time(1000); # 1 second per unfulfilled "read" call
$port->write_settings || die 'fail write settings';
### Anforderungstelegramm senden
my $data="2f3f210d0a"; #Anfrage als HEX "/?!<CR><LF>"
my $request = pack('H*',$data);
my $num_out = $port->write($request);
print $request;
die "write failed\n" unless ($num_out);
die "write inclomplete\n" unless ($num_out == length($request));
print "$num_out Bytes written\n";
### Warte auf Zaehlerkennung
select(undef, undef, undef, 1.5); # 1.5 Sekunden warten
### Telegramm mit ACK und neuer Geschwindigkeit senden
my $data2="063030300d0a"; #ACK und neue Geschwindigkeit in HEX "<ACK>040<CR><LF>"
my $baudwechsel = pack('H*',$data2); # 000 = 300baud, 040 = 4800baud
my $num_out2 = $port->write($baudwechsel);
print $baudwechsel;
die "write failed\n" unless ($num_out2);
die "write inclomplete\n" unless ($num_out2 == length($baudwechsel));
print "$num_out2 Bytes written\n";
### AUSLESEN
my $STALL_DEFAULT=5; # how many seconds to wait for new input
my $timeout=$STALL_DEFAULT;
my $chars=0;
my $buffer="";
while ($timeout>0) {
my ($count,$saw)=$port->read(25); # Liest 25 Zeichen je Durchlauf aus
if ($count > 0) {
$chars+=$count;
$buffer.=$saw;
#print ($buffer,"\n"); # Ausgabe der eingelesenen Daten
### FILTER FOR END
if ($buffer =~ /\Q$endsign\E/) # \Q \E entwertet alle Sonderzeichen dazwischen
{
$port->close || die "can't close $device: $!"; # Port schlieen
last; # Schleife verlassen
}
### ENDE FILTER FOR END
}
else {
$timeout--;
}
}
if ($timeout<=0) {
$port->close || die "can't close $device: $!";
print "Waited $STALL_DEFAULT seconds and never saw what I wanted\n";
}
print $buffer; #Nur zur Kontrolle





Einen Kommentar schreiben: