36_PCA301.pm 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. # $Id: 36_PCA301.pm 12056 2016-08-22 19:30:31Z justme1968 $
  2. #
  3. # TODO:
  4. package main;
  5. use strict;
  6. use warnings;
  7. use SetExtensions;
  8. sub PCA301_Parse($$);
  9. sub PCA301_Send($$@);
  10. sub
  11. PCA301_Initialize($)
  12. {
  13. my ($hash) = @_;
  14. $hash->{Match} = "^\\S+\\s+24";
  15. $hash->{SetFn} = "PCA301_Set";
  16. #$hash->{GetFn} = "PCA301_Get";
  17. $hash->{DefFn} = "PCA301_Define";
  18. $hash->{UndefFn} = "PCA301_Undef";
  19. $hash->{FingerprintFn} = "PCA301_Fingerprint";
  20. $hash->{ParseFn} = "PCA301_Parse";
  21. $hash->{AttrFn} = "PCA301_Attr";
  22. $hash->{AttrList} = "IODev"
  23. ." ignore:1,0"
  24. ." readonly:1,0"
  25. ." forceOn:1,0"
  26. ." offLevel"
  27. ." $readingFnAttributes";
  28. }
  29. sub
  30. PCA301_Define($$)
  31. {
  32. my ($hash, $def) = @_;
  33. my @a = split("[ \t][ \t]*", $def);
  34. if(@a != 4 ) {
  35. my $msg = "wrong syntax: define <name> PCA301 <addr> <channel>";
  36. Log3 undef, 2, $msg;
  37. return $msg;
  38. }
  39. $a[2] =~ m/^([\da-f]{6})$/i;
  40. return "$a[2] is not a valid PCA301 address" if( !defined($1) );
  41. $a[3] =~ m/^([\da-f]{2})$/i;
  42. return "$a[3] is not a valid PCA301 channel" if( !defined($1) );
  43. my $name = $a[0];
  44. my $addr = $a[2];
  45. my $channel = $a[3];
  46. #return "$addr is not a 1 byte hex value" if( $addr !~ /^[\da-f]{2}$/i );
  47. #return "$addr is not an allowed address" if( $addr eq "00" );
  48. return "PCA301 device $addr already used for $modules{PCA301}{defptr}{$addr}->{NAME}." if( $modules{PCA301}{defptr}{$addr}
  49. && $modules{PCA301}{defptr}{$addr}->{NAME} ne $name );
  50. $hash->{addr} = $addr;
  51. $hash->{channel} = $channel;
  52. $modules{PCA301}{defptr}{$addr} = $hash;
  53. AssignIoPort($hash);
  54. if(defined($hash->{IODev}->{NAME})) {
  55. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  56. } else {
  57. Log3 $name, 1, "$name: no I/O device";
  58. }
  59. $attr{$name}{devStateIcon} = 'on:on:toggle off:off:toggle set.*:light_exclamation:off' if( !defined( $attr{$name}{devStateIcon} ) );
  60. $attr{$name}{webCmd} = 'on:off:toggle:statusRequest' if( !defined( $attr{$name}{webCmd} ) );
  61. CommandAttr( undef, "$name userReadings consumptionTotal:consumption.* monotonic {ReadingsVal(\$name,'consumption',0)}" ) if( !defined( $attr{$name}{userReadings} ) );
  62. #PCA301_Send($hash, $addr, "00" );
  63. return undef;
  64. }
  65. #####################################
  66. sub
  67. PCA301_Undef($$)
  68. {
  69. my ($hash, $arg) = @_;
  70. my $name = $hash->{NAME};
  71. my $addr = $hash->{addr};
  72. delete( $modules{PCA301}{defptr}{$addr} );
  73. return undef;
  74. }
  75. #####################################
  76. sub
  77. PCA301_Set($@)
  78. {
  79. my ($hash, $name, @aa) = @_;
  80. my $cnt = @aa;
  81. return "\"set $name\" needs at least one parameter" if($cnt < 1);
  82. my $cmd = $aa[0];
  83. my $arg = $aa[1];
  84. my $arg2 = $aa[2];
  85. my $arg3 = $aa[3];
  86. my $readonly = AttrVal($name, "readonly", "0" );
  87. my $list = "identify:noArg reset:noArg statusRequest:noArg";
  88. $list .= " off:noArg on:noArg toggle:noArg" if( !$readonly );
  89. if( $cmd eq 'toggle' ) {
  90. $cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off";
  91. }
  92. if( !$readonly && $cmd eq 'off' ) {
  93. readingsSingleUpdate($hash, "state", "set-$cmd", 1);
  94. PCA301_Send( $hash, 0x05, 0x00 );
  95. } elsif( !$readonly && $cmd eq 'on' ) {
  96. readingsSingleUpdate($hash, "state", "set-$cmd", 1);
  97. PCA301_Send( $hash, 0x05, 0x01 );
  98. } elsif( $cmd eq 'statusRequest' ) {
  99. readingsSingleUpdate($hash, "state", "set-$cmd", 1);
  100. PCA301_Send( $hash, 0x04, 0x00 );
  101. } elsif( $cmd eq 'reset' ) {
  102. readingsSingleUpdate($hash, "state", "set-$cmd", 1);
  103. PCA301_Send( $hash, 0x04, 0x01 );
  104. } elsif( $cmd eq 'identify' ) {
  105. PCA301_Send( $hash, 0x06, 0x00 );
  106. } else {
  107. return SetExtensions($hash, $list, $name, @aa);
  108. }
  109. return undef;
  110. }
  111. #####################################
  112. sub
  113. PCA301_Get($@)
  114. {
  115. my ($hash, $name, $cmd, @args) = @_;
  116. return "\"get $name\" needs at least one parameter" if(@_ < 3);
  117. my $list = "";
  118. return "Unknown argument $cmd, choose one of $list";
  119. }
  120. sub
  121. PCA301_Fingerprint($$)
  122. {
  123. my ($name, $msg) = @_;
  124. return ( "", $msg );
  125. }
  126. sub
  127. PCA301_ForceOn($)
  128. {
  129. my ($hash) = @_;
  130. PCA301_Send( $hash, 0x05, 0x01 );
  131. }
  132. sub
  133. PCA301_Parse($$)
  134. {
  135. my ($hash, $msg) = @_;
  136. my $name = $hash->{NAME};
  137. #return undef if( $msg !~ m/^[\dA-F]{12,}$/ );
  138. if( $msg =~ m/^L/ ) {
  139. my @parts = split( ' ', substr($msg, 5), 4 );
  140. $msg = "OK 24 $parts[3]";
  141. }
  142. my( @bytes, $channel,$cmd,$addr,$data,$power,$consumption );
  143. if( $msg =~ m/^OK/ ) {
  144. @bytes = split( ' ', substr($msg, 6) );
  145. $channel = sprintf( "%02X", $bytes[0] );
  146. $cmd = $bytes[1];
  147. $addr = sprintf( "%02X%02X%02X", $bytes[2], $bytes[3], $bytes[4] );
  148. $data = $bytes[5];
  149. return "" if( $cmd == 0x04 && $bytes[6] == 170 && $bytes[7] == 170 && $bytes[8] == 170 && $bytes[9] == 170 ); # ignore commands from display unit
  150. return "" if( $cmd == 0x05 && ( $bytes[6] != 170 || $bytes[7] != 170 || $bytes[8] != 170 || $bytes[9] != 170 ) ); # ignore commands not from the plug
  151. } elsif ( $msg =~ m/^TX/ ) {
  152. # ignore TX
  153. return "";
  154. } else {
  155. DoTrigger($name, "UNKNOWNCODE $msg");
  156. Log3 $name, 3, "$name: Unknown code $msg, help me!";
  157. return "";
  158. }
  159. my $raddr = $addr;
  160. my $rhash = $modules{PCA301}{defptr}{$raddr};
  161. my $rname = $rhash?$rhash->{NAME}:$raddr;
  162. return "" if( IsIgnored($rname) );
  163. if( !$modules{PCA301}{defptr}{$raddr} ) {
  164. Log3 $name, 3, "PCA301 Unknown device $rname, please define it";
  165. return "UNDEFINED PCA301_$rname PCA301 $raddr $channel";
  166. }
  167. #CommandAttr( undef, "$rname userReadings consumptionTotal:consumption.* monotonic {ReadingsVal($rname,'consumption',0)}" ) if( !defined( $attr{$rname}{userReadings} ) );
  168. my @list;
  169. push(@list, $rname);
  170. $rhash->{PCA301_lastRcv} = TimeNow();
  171. if( $rhash->{channel} ne $channel ) {
  172. Log3 $rname, 3, "PCA301 $rname, channel changed from $rhash->{channel} to $channel";
  173. $rhash->{channel} = $channel;
  174. $rhash->{DEF} = "$rhash->{addr} $rhash->{channel}";
  175. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  176. }
  177. my $readonly = AttrVal($rname, "readonly", "0" );
  178. my $state = "";
  179. if( $cmd eq 0x04 ) {
  180. $state = $data==0x00?"off":"on";
  181. my $power = ($bytes[6]*256 + $bytes[7]) / 10.0;
  182. my $consumption = ($bytes[8]*256 + $bytes[9]) / 100.0;
  183. my $state = $state; $state = $power if( $readonly );
  184. my $off_level = AttrVal($rname, "offLevel", 0);
  185. $state = "off" if( $readonly && $off_level && $power <= $off_level );
  186. readingsBeginUpdate($rhash);
  187. readingsBulkUpdate($rhash, "power", $power) if( $power != ReadingsVal($rname,"power",0) );
  188. readingsBulkUpdate($rhash, "consumption", $consumption) if( $consumption != ReadingsVal($rname,"consumption",0) );
  189. readingsBulkUpdate($rhash, "state", $state) if( $state ne ReadingsVal($rname,"state","") );
  190. readingsEndUpdate($rhash,1);
  191. } elsif( $cmd eq 0x05 ) {
  192. $state = $data==0x00?"off":"on";
  193. readingsSingleUpdate($rhash, "state", $state, 1)
  194. }
  195. if( AttrVal($rname, "forceOn", 0 ) == 1
  196. && $state eq "off" ) {
  197. readingsSingleUpdate($rhash, "state", "set-forceOn", 1);
  198. InternalTimer(gettimeofday()+3, "PCA301_ForceOn", $rhash, 0);
  199. }
  200. return @list;
  201. }
  202. sub
  203. PCA301_Send($$@)
  204. {
  205. my ($hash, $cmd, $data) = @_;
  206. $hash->{PCA301_lastSend} = TimeNow();
  207. my $msg = sprintf( "%i,%i,%i,%i,%i,%i,255,255,255,255s", hex($hash->{channel}),
  208. $cmd,
  209. hex(substr($hash->{addr},0,2)), hex(substr($hash->{addr},2,2)), hex(substr($hash->{addr},4,2)),
  210. $data );
  211. IOWrite( $hash, $msg );
  212. }
  213. sub
  214. PCA301_Attr(@)
  215. {
  216. my ($cmd, $name, $attrName, $attrVal) = @_;
  217. return undef;
  218. }
  219. 1;
  220. =pod
  221. =item summary PCA301 devices
  222. =item summary_DE PCA301 Ger&auml;te
  223. =begin html
  224. <a name="PCA301"></a>
  225. <h3>PCA301</h3>
  226. <ul>
  227. The PCA301 is a RF controlled AC mains plug with integrated power meter functionality from ELV.<br><br>
  228. It can be integrated in to FHEM via a <a href="#JeeLink">JeeLink</a> as the IODevice.<br><br>
  229. The JeeNode sketch required for this module can be found in .../contrib/arduino/36_PCA301-pcaSerial.zip.<br><br>
  230. <a name="PCA301Define"></a>
  231. <b>Define</b>
  232. <ul>
  233. <code>define &lt;name&gt; PCA301 &lt;addr&gt; &lt;channel&gt;</code> <br>
  234. <br>
  235. addr is a 6 digit hex number to identify the PCA301 device.
  236. channel is a 2 digit hex number to identify the PCA301 device.<br><br>
  237. Note: devices are autocreated on reception of the first message.<br>
  238. </ul>
  239. <br>
  240. <a name="PCA301_Set"></a>
  241. <b>Set</b>
  242. <ul>
  243. <li>on</li>
  244. <li>off</li>
  245. <li>identify<br>
  246. Blink the status led for ~5 seconds.</li>
  247. <li>reset<br>
  248. Reset consumption counters</li>
  249. <li>statusRequest<br>
  250. Request device status update.</li>
  251. <li><a href="#setExtensions"> set extensions</a> are supported.</li>
  252. </ul><br>
  253. <a name="PCA301_Get"></a>
  254. <b>Get</b>
  255. <ul>
  256. </ul><br>
  257. <a name="PCA301_Readings"></a>
  258. <b>Readings</b>
  259. <ul>
  260. <li>power</li>
  261. <li>consumption</li>
  262. <li>consumptionTotal<br>
  263. will be created as a default user reading to have a continous consumption value that is not influenced
  264. by the regualar reset or overflow of the normal consumption reading</li>
  265. </ul><br>
  266. <a name="PCA301_Attr"></a>
  267. <b>Attributes</b>
  268. <ul>
  269. <li>forceOn<br>
  270. try to switch on the device whenever an off status is received.</li>
  271. <li>offLevel<br>
  272. a power level less or equal <code>offLevel</code> is considered to be off. used only in conjunction with readonly.</li>
  273. <li>readonly<br>
  274. if set to a value != 0 all switching commands (on, off, toggle, ...) will be disabled.</li>
  275. <li>ignore<br>
  276. 1 -> ignore this device.</li>
  277. </ul><br>
  278. </ul>
  279. =end html
  280. =cut