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: