############################################## # $Id$ package main; use strict; use warnings; # Adjust TOTAL to you meter: # {$defs{emwz}{READINGS}{basis}{VAL}=/- } ##################################### sub CUL_EM_Initialize($) { my ($hash) = @_; # Message is like # K41350270 $hash->{Match} = "^E0.................\$"; $hash->{DefFn} = "CUL_EM_Define"; $hash->{UndefFn} = "CUL_EM_Undef"; $hash->{ParseFn} = "CUL_EM_Parse"; $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 " . "model:EMEM,EMWZ,EMGZ ignore:0,1 ". "maxPeak CounterOffset ". $readingFnAttributes; $hash->{AutoCreate}= { "CUL_EM.*" => { GPLOT => "power8:Power,", FILTER => "%NAME:CNT.*" } }; } ##################################### sub CUL_EM_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); return "wrong syntax: define CUL_EM ". "[corr1 corr2 CostPerUnit BasicFeePerMonth]" if(int(@a) < 3 || int(@a) > 7); return "Define $a[0]: wrong CODE format: valid is 1-12" if($a[2] !~ m/^\d+$/ || $a[2] < 1 || $a[2] > 12); $hash->{CODE} = $a[2]; if($a[2] >= 1 && $a[2] <= 4) { # EMWZ: nRotation in 5 minutes my $c = (int(@a) > 3 ? $a[3] : 150); $hash->{corr1} = (12/$c); # peak/current $c = (int(@a) > 4 ? $a[4] : 1800); $hash->{corr2} = (12/$c); # total } elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM # corr1 is the correction factor for power $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01); # corr2 is the correction factor for energy $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.001); } elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ: 0.01 $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01); $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.01); } else { $hash->{corr1} = 1; $hash->{corr2} = 1; } $hash->{CostPerUnit} = (int(@a) > 5 ? $a[5] : 0); $hash->{BasicFeePerMonth} = (int(@a) > 6 ? $a[6] : 0); $modules{CUL_EM}{defptr}{$a[2]} = $hash; AssignIoPort($hash); return undef; } ##################################### sub CUL_EM_Undef($$) { my ($hash, $name) = @_; delete($modules{CUL_EM}{defptr}{$hash->{CODE}}); return undef; } ##################################### sub CUL_EM_Parse($$) { my ($hash,$msg) = @_; # 0123456789012345678 # E01012471B80100B80B -> Type 01, Code 01, Cnt 10 my @a = split("", $msg); my $tpe = ($a[1].$a[2])+0; my $cde = hex($a[3].$a[4]); # seqno = number of received datagram in sequence, runs from 2 to 255 # total_cnt= total (cumulated) value in ticks as read from the device # basis_cnt= correction to total (cumulated) value in ticks to account for # counter wraparounds # total = total (cumulated) value in device units # current_cnt = current value (average over latest 5 minutes) in device units # peak = maximum value in device units my $seqno = hex($a[5].$a[6]); my $total_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]); my $current_cnt = hex($a[13].$a[14].$a[11].$a[12]); my $peak_cnt = hex($a[17].$a[18].$a[15].$a[16]); # these are the raw readings from the device my $val = sprintf("CNT: %d CUM: %d 5MIN: %d TOP: %d", $seqno, $total_cnt, $current_cnt, $peak_cnt); if($modules{CUL_EM}{defptr}{$cde}) { my $def = $modules{CUL_EM}{defptr}{$cde}; $hash = $def; my $n = $hash->{NAME}; return "" if(IsIgnored($n)); my $tn = TimeNow(); # current time my $c= 0; # count changes my %readings; Log3 $n, 5, "CUL_EM $n: $val"; $readings{RAW} = $val; # # calculate readings # # initialize total_cnt_last my $total_cnt_last = 0; if(defined($hash->{READINGS}{total_cnt})) { $total_cnt_last= $hash->{READINGS}{total_cnt}{VAL}; } # initialize basis_cnt_last my $basis_cnt = 0; if(defined($hash->{READINGS}{basis})) { $basis_cnt = $hash->{READINGS}{basis}{VAL}; } # # translate into device units # my $corr1 = $hash->{corr1}; # EMEM power correction factor my $corr2 = $hash->{corr2}; # EMEM energy correction factor my $peak; if($tpe ne 2) { $peak = $current_cnt && $peak_cnt ? 3000/$peak_cnt*$corr1 : 0; # when EM detection toggles/glitches somewhere the internal # EM-Counter increments by one and the device registers a # very hi peak value # Here we fix this by checking against a maximum peak # level, removing the wrong counter increment and # setting peak to the current value. my $maxpeak = $attr{$n}{"maxPeak"}; if(defined $maxpeak and $peak > $maxpeak){ Log3 $n, 2, "CUL_EM $n: max peak detected: $peak kW > $maxpeak kW"; $current_cnt--; # as total_cnt is "owned" by EM we decrement our basis_cnt $basis_cnt--; $readings{basis} = $basis_cnt; $peak = $current_cnt*$corr1; $peak_cnt = $peak ? int(3000*$corr1/$peak) : 0; } } else { $peak = $peak_cnt*$corr1; } # correct counter wraparound if($total_cnt < $total_cnt_last) { # check: real wraparound or reset only $basis_cnt += ($total_cnt_last > 65000 ? 65536 : $total_cnt_last); $readings{basis} = $basis_cnt; } my $counter_offset = AttrVal($n,"CounterOffset",0); my $total = (($basis_cnt+$total_cnt)*$corr2)+$counter_offset; my $current = $current_cnt*$corr1; $val = sprintf("CNT: %d CUM: %0.3f 5MIN: %0.3f TOP: %0.3f", $seqno, $total, $current, $peak); readingsBeginUpdate($hash); readingsBulkUpdate($hash, "state", $val); $readings{total_cnt} = $total_cnt; $readings{current_cnt} = $current_cnt; $readings{peak_cnt} = $peak_cnt; $readings{seqno} = $seqno; $readings{total} = $total; $readings{current} = $current; $readings{peak} = $peak; ################################### # Start CUMULATE day and month Log3 $n, 4, "CUL_EM $n: $val"; my $tsecs_prev; #----- get previous tsecs if(defined($hash->{READINGS}{tsecs})) { $tsecs_prev= $hash->{READINGS}{tsecs}{VAL}; } else { $tsecs_prev= 0; # 1970-01-01 } #----- save actual tsecs my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC $readings{tsecs} = $tsecs; #----- get cost parameter my $cost = $hash->{CostPerUnit}; my $basicfee = $hash->{BasicFeePerMonth}; #----- check whether day or month was changed if(!defined($hash->{READINGS}{cum_day})) { #----- init cum_day if it is not set $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f", 0,$total,0); $readings{cum_day} = $val; } else { if( (localtime($tsecs_prev))[3] != (localtime($tsecs))[3] ) { #----- day has changed (#3) my @cmv = split(" ", $hash->{READINGS}{cum_day}{VAL}); $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f", $total-$cmv[3], $total, ($total-$cmv[3])*$cost); $readings{cum_day} = $val; Log3 $n, 3, "CUL_EM $n: $val"; if( (localtime($tsecs_prev))[4] != (localtime($tsecs))[4] ) { #----- month has changed (#4) if(!defined($hash->{READINGS}{cum_month})) { # init cum_month if not set $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f", 0, $total, 0); $readings{cum_month} = $val; } else { @cmv = split(" ", $hash->{READINGS}{cum_month}{VAL}); $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f", $total-$cmv[3], $total,($total-$cmv[3])*$cost+$basicfee); $readings{cum_month} = $val; Log3 $n, 3, "CUL_EM $n: $val"; } } } } # End CUMULATE day and month ################################### foreach my $k (keys %readings) { readingsBulkUpdate($hash, $k, $readings{$k}); } readingsEndUpdate($hash, 1); return $hash->{NAME}; } else { Log3 $hash, 1, "CUL_EM detected, Code $cde $val"; return "UNDEFINED CUL_EM_$cde CUL_EM $cde"; } } 1; =pod =item summary devices communicating via the ELV EM protocol (EM1000WZ, etc) =item summary_DE Anbindung von ELV Geräten mit dem EM Protokoll (EM1000WZ, usw.) =begin html

