fhem-mirror/FHEM/15_CUL_EM.pm
2018-02-28 21:33:57 +00:00

468 lines
15 KiB
Perl

##############################################
# $Id$
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&auml;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 &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
CostPerUnit BasicFeePerMonth]</code> <br>
<br>
&lt;code&gt; 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> &lt;number&gt;<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&auml;ten.
<br><br>
<a name="CUL_EMdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
CostPerUnit BasicFeePerMonth]</code> <br>
<br>
&lt;code&gt; ist der Code, der am EM Ger&auml;t eingestellt wird.
G&uuml;tige Werte sind 1 bis 12. 1-4 gilt f&uuml;r EMWZ, 5-8 f&uuml;r EMEM
und 9-12 f&uuml;r EMGZ Ger&auml;te.<br><br>
<b>corr1</b> ist der Kalibrierfaktor f&uuml;r den Momentanverbrauch,
<b>corr2</b> f&uuml;r den Gesamtverbrauch.
<ul>
<li>f&uuml;r EMWZ Ger&auml;te wird die Umdrehungsgeschwindigkeit (U/kW)
des verwendeten Stromz&auml;hlers (z.B. 150) f&uuml;r corr1 und 12 mal
diesen Wert f&uuml;r corr2 verwendet</li>
<li>f&uuml;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&auml;gliche bzw. monatliche Kosten zu berechnen. Die Kosten werden in der
Logdatei einmal t&auml;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&auml;t kann so konfiguriert werden, dass es in der CUM
Spalte des STATE Wertes den aktuellen Wert des Stromz&auml;hlers anzeigt.
Hierf&uuml;r muss der aktuell am Stromz&auml;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&auml;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> &lt;number&gt;<br>
Gibt den maximal m&ouml;glichen Spitzenwert f&uuml;r das EM-Meter an
("TOP:"-Wert in Logdatei). Spitzenwerte gr&ouml;&szlig;er als dieser
Wert gelten als EM-Lesefehler und werden ignoriert.
Wenn es z.B. nicht m&ouml;glich ist mehr zu 40kW Leistung
zu beziehen setzt man maxPeak auf 40 um das Auslesen des
Stromz&auml;hlers robuster zu machen.
</li><br>
<li><a name="CounterOffset">CounterOffset</a><br>
Gibt den Unterschied zwischen dem tats&auml;chlichen Z&auml;hlerstand und
dem vom EMGZ gemeldeten Wert an.<br>
CounterOffset = tats&auml;chlicher Z&auml;hlerstand - Reading "total"<br>
Beispiel:
<ul>
<code>attr Gaszaehler CounterOffset 15427.434</code><br>
</ul>
</li>
</ul>
<br>
</ul>
=end html_DE
=cut