Der EM 1010 PC ist ein Funk-Energiemonitor mit USB-Anschluss, der seine Daten von einem oder mehreren Funksendern (z. B. EM 1000-WZ) erhält. Die beigelegte Software ist nur unter Windows lauffähig. Ich verwende allerdings nur Linux-Systeme. Außerdem wäre es interessant, den Verbrauch mit der von mir bevorzugten Monitoringanwendung Munin zu erfassen.
Wie es grundsätzlich funktioniert, steht hier. Im Softwarepakt Fhem ist ein Script namens „em1010.pl“ enthalten, dass die Daten aus dem EM 1010 via USB ausliest. Ich habe das Script genommen, abgespeckt und so umgeschrieben, dass es als Munin-Plugin fungiert. Auf einem aktuellen Debian braucht man auch die Kernelmodule nicht mehr anfassen, das EM1010 wird beim Anstecken an den PC als serielles USB-Device erkannt und unter /dev/ttyUSB0 eingehängt.
Den Quell-Text des Munin-Plugins, speichert man unter /etc/munin/plugins/em1010 ab. Danach muss man das Script ausführbar machen und in der Datei /etc/munin/plugin-conf.d/munin-node einen Eintrag
[em1010] user root
hinzufügen und den Munin-Node neu starten.
Im Plugin sind eventuell die Variablen devicenumber und ser an die eigenen Gegebenheiten anzupassen.
#!/usr/bin/perl use strict; use warnings; use Device::SerialPort; sub b($$); sub w($$); sub docrc($$); sub checkcrc($$); sub getData($); sub makemsg($); my $ser = "/dev/ttyUSB0"; my $devicenumber = "1"; my $cmd = ""; if ($ARGV[0]) { $cmd = $ARGV[0]; } ##################### # Open serial port my $serport = new Device::SerialPort ($ser); die "Can't open $ser: $!\n" if(!$serport); $serport->reset_error(); $serport->baudrate(38400); $serport->databits(8); $serport->parity('none'); $serport->stopbits(1); $serport->handshake('none'); ######################### sub b($$) { my ($t,$p) = @_; return ord(substr($t,$p,1)); } ######################### sub w($$) { my ($t,$p) = @_; return b($t,$p+1)*256 + b($t,$p); } ######################### sub dw($$) { my ($t,$p) = @_; return w($t,$p+2)*65536 + w($t,$p); } ######################### sub docrc($$) { my ($in, $val) = @_; my ($crc, $bits) = (0, 8); my $k = (($in >> 8) ^ $val) << 8; while($bits--) { if(($crc ^ $k) & 0x8000) { $crc = ($crc << 1) ^ 0x8005; } else { $crc <<= 1; } $k <<= 1; } return (($in << 8) ^ $crc) & 0xffff; } ######################### sub checkcrc($$) { my ($otxt, $len) = @_; my $crc = 0x8c27; for(my $l = 2; $l < $len+4; $l++) { my $b = ord(substr($otxt,$l,1)); $crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10); $crc = docrc($crc, $b); } return ($crc == w($otxt, $len+4)); } ######################### sub esc($) { my ($b) = @_; my $out = ""; $out .= chr(0x10) if($b==0x02 || $b==0x03 || $b==0x10); $out .= chr($b); } ######################### sub makemsg($) { my ($data) = @_; my $len = length($data); $data = chr($len&0xff) . chr(int($len/256)) . $data; my $out = pack('H*', "0200"); my $crc = 0x8c27; for(my $l = 0; $l < $len+2; $l++) { my $b = ord(substr($data,$l,1)); $crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10); $crc = docrc($crc, $b); $out .= esc($b); } $out .= esc($crc&0xff); $out .= esc($crc/256); $out .= chr(0x03); return $out; } ######################### sub getData($) { my ($d) = @_; $d = makemsg(pack('H*', $d)); for(my $rep = 0; $rep < 3; $rep++) { $serport->write($d); my $retval = ""; my $esc = 0; my $started = 0; my $complete = 0; for(;;) { my ($rout, $rin) = ('', ''); vec($rin, $serport->FILENO, 1) = 1; my $nfound = select($rout=$rin, undef, undef, 1.0); die("Select error $nfound / $!\n") if($nfound < 0); last if($nfound == 0); my $buf = $serport->input(); die "EOF on $ser\n" if(!defined($buf) || length($buf) == 0); for(my $i = 0; $i < length($buf); $i++) { my $b = ord(substr($buf,$i,1)); if(!$started && $b != 0x02) { next; } $started = 1; if($esc) { $retval .= chr($b); $esc = 0; next; } if($b == 0x10) { $esc = 1; next; } $retval .= chr($b); if($b == 0x03) { $complete = 1; last; } } if($complete) { my $l = length($retval); if($l < 8) { printf("Msg too short\n"); last; } if(b($retval,1) != 0) { printf("Bad second byte\n"); last; } if(w($retval,2) != $l-7) { printf("Length mismatch\n"); last; } if(!checkcrc($retval,$l-7)) { printf("Bad CRC\n"); last; } return substr($retval, 4, $l-7); } } } printf "Timeout reading the answer\n"; exit(1); } ######################### sub hexdump($) { my ($d) = @_; for(my $i = 0; $i < length($d); $i += 16) { my $h = unpack("H*", substr($d, $i, 16)); $h =~ s/(....)/$1 /g; printf "RAW %-40s\n", $h; } } ######################### sub outputData() { my $d = getData(sprintf("7a%02x",$devicenumber-1)); if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) { printf("No device no. $devicenumber present\n"); return; } my $pulses=w($d,13); my $pulses_max=w($d,15); my $ec=w($d,49) / 10; my $cur_energy=0; my $cur_power=0; my $cur_power_max=0; my $iec=0; if ($ec eq 0) { # Sensor 5.. $iec = 1000; $cur_power = $pulses / 100; $cur_power_max = $pulses_max / 100; } else { # Sensor 1..4 $iec = $ec; $cur_energy = $pulses / $ec; # ec = U/kWh $cur_power = $cur_energy / 5 * 60; # 5minute interval scaled to 1h } printf("power.value %.3f\n", $cur_power); } ############# # config or data output if($cmd eq "config") { print <<EOT; graph_title Power consumption graph_vlabel kW power.label power graph_category homeautomation graph_info Monitor the electrical energy consumption of the whole building via a EM 1010 PC EOT exit(0); } else { outputData(); }