CUL_EM

    The CUL_EM module interprets EM type of messages received by the CUL, notably from EMEM, EMWZ or EMGZ devices.

    Define
      define <name> CUL_EM <code> [corr1 corr2 CostPerUnit BasicFeePerMonth]

      <code> is the code which must be set on the EM device. Valid values are 1 through 12. 1-4 denotes EMWZ, 5-8 EMEM and 9-12 EMGZ devices.

      corr1 is used to correct the current number, corr2 for the total number.
      • for EMWZ devices you should specify the rotation speed (R/kW) of your watt-meter (e.g. 150) for corr1 and 12 times this value for corr2
      • for EMEM devices the corr1 value is 0.01, and the corr2 value is 0.001

      CostPerUnit and BasicFeePerMonth are used to compute your daily and monthly fees. Your COST will appear in the log, generated once daily (without the basic fee) or month (with the bassic fee included). Your definition should look like e.g.:
        define emwz 1 75 900 0.15 12.50
      and the Log looks like:
        CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02
        CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34
      Tip: You can configure your EMWZ device to show in the CUM column of the STATE reading the current reading of your meter. For this purpose: multiply the current reading (from the real device) with the corr1 value (RperKW), and subtract the RAW CUM value from it. Now set the basis reading of your EMWZ device (named emwz) to this value.

    Set
      N/A

    Get
      N/A

    Attributes
    • ignore

    • do_not_notify

    • showtime

    • model (EMEM,EMWZ,EMGZ)

    • IODev

    • eventMap

    • readingFnAttributes

    • maxPeak <number>
      Specifies the maximum possible peak value for the EM meter ("TOP:" value in logfile). Peak values greater than this value are considered as EM read errors and are ignored. For example if it's not possible to consume more than 40kW of power set maxPeak to 40 to make the readings of the power meter more robust.

    • CounterOffset
      Specifies the difference between true (gas) meter value and value reported by the EMGZ.
      CounterOffset = true Value - Reading "total"
      Example:
        attr Gaszaehler CounterOffset 15427.434

