42_RFXMETER.pm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #################################################################################
  2. # 42_RFXMETER.pm
  3. # Modul for FHEM to decode RFXMETER messages
  4. #
  5. # This code is derived from http://www.xpl-perl.org.uk/.
  6. # Thanks a lot to Mark Hindess who wrote xPL.
  7. #
  8. # Special thanks to RFXCOM, http://www.rfxcom.com/, for their
  9. # help. I own an USB-RFXCOM-Receiver (433.92MHz, USB, order code 80002)
  10. # and highly recommend it.
  11. #
  12. # (c) 2010-2014 Copyright: Willi Herzig (Willi.Herzig@gmail.com)
  13. #
  14. # This script is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  17. #
  18. ##################################
  19. #
  20. # values for "set global verbose"
  21. # 4: log unknown protocols
  22. # 5: log decoding hexlines for debugging
  23. #
  24. # $Id: 42_RFXMETER.pm 5598 2014-04-22 15:26:25Z wherzig $
  25. package main;
  26. use strict;
  27. use warnings;
  28. my $time_old = 0;
  29. sub
  30. RFXMETER_Initialize($)
  31. {
  32. my ($hash) = @_;
  33. $hash->{Match} = "^30.*";
  34. $hash->{DefFn} = "RFXMETER_Define";
  35. $hash->{UndefFn} = "RFXMETER_Undef";
  36. $hash->{ParseFn} = "RFXMETER_Parse";
  37. $hash->{AttrList} = "IODev ignore:1,0 do_not_notify:1,0 loglevel:0,1,2,3,4,5,6";
  38. }
  39. #####################################
  40. sub
  41. RFXMETER_Define($$)
  42. {
  43. my ($hash, $def) = @_;
  44. my @a = split("[ \t][ \t]*", $def);
  45. my $a = int(@a);
  46. #print "a0 = $a[0]";
  47. #return "wrong syntax: define <name> RFXMETER code " if(int(@a) != 3);
  48. return "wrong syntax: define <name> RFXMETER code [<scalefactor>] [<unitname>]"
  49. if(int(@a) < 3 || int(@a) > 5);
  50. my $name = $a[0];
  51. my $code = $a[2];
  52. $hash->{scalefactor} = ((int(@a) > 3) ? $a[3] : 0.001);
  53. $hash->{unitname} = ((int(@a) > 4) ? $a[4] : "kwh");
  54. $hash->{CODE} = $code;
  55. #$modules{RFXMETER}{defptr}{$name} = $hash;
  56. $modules{RFXMETER}{defptr}{$code} = $hash;
  57. AssignIoPort($hash);
  58. return undef;
  59. }
  60. #########################################
  61. # From xpl-perl/lib/xPL/Util.pm:
  62. sub RFXMETER_hi_nibble {
  63. ($_[0]&0xf0)>>4;
  64. }
  65. sub RFXMETER_lo_nibble {
  66. $_[0]&0xf;
  67. }
  68. sub RFXMETER_nibble_sum {
  69. my $c = $_[0];
  70. my $s = 0;
  71. foreach (0..$_[0]-1) {
  72. $s += RFXMETER_hi_nibble($_[1]->[$_]);
  73. $s += RFXMETER_lo_nibble($_[1]->[$_]);
  74. }
  75. $s += RFXMETER_hi_nibble($_[1]->[$_[0]]) if (int($_[0]) != $_[0]);
  76. return $s;
  77. }
  78. #####################################
  79. sub
  80. RFXMETER_Undef($$)
  81. {
  82. my ($hash, $name) = @_;
  83. delete($modules{RFXMETER}{defptr}{$name});
  84. return undef;
  85. }
  86. #my $DOT = q{.};
  87. # Important: change it to _, because FHEM uses regexp
  88. my $DOT = q{_};
  89. sub parse_RFXmeter {
  90. my $bytes = shift;
  91. #($bytes->[0] == ($bytes->[1]^0xf0)) or return;
  92. if ( ($bytes->[0] + ($bytes->[1]^0xf)) != 0xff) {
  93. #Log 1, "RFXMETER: check1 failed";
  94. return;
  95. }
  96. #my $device = sprintf "%02x%02x", $bytes->[0], $bytes->[1];
  97. my $device = sprintf "%02x", $bytes->[0];
  98. Log 4, "RFXMETER: device=$device";
  99. my $type = RFXMETER_hi_nibble($bytes->[5]);
  100. #Log 1, "RFXMETER: type=$type";
  101. my $check = RFXMETER_lo_nibble($bytes->[5]);
  102. #Log 1, "RFXMETER: check=$check";
  103. my $nibble_sum = RFXMETER_nibble_sum(5.5, $bytes);
  104. my $parity = 0xf^($nibble_sum&0xf);
  105. unless ($parity == $check) {
  106. #warn "RFXMeter parity error $parity != $check\n";
  107. return "";
  108. }
  109. my $time =
  110. { 0x01 => '30s',
  111. 0x02 => '1m',
  112. 0x04 => '5m',
  113. 0x08 => '10m',
  114. 0x10 => '15m',
  115. 0x20 => '30m',
  116. 0x40 => '45m',
  117. 0x80 => '60m',
  118. };
  119. my $type_str =
  120. [
  121. 'normal data packet',
  122. 'new interval time set',
  123. 'calibrate value',
  124. 'new address set',
  125. 'counter value reset to zero',
  126. 'set 1st digit of counter value integer part',
  127. 'set 2nd digit of counter value integer part',
  128. 'set 3rd digit of counter value integer part',
  129. 'set 4th digit of counter value integer part',
  130. 'set 5th digit of counter value integer part',
  131. 'set 6th digit of counter value integer part',
  132. 'counter value set',
  133. 'set interval mode within 5 seconds',
  134. 'calibration mode within 5 seconds',
  135. 'set address mode within 5 seconds',
  136. 'identification packet',
  137. ]->[$type];
  138. unless ($type == 0) {
  139. warn "Unsupported rfxmeter message $type_str\n";
  140. return "";
  141. }
  142. #my $kwh = ( ($bytes->[4]<<16) + ($bytes->[2]<<8) + ($bytes->[3]) ) / 100;
  143. #Log 1, "RFXMETER: kwh=$kwh";
  144. my $current = ($bytes->[4] << 16) + ($bytes->[2] << 8) + ($bytes->[3]);
  145. Log 4, "RFXMETER: current=$current";
  146. my $device_name = "RFXMeter".$DOT.$device;
  147. Log 4, "device_name=$device_name";
  148. #my $def = $modules{RFXMETER}{defptr}{"$device_name"};
  149. my $def = $modules{RFXMETER}{defptr}{"$device"};
  150. if(!$def) {
  151. Log 3, "RFXMETER: Unknown device $device_name, please define it";
  152. return "UNDEFINED $device_name RFXMETER $device";
  153. }
  154. # Use $def->{NAME}, because the device may be renamed:
  155. my $name = $def->{NAME};
  156. #Log 1, "name=$new_name";
  157. return "" if(IsIgnored($name));
  158. my $n = 0;
  159. my $tm = TimeNow();
  160. my $val = "";
  161. my $hash = $def;
  162. if (defined($hash->{scalefactor})) {
  163. $current = $current * $hash->{scalefactor};
  164. #Log 1, "scalefactor=$hash->{scalefactor}, current=$current";
  165. }
  166. my $unitname = "kwh";
  167. if (defined($hash->{unitname})) {
  168. $unitname = $hash->{unitname};
  169. #Log 1, "unitname=$hash->{unitname}, current=$current";
  170. }
  171. my $sensor = "meter";
  172. $val .= "CNT: " . $current;
  173. $def->{READINGS}{$sensor}{TIME} = $tm;
  174. $def->{READINGS}{$sensor}{VAL} = $current . " " . $unitname;
  175. $def->{CHANGED}[$n++] = $sensor . ": " . $current . " " . $unitname;
  176. $def->{STATE} = $val;
  177. $def->{TIME} = $tm;
  178. $def->{CHANGED}[$n++] = $val;
  179. DoTrigger($name, undef);
  180. return "";
  181. }
  182. sub
  183. RFXMETER_Parse($$)
  184. {
  185. my ($hash, $msg) = @_;
  186. my $time = time();
  187. if ($time_old ==0) {
  188. Log 5, "RFXMETER: decoding delay=0 hex=$msg";
  189. } else {
  190. my $time_diff = $time - $time_old ;
  191. Log 5, "RFXMETER: decoding delay=$time_diff hex=$msg";
  192. }
  193. $time_old = $time;
  194. # convert to binary
  195. my $bin_msg = pack('H*', $msg);
  196. # convert string to array of bytes. Skip length byte
  197. my @rfxcom_data_array = ();
  198. foreach (split(//, substr($bin_msg,1))) {
  199. push (@rfxcom_data_array, ord($_) );
  200. }
  201. my $bits = ord($bin_msg);
  202. my $num_bytes = $bits >> 3; if (($bits & 0x7) != 0) { $num_bytes++; }
  203. Log 4, "RFXMETER: bits=$bits num_bytes=$num_bytes hex=$msg";
  204. my @res = "";
  205. if ($bits == 48) {
  206. @res = parse_RFXmeter(\@rfxcom_data_array);
  207. #parse_RFXmeter(\@rfxcom_data_array);
  208. } else {
  209. # this should never happen as this module parses only RFXmeter messages
  210. Log 1, "RFXMETER: error unknown hex=$msg";
  211. }
  212. return @res;
  213. }
  214. 1;
  215. =pod
  216. =begin html
  217. <a name="RFXMETER"></a>
  218. <h3>RFXMETER</h3>
  219. <ul>
  220. The RFXMETER module interprets RFXCOM RFXMeter messages received by a RFXCOM receiver. You need to define an RFXCOM receiver first.
  221. See the <a href="#RFXCOM">RFXCOM</a>.
  222. <br><br>
  223. <a name="RFXMETERdefine"></a>
  224. <b>Define</b>
  225. <ul>
  226. <code>define &lt;name&gt; RFXMETER &lt;deviceid&gt; [&lt;scalefactor&gt;] [&lt;unitname&gt;]</code> <br>
  227. <br>
  228. &lt;deviceid&gt; is the device identifier of the RFXMeter sensor and is a one byte hexstring (00-ff).
  229. <br>
  230. &lt;scalefactor&gt; is an optional scaling factor. It is multiplied to the value that is received from the RFXmeter sensor.
  231. <br>
  232. &lt;unitname&gt; is an optional string that describes the value units. It is added to the Reading generated to describe the values.
  233. <br><br>
  234. Example: <br>
  235. <code>define RFXWater RFXMETER 00 0.5 ltr</code>
  236. <br>
  237. <code>define RFXPower RFXMETER 01 0.001 kwh</code>
  238. <br>
  239. <code>define RFXGas RFXMETER 02 0.01 cu_m</code>
  240. <br>
  241. </ul>
  242. <br>
  243. <a name="RFXMETERset"></a>
  244. <b>Set</b> <ul>N/A</ul><br>
  245. <a name="RFXMETERget"></a>
  246. <b>Get</b> <ul>N/A</ul><br>
  247. <a name="RFXMETERattr"></a>
  248. <b>Attributes</b>
  249. <ul>
  250. <li><a href="#ignore">ignore</a></li><br>
  251. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  252. </ul>
  253. </ul>
  254. =end html
  255. =cut