| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- ##############################################
- # $Id: 15_CUL_EM.pm 16293 2018-02-28 21:33:57Z rudolfkoenig $
- package main;
- use strict;
- use warnings;
- # Adjust TOTAL to you meter:
- # {$defs{emwz}{READINGS}{basis}{VAL}=<meter>/<corr2>-<total_cnt> }
- #####################################
- 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 <name> CUL_EM <code> ".
- "[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
- <a name="CUL_EM"></a>
- <h3>CUL_EM</h3>
- <ul>
- The CUL_EM module interprets EM type of messages received by the CUL, notably
- from EMEM, EMWZ or EMGZ devices.
- <br><br>
- <a name="CUL_EMdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> CUL_EM <code> [corr1 corr2
- CostPerUnit BasicFeePerMonth]</code> <br>
- <br>
- <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.<br><br>
- <b>corr1</b> is used to correct the current number, <b>corr2</b>
- for the total number.
- <ul>
- <li>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</li>
- <li>for EMEM devices the corr1 value is 0.01, and the corr2 value is
- 0.001 </li>
- </ul>
- <br>
- <b>CostPerUnit</b> and <b>BasicFeePerMonth</b> 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.:
- <ul><code>
- define emwz 1 75 900 0.15 12.50<br>
- </code></ul>
- and the Log looks like:
- <ul><code>
- CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
- CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
- </code></ul>
- 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.<br>
- </ul>
- <br>
- <a name="CUL_EMset"></a>
- <b>Set</b> <ul>N/A</ul><br>
- <a name="CUL_EMget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="CUL_EMattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#ignore">ignore</a></li><br>
- <li><a href="#do_not_notify">do_not_notify</a></li><br>
- <li><a href="#showtime">showtime</a></li><br>
- <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
- <li><a href="#IODev">IODev</a></li><br>
- <li><a href="#eventMap">eventMap</a></li><br>
- <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
- <li><a name="maxPeak">maxPeak</a> <number><br>
- 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.
- </li><br>
- <li><a name="CounterOffset">CounterOffset</a><br>
- Specifies the difference between true (gas) meter value and
- value reported by the EMGZ.<br>
- CounterOffset = true Value - Reading "total"<br>
- Example:
- <ul>
- <code>attr Gaszaehler CounterOffset 15427.434</code><br>
- </ul>
- </li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="CUL_EM"></a>
- <h3>CUL_EM</h3>
- <ul>
- 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.
- <br><br>
- <a name="CUL_EMdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> CUL_EM <code> [corr1 corr2
- CostPerUnit BasicFeePerMonth]</code> <br>
- <br>
- <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.<br><br>
- <b>corr1</b> ist der Kalibrierfaktor für den Momentanverbrauch,
- <b>corr2</b> für den Gesamtverbrauch.
- <ul>
- <li>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</li>
- <li>für EMEM devices ist corr1 mit 0.01 und corr2 mit 0.001
- anzugeben</li>
- </ul>
- <br>
- <b>CostPerUnit</b> und <b>BasicFeePerMonth</b> 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:
- <ul><code>
- define emwz 1 75 900 0.15 12.50<br>
- </code></ul>
- und in der Logdatei sollten diese Zeilen erscheinen:
- <ul><code>
- CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
- CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
- </code></ul>
- 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.<br>
- </ul>
- <br>
- <a name="CUL_EMset"></a>
- <b>Set</b> <ul>N/A</ul><br>
- <a name="CUL_EMget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="CUL_EMattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#ignore">ignore</a></li><br>
- <li><a href="#do_not_notify">do_not_notify</a></li><br>
- <li><a href="#showtime">showtime</a></li><br>
- <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
- <li><a href="#IODev">IODev</a></li><br>
- <li><a href="#eventMap">eventMap</a></li><br>
- <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
- <li><a name="maxPeak">maxPeak</a> <number><br>
- 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.
- </li><br>
- <li><a name="CounterOffset">CounterOffset</a><br>
- Gibt den Unterschied zwischen dem tatsächlichen Zählerstand und
- dem vom EMGZ gemeldeten Wert an.<br>
- CounterOffset = tatsächlicher Zählerstand - Reading "total"<br>
- Beispiel:
- <ul>
- <code>attr Gaszaehler CounterOffset 15427.434</code><br>
- </ul>
- </li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|