=end html =begin html_DE

CUL_EM

    Das Modul CUL_EM wertet von einem CUL empfange Botschaften des Typs EM aus, dies sind aktuell Botschaften von EMEM, EMWZ bzw. EMGZ Geräten.

    Define
      define <name> CUL_EM <code> [corr1 corr2 CostPerUnit BasicFeePerMonth]

      <code> ist der Code, der am EM Gerät eingestellt wird. Gütige Werte sind 1 bis 12. 1-4 gilt für EMWZ, 5-8 für EMEM und 9-12 für EMGZ Geräte.

      corr1 ist der Kalibrierfaktor für den Momentanverbrauch, corr2 für den Gesamtverbrauch.
      • für EMWZ Geräte wird die Umdrehungsgeschwindigkeit (U/kW) des verwendeten Stromzählers (z.B. 150) für corr1 und 12 mal diesen Wert für corr2 verwendet
      • für EMEM devices ist corr1 mit 0.01 und corr2 mit 0.001 anzugeben

      CostPerUnit und BasicFeePerMonth werden dazu verwendet, die tägliche bzw. monatliche Kosten zu berechnen. Die Kosten werden in der Logdatei einmal täglich (ohne Fixkosten) bzw. monatlich (mit Fixkosten) generiert und angezeigt. Die Definition sollte in etwa so aussehen:
        define emwz 1 75 900 0.15 12.50
      und in der Logdatei sollten diese Zeilen erscheinen:
        CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02
        CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34
      Tipp: Das EMWZ Gerät kann so konfiguriert werden, dass es in der CUM Spalte des STATE Wertes den aktuellen Wert des Stromzählers anzeigt. Hierfür muss der aktuell am Stromzähler abgelesene Wert mit corr1 (U/kW) multipliziert werden und der CUM Rohwert aus der aktuellen fhem Messung ('reading') davon abgezogen werden. Dann muss dieser Wert als Basiswert des EMWZ Gerätes (im Beispiel emwz) gesetzt werden.

    Set
      N/A

    Get
      N/A

    Attributes
    • ignore

    • do_not_notify

    • showtime

    • model (EMEM,EMWZ,EMGZ)

    • IODev

    • eventMap

    • readingFnAttributes

    • maxPeak <number>
      Gibt den maximal möglichen Spitzenwert für das EM-Meter an ("TOP:"-Wert in Logdatei). Spitzenwerte größer als dieser Wert gelten als EM-Lesefehler und werden ignoriert. Wenn es z.B. nicht möglich ist mehr zu 40kW Leistung zu beziehen setzt man maxPeak auf 40 um das Auslesen des Stromzählers robuster zu machen.

    • CounterOffset
      Gibt den Unterschied zwischen dem tatsächlichen Zählerstand und dem vom EMGZ gemeldeten Wert an.
      CounterOffset = tatsächlicher Zählerstand - Reading "total"
      Beispiel:
        attr Gaszaehler CounterOffset 15427.434

=end html_DE =cut