15_CUL_EM.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. ##############################################
  2. # $Id: 15_CUL_EM.pm 16293 2018-02-28 21:33:57Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. # Adjust TOTAL to you meter:
  7. # {$defs{emwz}{READINGS}{basis}{VAL}=<meter>/<corr2>-<total_cnt> }
  8. #####################################
  9. sub
  10. CUL_EM_Initialize($)
  11. {
  12. my ($hash) = @_;
  13. # Message is like
  14. # K41350270
  15. $hash->{Match} = "^E0.................\$";
  16. $hash->{DefFn} = "CUL_EM_Define";
  17. $hash->{UndefFn} = "CUL_EM_Undef";
  18. $hash->{ParseFn} = "CUL_EM_Parse";
  19. $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 " .
  20. "model:EMEM,EMWZ,EMGZ ignore:0,1 ".
  21. "maxPeak CounterOffset ".
  22. $readingFnAttributes;
  23. $hash->{AutoCreate}=
  24. { "CUL_EM.*" => { GPLOT => "power8:Power,", FILTER => "%NAME:CNT.*" } };
  25. }
  26. #####################################
  27. sub
  28. CUL_EM_Define($$)
  29. {
  30. my ($hash, $def) = @_;
  31. my @a = split("[ \t][ \t]*", $def);
  32. return "wrong syntax: define <name> CUL_EM <code> ".
  33. "[corr1 corr2 CostPerUnit BasicFeePerMonth]"
  34. if(int(@a) < 3 || int(@a) > 7);
  35. return "Define $a[0]: wrong CODE format: valid is 1-12"
  36. if($a[2] !~ m/^\d+$/ || $a[2] < 1 || $a[2] > 12);
  37. $hash->{CODE} = $a[2];
  38. if($a[2] >= 1 && $a[2] <= 4) { # EMWZ: nRotation in 5 minutes
  39. my $c = (int(@a) > 3 ? $a[3] : 150);
  40. $hash->{corr1} = (12/$c); # peak/current
  41. $c = (int(@a) > 4 ? $a[4] : 1800);
  42. $hash->{corr2} = (12/$c); # total
  43. } elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM
  44. # corr1 is the correction factor for power
  45. $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
  46. # corr2 is the correction factor for energy
  47. $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.001);
  48. } elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ: 0.01
  49. $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
  50. $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.01);
  51. } else {
  52. $hash->{corr1} = 1;
  53. $hash->{corr2} = 1;
  54. }
  55. $hash->{CostPerUnit} = (int(@a) > 5 ? $a[5] : 0);
  56. $hash->{BasicFeePerMonth} = (int(@a) > 6 ? $a[6] : 0);
  57. $modules{CUL_EM}{defptr}{$a[2]} = $hash;
  58. AssignIoPort($hash);
  59. return undef;
  60. }
  61. #####################################
  62. sub
  63. CUL_EM_Undef($$)
  64. {
  65. my ($hash, $name) = @_;
  66. delete($modules{CUL_EM}{defptr}{$hash->{CODE}});
  67. return undef;
  68. }
  69. #####################################
  70. sub
  71. CUL_EM_Parse($$)
  72. {
  73. my ($hash,$msg) = @_;
  74. # 0123456789012345678
  75. # E01012471B80100B80B -> Type 01, Code 01, Cnt 10
  76. my @a = split("", $msg);
  77. my $tpe = ($a[1].$a[2])+0;
  78. my $cde = hex($a[3].$a[4]);
  79. # seqno = number of received datagram in sequence, runs from 2 to 255
  80. # total_cnt= total (cumulated) value in ticks as read from the device
  81. # basis_cnt= correction to total (cumulated) value in ticks to account for
  82. # counter wraparounds
  83. # total = total (cumulated) value in device units
  84. # current_cnt = current value (average over latest 5 minutes) in device units
  85. # peak = maximum value in device units
  86. my $seqno = hex($a[5].$a[6]);
  87. my $total_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]);
  88. my $current_cnt = hex($a[13].$a[14].$a[11].$a[12]);
  89. my $peak_cnt = hex($a[17].$a[18].$a[15].$a[16]);
  90. # these are the raw readings from the device
  91. my $val = sprintf("CNT: %d CUM: %d 5MIN: %d TOP: %d",
  92. $seqno, $total_cnt, $current_cnt, $peak_cnt);
  93. if($modules{CUL_EM}{defptr}{$cde}) {
  94. my $def = $modules{CUL_EM}{defptr}{$cde};
  95. $hash = $def;
  96. my $n = $hash->{NAME};
  97. return "" if(IsIgnored($n));
  98. my $tn = TimeNow(); # current time
  99. my $c= 0; # count changes
  100. my %readings;
  101. Log3 $n, 5, "CUL_EM $n: $val";
  102. $readings{RAW} = $val;
  103. #
  104. # calculate readings
  105. #
  106. # initialize total_cnt_last
  107. my $total_cnt_last = 0;
  108. if(defined($hash->{READINGS}{total_cnt})) {
  109. $total_cnt_last= $hash->{READINGS}{total_cnt}{VAL};
  110. }
  111. # initialize basis_cnt_last
  112. my $basis_cnt = 0;
  113. if(defined($hash->{READINGS}{basis})) {
  114. $basis_cnt = $hash->{READINGS}{basis}{VAL};
  115. }
  116. #
  117. # translate into device units
  118. #
  119. my $corr1 = $hash->{corr1}; # EMEM power correction factor
  120. my $corr2 = $hash->{corr2}; # EMEM energy correction factor
  121. my $peak;
  122. if($tpe ne 2) {
  123. $peak = $current_cnt && $peak_cnt ? 3000/$peak_cnt*$corr1 : 0;
  124. # when EM detection toggles/glitches somewhere the internal
  125. # EM-Counter increments by one and the device registers a
  126. # very hi peak value
  127. # Here we fix this by checking against a maximum peak
  128. # level, removing the wrong counter increment and
  129. # setting peak to the current value.
  130. my $maxpeak = $attr{$n}{"maxPeak"};
  131. if(defined $maxpeak and $peak > $maxpeak){
  132. Log3 $n, 2,
  133. "CUL_EM $n: max peak detected: $peak kW > $maxpeak kW";
  134. $current_cnt--;
  135. # as total_cnt is "owned" by EM we decrement our basis_cnt
  136. $basis_cnt--;
  137. $readings{basis} = $basis_cnt;
  138. $peak = $current_cnt*$corr1;
  139. $peak_cnt = $peak ? int(3000*$corr1/$peak) : 0;
  140. }
  141. } else {
  142. $peak = $peak_cnt*$corr1;
  143. }
  144. # correct counter wraparound
  145. if($total_cnt < $total_cnt_last) {
  146. # check: real wraparound or reset only
  147. $basis_cnt += ($total_cnt_last > 65000 ? 65536 : $total_cnt_last);
  148. $readings{basis} = $basis_cnt;
  149. }
  150. my $counter_offset = AttrVal($n,"CounterOffset",0);
  151. my $total = (($basis_cnt+$total_cnt)*$corr2)+$counter_offset;
  152. my $current = $current_cnt*$corr1;
  153. $val = sprintf("CNT: %d CUM: %0.3f 5MIN: %0.3f TOP: %0.3f",
  154. $seqno, $total, $current, $peak);
  155. readingsBeginUpdate($hash);
  156. readingsBulkUpdate($hash, "state", $val);
  157. $readings{total_cnt} = $total_cnt;
  158. $readings{current_cnt} = $current_cnt;
  159. $readings{peak_cnt} = $peak_cnt;
  160. $readings{seqno} = $seqno;
  161. $readings{total} = $total;
  162. $readings{current} = $current;
  163. $readings{peak} = $peak;
  164. ###################################
  165. # Start CUMULATE day and month
  166. Log3 $n, 4, "CUL_EM $n: $val";
  167. my $tsecs_prev;
  168. #----- get previous tsecs
  169. if(defined($hash->{READINGS}{tsecs})) {
  170. $tsecs_prev= $hash->{READINGS}{tsecs}{VAL};
  171. } else {
  172. $tsecs_prev= 0; # 1970-01-01
  173. }
  174. #----- save actual tsecs
  175. my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC
  176. $readings{tsecs} = $tsecs;
  177. #----- get cost parameter
  178. my $cost = $hash->{CostPerUnit};
  179. my $basicfee = $hash->{BasicFeePerMonth};
  180. #----- check whether day or month was changed
  181. if(!defined($hash->{READINGS}{cum_day})) {
  182. #----- init cum_day if it is not set
  183. $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f", 0,$total,0);
  184. $readings{cum_day} = $val;
  185. } else {
  186. if( (localtime($tsecs_prev))[3] != (localtime($tsecs))[3] ) {
  187. #----- day has changed (#3)
  188. my @cmv = split(" ", $hash->{READINGS}{cum_day}{VAL});
  189. $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f",
  190. $total-$cmv[3], $total, ($total-$cmv[3])*$cost);
  191. $readings{cum_day} = $val;
  192. Log3 $n, 3, "CUL_EM $n: $val";
  193. if( (localtime($tsecs_prev))[4] != (localtime($tsecs))[4] ) {
  194. #----- month has changed (#4)
  195. if(!defined($hash->{READINGS}{cum_month})) {
  196. # init cum_month if not set
  197. $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
  198. 0, $total, 0);
  199. $readings{cum_month} = $val;
  200. } else {
  201. @cmv = split(" ", $hash->{READINGS}{cum_month}{VAL});
  202. $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
  203. $total-$cmv[3], $total,($total-$cmv[3])*$cost+$basicfee);
  204. $readings{cum_month} = $val;
  205. Log3 $n, 3, "CUL_EM $n: $val";
  206. }
  207. }
  208. }
  209. }
  210. # End CUMULATE day and month
  211. ###################################
  212. foreach my $k (keys %readings) {
  213. readingsBulkUpdate($hash, $k, $readings{$k});
  214. }
  215. readingsEndUpdate($hash, 1);
  216. return $hash->{NAME};
  217. } else {
  218. Log3 $hash, 1, "CUL_EM detected, Code $cde $val";
  219. return "UNDEFINED CUL_EM_$cde CUL_EM $cde";
  220. }
  221. }
  222. 1;
  223. =pod
  224. =item summary devices communicating via the ELV EM protocol (EM1000WZ, etc)
  225. =item summary_DE Anbindung von ELV Ger&auml;ten mit dem EM Protokoll (EM1000WZ, usw.)
  226. =begin html
  227. <a name="CUL_EM"></a>
  228. <h3>CUL_EM</h3>
  229. <ul>
  230. The CUL_EM module interprets EM type of messages received by the CUL, notably
  231. from EMEM, EMWZ or EMGZ devices.
  232. <br><br>
  233. <a name="CUL_EMdefine"></a>
  234. <b>Define</b>
  235. <ul>
  236. <code>define &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
  237. CostPerUnit BasicFeePerMonth]</code> <br>
  238. <br>
  239. &lt;code&gt; is the code which must be set on the EM device. Valid values
  240. are 1 through 12. 1-4 denotes EMWZ, 5-8 EMEM and 9-12 EMGZ devices.<br><br>
  241. <b>corr1</b> is used to correct the current number, <b>corr2</b>
  242. for the total number.
  243. <ul>
  244. <li>for EMWZ devices you should specify the rotation speed (R/kW)
  245. of your watt-meter (e.g. 150) for corr1 and 12 times this value for
  246. corr2</li>
  247. <li>for EMEM devices the corr1 value is 0.01, and the corr2 value is
  248. 0.001 </li>
  249. </ul>
  250. <br>
  251. <b>CostPerUnit</b> and <b>BasicFeePerMonth</b> are used to compute your
  252. daily and monthly fees. Your COST will appear in the log, generated once
  253. daily (without the basic fee) or month (with the bassic fee included). Your
  254. definition should look like e.g.:
  255. <ul><code>
  256. define emwz 1 75 900 0.15 12.50<br>
  257. </code></ul>
  258. and the Log looks like:
  259. <ul><code>
  260. CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
  261. CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
  262. </code></ul>
  263. Tip: You can configure your EMWZ device to show in the CUM column of the
  264. STATE reading the current reading of your meter. For this purpose: multiply
  265. the current reading (from the real device) with the corr1 value (RperKW),
  266. and subtract the RAW CUM value from it. Now set the basis reading of your
  267. EMWZ device (named emwz) to this value.<br>
  268. </ul>
  269. <br>
  270. <a name="CUL_EMset"></a>
  271. <b>Set</b> <ul>N/A</ul><br>
  272. <a name="CUL_EMget"></a>
  273. <b>Get</b> <ul>N/A</ul><br>
  274. <a name="CUL_EMattr"></a>
  275. <b>Attributes</b>
  276. <ul>
  277. <li><a href="#ignore">ignore</a></li><br>
  278. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  279. <li><a href="#showtime">showtime</a></li><br>
  280. <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
  281. <li><a href="#IODev">IODev</a></li><br>
  282. <li><a href="#eventMap">eventMap</a></li><br>
  283. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  284. <li><a name="maxPeak">maxPeak</a> &lt;number&gt;<br>
  285. Specifies the maximum possible peak value for the EM meter
  286. ("TOP:" value in logfile). Peak values greater than this value
  287. are considered as EM read errors and are ignored.
  288. For example if it's not possible to consume more than 40kW of
  289. power set maxPeak to 40 to make the readings of the power meter
  290. more robust.
  291. </li><br>
  292. <li><a name="CounterOffset">CounterOffset</a><br>
  293. Specifies the difference between true (gas) meter value and
  294. value reported by the EMGZ.<br>
  295. CounterOffset = true Value - Reading "total"<br>
  296. Example:
  297. <ul>
  298. <code>attr Gaszaehler CounterOffset 15427.434</code><br>
  299. </ul>
  300. </li>
  301. </ul>
  302. <br>
  303. </ul>
  304. =end html
  305. =begin html_DE
  306. <a name="CUL_EM"></a>
  307. <h3>CUL_EM</h3>
  308. <ul>
  309. Das Modul CUL_EM wertet von einem CUL empfange Botschaften des Typs EM aus,
  310. dies sind aktuell Botschaften von EMEM, EMWZ bzw. EMGZ Ger&auml;ten.
  311. <br><br>
  312. <a name="CUL_EMdefine"></a>
  313. <b>Define</b>
  314. <ul>
  315. <code>define &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
  316. CostPerUnit BasicFeePerMonth]</code> <br>
  317. <br>
  318. &lt;code&gt; ist der Code, der am EM Ger&auml;t eingestellt wird.
  319. G&uuml;tige Werte sind 1 bis 12. 1-4 gilt f&uuml;r EMWZ, 5-8 f&uuml;r EMEM
  320. und 9-12 f&uuml;r EMGZ Ger&auml;te.<br><br>
  321. <b>corr1</b> ist der Kalibrierfaktor f&uuml;r den Momentanverbrauch,
  322. <b>corr2</b> f&uuml;r den Gesamtverbrauch.
  323. <ul>
  324. <li>f&uuml;r EMWZ Ger&auml;te wird die Umdrehungsgeschwindigkeit (U/kW)
  325. des verwendeten Stromz&auml;hlers (z.B. 150) f&uuml;r corr1 und 12 mal
  326. diesen Wert f&uuml;r corr2 verwendet</li>
  327. <li>f&uuml;r EMEM devices ist corr1 mit 0.01 und corr2 mit 0.001
  328. anzugeben</li>
  329. </ul>
  330. <br>
  331. <b>CostPerUnit</b> und <b>BasicFeePerMonth</b> werden dazu verwendet, die
  332. t&auml;gliche bzw. monatliche Kosten zu berechnen. Die Kosten werden in der
  333. Logdatei einmal t&auml;glich (ohne Fixkosten) bzw. monatlich (mit Fixkosten)
  334. generiert und angezeigt.
  335. Die Definition sollte in etwa so aussehen:
  336. <ul><code>
  337. define emwz 1 75 900 0.15 12.50<br>
  338. </code></ul>
  339. und in der Logdatei sollten diese Zeilen erscheinen:
  340. <ul><code>
  341. CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
  342. CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
  343. </code></ul>
  344. Tipp: Das EMWZ Ger&auml;t kann so konfiguriert werden, dass es in der CUM
  345. Spalte des STATE Wertes den aktuellen Wert des Stromz&auml;hlers anzeigt.
  346. Hierf&uuml;r muss der aktuell am Stromz&auml;hler abgelesene Wert mit corr1
  347. (U/kW) multipliziert werden und der CUM Rohwert aus der aktuellen fhem
  348. Messung ('reading') davon abgezogen werden. Dann muss dieser Wert als
  349. Basiswert des EMWZ Ger&auml;tes (im Beispiel emwz) gesetzt werden.<br>
  350. </ul>
  351. <br>
  352. <a name="CUL_EMset"></a>
  353. <b>Set</b> <ul>N/A</ul><br>
  354. <a name="CUL_EMget"></a>
  355. <b>Get</b> <ul>N/A</ul><br>
  356. <a name="CUL_EMattr"></a>
  357. <b>Attributes</b>
  358. <ul>
  359. <li><a href="#ignore">ignore</a></li><br>
  360. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  361. <li><a href="#showtime">showtime</a></li><br>
  362. <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
  363. <li><a href="#IODev">IODev</a></li><br>
  364. <li><a href="#eventMap">eventMap</a></li><br>
  365. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  366. <li><a name="maxPeak">maxPeak</a> &lt;number&gt;<br>
  367. Gibt den maximal m&ouml;glichen Spitzenwert f&uuml;r das EM-Meter an
  368. ("TOP:"-Wert in Logdatei). Spitzenwerte gr&ouml;&szlig;er als dieser
  369. Wert gelten als EM-Lesefehler und werden ignoriert.
  370. Wenn es z.B. nicht m&ouml;glich ist mehr zu 40kW Leistung
  371. zu beziehen setzt man maxPeak auf 40 um das Auslesen des
  372. Stromz&auml;hlers robuster zu machen.
  373. </li><br>
  374. <li><a name="CounterOffset">CounterOffset</a><br>
  375. Gibt den Unterschied zwischen dem tats&auml;chlichen Z&auml;hlerstand und
  376. dem vom EMGZ gemeldeten Wert an.<br>
  377. CounterOffset = tats&auml;chlicher Z&auml;hlerstand - Reading "total"<br>
  378. Beispiel:
  379. <ul>
  380. <code>attr Gaszaehler CounterOffset 15427.434</code><br>
  381. </ul>
  382. </li>
  383. </ul>
  384. <br>
  385. </ul>
  386. =end html_DE
  387. =cut