64_ESA2000.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. ##############################################
  2. # (c) by STefan Mayer (stefan(at)clumsy.ch) #
  3. # #
  4. # please feel free to contact me for any #
  5. # changes, improvments, suggestions, etc #
  6. # #
  7. ##############################################
  8. # $Id: 64_ESA2000.pm 7243 2014-12-17 13:04:32Z stromer-12 $
  9. package main;
  10. use strict;
  11. use warnings;
  12. my %codes = (
  13. "01.e" => "ESAx000WZ",
  14. "03.e" => "ESA1000Z",
  15. );
  16. #####################################
  17. sub
  18. ESA2000_Initialize($)
  19. {
  20. my ($hash) = @_;
  21. # S0119FA011E00007D6E003100000007C9 ESA2000_LED
  22. $hash->{Match} = "^S................................\$";
  23. $hash->{DefFn} = "ESA2000_Define";
  24. $hash->{UndefFn} = "ESA2000_Undef";
  25. $hash->{ParseFn} = "ESA2000_Parse";
  26. $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 ignore:0,1 ".
  27. "model:esa2000-led,esa2000-wz,esa2000-s0,esa1000wz-ir,esa1000wz-s0,esa1000wz-led,esa1000gas base_1 base_2 ".
  28. $readingFnAttributes;
  29. }
  30. #####################################
  31. sub
  32. ESA2000_Define($$)
  33. {
  34. my ($hash, $def) = @_;
  35. my @a = split("[ \t][ \t]*", $def);
  36. return "wrong syntax: define <name> ESA2000 CODE" if(int(@a) != 3);
  37. $a[2] = lc($a[2]);
  38. return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
  39. if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
  40. $hash->{CODE} = $a[2];
  41. $modules{ESA2000}{defptr}{$a[2]} = $hash;
  42. AssignIoPort($hash);
  43. return undef;
  44. }
  45. #####################################
  46. sub
  47. ESA2000_Undef($$)
  48. {
  49. my ($hash, $name) = @_;
  50. delete($modules{ESA2000}{defptr}{$hash->{CODE}})
  51. if(defined($hash->{CODE}) &&
  52. defined($modules{ESA2000}{defptr}{$hash->{CODE}}));
  53. return undef;
  54. }
  55. #####################################
  56. sub
  57. ESA2000_Parse($$)
  58. {
  59. my ($hash, $msg) = @_;
  60. # 0 00 0000 0001 11111111 1222 222222 2333
  61. # 0 12 3456 7890 12345678 9012 345678 9012
  62. # S Sensorkennung
  63. # ss Sequenze und Sequenzwiederhohlung mit gesetzten höchsten Bit
  64. # dddd Device
  65. # cccc Code + Batterystate
  66. # vvvvvvvv vvvv vvvvvv vvvv Valves
  67. # tttttttt Gesamtimpules
  68. # aaaa Impule je Sequenz
  69. # zzzzzz Zeitstempel seit Start des Adapters (ESA1000)
  70. # kkkk Impulse je kWh/m3
  71. #
  72. # Examples:
  73. # ---------
  74. # S 01 19FA 011E 00007D6E 0031 000000 07C9 ESA2000_LED Zählerkonstante = 2000
  75. # S 12 5E42 011E 00000030 0002 000000 0206 ESA2000_WZ Zählerkonstante = 600
  76. # S 48 6062 011E 00000061 0001 000000 002B ESA2000_WZ Zählerkonstante = 75
  77. # S 93 5DDA 011E 00004F85 0000 000000 0205 ESA2000_WZ Zählerkonstante = 600
  78. # S 16 68C5 011E 000000BB 0000 001FB4 03CB ESA1000WZ_LED Zählerkonstante = 1000
  79. # S AB 0595 031E 000A047E 0000 227C46 0004 ESA1000GAS Zählerkonstante = 1
  80. # S 1C 0785 011E 00011CDA 0002 0D056C 004C ESA1000WZ_LED Zählerkonstante = 75
  81. # S 6E 003D 011E 00037650 0011 02C1DA 07D0 ESA1000WZ_S0 Zählerkonstante = 2000
  82. # S A3 0543 031E 0000099C 0064 001147 000F ESA1000GAS Zählerkonstante = 10
  83. $msg = lc($msg);
  84. my $seq = substr($msg, 1, 2);
  85. my $dev = substr($msg, 3, 4);
  86. my $cde = substr($msg, 7, 4);
  87. my $val = substr($msg, 11, 22);
  88. Log3 $hash, 5, "ESA2000 msg $msg";
  89. Log3 $hash, 5, "ESA2000 seq $seq";
  90. Log3 $hash, 5, "ESA2000 device $dev";
  91. Log3 $hash, 5, "ESA2000 code $cde";
  92. my $type = "$cde";
  93. foreach my $c (keys %codes) {
  94. $c = lc($c);
  95. if($cde =~ m/$c/) {
  96. $type = $codes{$c};
  97. last;
  98. }
  99. }
  100. if(!defined($modules{ESA2000}{defptr}{$dev})) {
  101. Log3 $hash, 3, "Unknown ESA2000 device $dev, please define it";
  102. $type = "ESA2000" if(!$type);
  103. return "UNDEFINED ${type}_$dev ESA2000 $dev";
  104. }
  105. my $def = $modules{ESA2000}{defptr}{$dev};
  106. my $name = $def->{NAME};
  107. return "" if(IsIgnored($name));
  108. my $now = TimeNow();
  109. my (@v, @txt);
  110. # ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
  111. # $year = $year + 1900;
  112. # 0- 4 repeat, sequence, total_ticks, actual_ticks, ticks
  113. # 5-10 raw, total, actual, diff, diff_sec, diff_ticks
  114. # 11-17 last_sec, raw_total, max, day, month, year, rate
  115. # 18-23 day_hr, day_lr, month_hr, month_lr, year_hr, year_lr
  116. # 24-28 day_last, month_last, year_last, hour, hour_last
  117. # 29 battery
  118. if(($type eq "ESAx000WZ") || ($type eq "ESA1000Z")) {
  119. @txt = ( "repeat", "sequence", "total_ticks", "actual_ticks", "ticks",
  120. "raw", "total", "actual", "diff", "diff_sec", "diff_ticks",
  121. "last_sec", "raw_total", "max", "day", "month", "year", "rate",
  122. "day_hr", "day_lr", "month_hr", "month_lr", "year_hr", "year_lr",
  123. "day_last", "month_last", "year_last", "hour", "hour_last",
  124. "battery" );
  125. } else {
  126. Log3 $name, 3, "ESA2000 Device $dev (Unknown type: $type)";
  127. return "";
  128. }
  129. # Codierung Hex
  130. $v[29]= int(hex(substr($cde,2,2)) / 128) ? "low" : "ok";
  131. $v[0] = int(hex($seq) / 128) ? "+" : "-";
  132. $v[1] = hex($seq) % 128;
  133. $v[2] = hex(substr($val,0,8));
  134. $v[3] = hex(substr($val,8,4));
  135. $v[4] = hex(substr($val,18,4)) ^ hex(substr($msg,3,2)); # XOR high byte of device-id
  136. my $corr = 1;
  137. if ($type eq "ESA1000Z") {
  138. $corr = 1000/$v[4];
  139. }
  140. # check if low-rate or high-rate. note that this is different per electricity company! (Here weekday from 6-20 is high rate)
  141. my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime;
  142. if ( (0 < $wday ) && ($wday < 6) && (5 < $hour) && ($hour < 20) ) {
  143. $v[17] = "HR";
  144. } else {
  145. $v[17] = "LR";
  146. }
  147. $v[5] = sprintf("CNT: %d%s CUM: %d CUR: %d TICKS: %d %s",
  148. $v[1], $v[0], $v[2], $v[3], $v[4], $v[17] );
  149. $v[11] = time();
  150. $v[9] = $v[11] - (defined($def->{READINGS}{$txt[11]}{VAL}) ? $def->{READINGS}{$txt[11]}{VAL} : $v[11]); # seconds since last update
  151. $v[7] = -1;
  152. $v[8] = sprintf("%.4f", $v[3]/$v[4]/$corr); # calculate kWh diff from readings (raw from device....), whats this relly?
  153. if(defined($def->{READINGS}{$txt[2]}{VAL}) && $def->{READINGS}{$txt[2]}{VAL} <=$v[2]) { # check for resetted counter.... only accept increase in counter
  154. $v[10] = $v[2] - $def->{READINGS}{$txt[2]}{VAL}; # should be the same as actual_ticks if no packets are lost
  155. }
  156. if(defined($v[10])) {
  157. my $con = $v[10]/$v[4]/$corr;
  158. if($v[9] >= 110) {
  159. # Zeitdifferenz zu gering (ESA 120s bis 184s)
  160. # $v[9] = (($v[9] lt 110) ? 150 : $v[9]);
  161. $v[7] = $con/$v[9]*3600; # calculate kW/h since last update
  162. }
  163. $v[6] = $con + (defined($def->{READINGS}{$txt[6]}{VAL}) ? $def->{READINGS}{$txt[6]}{VAL} : 0); # cumulate kWh to ensure tick-changes are calculated correctly (does this ever happen?)
  164. # 27 "hour"
  165. # 28 "hour_last"
  166. if(defined($def->{READINGS}{$txt[27]}{VAL})) {
  167. $v[27] = $con + ((substr($now,0,13) eq substr($def->{READINGS}{$txt[27]}{TIME},0,13)) ? $def->{READINGS}{$txt[27]}{VAL} : 0);
  168. $v[28] = $def->{READINGS}{$txt[27]}{VAL} if(substr($now,0,13) ne substr($def->{READINGS}{$txt[27]}{TIME},0,13));
  169. } else {
  170. $v[27] = $con
  171. }
  172. # Day # Month # Year
  173. # 14 "day" # 15 "month" # 16 "year"
  174. # 18 "day_hr" # 20 "month_hr" # 22 "year_hr"
  175. # 19 "day_lr" # 21 "month_lr" # 23 "year_lr"
  176. # 24 "day_last" # 25 "month_last" # 26 "year_last"
  177. for(my $i = 0; $i < 3; $i++) {
  178. if(defined($def->{READINGS}{$txt[$i+14]}{VAL})) {
  179. $v[14+$i] = $con + ((substr($now,0,10-$i*3) eq substr($def->{READINGS}{$txt[$i+14]}{TIME},0,10-$i*3)) ? $def->{READINGS}{$txt[14+$i]}{VAL} : 0);
  180. $v[24+$i] = $def->{READINGS}{$txt[14+$i]}{VAL} if(substr($now,0,10-$i*3) ne substr($def->{READINGS}{$txt[$i+14]}{TIME},0,10-$i*3));
  181. } else {
  182. $v[14+$i] = $con
  183. }
  184. if ($v[17] eq "HR" ) {
  185. # high-rate
  186. $v[18+2*$i] = $con + (defined($def->{READINGS}{$txt[18+2*$i]}{VAL}) && (substr($now,0,10-3*$i) eq substr($def->{READINGS}{$txt[18+2*$i]}{TIME},0,10-3*$i)) ? $def->{READINGS}{$txt[18+2*$i]}{VAL} : 0);
  187. } else {
  188. # low-rate
  189. $v[19+2*$i] = $con + (defined($def->{READINGS}{$txt[19+2*$i]}{VAL}) && (substr($now,0,10-3*$i) eq substr($def->{READINGS}{$txt[19+2*$i]}{TIME},0,10-3*$i)) ? $def->{READINGS}{$txt[19+2*$i]}{VAL} : 0);
  190. }
  191. }
  192. if(!defined($def->{READINGS}{$txt[13]}{VAL})) {
  193. $v[13] = $v[7]; # update max kw/h
  194. } elsif($v[7] >= $def->{READINGS}{$txt[13]}{VAL}) {
  195. $v[13] = $v[7]; # update max kw/h
  196. }
  197. $v[12] = $v[2]/$v[4]/$corr; # calculate kWh total since reset of device (does only make sense if ticks per kWh does not change!!)
  198. # add counter_1 and counter_2 (Hoch- und Niedertarif Basiswerte)
  199. if(defined($attr{$name}) &&
  200. defined($attr{$name}{"base_1"})) {
  201. $v[12] = sprintf("%.3f", $v[12] + $attr{$name}{"base_1"});
  202. }
  203. if(defined($attr{$name}) &&
  204. defined($attr{$name}{"base_2"})) {
  205. $v[12] = sprintf("%.3f", $v[12] + $attr{$name}{"base_2"});
  206. }
  207. } else {
  208. # 6 "total_kwh"
  209. $v[6] = (defined($def->{READINGS}{$txt[6]}{VAL}) ? $def->{READINGS}{$txt[6]}{VAL} : 0);
  210. }
  211. $val = sprintf("CNT: %d%s CUM: %0.3f CUR: %0.3f TICKS: %d %s",
  212. $v[1], $v[0], $v[6], $v[7], $v[4], $v[17]);
  213. #
  214. # from here readings are effectively updated
  215. #
  216. readingsBeginUpdate($def);
  217. Log3 $name, 4, "ESA2000 $name: $val";
  218. if ( (defined($def->{READINGS}{"sequence"}{VAL}) ? $def->{READINGS}{"sequence"}{VAL} : "") ne $v[1] ) {
  219. my $max = int(@txt);
  220. for( my $i = 0; $i < $max; $i++) {
  221. if (defined($v[$i])) {
  222. readingsBulkUpdate($def, $txt[$i], $v[$i]);
  223. }
  224. }
  225. readingsBulkUpdate($def, "type", $type);
  226. readingsBulkUpdate($def, "state", $val);
  227. } else {
  228. Log3 $name, 4, "ESA2000/DISCARDED $name: $val";
  229. }
  230. #
  231. # now we are done with updating readings
  232. #
  233. readingsEndUpdate($def, 1);
  234. return $name;
  235. }
  236. 1;
  237. =pod
  238. =begin html
  239. <a name="ESA2000"></a>
  240. <h3>ESA2000</h3>
  241. <ul>
  242. The ESA2000 module interprets ESA1000 or ESA2000 type of messages received by the CUL.
  243. <br><br>
  244. <a name="ESA2000define"></a>
  245. <b>Define</b>
  246. <ul>
  247. <code>define &lt;name&gt; ESA2000 &lt;code&gt;
  248. [base1 base2]</code> <br>
  249. <br>
  250. &lt;code&gt; is the 4 digit HEX code identifying the devices.<br><br>
  251. <b>base1/2</b> is added to the total kwh as a base (Hoch- und Niedertarifz&auml;hlerstand).
  252. </ul>
  253. <br>
  254. <a name="ESA2000set"></a>
  255. <b>Set</b> <ul>N/A</ul><br>
  256. <a name="ESA2000get"></a>
  257. <b>Get</b> <ul>N/A</ul><br>
  258. <a name="ESA2000attr"></a>
  259. <b>Attributes</b>
  260. <ul>
  261. <li><a href="#ignore">ignore</a></li><br>
  262. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  263. <li><a href="#showtime">showtime</a></li><br>
  264. <li><a href="#model">model</a> (esa2000-led, esa2000-wz, esa2000-s0, esa1000wz-ir, esa1000wz-s0, esa1000wz-led, esa1000gas)</li><br>
  265. <li><a href="#IODev">IODev</a></li><br>
  266. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  267. </ul>
  268. <br>
  269. </ul>
  270. =end html
  271. =cut