Wenn dies dein erster Besuch hier ist, lies bitte zuerst die Hilfe - Häufig gestellte Fragen durch. Du musst dich vermutlich registrieren, bevor du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um den Registrierungsprozess zu starten. Du kannst auch jetzt schon Beiträge lesen. Suche dir einfach das Forum aus, das dich am meisten interessiert.
...und erklärt, warum Hyperterminal gereicht hat :-)
Wenn keine Umschaltung der Geschwindigkeit nötig ist und die Werte als Klartext kommen, funktioniert sogar diese Uraltsoftware :-)
Poste mal das ganze script.
Besser vielleicht gleich eine Revision vorher aus dem SVN nehmen. Vielleicht reicht es dort überall die 300 baud gegen 9600 baud auszutauschen. Das sollte nur in 2 Zeilen der Fall sein die die socat Abfrage machen.
sei so Gut und pack das in Code-Tags (das Symbol mit der Raute) das liest sich einfach besser.
Ein Problem ist mir gestern Abend noch aufgefallen, Dein Zähler fragt mehrere Werte ab oder? Du hast z.B. die 1.8.0 zweimal mit unterschiedlichen Ständen, das wird hier ohne Codeänderung sicherlich zu Problemen führen.
Wie zuverlässig arbeitet denn das init.pl?
Als Code probiere mal das hier:
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 umgesetzt.
# Basis des Scripts von volkszaehler.org / Autor: Andreas Schulze & Bugfix: Eric Schanze
# DPT9 sub: makki / www.knx-user-forum.de
# Baudwechsel: panzaeron / www.knx-user-forum.de
# Erweiterung um RRD,KNX-Anbindung und gezielte Wertsuche auf Wiregate:
# JuMi2006 / www.knx-user-forum.de
# Version: 0.1.6
# Datum: 16.08.2012
use warnings;
use strict;
use RRDs;
### KONFIGURATION ###
my $device = "/dev/ttyS0"; #Port
my $rrdpath = "/var/www/rrd"; #Pfad fuer RRDs
my $counterid = "test"; #Grundname fuer RRDs
my $baudrate = 9600; #Baudrate fuer Zaehlerauslesung "auto" oder 300,600,etc
my %channels = ( #Obis-Zahl => Gruppenadresse
"15.7.0"=>"12/1/1", #akt. Leistung
"17.0.0"=>"12/1/11", #akt. Stromstärke ?
"1.8.0"=>"12/1/0" #Zaehlerstand gesamt
);
my @countermodes = (5,15,60,1440); #Aufloesungen fuer COUNTER RRDs in Minuten (1440 = Tagesverbrauch)
my $debug = 0;
### ENDE KONFIGURATION ###
#Je nach Geschwindigkeit andere Befehle an Zaehler senden
my %speedrate = (
"300"=>"'\x06\x30\x30\x30\x0d\x0a'",
"600"=>"'\x06\x30\x31\x30\x0d\x0a'",
"1200"=>"'\x06\x30\x32\x30\x0d\x0a'",
"2400"=>"'\x06\x30\x33\x30\x0d\x0a'",
"4800"=>"'\x06\x30\x34\x30\x0d\x0a'",
"9600"=>"'\x06\x30\x35\x30\x0d\x0a'"
);
my %speedrate_auto = (
"0"=>"'\x06\x30\x30\x30\x0d\x0a'",
"1"=>"'\x06\x30\x31\x30\x0d\x0a'",
"2"=>"'\x06\x30\x32\x30\x0d\x0a'",
"3"=>"'\x06\x30\x33\x30\x0d\x0a'",
"4"=>"'\x06\x30\x34\x30\x0d\x0a'",
"5"=>"'\x06\x30\x35\x30\x0d\x0a'"
);
my %baud = (
"0"=>"300",
"1"=>"600",
"2"=>"1200",
"3"=>"2400",
"4"=>"4800",
"5"=>"9600"
);
### DATEN EMPFANGEN ###
### Anfrage Senden ###
my $id = qx(echo '\x2f\x3f\x21\x0d\x0a' | socat -T1 - $device,raw,echo=0,b9600,parenb=1,parodd=0,cs7,csto pb=0);
my $speedcode = substr($id,4,1);
my $ack = $speedrate{$baudrate};
### Zählerkennung auswerten - Geschwindigkeit ermitteln ###
if ($baudrate eq "auto")
{
$ack = $speedrate_auto{$speedcode};
$baudrate = $baud{$speedcode};
}
else
{
$ack = $speedrate{$baudrate};
}
if ($debug==1){print ($id,"\n")};
if ($debug==1){print ($baudrate,"\n")};
select(undef, undef, undef, 1);
### Abfrage starten ###
my @buffer = qx(echo $ack | socat -t1 - $device,raw,echo=0,b9600,parenb=1,parodd=0,cs7,csto pb=0; socat -T 1 - $device,raw,echo=0,b$baudrate,parenb=1,parodd=0,cs 7,cstopb=0 );
if ($debug==1){print (@buffer,"\n")};
### AUSWERTUNG ###
foreach (@buffer)
{
foreach my $obis(%channels)
{
my $obiskey = $obis."\(";
if ($_ =~ /\Q$obiskey\E/)
{
$_ =~ m/[^(]+\(([^*]+)\*([^)]+)/;
my $value = $1;
my $unit = $2;
my $ga = $channels{$obis};
if ($debug==1){print ($obis,"\n")};
if ($debug==1){print ($value,"\n")};
if ($debug==1){print ($unit,"\n")};
if ($debug==1){print ($ga,"\n")};
if ($unit =~ /\Qh\E/)
{
&rrd_counter ($obis,$value)
}
else
{
&rrd_gauge ($obis,$value)
}
&knx_write ($ga,$value);
}
}
}
### SUBS ###
sub rrd_counter
{
if ($debug==1){print ("COUNTER","\n")};
foreach (@countermodes)
{
my $obisname = $_[0];
my $value = $_[1];
$obisname =~ tr/./-/;
my $rrdname = $counterid."_".$obisname."_".$_."\.rrd";
if ($debug==1){print ($rrdname,"\n")};
my $rrdfile = $rrdpath."\/".$rrdname;
unless (-e $rrdfile)
{
RRDs::create ($rrdfile,"DS:value:COUNTER:".(($_*60)+600).":0:10 000000000","RRA:AVERAGE:0.5:1:365","RRA:AVERAGE:0. 5:7:300","-s ".($_*60));
}
my $countervalue = int($value*$_*60);
RRDs::update("$rrdfile", "N:$countervalue");
}
}
sub rrd_gauge
{
if ($debug==1){print ("GAUGE","\n")};
my $obisname = $_[0];
my $value = $_[1];
$obisname =~ tr/./-/;
my $rrdname = $counterid."_".$obisname."\.rrd";
if ($debug==1){print ($rrdname,"\n")};
my $rrdfile = $rrdpath."\/".$rrdname;
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");
}
RRDs::update("$rrdfile", "N:$value");
}
sub knx_write
{
### Wert in DPT9 umwandeln und in Konsole ausgeben
my @hexdec = encode_dpt9($_[1]);
my $hexval = sprintf("%x", $hexdec[0]) . " " . sprintf("%x", $hexdec[1]);
#print ($hexval,"\n");
system("groupwrite ip:localhost $_[0] $hexval");
}
sub encode_dpt9
{
# 2byte signed float
my $state = shift;
my $data;
my $sign = ($state <0 ? 0x8000 : 0);
my $exp = 0;
my $mant = 0;
$mant = int($state * 100.0);
while (abs($mant) > 2047)
{
$mant /= 2;
$exp++;
}
$data = $sign | ($exp << 11) | ($mant & 0x07ff);
return $data >> 8, $data & 0xff;
}
in Deinem Code haben sich ein paar Fehler (Leerzeichen) bei den socat Zeilen eingeschlichen. Habe ich bereinigt. Code sieht jetzt so aus:
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 umgesetzt.
# Basis des Scripts von volkszaehler.org / Autor: Andreas Schulze & Bugfix: Eric Schanze
# DPT9 sub: makki / www.knx-user-forum.de
# Baudwechsel: panzaeron / www.knx-user-forum.de
# Erweiterung um RRD,KNX-Anbindung und gezielte Wertsuche auf Wiregate:
# JuMi2006 / www.knx-user-forum.de
# Version: 0.1.6
# Datum: 16.08.2012
use warnings;
use strict;
use RRDs;
### KONFIGURATION ###
my $device = "/dev/ttyS0"; #Port
my $rrdpath = "/var/www/rrd"; #Pfad fuer RRDs
my $counterid = "test"; #Grundname fuer RRDs
my $baudrate = 9600; #Baudrate fuer Zaehlerauslesung "auto" oder 300,600,etc
my %channels = ( #Obis-Zahl => Gruppenadresse
"15.7.0"=>"12/1/1", #akt. Leistung
"17.0.0"=>"12/1/11", #akt. Stromstärke ?
"1.8.0"=>"12/1/0" #Zaehlerstand gesamt
);
my @countermodes = (5,15,60,1440); #Aufloesungen fuer COUNTER RRDs in Minuten (1440 = Tagesverbrauch)
my $debug = 0;
### ENDE KONFIGURATION ###
#Je nach Geschwindigkeit andere Befehle an Zaehler senden
my %speedrate = (
"300"=>"'\x06\x30\x30\x30\x0d\x0a'",
"600"=>"'\x06\x30\x31\x30\x0d\x0a'",
"1200"=>"'\x06\x30\x32\x30\x0d\x0a'",
"2400"=>"'\x06\x30\x33\x30\x0d\x0a'",
"4800"=>"'\x06\x30\x34\x30\x0d\x0a'",
"9600"=>"'\x06\x30\x35\x30\x0d\x0a'"
);
my %speedrate_auto = (
"0"=>"'\x06\x30\x30\x30\x0d\x0a'",
"1"=>"'\x06\x30\x31\x30\x0d\x0a'",
"2"=>"'\x06\x30\x32\x30\x0d\x0a'",
"3"=>"'\x06\x30\x33\x30\x0d\x0a'",
"4"=>"'\x06\x30\x34\x30\x0d\x0a'",
"5"=>"'\x06\x30\x35\x30\x0d\x0a'"
);
my %baud = (
"0"=>"300",
"1"=>"600",
"2"=>"1200",
"3"=>"2400",
"4"=>"4800",
"5"=>"9600"
);
### DATEN EMPFANGEN ###
### Anfrage Senden ###
my $id = qx(echo '\x2f\x3f\x21\x0d\x0a' | socat -T1 - $device,raw,echo=0,b9600,parenb=1,parodd=0,cs7,cstopb=0);
my $speedcode = substr($id,4,1);
my $ack = $speedrate{$baudrate};
### Zählerkennung auswerten - Geschwindigkeit ermitteln ###
if ($baudrate eq "auto")
{
$ack = $speedrate_auto{$speedcode};
$baudrate = $baud{$speedcode};
}
else
{
$ack = $speedrate{$baudrate};
}
if ($debug==1){print ($id,"\n")};
if ($debug==1){print ($baudrate,"\n")};
select(undef, undef, undef, 1);
### Abfrage starten ###
my @buffer = qx(echo $ack | socat -t1 - $device,raw,echo=0,b9600,parenb=1,parodd=0,cs7,cstopb=0; socat -T 1 - $device,raw,echo=0,b$baudrate,parenb=1,parodd=0,cs7,cstopb=0 );
if ($debug==1){print (@buffer,"\n")};
### AUSWERTUNG ###
foreach (@buffer)
{
foreach my $obis(%channels)
{
my $obiskey = $obis."\(";
if ($_ =~ /\Q$obiskey\E/)
{
$_ =~ m/[^(]+\(([^*]+)\*([^)]+)/;
my $value = $1;
my $unit = $2;
my $ga = $channels{$obis};
if ($debug==1){print ($obis,"\n")};
if ($debug==1){print ($value,"\n")};
if ($debug==1){print ($unit,"\n")};
if ($debug==1){print ($ga,"\n")};
if ($unit =~ /\Qh\E/)
{
&rrd_counter ($obis,$value)
}
else
{
&rrd_gauge ($obis,$value)
}
&knx_write ($ga,$value);
}
}
}
### SUBS ###
sub rrd_counter
{
if ($debug==1){print ("COUNTER","\n")};
foreach (@countermodes)
{
my $obisname = $_[0];
my $value = $_[1];
$obisname =~ tr/./-/;
my $rrdname = $counterid."_".$obisname."_".$_."\.rrd";
if ($debug==1){print ($rrdname,"\n")};
my $rrdfile = $rrdpath."\/".$rrdname;
unless (-e $rrdfile)
{
RRDs::create ($rrdfile,"DS:value:COUNTER:".(($_*60)+600).":0:10 000000000","RRA:AVERAGE:0.5:1:365","RRA:AVERAGE:0. 5:7:300","-s ".($_*60));
}
my $countervalue = int($value*$_*60);
RRDs::update("$rrdfile", "N:$countervalue");
}
}
sub rrd_gauge
{
if ($debug==1){print ("GAUGE","\n")};
my $obisname = $_[0];
my $value = $_[1];
$obisname =~ tr/./-/;
my $rrdname = $counterid."_".$obisname."\.rrd";
if ($debug==1){print ($rrdname,"\n")};
my $rrdfile = $rrdpath."\/".$rrdname;
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");
}
RRDs::update("$rrdfile", "N:$value");
}
sub knx_write
{
### Wert in DPT9 umwandeln und in Konsole ausgeben
my @hexdec = encode_dpt9($_[1]);
my $hexval = sprintf("%x", $hexdec[0]) . " " . sprintf("%x", $hexdec[1]);
#print ($hexval,"\n");
system("groupwrite ip:localhost $_[0] $hexval");
}
sub encode_dpt9
{
# 2byte signed float
my $state = shift;
my $data;
my $sign = ($state <0 ? 0x8000 : 0);
my $exp = 0;
my $mant = 0;
$mant = int($state * 100.0);
while (abs($mant) > 2047)
{
$mant /= 2;
$exp++;
}
$data = $sign | ($exp << 11) | ($mant & 0x07ff);
return $data >> 8, $data & 0xff;
}
und der Output so:
root@wiregate723:/home/user# perl acm1.pl
substr outside of string at acm1.pl line 65.
root@wiregate723:/home/user#
unter /var/www/rrd ist auch nichts neues zu finden.
Und Du hast Recht. Es handelt sich um zwei Zähler. Einer für den Hausstrom und einer für die Wärmepumpe. Wobei der Hausstromzähler via internem Zähler Bus an den WP Zähler angebunden ist, da dieser die Daten via GPRS an meine Stadtwerke sendet.
EDIT:
das init.pl script läuft stabil. kann ich beliebig starten und das läuft sauber durch.
Und Du hast Recht. Es handelt sich um zwei Zähler. Einer für den Hausstrom und einer für die Wärmepumpe. Wobei der Hausstromzähler via internem Zähler Bus an den WP Zähler angebunden ist, da dieser die Daten via GPRS an meine Stadtwerke sendet.
#!/usr/bin/perl
use warnings;
use strict;
use Device::SerialPort;
### KONFIGURATION ###
my $device = "/dev/ttyS0"; #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(9600) || 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
ich würde mir (naiv) folgende Lösung vorstellen. Für die Aufbereitung der Charts muss ich ja eh wissen welcher Zähler für was ist (Hausstrom oder WP). Das müsste ich also im Script hinterlegen. Zähler eins (Hausstrom) hätte damit die idZ=0, Zähler (WP) zwei die idZ=1. Es werden gesamt also zwei Zähler hinterlegt. Wenn mehrere Zähler im Script eingebunden werden, müssten zwei Zählerstände ausgelesen und verarbeitet werden. Das Script liest also im Zähler Output 1-id:1.8.0 und fängt mit id=0 an. Das ganze kann ich vielleicht in Form einer Schleife erfolgen..
id=0
if id<idZ then auslesen von 1-id:1.8.0
id=id+1
return
das ganze wird so lange gemacht bis id=idZ und damit alle Zähler erschlagen sind. abhängig von vom wert von id wird ein extra chart erstellt.
EDIT:
da der Zähler die Daten selber in Intervallen sendet, muss ich das script ggf. mehrmals ausführen, um den richtigen Zeitpunkt für die Datenerfassung zu erwischen... sonst ist der Output tatsächlich leer..
Wenn der Zähler wirklich von alleine sendet dann macht das mit dem Script hier keinen Sinn mehr. Dafür hast Du aber schonmal ne gute Vorlage für was eigenes über socat (Webmin->Socketverbindungen).
Deinem Zähler scheint es egal zu sein ob er aktiv abgefragt wird oder nicht.
Das '\x2f\x3f\x21\x0d\x0a' durch $device zu ersetzen macht keinen Sinn, hat also nicht zur Datenabfrage beigetragen. Das ist nichts weiter als die in HEX verpackte Abfrageinitialisierung. Da die nicht mehr kommt scheint die Ihm egal zu sein denn er sendet trotzdem.
Ein Zwitter aus SML und IEC62056 mit 2 Zählerständen
Ganz ehrlich hier fehlt im Moment die Zeit da richtig drauf einzugehen. Im Kenr würde ich folgendes sagen:
- Socketverbindung herstellen
- Plugin machen und auf Socket subscriben
- im Plugin dann bei ankommenden Daten gucken ob der gesamte Datensatz kommt oder ob socat mal lustig ein par Zeichen ausspuckt
- Daten ggf. zusammensetzen.
- Daten verarbeiten
Sorry aber das ist hier wirklich speziell, ne schnelle Lösung hab ich dafür nicht, bin aber bereit zu helfen.
danke dennoch für die bisherige Unterstützung. Ich werde mich mal weiter an dem Script probieren. Meine Anforderungen sind eigentlich gering. Der Zählerstand vom WP Zähler reicht und dann auch nur als Zahl. Chart brauche ich gar nicht.So ich Fortschritte machen sollte, stelle ich diese hier ein.
Naja so schwer ist es ja nicht ... momentan hab ich bloss einfach keine Zeit da tiefer einzusteigen ... die Charts/RRDs fallen da eher mit ab bzw. kann man sich dann ja auch aus der GA erzeugen lassen.
Wie gesagt guck mal ob du mit socket_subscribe was machen kannst...da finden sich viele Beispielplugins hier!
Wir verarbeiten personenbezogene Daten über die Nutzer unserer Website mithilfe von Cookies und anderen Technologien, um unsere Dienste bereitzustellen. Weitere Informationen findest Du in unserer Datenschutzerklärung.
Indem Du unten auf "ICH stimme zu" klickst, stimmst Du unserer Datenschutzerklärung und unseren persönlichen Datenverarbeitungs- und Cookie-Praktiken zu, wie darin beschrieben. Du erkennst außerdem an, dass dieses Forum möglicherweise außerhalb Deines Landes gehostet wird und bist damit einverstanden, dass Deine Daten in dem Land, in dem dieses Forum gehostet wird, gesammelt, gespeichert und verarbeitet werden.
Kommentar