37_NinjaPiCrust.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. # $Id: $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Time::HiRes qw(gettimeofday);
  6. #use JSON;
  7. ##########################
  8. # This block is only needed when FileLog is checked outside fhem
  9. #
  10. sub Log3($$$);
  11. sub Log($$);
  12. sub RemoveInternalTimer($);
  13. use vars qw(%attr);
  14. use vars qw(%defs);
  15. use vars qw(%modules);
  16. use vars qw($readingFnAttributes);
  17. use vars qw($reread_active);
  18. ##########################
  19. sub NinjaPiCrust_Attr(@);
  20. sub NinjaPiCrust_Clear($);
  21. sub NinjaPiCrust_HandleWriteQueue($);
  22. sub NinjaPiCrust_Parse($$$$);
  23. sub NinjaPiCrust_Read($);
  24. sub NinjaPiCrust_ReadAnswer($$$$);
  25. sub NinjaPiCrust_Ready($);
  26. sub NinjaPiCrust_Write($$);
  27. sub NinjaPiCrust_SimpleWrite(@);
  28. my $dl = 4; # debug level for log - and yes, it's dirty..
  29. my $clientsNinjaPiCrust = ":NINJA:";
  30. my %matchListNinjaPiCrust = (
  31. "1:NINJA" => "^.+"
  32. );
  33. my %RxListNinjaPiCrust = (
  34. "HX2272" => "Or",
  35. "FS20" => "Or",
  36. "LaCrosse" => "Fr01",
  37. );
  38. sub
  39. NinjaPiCrust_Initialize($)
  40. {
  41. my ($hash) = @_;
  42. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  43. # Provider
  44. $hash->{ReadFn} = "NinjaPiCrust_Read";
  45. $hash->{WriteFn} = "NinjaPiCrust_Write";
  46. $hash->{ReadyFn} = "NinjaPiCrust_Ready";
  47. # Normal devices
  48. $hash->{DefFn} = "NinjaPiCrust_Define";
  49. $hash->{FingerprintFn} = "NinjaPiCrust_Fingerprint";
  50. $hash->{UndefFn} = "NinjaPiCrust_Undef";
  51. $hash->{GetFn} = "NinjaPiCrust_Get";
  52. $hash->{SetFn} = "NinjaPiCrust_Set";
  53. $hash->{AttrFn} = "NinjaPiCrust_Attr";
  54. $hash->{AttrList} = "Clients MatchList"
  55. ." DebounceTime BeepLong BeepShort BeepDelay"
  56. ." tune " . join(" ", map { "tune_$_" } keys %RxListNinjaPiCrust)
  57. ." preferSketchReset:0,1 resetPulseWidth"
  58. ." $readingFnAttributes";
  59. $hash->{ShutdownFn} = "NinjaPiCrust_Shutdown";
  60. }
  61. sub
  62. NinjaPiCrust_Fingerprint($$)
  63. {
  64. }
  65. #####################################
  66. sub
  67. NinjaPiCrust_Define($$)
  68. {
  69. my ($hash, $def) = @_;
  70. my @a = split("[ \t][ \t]*", $def);
  71. if(@a != 3) {
  72. my $msg = "wrong syntax: define <name> NinjaPiCrust {devicename[\@baudrate] ".
  73. "| devicename\@directio}";
  74. Log3 undef, $dl, $msg;
  75. return $msg;
  76. }
  77. DevIo_CloseDev($hash);
  78. my $name = $a[0];
  79. my $dev = $a[2];
  80. $dev .= "\@9600" if( $dev !~ m/\@/ );
  81. $hash->{Clients} = $clientsNinjaPiCrust;
  82. $hash->{MatchList} = \%matchListNinjaPiCrust;
  83. $hash->{DeviceName} = $dev;
  84. my $ret = DevIo_OpenDev($hash, 0, "NinjaPiCrust_DoInit");
  85. return $ret;
  86. }
  87. #####################################
  88. sub
  89. NinjaPiCrust_Undef($$)
  90. {
  91. my ($hash, $arg) = @_;
  92. my $name = $hash->{NAME};
  93. foreach my $d (sort keys %defs) {
  94. if(defined($defs{$d}) &&
  95. defined($defs{$d}{IODev}) &&
  96. $defs{$d}{IODev} == $hash)
  97. {
  98. my $lev = ($reread_active ? 4 : 2);
  99. Log3 $name, $lev, "deleting port for $d";
  100. delete $defs{$d}{IODev};
  101. }
  102. }
  103. NinjaPiCrust_Shutdown($hash);
  104. DevIo_CloseDev($hash);
  105. return undef;
  106. }
  107. #####################################
  108. sub
  109. NinjaPiCrust_Shutdown($)
  110. {
  111. my ($hash) = @_;
  112. ###NinjaPiCrust_SimpleWrite($hash, "X00");
  113. return undef;
  114. }
  115. #sub
  116. #NinjaPiCrust_RemoveLaCrossePair($)
  117. #{
  118. # my $hash = shift;
  119. # delete($hash->{LaCrossePair});
  120. #}
  121. use JSON;
  122. sub
  123. NinjaPiCrust_ParseJSON($)
  124. {
  125. my ($str) = @_;
  126. #Log 0, "NinjaPiCrust_ParseJSON('$str')";
  127. return decode_json $str;
  128. }
  129. sub
  130. NinjaPiCrust_encode($$$$)
  131. {
  132. my ($g,$v,$d,$da) = @_;
  133. return '{"DEVICE":[{"G":"'.$g.'","V":'.$v.',"D":'.$d.',"DA":"'.$da.'"}]}';
  134. }
  135. #####################################
  136. sub
  137. NinjaPiCrust_Set($@)
  138. {
  139. my ($hash, @a) = @_;
  140. my $name = shift @a;
  141. my $cmd = shift @a;
  142. my $arg = join(" ", @a);
  143. #my $list = "led:on,off led-on-for-timer reset LaCrossePairForSec setReceiverMode:LaCrosse,HX2272,FS20";
  144. my $list = "eyes:rgb led:on,off,red,green,blue,yellow,cyan,magenta";
  145. return $list if( $cmd eq '?' || $cmd eq '');
  146. my %rgb = (
  147. on => "FFFFFF",
  148. off => "000000",
  149. red => "FF0000",
  150. green => "00FF00",
  151. blue => "0000FF",
  152. cyan => "00FFFF",
  153. magenta => "FF00FF",
  154. yellow => "FFFF00"
  155. );
  156. if($cmd eq "raw") {
  157. Log3 $name, 4, "set $name $cmd $arg";
  158. NinjaPiCrust_SimpleWrite($hash, $arg);
  159. } elsif ($cmd =~ m/^eyes$/i) {
  160. return "Unknown argument $cmd, choose one of $list"
  161. if($arg !~ m/^(on|off|red|green|blue|yellow|cyan|magenta|[0-9a-f]{6})$/i);
  162. Log3 $name, 4, "set $name $cmd $arg";
  163. NinjaPiCrust_Write($hash, (exists $rgb{$arg}) ? NinjaPiCrust_encode("0",0,1007,$rgb{$arg}) :
  164. NinjaPiCrust_encode("0",0,1007,$arg) );
  165. } elsif ($cmd =~ m/^led$/i) {
  166. return "Unknown argument $cmd, choose one of $list"
  167. if($arg !~ m/^(on|off|red|green|blue|yellow|cyan|magenta|[0-9a-f]{6})$/i);
  168. Log3 $name, 4, "set $name $cmd $arg";
  169. NinjaPiCrust_Write($hash, (exists $rgb{$arg}) ? NinjaPiCrust_encode("0",0,999,$rgb{$arg}) :
  170. NinjaPiCrust_encode("0",0,999,$arg));
  171. } elsif ($cmd =~ m/led-on-for-timer/i) {
  172. return "Unknown argument $cmd, choose one of $list" if($arg !~ m/^[0-9]+$/i);
  173. #remove timer if there is one active
  174. if($modules{NinjaPiCrust}{ldata}{$name}) {
  175. CommandDelete(undef, $name . "_timer");
  176. delete $modules{NinjaPiCrust}{ldata}{$name};
  177. }
  178. Log3 $name, 4, "set $name on";
  179. #TODO: NinjaPiCrust_Write($hash, "l" . "1");
  180. my $to = sprintf("%02d:%02d:%02d", $arg/3600, ($arg%3600)/60, $arg%60);
  181. $modules{NinjaPiCrust}{ldata}{$name} = $to;
  182. Log3 $name, 4, "Follow: +$to setstate $name off";
  183. CommandDefine(undef, $name."_timer at +$to {fhem(\"set $name led" ." off\")}");
  184. } elsif ($cmd =~ m/reset/i) {
  185. NinjaPiCrust_ResetDevice($hash);
  186. } else {
  187. return "Unknown argument $cmd, choose one of ".$list;
  188. }
  189. return undef;
  190. }
  191. #####################################
  192. sub
  193. NinjaPiCrust_Get($@)
  194. {
  195. my ($hash, $name, $cmd, @msg ) = @_;
  196. my $arg = join(" ", @msg);
  197. #my $list = "devices:noArg initNinjaPiCrust:noArg RFMconfig:noArg updateAvailRam:noArg raw";
  198. my $list = "version";
  199. if ($cmd eq "raw" ) {
  200. return "raw => 01" if($arg =~ m/^Ir/); ## Needed for CUL_IR usage (IR-Receive is always on for NinjaPiCrusts
  201. }
  202. elsif ($cmd eq "version" ) {
  203. NinjaPiCrust_SimpleWrite($hash, NinjaPiCrust_encode("0",0,1003,"VNO") );
  204. }
  205. #elsif ($cmd eq "updateAvailRam" ) {
  206. # NinjaPiCrust_SimpleWrite($hash, "m");
  207. #
  208. #}
  209. else {
  210. return "Unknown argument $cmd, choose one of ".$list;
  211. }
  212. return undef;
  213. }
  214. sub
  215. NinjaPiCrust_Clear($)
  216. {
  217. my $hash = shift;
  218. return undef; #TODO: do we need this?
  219. # Clear the pipe
  220. $hash->{RA_Timeout} = 1;
  221. for(;;) {
  222. my ($err, undef) = NinjaPiCrust_ReadAnswer($hash, "Clear", 0, undef);
  223. last if($err && $err =~ m/^Timeout/);
  224. }
  225. delete($hash->{RA_Timeout});
  226. }
  227. #####################################
  228. sub
  229. NinjaPiCrust_DoInit($)
  230. {
  231. my $hash = shift;
  232. my $name = $hash->{NAME};
  233. my $err;
  234. my $msg = undef;
  235. my $val;
  236. NinjaPiCrust_Clear($hash);
  237. $hash->{STATE} = "Opened";
  238. # Reset the counter
  239. delete($hash->{XMIT_TIME});
  240. delete($hash->{NR_CMD_LAST_H});
  241. return undef;
  242. }
  243. #####################################
  244. # This is a direct read for commands like get
  245. # Anydata is used by read file to get the filesize
  246. sub
  247. NinjaPiCrust_ReadAnswer($$$$)
  248. {
  249. my ($hash, $arg, $anydata, $regexp) = @_;
  250. my $type = $hash->{TYPE};
  251. return ("No FD", undef)
  252. if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  253. my ($mpandata, $rin) = ("", '');
  254. my $buf;
  255. my $to = 3; # 3 seconds timeout
  256. $to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less
  257. for(;;) {
  258. if($^O =~ m/Win/ && $hash->{USBDev}) {
  259. $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
  260. # Read anstatt input sonst funzt read_const_time nicht.
  261. $buf = $hash->{USBDev}->read(999);
  262. return ("Timeout reading answer for get $arg", undef)
  263. if(length($buf) == 0);
  264. } else {
  265. return ("Device lost when reading answer for get $arg", undef)
  266. if(!$hash->{FD});
  267. vec($rin, $hash->{FD}, 1) = 1;
  268. my $nfound = select($rin, undef, undef, $to);
  269. if($nfound < 0) {
  270. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  271. my $err = $!;
  272. DevIo_Disconnected($hash);
  273. return("NinjaPiCrust_ReadAnswer $arg: $err", undef);
  274. }
  275. return ("Timeout reading answer for get $arg", undef)
  276. if($nfound == 0);
  277. $buf = DevIo_SimpleRead($hash);
  278. return ("No data", undef) if(!defined($buf));
  279. }
  280. if($buf) {
  281. #Log3 $hash->{NAME}, 5, "NinjaPiCrust/RAW (ReadAnswer): $buf";
  282. $mpandata .= $buf;
  283. }
  284. chop($mpandata);
  285. chop($mpandata);
  286. return (undef, $mpandata)
  287. }
  288. }
  289. #####################################
  290. # Check if the 1% limit is reached and trigger notifies
  291. sub
  292. NinjaPiCrust_XmitLimitCheck($$)
  293. {
  294. my ($hash,$fn) = @_;
  295. my $now = time();
  296. if(!$hash->{XMIT_TIME}) {
  297. $hash->{XMIT_TIME}[0] = $now;
  298. $hash->{NR_CMD_LAST_H} = 1;
  299. return;
  300. }
  301. my $nowM1h = $now-3600;
  302. my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
  303. if(@b > 163) { # 163 comes from fs20. todo: verify if correct for NinjaPiCrust modulation
  304. my $name = $hash->{NAME};
  305. Log3 $name, 2, "NinjaPiCrust TRANSMIT LIMIT EXCEEDED";
  306. DoTrigger($name, "TRANSMIT LIMIT EXCEEDED");
  307. } else {
  308. push(@b, $now);
  309. }
  310. $hash->{XMIT_TIME} = \@b;
  311. $hash->{NR_CMD_LAST_H} = int(@b);
  312. }
  313. #####################################
  314. sub
  315. NinjaPiCrust_Write($$)
  316. {
  317. my ($hash, $cmd, $msg) = @_;
  318. my $name = $hash->{NAME};
  319. my $arg = $cmd;
  320. #TODO: $arg .= " " . $msg if(defined($msg));
  321. #Modify command for CUL_IR
  322. #$arg =~ s/^\s+|\s+$//g;
  323. #$arg =~ s/^Is/I/i; #SendIR command is "I" not "Is" for NinjaPiCrust devices
  324. Log3 $name, 5, "$name sending $arg";
  325. NinjaPiCrust_AddQueue($hash, $arg);
  326. #TODO: NinjaPiCrust_SimpleWrite($hash, $msg);
  327. }
  328. sub
  329. NinjaPiCrust_SendFromQueue($$)
  330. {
  331. my ($hash, $bstring) = @_;
  332. my $name = $hash->{NAME};
  333. my $to = 0.05;
  334. if($bstring ne "") {
  335. my $sp = AttrVal($name, "sendpool", undef);
  336. if($sp) { # Is one of the NinjaPiCrust-fellows sending data?
  337. my @fellows = split(",", $sp);
  338. foreach my $f (@fellows) {
  339. if($f ne $name &&
  340. $defs{$f} &&
  341. $defs{$f}{QUEUE} &&
  342. $defs{$f}{QUEUE}->[0] ne "")
  343. {
  344. unshift(@{$hash->{QUEUE}}, "");
  345. InternalTimer(gettimeofday()+$to, "NinjaPiCrust_HandleWriteQueue", $hash, 0);
  346. return;
  347. }
  348. }
  349. }
  350. NinjaPiCrust_XmitLimitCheck($hash,$bstring);
  351. NinjaPiCrust_SimpleWrite($hash, $bstring);
  352. }
  353. InternalTimer(gettimeofday()+$to, "NinjaPiCrust_HandleWriteQueue", $hash, 0);
  354. }
  355. sub
  356. NinjaPiCrust_AddQueue($$)
  357. {
  358. my ($hash, $bstring) = @_;
  359. if(!$hash->{QUEUE}) {
  360. $hash->{QUEUE} = [ $bstring ];
  361. NinjaPiCrust_SendFromQueue($hash, $bstring);
  362. } else {
  363. push(@{$hash->{QUEUE}}, $bstring);
  364. }
  365. }
  366. #####################################
  367. sub
  368. NinjaPiCrust_HandleWriteQueue($)
  369. {
  370. my $hash = shift;
  371. my $arr = $hash->{QUEUE};
  372. if(defined($arr) && @{$arr} > 0) {
  373. shift(@{$arr});
  374. if(@{$arr} == 0) {
  375. delete($hash->{QUEUE});
  376. return;
  377. }
  378. my $bstring = $arr->[0];
  379. if($bstring eq "") {
  380. NinjaPiCrust_HandleWriteQueue($hash);
  381. } else {
  382. NinjaPiCrust_SendFromQueue($hash, $bstring);
  383. }
  384. }
  385. }
  386. #####################################
  387. # called from the global loop, when the select for hash->{FD} reports data
  388. sub
  389. NinjaPiCrust_Read($)
  390. {
  391. my ($hash) = @_;
  392. my $buf = DevIo_SimpleRead($hash);
  393. return "" if(!defined($buf));
  394. my $name = $hash->{NAME};
  395. my $pandata = $hash->{PARTIAL};
  396. #Log3 $name, $dl+2, "NinjaPiCrust/RAW: $pandata/$buf";
  397. $pandata .= $buf;
  398. while($pandata =~ m/\n/) {
  399. my $rmsg;
  400. ($rmsg,$pandata) = split("\n", $pandata, 2);
  401. $rmsg =~ s/\r//;
  402. NinjaPiCrust_Parse($hash, $hash, $name, $rmsg) if($rmsg);
  403. }
  404. $hash->{PARTIAL} = $pandata;
  405. }
  406. sub
  407. NinjaPiCrust_Parse($$$$)
  408. {
  409. my ($hash, $iohash, $name, $rmsg) = @_;
  410. my $dmsg = $rmsg;
  411. my $rssi = 0;
  412. my $lqi = 0;
  413. Log3 $hash, $dl, "$name: NinjaPiCrust_Parse '$dmsg'";
  414. #next if(!$dmsg || length($dmsg) < 1); # Bogus messages
  415. #return if($dmsg =~ m/^Available commands:/ ); # ignore startup messages
  416. #return if($dmsg =~ m/^ / ); # ignore startup messages
  417. #return if($dmsg =~ m/^-> ack/ ); # ignore send ack
  418. my ($isdup, $idx) = CheckDuplicate("", "$name: $dmsg", undef);
  419. return if ($isdup);
  420. if($dmsg =~ m/^\[/ ) {
  421. Log3 $name, 1, "NinjaPiCrust $name got special: $dmsg";
  422. $hash->{model} = $dmsg;
  423. if( $hash->{STATE} eq "Opened" ) {
  424. if( $dmsg =~m /pcaSerial/ ) {
  425. Log3 $hash, $dl, "nono";
  426. }
  427. $hash->{STATE} = "Initialized";
  428. }
  429. return;
  430. }
  431. readingsSingleUpdate($hash,"${name}_LASTMSG",$dmsg,1);
  432. my $jsonref = NinjaPiCrust_ParseJSON($dmsg);
  433. my %datagram = %$jsonref;
  434. #Log3 $name, $dl, "NinjaPiCrust_Parse: \%datagram is @{[%datagram]}";
  435. my %addvals;
  436. my $msgtype = (keys %datagram)[0];
  437. Log3 $name, $dl, "$name: got message type '$msgtype'";
  438. my %data = %{$datagram{$msgtype}[0]};
  439. $data{MSGTYPE} = $msgtype;
  440. %addvals = %data;
  441. Log3 $name, $dl, "$name: Got $msgtype $data{G} $data{V} $data{D} $data{DA} from $rmsg"
  442. if (defined $data{G} and defined $data{V} and defined $data{D} and defined $data{DA});
  443. $addvals{RAWMSG} = $rmsg;
  444. $hash->{"${name}_MSGCNT"}++;
  445. $hash->{"${name}_TIME"} = TimeNow();
  446. $hash->{RAWMSG} = $rmsg;
  447. #if(defined($rssi)) {
  448. # $hash->{RSSI} = $rssi;
  449. # $addvals{RSSI} = $rssi;
  450. #}
  451. #if(defined($lqi)) {
  452. # $hash->{LQI} = $lqi;
  453. # $addvals{LQI} = $lqi;
  454. #}
  455. if ($msgtype =~ m/ACK/i) {
  456. my $omsg = $rmsg;
  457. $omsg =~ s/ACK/DEVICE/;
  458. Log3 $name, $dl, "$name: got ACK for command: $omsg";
  459. # for now, do nothing...
  460. return;
  461. } elsif ($msgtype =~ m/ERROR/i) {
  462. Log3 $name, 0, "$name: ERROR: got $rmsg from ".$hash->{RAWREQ};
  463. $hash->{RAWREQ} = undef;
  464. $hash->{RAWMSG} = undef;
  465. return;
  466. }
  467. if (($data{G} eq "0") and ($data{V} == 0)) {
  468. # message information pertains PiCrust hardware, so we handle it here:
  469. my $D = int($data{D});
  470. my $DA = $data{DA};
  471. Log3 $name, $dl, "$name: Got shield related data $msgtype: $D => '$DA'";
  472. if ($D == 1003) { # may be ACK (or even DEVICE?)
  473. my $version = substr $DA, 1;
  474. Log3 $name, $dl, "$name: Arduino version is $version";
  475. $hash->{VERSION} = $version;
  476. } elsif ($msgtype =~ m/DEVICE/) {
  477. if ($D == 999) {
  478. Log3 $name, $dl, "$name: led is '$DA'";
  479. readingsSingleUpdate($hash,"led",$DA,1);
  480. } elsif ($D == 1007) {
  481. Log3 $name, $dl, "$name: eyes are '$DA'";
  482. readingsSingleUpdate($hash,"eyes",$DA,1);
  483. } else {
  484. Log3 $name, 0, "$name: ERROR: got unsupported DID $D in '$rmsg'";
  485. }
  486. } else {
  487. Log3 $name, $dl, "$name: ignoring $msgtype-type message '$rmsg'";
  488. }
  489. } else {
  490. Log3 $hash, $dl, "$name: now dispatching '$dmsg'";
  491. Dispatch($hash, $dmsg, \%addvals);
  492. Log3 $hash, $dl, "$name: end dispatching '$dmsg'";
  493. }
  494. }
  495. #my devinfo = (
  496. # "0:0:999" => ( SENSE => 1 ),
  497. # "0:0:1003 => ( SENSE => 1 )
  498. #)
  499. #####################################
  500. sub
  501. NinjaPiCrust_Ready($)
  502. {
  503. my ($hash) = @_;
  504. return DevIo_OpenDev($hash, 1, "NinjaPiCrust_DoInit")
  505. if($hash->{STATE} eq "disconnected");
  506. # This is relevant for windows/USB only
  507. my $po = $hash->{USBDev};
  508. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
  509. if($po) {
  510. ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  511. }
  512. return ($InBytes && $InBytes>0);
  513. }
  514. ########################
  515. sub
  516. NinjaPiCrust_ResetDevice($)
  517. {
  518. my ($hash) = @_;
  519. return if(!$hash);
  520. my $name = $hash->{NAME};
  521. my $pulse = AttrVal($name, "resetPulseWidth", 0.5);
  522. $pulse= 0.01 if ($pulse < 0.01);
  523. $pulse= 2 if ($pulse > 2);
  524. Log3 $name, 1, "NinjaPiCrust_ResetDevice with pulse with $pulse sec.";
  525. #$hash->{USBDev}->pulse_dtr_on($pulse * 1000.0) if($hash->{USBDev});
  526. return undef;
  527. }
  528. sub
  529. NinjaPiCrust_SimpleWrite(@)
  530. {
  531. my ($hash, $msg, $nocr) = @_;
  532. return if(!$hash);
  533. my $name = $hash->{NAME};
  534. Log3 $name, $dl, "$name: NinjaPiCrust_SW '$msg'";
  535. $hash->{RAWREQ} = $msg;
  536. $msg .= "\n" unless($nocr);
  537. $hash->{USBDev}->write($msg) if($hash->{USBDev});
  538. syswrite($hash->{DIODev}, $msg) if($hash->{DIODev});
  539. # Some linux installations are broken with 0.001, T01 returns no answer
  540. select(undef, undef, undef, 0.01);
  541. }
  542. sub
  543. NinjaPiCrust_Attr(@)
  544. {
  545. my ($cmd,$name,$aName,$aVal) = @_;
  546. my $hash = $defs{$name};
  547. if( $aName eq "Clients" ) {
  548. $hash->{Clients} = $aVal;
  549. $hash->{Clients} = $clientsNinjaPiCrust if( !$hash->{Clients}) ;
  550. } elsif( $aName eq "MatchList" ) {
  551. $hash->{MatchList} = $aVal;
  552. $hash->{MatchList} = \%matchListNinjaPiCrust if( !$hash->{MatchList} );
  553. } elsif($aName =~ m/^tune/i) { #tune attribute freq / rx:bWidth / rx:rAmpl / rx:sens / tx:deviation / tx:power
  554. # Frequenze: Fc =860+ F x0.0050MHz
  555. # LNA Gain [dB] = MAX -6, -14, -20
  556. # RX Bandwidth [kHz] = -, 400, 340, 270, 200, 134, 67
  557. # DRSSI [dB] = -103, -97, -91, -85, -79, -73
  558. # Deviation [kHz] = 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240
  559. # OuputPower [dBm] = 0, -3, -6, -9, -12, -15, -18, -21
  560. return "Usage: attr $name $aName <Frequence> <Rx:Bandwidth> <Rx:Amplitude> <Rx:Sens> <Tx:Deviation> <Tx:Power>"
  561. if(!$aVal || $aVal !~ m/^(4|8)[\d]{2}.[\d]{3} (0|400|340|270|200|134|67) (0|\-6|\-14|\-20) (\-103|\-97|\-91|\-85|\-79|\-73) (15|30|45|60|75|90|105|120|135|150|165|180|195|210|225|240) (0|\-3|\-6|\-9|\-12|\-15|\-18|\-21)/ );
  562. my $TuneStr = NinjaPiCrust_CalcTuneCmd($aVal);
  563. NinjaPiCrust_Write($hash, "t" . $TuneStr);
  564. } elsif ($aName eq "DebounceTime") {
  565. return "Usage: attr $name $aName <OOK-Protocol-Number><DebounceTime>"
  566. if($aVal !~ m/^[0-9]{3,5}$/);
  567. #Log3 $name, 4, "set $name $cmd $arg";
  568. NinjaPiCrust_Write($hash, "Od" . $aVal);
  569. }
  570. return undef;
  571. }
  572. sub NinjaPiCrust_CalcTuneCmd($) {
  573. my ($str) = @_;
  574. my ($freq, $rxbwidth, $rxampl, $rxsens, $txdev, $txpower) = split(' ', $str ,6);
  575. my $sfreq;
  576. if($freq < 800) {
  577. $sfreq = sprintf("%03X", ($freq-430)/0.0025);
  578. } else {
  579. $sfreq = sprintf("%03X", ($freq-860)/0.0050);
  580. }
  581. my $sbwidth = sprintf("%01X", NinjaPiCrust_getIndexOfArray($rxbwidth,(0, 400, 340, 270, 200, 134, 67)));
  582. my $sampl = sprintf("%01X", NinjaPiCrust_getIndexOfArray($rxampl,(0, -6, -14, -20)));
  583. my $ssens = sprintf("%01X", NinjaPiCrust_getIndexOfArray($rxsens, (-103, -97, -91, -85, -79, -73)));
  584. my $sdev = sprintf("%01X", NinjaPiCrust_getIndexOfArray($txdev, (15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240)));
  585. my $soutpupower = sprintf("%01X", NinjaPiCrust_getIndexOfArray($txpower, (0, -3, -6, -9, -12, -15, -18, -21)));
  586. return $sfreq . $sbwidth . $sampl . $ssens . $sdev . $soutpupower;
  587. }
  588. sub NinjaPiCrust_getIndexOfArray($@) {
  589. my ($value, @array) = @_;
  590. my ($ivalue) = grep { $array[$_] == $value } 0..$#array;
  591. return $ivalue;
  592. }
  593. 1;
  594. =pod
  595. =begin html
  596. <a name="NinjaPiCrust"></a>
  597. <h3>NinjaPiCrust</h3>
  598. <ul>
  599. The NinjaPiCrust is a family of RF devices sold by <a href="http://jeelabs.com">jeelabs.com</a>.
  600. It is possible to attach more than one device in order to get better
  601. reception, fhem will filter out duplicate messages.<br><br>
  602. This module provides the IODevice for the <a href="#PCA301">PCA301</a> modules that implements the PCA301 protocoll.<br><br>
  603. In the future other RF devices like the Energy Controll 3000, JeeLabs room nodes, fs20 or kaku devices will be supportet.<br><br>
  604. Note: this module may require the Device::SerialPort or Win32::SerialPort
  605. module if you attach the device via USB and the OS sets strange default
  606. parameters for serial devices.
  607. <br><br>
  608. <a name="NinjaPiCrust_Define"></a>
  609. <b>Define</b>
  610. <ul>
  611. <code>define &lt;name&gt; NinjaPiCrust &lt;device&gt;</code> <br>
  612. <br>
  613. USB-connected devices:<br><ul>
  614. &lt;device&gt; specifies the serial port to communicate with the NinjaPiCrust.
  615. The name of the serial-device depends on your distribution, under
  616. linux the cdc_acm kernel module is responsible, and usually a
  617. /dev/ttyACM0 device will be created. If your distribution does not have a
  618. cdc_acm module, you can force usbserial to handle the NinjaPiCrust by the
  619. following command:<ul>modprobe usbserial vendor=0x0403
  620. product=0x6001</ul>In this case the device is most probably
  621. /dev/ttyUSB0.<br><br>
  622. You can also specify a baudrate if the device name contains the @
  623. character, e.g.: /dev/ttyACM0@57600<br><br>
  624. If the baudrate is "directio" (e.g.: /dev/ttyACM0@directio), then the
  625. perl module Device::SerialPort is not needed, and fhem opens the device
  626. with simple file io. This might work if the operating system uses sane
  627. defaults for the serial parameters, e.g. some Linux distributions and
  628. OSX. <br><br>
  629. </ul>
  630. <br>
  631. </ul>
  632. <br>
  633. <a name="NinjaPiCrust_Set"></a>
  634. <b>Set</b>
  635. <ul>
  636. <li>raw &lt;datar&gt;<br>
  637. send &lt;data&gt; as a raw message to the NinjaPiCrust to be transmitted over the RF link.
  638. </li><br>
  639. <li>LaCrossePairForSec &lt;sec&gt; [ignore_battery]<br>
  640. enable autocreate of new LaCrosse sensors for &lt;sec&gt; seconds. if ignore_battery is not given only sensors
  641. sending the 'new battery' flag will be created.
  642. </li>
  643. </ul>
  644. <a name="NinjaPiCrust_Get"></a>
  645. <b>Get</b>
  646. <ul>
  647. </ul>
  648. <a name="NinjaPiCrust_Attr"></a>
  649. <b>Attributes</b>
  650. <ul>
  651. <li>Clients</li>
  652. <li>MatchList</li>
  653. </ul>
  654. <br>
  655. </ul>
  656. =end html
  657. =cut