45_Plugwise.pm 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. #################################################################################
  2. # 45_Plugwise.pm
  3. #
  4. # FHEM Module for Plugwise
  5. #
  6. # Copyright (C) 2014 Stefan Guttmann
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. #
  22. # Info on protocol:
  23. # http://roheve.wordpress.com/2011/05/15/plugwise-protocol-analysis-part-3/
  24. # http://www.domoticaforum.eu/viewtopic.php?f=39&t=4319&start=30
  25. #
  26. # The GNU General Public License may also be found at http://www.gnu.org/licenses/gpl-2.0.html .
  27. #
  28. # define myPlugwise Plugwise /dev/ttyPlugwise
  29. #
  30. #
  31. ###########################
  32. # # $Id: 45_Plugwise.pm 17320 2018-09-10 18:12:55Z Icinger $
  33. package main;
  34. use strict;
  35. use warnings;
  36. use Time::HiRes qw(gettimeofday);
  37. use Digest::CRC qw(crc);
  38. #use Math::Round;
  39. use Data::Dumper;
  40. my $Make2Channels = 1;
  41. my $testcount=-1;
  42. my $firstrun=0;
  43. my $device=0;
  44. my $data="";
  45. my $toggle=0;
  46. my $status="";
  47. my ($self)=();
  48. my $lastcircle="";
  49. my $buffer="";
  50. my $LastTime=time;
  51. my $lastSync=-1;
  52. my @buffer=();
  53. my $initdone=0;
  54. my %PW_gets = (
  55. "features" => 'Z'
  56. );
  57. my %PW_sets = (
  58. "Scan_Circles" => " ",
  59. "reOpen" => " ",
  60. "syncTime" => " ",
  61. # "pwPairForSec" => ""
  62. );
  63. my %PWType = (
  64. "00" => "PW_Circle",
  65. "01" => "PW_Circle",
  66. "02" => "PW_Circle",
  67. "03" => "PW_Switch",
  68. "04" => "PW_Switch",
  69. "05" => "PW_Sense",
  70. "06" => "PW_Scan"
  71. );
  72. sub PW_Read($);
  73. sub PW_Ready($);
  74. sub PW_Undef($$);
  75. sub PW_Write;
  76. sub PW_Parse($$$$);
  77. sub PW_DoInit($);
  78. sub Plugwise_Initialize($$)
  79. {
  80. my ($hash) = @_;
  81. $self = bless {
  82. _buf => '',
  83. baud => 115200,
  84. device => '',
  85. _awaiting_stick_response => 0
  86. };
  87. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  88. # Provider
  89. $hash->{Clients} = ":PW_Circle:PW_Scan:PW_Switch:PW_Sense:";
  90. my %mc = (
  91. "1:PW_Circle" => "^PW_Circle",
  92. "2:PW_Scan" => "^PW_Scan",
  93. "3:PW_Switch" => "^PW_Switch",
  94. "4:PW_Sense" => "^PW_Sense"
  95. );
  96. $hash->{MatchList} = \%mc;
  97. # Normal devices
  98. $hash->{ReadFn} = "PW_Read";
  99. $hash->{WriteFn} = "PW_Write";
  100. $hash->{ReadyFn} = "PW_Ready";
  101. $hash->{DefFn} = "PW_Define";
  102. $hash->{UndefFn} = "PW_Undef";
  103. $hash->{SetFn} = "PW_Set";
  104. $hash->{GetFn} = "PW_Get";
  105. $hash->{StateFn} = "PW_SetState";
  106. $hash->{AttrList}= "do_not_notify:1,0 interval circlecount WattFormat showCom autosync ".
  107. $readingFnAttributes;;
  108. $hash->{ShutdownFn} = "PW_Shutdown";
  109. }
  110. #####################################
  111. sub PW_Define($$)
  112. {
  113. my ($hash, $def) = @_;
  114. my @a = split("[ \t][ \t]*", $def);
  115. my $inter = 10;
  116. if (@a != 3 ) {
  117. my $msg = "wrong syntax: define <name> Plugwise devicename";
  118. Log3 $hash, 2, $msg;
  119. return $msg;
  120. }
  121. my $name = $a[0];
  122. my $dev = $a[2];
  123. $hash->{NAME}=$name;
  124. DevIo_CloseDev($hash);
  125. $firstrun=0;
  126. $hash->{DeviceName} = $dev;
  127. # if( $init_done ) {
  128. # $attr{$name}{room}="Plugwise";
  129. # $attr{$name}{interval}=10;
  130. # $attr{$name}{circlecount}=50;
  131. # $attr{$name}{WattFormat}="%0.f";
  132. # }
  133. my $ret = DevIo_OpenDev( $hash, 0, undef);
  134. InternalTimer(gettimeofday()+5, "PW_GetUpdate", $hash, 0);
  135. return undef;
  136. }
  137. sub PW_GetUpdate($)
  138. {
  139. my $hash = shift;
  140. my $name = $hash->{NAME};
  141. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  142. my $n=0;
  143. if ($firstrun==2) {return undef};
  144. InternalTimer(gettimeofday()+$attr{$name}{interval}, "PW_GetUpdate", $hash, 1);
  145. # Log 3,Dumper($hash->{helper});
  146. if ($firstrun==0) {
  147. PW_DoInit($hash);
  148. return undef;
  149. }
  150. delete $self->{_waiting};
  151. foreach ( keys %{ $hash->{helper}->{circles} } ) {
  152. $n=$_;
  153. if (defined $hash->{helper}->{circles}->{$n}->{lastContact}) {
  154. if (time>$hash->{helper}->{circles}->{$n}->{lastContact} +960) {
  155. Log3 $hash,3,"Set Circle $n offline";
  156. my %xplmsg = ( schema => 'plugwise.basic', );
  157. my $saddr=$hash->{helper}->{circles}->{$n}->{name};
  158. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type};
  159. $xplmsg{type} = 'output';
  160. $xplmsg{text}= 'offline';
  161. $xplmsg{short} = $saddr;
  162. PW_Parse($hash, $hash, $hash->{NAME}, \%xplmsg);
  163. delete $hash->{helper}->{circles}->{$n};
  164. } elsif (time > $hash->{helper}->{circles}->{$n}->{lastContact} +900) {
  165. command($hash,'history',$hash->{helper}->{circles}->{$n}->{name},4);
  166. Log3 $hash,4,"GetLog offline Circle $n";
  167. }
  168. }
  169. }
  170. foreach ( keys %{ $self->{_plugwise}->{circles} } ) {
  171. $n=$_;
  172. if (!defined $self->{_plugwise}->{circles}->{$n}->{type}) {$self->{_plugwise}->{circles}->{$n}->{type}=""};
  173. if ($self->{_plugwise}->{circles}->{$n}->{type} eq "" ||
  174. !defined $self->{_plugwise}->{circles}->{$n}->{type}) {
  175. command($hash,'status',$n);
  176. }
  177. if (defined $attr{$name}{autosync}) {
  178. if ($attr{$name}{autosync}>0 && time > $lastSync+$attr{$name}{autosync}) {
  179. $lastSync=time;
  180. command($hash,'syncTime',$n);
  181. }
  182. }
  183. }
  184. }
  185. sub MyRead($$)
  186. {
  187. my ($hash, $msg) = @_;
  188. return if (!defined $msg->{dest} );
  189. PW_Parse($hash, $hash, $hash->{NAME}, $msg) if defined $msg;
  190. }
  191. sub PW_Write
  192. {
  193. my ($hash,$reciever,$fn,$a) = @_;
  194. my $name = $hash->{NAME};
  195. return if(!defined($fn));
  196. my $msg = "$hash->{NAME} sending $fn";
  197. $msg .= " to Adress $a" if defined $a;
  198. if ($fn =~ /(on|off)/) {
  199. command($hash,$fn,$reciever,$a);
  200. }
  201. elsif ($fn =~ /(syncTime|ping|removeNode|livepower|status)/) {
  202. command($hash,$fn,$reciever)
  203. }
  204. elsif ($fn eq "getLog") {
  205. command($hash,'history',$reciever,$a) if($a ne -1);
  206. }
  207. else
  208. { Log3 $hash,4,$msg;
  209. }
  210. return undef;
  211. }
  212. #####################################
  213. sub PW_Undef($$)
  214. {
  215. my ($hash, $arg) = @_;
  216. $firstrun=2;
  217. DevIo_CloseDev($hash);
  218. return undef;
  219. }
  220. #####################################
  221. sub PW_Shutdown($)
  222. {
  223. my ($hash) = @_;
  224. DevIo_CloseDev($hash);
  225. return undef;
  226. }
  227. #NeedsEdit
  228. #####################################
  229. sub PW_Set($@)
  230. {
  231. my ($hash, @a) = @_;
  232. my $msg;
  233. my $name=$a[0]; my $reading= $a[1];
  234. my $n=1;
  235. return "\"set X\" needs at least one argument" if ( @a < 2 );
  236. if ($reading eq "myTest") {
  237. $testcount=0;
  238. } elsif ($reading eq "myTestOff") {
  239. $testcount=-1;
  240. }
  241. elsif(!$PW_sets{$reading}) {
  242. my @cList = keys %PW_sets;
  243. return "Unknown argument $reading, choose one of " . join(" ", @cList);
  244. }
  245. if ($reading eq "Scan_Circles") {
  246. PW_DoInit($hash);
  247. #query_connected_circles($hash);
  248. }
  249. elsif ($reading eq "syncTime") {
  250. foreach ( keys %{ $self->{_plugwise}->{circles} } ) {
  251. $n=$_;
  252. command($hash,'syncTime',$n);
  253. }
  254. }
  255. elsif ($reading eq "reOpen") {
  256. DevIo_CloseDev($hash);
  257. $hash->{ERRCNT} = 0;
  258. $firstrun=0;
  259. my $ret = DevIo_OpenDev( $hash, 0, undef);
  260. RemoveInternalTimer($hash);
  261. InternalTimer(gettimeofday()+2, "PW_GetUpdate", $hash, 0);
  262. return undef;
  263. }
  264. elsif ($reading eq "pwPairForSec") {
  265. # RemoveInternalTimer("pairTimer");
  266. # InternalTimer(gettimeofday()+$value, "PW_Circle_OnOffTimer", 'pairTimer'), 1);
  267. Mywrite( $hash, "000701" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) ,1 );
  268. }
  269. }
  270. sub PW_Get($@)
  271. {
  272. my ( $hash, @a ) = @_;
  273. my $n=1;
  274. return "\"get X\" needs at least one argument" if ( @a < 2 );
  275. my $name = shift @a;
  276. my $opt = shift @a;
  277. if(!$PW_gets{$opt}) {
  278. my @cList = keys %PW_gets;
  279. return "Unknown argument $opt, choose one of " . join(" ", @cList);
  280. }
  281. if($opt eq 'features') {
  282. foreach ( keys %{ $self->{_plugwise}->{circles} } ) {
  283. $n=$_;
  284. command($hash,'feature',$n);
  285. }
  286. return undef;
  287. }
  288. }
  289. #NeedsEdit
  290. #####################################
  291. sub PW_SetState($$$$)
  292. {
  293. my ($hash, $tim, $vt, $val) = @_;
  294. return undef;
  295. }
  296. #####################################
  297. sub PW_DoInit($)
  298. {
  299. my $hash = shift;
  300. Mywrite($hash,"000A",1);
  301. return undef;
  302. }
  303. #####################################
  304. # called from the global loop, when the select for hash->{FD} reports data
  305. sub PW_Read($)
  306. {
  307. my ($hash) = @_;
  308. my $name = $hash->{NAME};
  309. my $char;
  310. my $body;
  311. # read from serial device
  312. my $buf = DevIo_SimpleRead($hash);
  313. my $buf2= $buf;
  314. $buf2=~s/\r\n/\|/g;
  315. return "" if ( !defined($buf) );
  316. $hash->{helper}{buffer} .= $buf;
  317. return unless ( $hash->{helper}{buffer} =~ s/(.+)\r\n// );
  318. do {
  319. my $v=$1;
  320. if ($v=~/\x83?\x05\x05\x03\x03(\w+)/) {
  321. $body = process_response($hash,$1);
  322. if (length $body) {
  323. # if ($body ne undef) {
  324. my $str2=AttrVal($hash->{NAME},"showCom","xyz");
  325. my $showcom=qr/$str2/;
  326. if ($body->{showCom} =~ $showcom) {readingsSingleUpdate($hash,"communication", $body->{showCom},1)}
  327. if ($body->{text} eq "Found circle") {
  328. if ($body->{device} ne "FFFFFFFFFFFFFFFF") {
  329. PW_Write($hash,$body->{short},"getLog",1);
  330. command($hash,'status',$body->{short});
  331. command($hash,'livepower',$body->{short});
  332. } else {$body->{type}=""}
  333. }
  334. if ($body->{text} eq "Connected") {$hash->{"STATE"}="Connected";}
  335. if ($body->{type} =~ /output|power|sense|humtemp|energy|ping/) {
  336. if ($body->{dest} eq "PW_Switch" && $Make2Channels==1) {
  337. my $dest=$body->{short};
  338. $body->{short}=$dest . "_Ch".$body->{val3};
  339. MyRead($hash,$body);
  340. } else {
  341. MyRead($hash,$body);
  342. }
  343. }
  344. if ($body->{dest} eq "none"
  345. && $body->{type} eq "stat"
  346. && $body->{text} ne "ack") {
  347. readingsSingleUpdate($hash,"LastMsg", $body->{text}." - Device: ".$body->{code},1) ;
  348. }
  349. if ($body->{type} eq "err3") {Mywrite($hash, "0026" . $body->{device} ,1);}
  350. if ($body->{type} eq "err") {readingsSingleUpdate($hash,"LastMsg", $body->{text}." - Device: ".$body->{code},1) ;;
  351. my $tm=(time - $LastTime);
  352. if ($tm>600) {$hash->{"ERRCNT"}=0};
  353. $LastTime=time;
  354. $hash->{"ERRCNT"}++;
  355. Log 3,Dumper($body);
  356. if ($hash->{"ERRCNT"} == 50) {
  357. Log3 $hash,3,"$name - Too many Errors, giving up. Use 'get $name reOpen' after correcting the Problem";
  358. DevIo_CloseDev($hash);
  359. $firstrun=2;
  360. DevIo_CloseDev($hash);
  361. Log 3,Dumper($hash->{helper}{buffer});
  362. Log 3,"$name Disconnected...........................................";
  363. $hash->{"STATE"}="Disconnected";
  364. @buffer=();
  365. return undef;
  366. }
  367. }
  368. # }
  369. }
  370. } elsif ($v=~/\x23.*/) {}
  371. elsif ($v=~/[0-9A-F]{16}/){}
  372. else
  373. {
  374. Log3 $hash,3,"Not processed: $v";
  375. }
  376. } while ( $hash->{helper}{buffer} =~ s/(.+)\r\n// );
  377. $self->{_awaiting_stick_response} = 0;
  378. if (@buffer) {real_write($hash)};
  379. return $body;
  380. }
  381. sub PW_Parse($$$$)
  382. {
  383. my ($hash, $iohash, $name, $rmsg) = @_;
  384. my %addvals;
  385. Log3 $hash, 5, "PW_Parse() ".Dumper($rmsg);
  386. $hash->{"MSGCNT"}++;
  387. $hash->{"TIME"} = TimeNow();
  388. $hash->{RAWMSG} = $rmsg;
  389. %addvals = (RAWMSG => $rmsg);
  390. Dispatch($iohash, $rmsg->{'dest'}, \%addvals);
  391. }
  392. #NeedEdit
  393. #####################################
  394. sub PW_Ready($)
  395. {
  396. my ($hash) = @_;
  397. return undef;
  398. return DevIo_OpenDev($hash, 1, "PW_Ready") if($hash->{STATE} eq "disconnected");
  399. # This is relevant for windows/USB only
  400. my $po = $hash->{USBDev};
  401. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  402. return ($InBytes>0);
  403. }
  404. ############# Plugwise-Tools
  405. sub Mywrite {
  406. my ( $hash, $cmd, $cb, $pri ) = @_;
  407. my $packet = "\05\05\03\03" . $cmd . _plugwise_crc($cmd) . "\r\n"; # "\05\05\03\03" .
  408. real_write($hash,$packet,$pri);
  409. }
  410. sub _plugwise_crc {
  411. my ( $data ) = @_;
  412. sprintf( "%04X", crc( $data, 16, 0, 0, 0, 0x1021, 0, 0 ) );
  413. }
  414. sub real_write {
  415. my ( $hash, $str, $pri ) = @_;
  416. if (!$pri) {$pri=0;}
  417. if (defined $str) {
  418. if ($pri==0) {
  419. push @buffer,$str;
  420. } else {
  421. unshift @buffer,$str;
  422. }
  423. }
  424. if ($self->{_awaiting_stick_response} == 1) {
  425. return undef;
  426. }
  427. $str=shift @buffer;
  428. DevIo_SimpleWrite($hash,$str,undef);
  429. $str=~s/\r\n//;
  430. $self->{_awaiting_stick_response} = 1;
  431. my $str2=AttrVal($hash->{NAME},"showCom","xyz");
  432. my $showcom=qr/$str2/;
  433. $str=~s/....([[:xdigit:]]{4})([[:xdigit:]]{4})(.*)/$1 $2 $3/g;
  434. if ($str =~ $showcom) {readingsSingleUpdate($hash,"communication", ">> $str",1)}
  435. readingsSingleUpdate($hash,"Buffer",@buffer,1);
  436. }
  437. # Print the data in hex
  438. sub _hexdump {
  439. my $s = shift;
  440. my $r = unpack 'H*', $s;
  441. $s =~ s/[^ -~]/./g;
  442. $r . ' ' . $s;
  443. }
  444. # This function processes a response received from the USB stick.
  445. #
  446. # In a first step, the ACK response from the stick is handled. This means that the
  447. # communication sequence number is captured, and a new entry is made in the response queue.
  448. #
  449. # Second step, if we receive an error response from the stick, pass this message back
  450. #
  451. # Finally, of course, decode actual useful messages and return their value to the caller
  452. #
  453. # The input to this function is the message with CRC, with the header and trailing part removed
  454. sub process_response {
  455. # my ( $hash, $frame ) = @_;
  456. my $hash= shift;
  457. my $frame= shift;
  458. if (!defined $frame||$frame eq '') {
  459. return undef
  460. }
  461. $frame =~ s/\x83*//g;
  462. Log3 $hash,5, "Response-Processing '$frame'";
  463. my $name=$hash->{NAME};
  464. my %xplmsg = ( schema => 'plugwise.basic', );
  465. # Check if the CRC matches
  466. if (!( _plugwise_crc( substr( $frame, 0, -4 ) ) eq
  467. substr( $frame, -4, 4 )
  468. )
  469. )
  470. {
  471. # Send out notification...
  472. #$xpl->ouch("PLUGWISE: received a frame with an invalid CRC");
  473. $xplmsg{dest}='none';
  474. $xplmsg{type} = 'err';
  475. $xplmsg{text}="Received frame with invalid CRC";
  476. $xplmsg{code} = $frame;
  477. return \%xplmsg;
  478. }
  479. #Switch:
  480. if ($testcount>=0){
  481. $testcount=$testcount+1;
  482. if ($testcount == 41) { $frame = "002403C1000D6F0002907DC90F09412E0006A17000856539070140264E0844C2030000";Log 3,"Insert 0024";}
  483. if ($testcount == 39) { $frame = "0061FFFE000D6F0002907DC90000";Log 3,"Insert 0061";}
  484. if ($testcount == 47) { $frame = "004FFFFE000D6F0002907DC9000000";Log 3,"Insert 004F";}
  485. if ($testcount == 53) { $frame = "0056FFFF000D6F0002907DC90101xxxx";Log 3,"Insert 0056";}
  486. if ($testcount == 56) { $frame = "0056FFFF000D6F0002907DC90100xxxx";Log 3,"Insert keypress";}
  487. if ($testcount == 62) { $frame = "0056FFFF000D6F0002907DC90201xxxx";Log 3,"Insert keypress";}
  488. if ($testcount == 69) { $frame = "0056FFFF000D6F0002907DC90200xxxx";Log 3,"Insert keypress";}
  489. # Circle
  490. # $testcount=$testcount+1;
  491. # if ($testcount == 40) { $frame = "0027527B000D6F0002907DC83F807E5AB5DCBDA03D74A00400000000xxxx";Log 3,"Insert Calibration";}
  492. # if ($testcount == 42) { $frame = "0024527C000D6F0002907DC80F097CA60006A95801856539070140264E0844C202xxxx";Log 3,"Insert Status";}
  493. # if ($testcount == 48) { $frame = "0013527E000D6F0002907DC800000000xxxx";Log 3,"Insert PowerInfo";}
  494. # if ($testcount == 54) { $frame = "0024527C000D6F0002907DC80F097CA60006A95800856539070140264E0844C202xxxx";Log 3,"Insert Status";}
  495. }
  496. # Strip CRC, we already know it is correct
  497. $frame =~ s/(.{4}$)//;
  498. # my $str2=AttrVal($hash->{NAME},"showCom","xyz");
  499. # my $showcom=qr/$str2/;
  500. # if ($frame =~ $showcom) {readingsSingleUpdate($hash,"communication", "<< $frame",1)}
  501. Log3 $hash,4,"Frame after CRC-strip: $frame";
  502. # After a command is sent to the stick, we first receive an 'ACK'. This 'ACK' contains a sequence number that we want to track and that notifies us of errors.
  503. if ( $frame =~ /^0000([[:xdigit:]]{4})([[:xdigit:]]{4})(.*)/ ) {
  504. # ack | seq. nr. || response code |
  505. my $seqnr = $1;
  506. $xplmsg{showCom}="<< 0000 $1 $2 $3";
  507. $xplmsg{dest}='none';
  508. $xplmsg{type} = 'stat';
  509. $xplmsg{code} = $frame;
  510. if ( $2 eq "00C1" ) { $xplmsg{text}="ack"; return \%xplmsg;}
  511. elsif ( $2 eq "00C2" ) { $xplmsg{text}="Error on Ack-Signal"; $xplmsg{type} = 'err';Log 3,Dumper(%xplmsg);return \%xplmsg;}
  512. #Mywrite($hash,"000A");
  513. elsif ( $2 eq "00E1" ) { $xplmsg{text}="Circle out of range"; $xplmsg{text}="ack";return \%xplmsg;}
  514. elsif ( $2 eq "00DE" ) {
  515. if ( $initdone == 0) {return undef};
  516. my $saddr = _addr_l2s($3);
  517. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type};
  518. $xplmsg{type} = 'output';
  519. $xplmsg{text}= 'off';
  520. $xplmsg{code} = $frame;
  521. $xplmsg{device} = $3;
  522. $xplmsg{short} = $saddr;
  523. return \%xplmsg;
  524. }
  525. elsif ( $2 eq "00D8" ) {
  526. if ( $initdone == 0) {return undef};
  527. my $saddr = _addr_l2s($3);
  528. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type};
  529. $xplmsg{type} = 'output';
  530. $xplmsg{text}= 'on';
  531. $xplmsg{code} = $frame;
  532. $xplmsg{device} = $3;
  533. $xplmsg{short} = $saddr;
  534. return \%xplmsg;
  535. }
  536. elsif ( $2 eq "00F9" ) { $xplmsg{text}="Clear group MAC-Table" ; return \%xplmsg;}
  537. elsif ( $2 eq "00FA" ) { $xplmsg{text}="Fill Switch-schedule" ; return \%xplmsg;}
  538. elsif ( $2 eq "00F7" ) { $xplmsg{text}="Request self-removal from network" ; return \%xplmsg;}
  539. elsif ( $2 eq "00F1" ) { $xplmsg{text}="Set broadcast-time interval" ; return \%xplmsg;}
  540. elsif ( $2 eq "00F5" ) { $xplmsg{text}="Set handle off" ; return \%xplmsg;}
  541. elsif ( $2 eq "00F4" ) { $xplmsg{text}="Set handle on" ; return \%xplmsg;}
  542. elsif ( $2 eq "00E6" ) { $xplmsg{text}="Set PN" ; return \%xplmsg;}
  543. elsif ( $2 eq "00F8" ) { $xplmsg{text}="Set powerrecording" ; return \%xplmsg;}
  544. elsif ( $2 eq "00BE" ) { $xplmsg{text}="Set scan-params ACK" ; return \%xplmsg;}
  545. elsif ( $2 eq "00BF" ) { $xplmsg{text}="Set scan-params NACK" ; $xplmsg{type} = 'err';return \%xplmsg;}
  546. elsif ( $2 eq "00B5" ) { $xplmsg{text}="Set sense-boundaries ACK" ; return \%xplmsg;}
  547. elsif ( $2 eq "00B6" ) { $xplmsg{text}="Set sense-boundaries NACK" ; $xplmsg{type} = 'err';return \%xplmsg;}
  548. elsif ( $2 eq "00B3" ) { $xplmsg{text}="Set sense-interval ACK" ; return \%xplmsg;}
  549. elsif ( $2 eq "00B4" ) { $xplmsg{text}="Set sense-interval NACK" ; $xplmsg{type} = 'err';return \%xplmsg;}
  550. elsif ( $2 eq "00F6" ) { $xplmsg{text}="Set sleep-behavior" ; return \%xplmsg;}
  551. elsif ( $2 eq "00E5" ) { $xplmsg{text}="Activate Switch-schedule on" ; return \%xplmsg;}
  552. elsif ( $2 eq "00E4" ) { $xplmsg{text}="Activate Switch-schedule off" ; return \%xplmsg;}
  553. elsif ( $2 eq "00DD" ) { $xplmsg{text}="Allow nodes to join ACK0" ; return \%xplmsg;}
  554. elsif ( $2 eq "00D9" ) { $xplmsg{text}="Allow nodes to join ACK1" ; return \%xplmsg;}
  555. elsif ( $2 eq "00C8" ) { $xplmsg{text}="Bootload aborted" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  556. elsif ( $2 eq "00C9" ) { $xplmsg{text}="Bootload done" ; return \%xplmsg;}
  557. elsif ( $2 eq "00D5" ) { $xplmsg{text}="Cancel read Powermeter-Info Logdata" ; return \%xplmsg;}
  558. elsif ( $2 eq "00C4" ) { $xplmsg{text}="Cannot join network" ; $xplmsg{type} = 'err';return \%xplmsg;}
  559. elsif ( $2 eq "00C3" ) { $xplmsg{text}="Command not allowed" ; $xplmsg{type} = 'err';return \%xplmsg;}
  560. elsif ( $2 eq "00D1" ) { $xplmsg{text}="Done reading Powermeter-Info Logdata" ; return \%xplmsg;}
  561. elsif ( $2 eq "00C0" ) { $xplmsg{text}="Ember stack error" ; $xplmsg{type} = 'err';return \%xplmsg;}
  562. elsif ( $2 eq "00C5" ) { $xplmsg{text}="Exceeding Tableindex" ;$xplmsg{type} = 'err'; return \%xplmsg;}
  563. elsif ( $2 eq "00CF" ) { $xplmsg{text}="Flash erased" ; return \%xplmsg;}
  564. elsif ( $2 eq "00C6" ) { $xplmsg{text}="Flash error" ; $xplmsg{type} = 'err';return \%xplmsg;}
  565. elsif ( $2 eq "00ED" ) { $xplmsg{text}="Group-MAC added" ; return \%xplmsg;}
  566. elsif ( $2 eq "00EF" ) { $xplmsg{text}="Group-MAC not added" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  567. elsif ( $2 eq "00F0" ) { $xplmsg{text}="Group-MAC not removed" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  568. elsif ( $2 eq "00EE" ) { $xplmsg{text}="Group-MAC removed" ; return \%xplmsg;}
  569. elsif ( $2 eq "00E8" ) { $xplmsg{text}="Image activate ACK" ; return \%xplmsg;}
  570. elsif ( $2 eq "00CC" ) { $xplmsg{text}="Image check timeout" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  571. elsif ( $2 eq "00CB" ) { $xplmsg{text}="Image invalid" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  572. elsif ( $2 eq "00CA" ) { $xplmsg{text}="Image valid" ; return \%xplmsg;}
  573. elsif ( $2 eq "00C7" ) { $xplmsg{text}="Node-change accepted" ; return \%xplmsg;}
  574. elsif ( $2 eq "00CD" ) { $xplmsg{text}="Ping timeout 1sec" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  575. elsif ( $2 eq "00EB" ) { $xplmsg{text}="Pingrun busy" ; return \%xplmsg;}
  576. elsif ( $2 eq "00EC" ) { $xplmsg{text}="Pingrun finished" ; return \%xplmsg;}
  577. elsif ( $2 eq "00CE" ) { $xplmsg{text}="Public network-info complete" ; return \%xplmsg;}
  578. elsif ( $2 eq "00D0" ) { $xplmsg{text}="Remote flash erased" ; return \%xplmsg;}
  579. elsif ( $2 eq "00F3" ) { $xplmsg{text}="Reply role changed NOK" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  580. elsif ( $2 eq "00F2" ) { $xplmsg{text}="Reply role changed OK" ; return \%xplmsg;}
  581. elsif ( $2 eq "00E0" ) { $xplmsg{text}="Send switchblock NACK" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  582. elsif ( $2 eq "00DA" ) { $xplmsg{text}="Send calib-params ACK" ; return \%xplmsg;}
  583. elsif ( $2 eq "00E2" ) { $xplmsg{text}="Set relais denied" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  584. elsif ( $2 eq "00DF" ) { $xplmsg{text}="Set RTC-Data ACK" ; return \%xplmsg;}
  585. elsif ( $2 eq "00E7" ) { $xplmsg{text}="Set RTC-Data NACK" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  586. elsif ( $2 eq "00D7" ) { $xplmsg{text}="Set year, month and flashadress DONE" ; return \%xplmsg;}
  587. elsif ( $2 eq "00BD" ) { $xplmsg{text}="Start Light-Calibration started" ; return \%xplmsg;}
  588. elsif ( $2 eq "00E9" ) { $xplmsg{text}="Start Pingrun ACK" ; return \%xplmsg;}
  589. elsif ( $2 eq "00EA" ) { $xplmsg{text}="Stop Pingrun ACK" ; return \%xplmsg;}
  590. elsif ( $2 eq "00DC" ) { $xplmsg{text}="Syncronize NC ACK" ; return \%xplmsg;}
  591. elsif ( $2 eq "00D6" ) { $xplmsg{text}="Timeout Powermeter Logdata" ; $xplmsg{type} = 'err'; return \%xplmsg;}
  592. else {
  593. if ( $initdone == 0) {return undef};
  594. $xplmsg{schema} = 'log.basic';
  595. # Default error message
  596. my $text = 'Received error response: $frame';
  597. my $error = $2;
  598. my $msg_causing_error = $frame;
  599. if ( $msg_causing_error =~ /^0026([[:xdigit:]]{16}$)/ ) {
  600. my $device = _addr_l2s($1);
  601. $text = "No calibration response received for $device";
  602. delete $self->{_plugwise}->{circles}->{$device};
  603. }
  604. Log3 $hash,3, "Received error response: $frame";
  605. $xplmsg{dest}='none';
  606. $xplmsg{type} = 'stat';
  607. $xplmsg{text}= $text;
  608. $xplmsg{code} = $frame . ":" . $error;
  609. return \%xplmsg;
  610. }
  611. }
  612. if ( $frame
  613. =~ /^0011([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})/
  614. )
  615. #0011 0063 000D6F00029014D 8010 14D0D6F00029C512 2BA4DFF4 6D2
  616. # init resp | seq. nr.|| stick MAC addr || don't care || network key || short key
  617. {
  618. $hash->{"STICK_MAC"} = $2;
  619. $hash->{"NET_KEY"} = $4;
  620. $self->{_plugwise}->{stick_MAC} = _addr_l2s( $2 );
  621. $self->{_plugwise}->{network_key} = $4;
  622. $self->{_plugwise}->{short_key} = $5;
  623. $self->{_plugwise}->{connected} = 1;
  624. $firstrun = 1;
  625. Log3 $hash,3, "PLUGWISE: Received a valid response to the init request from the Stick. Connected!";
  626. query_connected_circles($hash);
  627. $xplmsg{dest}='none';
  628. $xplmsg{type} = 'stat';
  629. $xplmsg{text}= 'Connected';
  630. $xplmsg{code} = $frame;
  631. $xplmsg{showCom}="<< 0011 $1 $2 $3 $4 $5";
  632. return \%xplmsg;
  633. }
  634. ##############################################################################
  635. ##### Heartbeat
  636. ##### 0061 ???? Circle-MAC CRC
  637. ##############################################################################
  638. if ( $frame =~ /^0061([[:xdigit:]]{4})([[:xdigit:]]{16})$/ ) {
  639. if ( $initdone == 0) {return undef};
  640. my $saddr = _addr_l2s($2);
  641. unless ($saddr ~~ $self->{_plugwise}->{circles}->{$saddr}) {
  642. Log3 $hash,3, "PLUGWISE:Heartbeat from Unknown Device $2";
  643. $self->{_plugwise}->{circles}->{ _addr_l2s( $2 ) } = {};
  644. Mywrite($hash, "0026" . $2 ,0);
  645. command($hash,'feature',_addr_l2s($2));
  646. $xplmsg{dest}='none';
  647. $xplmsg{type} = 'stat';
  648. $xplmsg{text}= 'Found circle';
  649. $xplmsg{code} = $frame;
  650. $xplmsg{device} = $2;
  651. $xplmsg{showCom}="<< 0061 $1 $2";
  652. $xplmsg{short} = $saddr;
  653. }
  654. return \%xplmsg;
  655. }
  656. # Process the response on a powerinfo request
  657. # powerinfo resp | seq. nr. || Circle MAC || pulse1 || pulse8 | other stuff we don't care about
  658. #0013 0051 000D6F0000994CAA 0000 FFFF 00000000 FFFFF DB9000D
  659. if ( $frame
  660. =~ /^0013([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{4})/
  661. )
  662. {
  663. my $saddr = _addr_l2s($2);
  664. my $pulse1 = $3;
  665. my $pulse8 = $4;
  666. # Assign the values to the data hash
  667. $self->{_plugwise}->{circles}->{$saddr}->{pulse1} = $pulse1;
  668. $self->{_plugwise}->{circles}->{$saddr}->{pulse8} = $pulse8;
  669. $xplmsg{showCom}="<< 0013 $1 $2 $3 $4";
  670. if ($4 eq "FFFF") {
  671. $xplmsg{dest}='none';
  672. $xplmsg{type} = 'ignore';
  673. $xplmsg{text}= 'Ignored Spike';
  674. $xplmsg{code} = $frame;
  675. $xplmsg{device} = $2;
  676. $xplmsg{short} = $saddr;
  677. return \%xplmsg;
  678. }
  679. # Ensure we have the calibration info before we try to calc the power,
  680. # if we don't have it, return an error reponse
  681. if ( !defined $self->{_plugwise}->{circles}->{$saddr}->{gainA} ) {
  682. $xplmsg{dest}='none';
  683. $xplmsg{type} = 'err3';
  684. $xplmsg{text}= 'Report power failed, calibration data not retrieved yet';
  685. $xplmsg{code} = $frame;
  686. $xplmsg{device} = $2;
  687. $xplmsg{short} = $saddr;
  688. return \%xplmsg;
  689. }
  690. # Calculate the live power
  691. my ( $pow1, $pow8 ) = _calc_live_power($hash,$saddr);
  692. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type};
  693. $xplmsg{type} = 'power';
  694. $xplmsg{text}= ' ';
  695. $xplmsg{code} = $frame;
  696. $xplmsg{device} = $2;
  697. $xplmsg{short} = $saddr;
  698. $xplmsg{val1} = $pow1;
  699. $xplmsg{val2} = $pow8;
  700. $xplmsg{unit1} = 'W';
  701. $xplmsg{unit2} = 'W';
  702. return \%xplmsg;
  703. }
  704. # Process the response on a query known circles command
  705. # circle query resp| seq. nr. || Circle+ MAC || Circle MAC on || memory position
  706. if ( $frame
  707. =~ /^0019([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{2})$/
  708. )
  709. {
  710. $xplmsg{showCom}="<< 0019 $1 $2 $3 $4";
  711. my $nr=sprintf( "%02X", AttrVal($hash->{NAME},"circlecount",50) -1);
  712. if ($4 eq $nr) {Log 3,$hash->{NAME} . "Init done, found ". (keys(%{$self->{_plugwise}->{circles}})) . " devices";$initdone=1}
  713. if ( $3 ne "FFFFFFFFFFFFFFFF" ) {
  714. $self->{_plugwise}->{circles}->{ _addr_l2s( $3 ) } = {};
  715. Mywrite($hash, "0026" . $3 ,0);
  716. }
  717. $xplmsg{schema} = 'log.basic';
  718. $xplmsg{dest}='none';
  719. $xplmsg{type} = 'stat';
  720. $xplmsg{text}='none';
  721. $xplmsg{text}= 'Found circle' if (defined $self->{_plugwise}->{circles}->{_addr_l2s($3)});
  722. $xplmsg{code} = $frame;
  723. $xplmsg{device} = $3;
  724. $xplmsg{short} = _addr_l2s($3);
  725. return \%xplmsg;
  726. }
  727. # Process the response on a status request
  728. # status response | seq. nr. || Circle+ MAC || year,mon, min || curr_log_addr || powerstate
  729. if ( $frame
  730. #0024 0050 000D6F0000994CAA 0F08595B 000440B 80 18 565390701402 24E0844C2 02
  731. =~ /^0024([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{12})([[:xdigit:]]{8})([[:xdigit:]]{2})/
  732. )
  733. {
  734. my $saddr = _addr_l2s($2);
  735. my $onoff = $5 eq '00' ? 'off' : 'on';
  736. my $current = $5 eq '00' ? 'LOW' : 'HIGH';
  737. $self->{_plugwise}->{circles}->{$saddr}->{onoff} = $onoff;
  738. if(!exists $PWType{$9}) {
  739. Log3 $name, 2, "$name: Autocreate: unknown familycode '$9' found. Please report this!";
  740. next;
  741. } else {
  742. # Log 3,"Device $saddr now is $PWType{$9}";
  743. $self->{_plugwise}->{circles}->{$saddr}->{type} = $PWType{$9};
  744. }
  745. # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Circle' if (hex($9) <= 2);
  746. # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Switch' if (hex($9) == 3 || hex($9) == 4);
  747. # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Sense' if (hex($9) == 5);
  748. # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Scan' if (hex($9) == 6);
  749. $self->{_plugwise}->{circles}->{$saddr}->{curr_logaddr} = ( hex($4) - 278528 ) / 8;
  750. Log 3, "Unknown Device-Code: $frame" if (!defined $self->{_plugwise}->{circles}->{$saddr}->{type});
  751. Log 3, "Unknown Circle: $saddr" if (!defined $self->{_plugwise}->{circles}->{$saddr});
  752. my $circle_date_time = _tstamp2time($hash,$3);
  753. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type};
  754. $xplmsg{type} = 'output';
  755. $xplmsg{text}= $onoff;
  756. $xplmsg{code} = $frame;
  757. $xplmsg{device} = $2;
  758. $xplmsg{short} = $saddr;
  759. $xplmsg{showCom}="<< 0024 $1 $2 $3 $4 $5 $6 $7 $8 $9";
  760. $xplmsg{val1} = $self->{_plugwise}->{circles}->{$saddr}->{curr_logaddr};
  761. $xplmsg{val2} = $circle_date_time;
  762. $xplmsg{val3} = 0;
  763. return \%xplmsg;
  764. }
  765. # Process the response on a calibration request
  766. if ( $frame
  767. =~ /^0027([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{8})$/
  768. )
  769. {
  770. # calibration resp | seq. nr. || Circle+ MAC || gainA || gainB || offtot || offruis
  771. #print "Received for $2 calibration response!\n";
  772. my $saddr = _addr_l2s($2);
  773. #print "Short address = $saddr\n";
  774. Log3 $hash,5,"PLUGWISE: Received calibration reponse for circle $saddr";
  775. $self->{_plugwise}->{circles}->{$saddr}->{gainA}
  776. = _hex2float($3);
  777. $self->{_plugwise}->{circles}->{$saddr}->{gainB}
  778. = _hex2float($4);
  779. $self->{_plugwise}->{circles}->{$saddr}->{offtot}
  780. = _hex2float($5);
  781. $self->{_plugwise}->{circles}->{$saddr}->{offruis}
  782. = _hex2float($6);
  783. Log3 $hash,5,"$2 - Calib: $3 - $4 - $5 - $6";
  784. $lastcircle=$saddr;
  785. $xplmsg{dest}='none';
  786. $xplmsg{type} = 'stat';
  787. $xplmsg{text}= 'Calibration-Info received.';
  788. $xplmsg{showCom}="<< 0027 $1 $2 $3 $4 $5 $6";
  789. $xplmsg{code} = $frame;
  790. $xplmsg{device} = $2;
  791. $xplmsg{short} = $saddr;
  792. return \%xplmsg;
  793. }
  794. # Process a Feature-Request
  795. if ( $frame
  796. =~ /^0060([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})/
  797. )
  798. {
  799. my $s_id = _addr_l2s($2);
  800. $xplmsg{dest}='none';
  801. $xplmsg{showCom}="<< 0060 $1 $2 $3";
  802. $xplmsg{type} = 'stat';
  803. $xplmsg{text}= 'Features';
  804. $xplmsg{code} = $frame;
  805. $xplmsg{device} = $2;
  806. $xplmsg{short} = $s_id;
  807. $xplmsg{val1} = $3;
  808. Log3 $hash,5,Dumper(%xplmsg);
  809. Log3 $hash,3, "PLUGWISE: Features for $s_id are $3";
  810. return \%xplmsg;
  811. }
  812. # Process the response of TempHum-Sensor
  813. if ( $frame
  814. =~ /^0105([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{4})/
  815. )
  816. {
  817. my $s_id = _addr_l2s($2);
  818. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type};
  819. $xplmsg{type} = 'humtemp';
  820. $xplmsg{showCom}="<< 0105 $1 $2 $3 $4";
  821. $xplmsg{text}= ' ';
  822. $xplmsg{code} = $frame;
  823. $xplmsg{device} = $2;
  824. $xplmsg{short} = $s_id;
  825. $xplmsg{val1} = (hex($3)-3145)/524.30;
  826. $xplmsg{val2} = (hex($4)-17473)/372.90;
  827. $xplmsg{unit1} = 'h';
  828. $xplmsg{unit2} = 'C';
  829. Log3 $hash,5,Dumper(%xplmsg);
  830. Log3 $hash,5, "PLUGWISE: Temperature for $s_id set";
  831. return \%xplmsg;
  832. }
  833. ## RemoveNode response
  834. if ( $frame
  835. =~ /^001D([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{2})/
  836. )
  837. #001D 1026 000D6F00029C5122 000D6F00029C5122 00
  838. {
  839. Log3 $hash,3,"Removed Node $3 (Index $4) from Network.";
  840. $xplmsg{showCom}="<< 0000 $1 $2 $3 $4";
  841. $xplmsg{dest}='none';
  842. $xplmsg{type} = 'stat';
  843. $xplmsg{code} = $frame;
  844. $xplmsg{text} = 'Removed Node $3 (Index $4) from Network.';
  845. return \%xplmsg;
  846. }
  847. ## Keypress on Switch
  848. #0056FFFF000D6F0002769C7D0101
  849. if ( $frame
  850. =~ /^0056([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})([[:xdigit:]]{2})/
  851. )
  852. {
  853. Log3 $hash,4,"Keypress";
  854. my $s_id = _addr_l2s($2);
  855. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type};
  856. $xplmsg{type} = 'sense';
  857. $xplmsg{showCom}="<< 0056 $1 $2 $3 $4";
  858. $xplmsg{text}= ' ';
  859. $xplmsg{code} = $frame;
  860. $xplmsg{device} = $2;
  861. $xplmsg{short} = $s_id;
  862. $xplmsg{val1} = hex($4);
  863. if ($Make2Channels==0) {
  864. $xplmsg{val1} = hex($4); #+((hex($3)-1)<<1) if ($self->{_plugwise}->{circles}->{$s_id}->{type} eq "PW_Switch");
  865. $xplmsg{val2} = (hex($3)-1)<<1;
  866. } else {
  867. $xplmsg{val3} = hex($3);
  868. }
  869. Log3 $hash,5,Dumper(%xplmsg);
  870. Log3 $hash,5, "PLUGWISE: Motion-Signal detected";
  871. return \%xplmsg;
  872. }
  873. # Ping-Response
  874. if ( $frame
  875. =~ /^000E([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{4})/
  876. )
  877. {
  878. if ( $initdone == 0) {return undef};
  879. Log3 $hash,4,"ping";
  880. my $s_id = _addr_l2s($2);
  881. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type};
  882. $xplmsg{type} = 'ping';
  883. $xplmsg{showCom}="<< 004F $1 $2 $3 $4 $5";
  884. $xplmsg{text}= ' ';
  885. $xplmsg{code} = $frame;
  886. $xplmsg{device} = $2;
  887. $xplmsg{short} = $s_id;
  888. $xplmsg{val1} = hex($3);
  889. $xplmsg{val2} = hex($4);
  890. $xplmsg{val3} = hex($5);
  891. Log3 $hash,5,Dumper(%xplmsg);
  892. return \%xplmsg;
  893. }
  894. # Pushbuttons
  895. if ( $frame
  896. =~ /^004F([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})/
  897. )
  898. {
  899. if ( $initdone == 0) {return undef};
  900. Log3 $hash,4,"Keypress";
  901. my $s_id = _addr_l2s($2);
  902. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type};
  903. $xplmsg{type} = 'press';
  904. $xplmsg{showCom}="<< 004F $1 $2 $3";
  905. $xplmsg{text}= ' ';
  906. $xplmsg{code} = $frame;
  907. $xplmsg{device} = $2;
  908. $xplmsg{short} = $s_id;
  909. $xplmsg{val1} = hex($3);
  910. Log3 $hash,5,Dumper(%xplmsg);
  911. return \%xplmsg;
  912. }
  913. # Process the response on a historic buffer readout
  914. if ( $frame
  915. =~ /^0049([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{8})$/
  916. )
  917. {
  918. # history resp | seq. nr. || Circle+ MAC || info 1 || info 2 || info 3 || info 4 || address
  919. my $s_id = _addr_l2s($2);
  920. my $log_addr = ( hex($7) - 278528 ) / 8;
  921. $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7";
  922. #print "Received history response for $2 and address $log_addr!\n";
  923. # Assign the values to the data hash
  924. $self->{_plugwise}->{circles}->{$s_id}->{history}->{logaddress}
  925. = $log_addr;
  926. $self->{_plugwise}->{circles}->{$s_id}->{history}->{info1} = $3;
  927. $self->{_plugwise}->{circles}->{$s_id}->{history}->{info2} = $4;
  928. $self->{_plugwise}->{circles}->{$s_id}->{history}->{info3} = $5;
  929. $self->{_plugwise}->{circles}->{$s_id}->{history}->{info4} = $6;
  930. # Ensure we have the calibration info before we try to calc the power,
  931. # if we don't have it, return an error reponse
  932. if ( !defined $self->{_plugwise}->{circles}->{$s_id}->{gainA} ) {
  933. #$xpl->ouch("Cannot report the power, calibration data not received yet for $s_id\n");
  934. $xplmsg{dest}='none';
  935. $xplmsg{type} = 'err';
  936. $xplmsg{text}= 'Report power failed, calibration data not retrieved yet';
  937. $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7";
  938. $xplmsg{code} = $frame;
  939. $xplmsg{device} = $2;
  940. $xplmsg{short} = $s_id;
  941. return \%xplmsg;
  942. }
  943. my ( $tstamp, $energy ) = _report_history($hash,$s_id);
  944. # If the timestamp is no good, we tried to retrieve a field that contains no valid data, generate an error response
  945. if ( $tstamp eq "000000000000" ) {
  946. #$xpl->ouch("Cannot report the power for interval $log_addr of circle $s_id, it is in the future\n");
  947. $xplmsg{dest}='none';
  948. $xplmsg{type} = 'err';
  949. $xplmsg{text}= 'Report power failed, no valid data in time interval';
  950. $xplmsg{code} = $frame;
  951. $xplmsg{device} = $2;
  952. $xplmsg{short} = $s_id;
  953. $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7";
  954. return \%xplmsg;
  955. }
  956. $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type};
  957. $xplmsg{type} = 'energy';
  958. $xplmsg{text}= ' ';
  959. $xplmsg{code} = $frame;
  960. $xplmsg{device} = $2;
  961. $xplmsg{short} = $s_id;
  962. $xplmsg{val1} = $energy;
  963. $xplmsg{val2} = $tstamp;
  964. $xplmsg{val3} = $log_addr;
  965. $xplmsg{unit1} = 'kWh';
  966. Log3 $hash,5,Dumper(%xplmsg);
  967. Log3 $hash,5, "PLUGWISE: Historic energy for $s_id [$log_addr] is $energy kWh on $tstamp";
  968. return \%xplmsg;
  969. }
  970. # We should not get here unless we receive responses that are not implemented...
  971. #$xpl->ouch("Received unknown response: '$frame'");
  972. Log3 $hash,3,"PLUGWISE: Unknown Frame received: $frame - Please report this";
  973. $xplmsg{dest}='none';
  974. $xplmsg{type} = 'err2';
  975. $xplmsg{text}= 'Unknown Frame received';
  976. $xplmsg{code} = $frame;
  977. return \%xplmsg;
  978. }
  979. sub command {
  980. my ( $hash, $command, $target, $parameter ) = @_;
  981. Log3 $hash,5,"Command=$command - Target=$target";
  982. if ( !defined($command) || !defined($target) ) {
  983. Log3 $hash,3,"A command to the stick needs a command and a target ID as parameter";
  984. return 0;
  985. }
  986. #Log 3,"Set $self->{_plugwise}->{circles}->{$target}->{type} $target $command to $parameter";
  987. my $packet = "";
  988. my $pri=0;
  989. if ( defined $target ) {
  990. # Commands that target a specific device might need to be sent multiple times
  991. # if multiple devices are defined
  992. my $circle = uc($target);
  993. if ( $command =~ /(on|off)/ ) {
  994. if ($self->{_plugwise}->{circles}->{$circle}->{type} eq "PW_Circle") {
  995. $packet = "0017" . _addr_s2l($circle) . ($1 eq 'on' ? '01' : '00');
  996. } elsif($self->{_plugwise}->{circles}->{$circle}->{type} eq "PW_Switch"){
  997. # Log 3,"Set Switch $circle $parameter to $1";
  998. if ($parameter eq "left") {
  999. $packet = "0017" . _addr_s2l($circle) . "01" . $1 eq 'on' ? '01' : '00';
  1000. } elsif ($parameter eq "right") {
  1001. $packet = "0017" . _addr_s2l($circle) . "02" . $1 eq 'on' ? '01' : '00';
  1002. }
  1003. }
  1004. $pri=1;
  1005. }
  1006. elsif ($command eq "feature") {
  1007. $packet = "005F" . _addr_s2l($circle);
  1008. }
  1009. elsif ($command eq "ping") {
  1010. $packet = "000D" . _addr_s2l($circle);
  1011. }
  1012. elsif ( $command eq 'syncTime' ) {
  1013. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  1014. $packet = "0016" .
  1015. _addr_s2l($circle) .
  1016. sprintf( "%02X", $year-100) .
  1017. sprintf( "%02X", $mon) .
  1018. sprintf( "%04X", ($mday * 24 * 60) + ($hour * 60) + $min) .
  1019. "FFFFFFFF" .
  1020. sprintf( "%02X", $hour) . sprintf( "%02X", $min) . sprintf( "%02X", $sec) . sprintf( "%02X", $wday) ;
  1021. # Log3 $hash,3,"syncTime-Frame: $packet";
  1022. # return (undef);
  1023. }
  1024. elsif ( $command eq 'status' ) {
  1025. $packet = "0023" . _addr_s2l($circle);
  1026. }
  1027. elsif ($command eq 'removeNode') {
  1028. $packet = "001C" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) . _addr_s2l($circle);
  1029. }
  1030. elsif ( $command eq 'livepower' ) {
  1031. # Ensure we have the calibration readings before we send the read command
  1032. # because the processing of the response of the read command required the
  1033. # calibration readings output to calculate the actual power
  1034. # Log3 $hash,3,Dumper( $self->{_plugwise});
  1035. if (!defined(
  1036. $self->{_plugwise}->{circles}->{$circle}->{offruis}
  1037. )
  1038. )
  1039. {
  1040. my $longaddr = _addr_s2l($circle);
  1041. Mywrite( $hash,"0026" . $longaddr ,0)
  1042. ; #, "Request calibration info");
  1043. }
  1044. $packet = "0012" . _addr_s2l($circle);
  1045. }
  1046. elsif ( $command eq 'history' ) {
  1047. # Ensure we have the calibration readings before we send the read command
  1048. # because the processing of the response of the read command required the
  1049. # calibration readings output to calculate the actual power
  1050. if (!defined(
  1051. $self->{_plugwise}->{circles}->{$circle}->{offruis}
  1052. )
  1053. )
  1054. {
  1055. my $longaddr = _addr_s2l($circle);
  1056. Mywrite($hash, "0026" . $longaddr ,0)
  1057. ; #, "Request calibration info");
  1058. }
  1059. if ( !defined $parameter ) {
  1060. Log3 $hash,3,"The 'history' command needs both a Circle ID and an address to read...";
  1061. return 0;
  1062. }
  1063. Log3 $hash,5,"requesting Log for $parameter";
  1064. my $address = ($parameter * 8) + 278528;#* 8 + 278528;
  1065. $packet
  1066. = "0048"
  1067. . _addr_s2l($circle)
  1068. . sprintf( "%08X", $address );
  1069. Log3 $hash,5,"Write command: $packet";
  1070. }
  1071. else {
  1072. Log3 $hash,3,"Received invalid command '$command'";
  1073. return 0;
  1074. }
  1075. # Send the packet to the stick!
  1076. Log3 $hash,5,"Write command: $packet";
  1077. Mywrite($hash,$packet,$pri) if ( defined $packet );
  1078. }
  1079. }
  1080. # Interrogate the network coordinator (Circle+) for all connected Circles
  1081. # This sub will generate the requests, and then the response parser function
  1082. # will generate a hash with all known circles
  1083. # When a circle is detected, a calibration request is sent to ge the relevant info
  1084. # required to calculate the power information.
  1085. # Circle info goes into a global hash like this:
  1086. # $object->{_plugwise}->{circles}
  1087. # A single circle entry contains the short id and the following info:
  1088. # short_id => { gainA => xxx,
  1089. # gainB => xxx,
  1090. # offtot => xxx,
  1091. # offruis => xxx }
  1092. sub query_connected_circles {
  1093. my ($hash) = @_;
  1094. # In this code we will scan all connected circles to be able to add them to the $self->{_plugwise}->{circles} hash
  1095. my $index = 0;
  1096. Log3 $hash,5,$hash->{NAME} . " - Looking for Circles.....";
  1097. # Interrogate the Circle+ and add its info into the circles hash
  1098. $self->{_plugwise}->{coordinator_MAC}
  1099. = _addr_l2s( $self->{_plugwise}->{network_key} );
  1100. $self->{_plugwise}->{circles} = {}; # Reset known circles hash
  1101. $self->{_plugwise}->{circles}->{ _addr_l2s( $self->{_plugwise}->{network_key} ) }
  1102. = {}; # Add entry for Circle+
  1103. Mywrite( $hash, "0026" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) ,0);
  1104. # Interrogate the first x connected devices
  1105. while ( $index < AttrVal($hash->{NAME},"circlecount",50) ) {
  1106. my $strindex = sprintf( "%02X", $index++ );
  1107. my $packet
  1108. = "0018"
  1109. . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} )
  1110. . $strindex;
  1111. Mywrite($hash,$packet,0); #, "Query connected device $strindex");
  1112. }
  1113. return;
  1114. }
  1115. # Convert the long Circle address notation to short
  1116. sub _addr_l2s {
  1117. my ( $address ) = @_;
  1118. my $saddr = substr( $address, -8, 8 );
  1119. # We will return at least 6 bytes, more if required
  1120. # This is to keep compatibility with existing code that only supports 6 byte short addresses
  1121. return sprintf( "%06X", hex($saddr) );
  1122. }
  1123. # Convert the short Circle address notation to long
  1124. sub _addr_s2l {
  1125. my ( $address ) = @_;
  1126. # Log 3,Dumper(caller) if ($address eq 0xffffffff);
  1127. return "000D6F00" . sprintf( "%08X", hex($address) );
  1128. }
  1129. # Convert hex values to float for power readout
  1130. sub _hex2float {
  1131. my ( $hexstr ) = @_;
  1132. my $floater = unpack( 'f', reverse pack( 'H*', $hexstr ) );
  1133. return $floater;
  1134. }
  1135. sub _report_history {
  1136. my ( $hash, $id ) = @_;
  1137. # Get the first data entry
  1138. my $data = $self->{_plugwise}->{circles}->{$id}->{history}->{info1};
  1139. my $energy = 0;
  1140. my $tstamp = 0;
  1141. if ( $data =~ /^([[:xdigit:]]{8})([[:xdigit:]]{8})$/ ) {
  1142. # Calculate Wh
  1143. my $corrected_pulses = _pulsecorrection( $hash,$id, hex($2) );
  1144. $energy = $corrected_pulses / 3600 / 468.9385193 * 1000;
  1145. $tstamp = _tstamp2time($hash,$1);
  1146. # Round to 1 Wh
  1147. $energy = sprintf($attr{$hash->{NAME}}{WattFormat},$energy);
  1148. # Report kWh
  1149. # $energy = $energy / 1000;
  1150. #print "info1 date: $tstamp, energy $energy kWh\n";
  1151. }
  1152. return ( $tstamp, $energy );
  1153. }
  1154. # Convert a Plugwise timestamp to a human-readable format
  1155. sub _tstamp2time {
  1156. my ( $hash, $tstamp ) = @_;
  1157. # Return empty time on empty timestamp
  1158. return "000000000000" if ( $tstamp eq "FFFFFFFF" );
  1159. # Convert
  1160. if ( $tstamp =~ /([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{4})/ ) {
  1161. my $circle_date = sprintf( "%04i-%02i-%02i",
  1162. 2000 + hex($1),
  1163. hex($2), int( hex($3) / 60 / 24 ) + 1 );
  1164. my $circle_time = hex($3) % ( 60 * 24 );
  1165. my $circle_hours = int( $circle_time / 60 );
  1166. my $circle_minutes = $circle_time % 60;
  1167. $circle_time = sprintf( " %02i:%02i", $circle_hours, $circle_minutes );
  1168. return $circle_date . $circle_time;
  1169. }
  1170. else {
  1171. return "000000000000";
  1172. }
  1173. }
  1174. # Calculate the live power consumption from the last report.
  1175. sub _calc_live_power {
  1176. my ( $hash, $id ) = @_;
  1177. #my ($pulse1, $pulse8) = $self->pulsecorrection($id);
  1178. my $pulse1 = _pulsecorrection( $hash,$id,
  1179. hex( $self->{_plugwise}->{circles}->{$id}->{pulse1} ) );
  1180. my $pulse8 = _pulsecorrection( $hash,$id,
  1181. hex( $self->{_plugwise}->{circles}->{$id}->{pulse8} ) /8 );
  1182. my $live1 = $pulse1 * 1000 / 468.9385193;
  1183. my $live8 = $pulse8 * 1000 / 468.9385193;
  1184. # Round
  1185. $live1 = sprintf($attr{$hash->{NAME}}{WattFormat},$live1);
  1186. $live8 = sprintf($attr{$hash->{NAME}}{WattFormat},$live8);
  1187. return ( $live1, $live8 );
  1188. }
  1189. # Correct the reported number of pulses based on the calibration values
  1190. sub _pulsecorrection {
  1191. my ( $hash, $id, $pulses ) = @_;
  1192. # Get the calibration values for the circle
  1193. my $offnoise = $self->{_plugwise}->{circles}->{$id}->{offruis};
  1194. my $offtot = $self->{_plugwise}->{circles}->{$id}->{offtot};
  1195. my $gainA = $self->{_plugwise}->{circles}->{$id}->{gainA};
  1196. my $gainB = $self->{_plugwise}->{circles}->{$id}->{gainB};
  1197. # Correct the pulses with the calibration data
  1198. my $out
  1199. = ( ( $pulses + $offnoise ) ^ 2 ) * $gainB
  1200. + ( ( $pulses + $offnoise ) * $gainA )
  1201. + $offtot;
  1202. # Never report negative values, can happen with really small values
  1203. $out = 0 if ( $out < 0 );
  1204. return $out;
  1205. }
  1206. "Cogito, ergo sum.";
  1207. =pod
  1208. =item device
  1209. =item summary Module for controling Plugwise-Devices
  1210. =item summary_DE Modul für das Plugwise-System
  1211. =begin html
  1212. <a name="Plugwise"></a>
  1213. <h3>Plugwise</h3>
  1214. <ul>
  1215. This module is for the Plugwise-System.
  1216. <br>
  1217. Note: this module requires the Device::SerialPort or Win32::SerialPort module
  1218. if the devices is connected via USB or a serial port.
  1219. Also needed: digest:CRC
  1220. You can install these modules using CPAN.
  1221. <br><br>
  1222. <b>Define</b>
  1223. <ul>
  1224. <code>define &lt;name&gt; Plugwise &lt;device&gt; </code><br>
  1225. </ul>
  1226. <br>
  1227. &lt;device&gt; specifies the serial port to communicate with the Plugwise-Stick.
  1228. Normally on Linux the device will be named /dev/ttyUSBx, where x is a number.
  1229. For example /dev/ttyUSB0. Please note that the Plugwise-Stick normally operates at 115200 baud. You may specify the baudrate used after the @ char.<br>
  1230. <br>
  1231. Example: <br>
  1232. <code>define myPlugwise Plugwise /dev/ttyPlugwise@115200</code>
  1233. <br>
  1234. </ul>
  1235. <br>
  1236. <a name="PLUGWISEset"></a>
  1237. <b>Set</b>
  1238. <ul>
  1239. <code>Scan_Circles</code>
  1240. <ul>
  1241. Initiates a scan for new devices and defines them.
  1242. </ul><br><br>
  1243. <code>syncTime</code>
  1244. <ul>
  1245. Syncs all reachable devices to the system-time.
  1246. </ul><br><br>
  1247. <code>reOpen</code>
  1248. <ul>
  1249. Closes and reopens the serial-Port. (useful in case of to many Errors)
  1250. </ul><br><br>
  1251. </ul>
  1252. <br><br>
  1253. <b>Attributes</b>
  1254. <ul>
  1255. <code>circlecount</code><br>
  1256. <ul>
  1257. Max. Number of Circles to be found by the Scan-Command
  1258. <br><br>
  1259. </ul>
  1260. <code>interval</code><br>
  1261. <ul>standard polling-interval for new Circles
  1262. </ul><br><br>
  1263. <code>autosync</code><br>
  1264. <ul>Sends every n seconds a SyncTime to each device
  1265. </ul><br><br>
  1266. <code>WattFormat</code><br>
  1267. <ul>A string representing the format of the power-readings.
  1268. If not defined, it defaults to %0.f
  1269. </ul><br><br>
  1270. <code>showCom</code><br>
  1271. <ul>Writes the complete communication matching a RegEx into the reading "communication"
  1272. (can be viewed in EventMonitor or used with a FileLog)
  1273. </ul><br><br>
  1274. <br>
  1275. </ul>
  1276. =end html
  1277. =begin html_DE
  1278. <a name="Plugwise"></a>
  1279. <h3>Plugwise</h3>
  1280. <ul>
  1281. Modul für das Plugwise-System.
  1282. <br>
  1283. Achtung: Dieses Modul benötigt folgende Perl-Module:
  1284. <ul><li>Device::SerialPort oder Win32::SerialPort</li>
  1285. <li>digest:CRC</li></ul>
  1286. <br><br>
  1287. <b>Define</b>
  1288. <ul>
  1289. <code>define &lt;name&gt; Plugwise &lt;device&gt; </code><br>
  1290. </ul>
  1291. <br>
  1292. &lt;device&gt; Gibt den COM-Port des Plugwise-Stick an.
  1293. Unter Linux ist dies im Normalfall /dev/ttyUSBx, wobei x eine fortlaufende Nummer ist. (zB /dev/ttyUSB0)
  1294. Wobei es unter Linux sinnvoller ist, den Port mittels UDEV-Regeln oder mittels /dev/by-id/ anzugeben.
  1295. Der Plugwise-Stick läuft fix auf 115200 Baud<br>
  1296. <br>
  1297. Beispiel: <br>
  1298. <code>define myPlugwise Plugwise /dev/ttyPlugwise</code>
  1299. <br>
  1300. </ul>
  1301. <br>
  1302. <a name="PLUGWISEset"></a>
  1303. <b>Set</b>
  1304. <ul>
  1305. <code>Scan_Circles</code>
  1306. <ul>
  1307. Startet eine Suche nach neuen Geräten und legt diese per Autocreate an.
  1308. </ul><br><br>
  1309. <code>syncTime</code>
  1310. <ul>
  1311. Syncronisiert die internen RTCs der Geräte mit der aktuellen Systemzeit.
  1312. </ul><br><br>
  1313. <code>reOpen</code>
  1314. <ul>
  1315. Öffnet den COM-Port neu (zB bei zu vielen Fehlern, nach deren Behebung)
  1316. </ul><br><br>
  1317. </ul>
  1318. <br><br>
  1319. <b>Attribute</b>
  1320. <ul>
  1321. <code>circlecount</code><br>
  1322. <ul>
  1323. Maximale Anzahl der Geräte, nach denengesucht wird.
  1324. <br><br>
  1325. </ul>
  1326. <code>interval</code><br>
  1327. <ul>Standard-Abfrageintervall der Circles
  1328. </ul><br><br>
  1329. <code>autosync</code><br>
  1330. <ul>Sendet alle >n< Sekunden ein "syncTime" an alle Geräte
  1331. </ul><br><br>
  1332. <code>WattFormat</code><br>
  1333. <ul>String, mit welchem die Power-Readings formatiert werden
  1334. Standard: %0.f
  1335. </ul><br><br>
  1336. <code>showCom</code><br>
  1337. <ul>Schreibt die gesamte Kommunikation (gefiltern nach >regEx<) in das Reading "communication"
  1338. (Am besten mit FileLog oder dem Eventmonitor anzusehen)
  1339. </ul><br><br>
  1340. <br>
  1341. </ul>
  1342. =end html
  1343. =cut