00_JeeLink.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. ################################################################################
  2. # FHEM-Modul see www.fhem.de
  3. # 00_JeeLink.pm
  4. # Modul to use a JeeLink with RF12DEMO as FHEM-IO-Device
  5. #
  6. # Usage: define <Name> JeeLink </dev/...> NodeID
  7. ################################################################################
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (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, see <http://www.gnu.org/licenses/>.
  20. ################################################################################
  21. # Autor: Axel Rieger
  22. # Version: 1.0
  23. # Datum: 07.2011
  24. # Kontakt: fhem [bei] anax [punkt] info
  25. ################################################################################
  26. package main;
  27. use strict;
  28. use warnings;
  29. use Data::Dumper;
  30. use vars qw(%defs);
  31. use vars qw(%attr);
  32. use vars qw(%data);
  33. use vars qw(%modules);
  34. sub JeeLink_Initialize($);
  35. sub JEE_Define($$);
  36. sub JEE_CloseDev($);
  37. sub JEE_OpenDev($$);
  38. sub JEE_Ready($);
  39. sub JEE_Read($);
  40. sub JEE_Set($);
  41. ################################################################################
  42. sub JeeLink_Initialize($)
  43. {
  44. my ($hash) = @_;
  45. # Provider
  46. $hash->{ReadFn} = "JEE_Read";
  47. $hash->{ReadyFn} = "JEE_Ready";
  48. $hash->{SetFn} = "JEE_Set";
  49. $hash->{WriteFn} = "JEE_Write";
  50. $hash->{Clients} = ":JSN:JME:JPU";
  51. $hash->{WriteFn} = "JEE_Write";
  52. my %mc = (
  53. "1:JSN" => "^JSN",
  54. "2:JME" => "^JME",
  55. "3:JPU" => "^JPU");
  56. $hash->{MatchList} = \%mc;
  57. # Normal devices
  58. $hash->{DefFn} = "JEE_Define";
  59. $hash->{AttrList} = "do_not_notify:1,0 dummy:1,0 loglevel:0,1 ";
  60. return undef;
  61. }
  62. ################################################################################
  63. sub JEE_Define($$) {
  64. # define JEE0001 JeeLink /dev/tty.usbserial-A600cKlS NodeID
  65. # defs = $a[0] <DEVICE-NAME> $a[1] DEVICE-TYPE $a[2]<Parameter-1->;
  66. my ($hash, $def) = @_;
  67. my @a = split(/\s+/, $def);
  68. my $name = $a[0];
  69. my $dev = $a[2];
  70. my $NodeID = $a[3];
  71. if($dev eq "none") {
  72. Log 1, "$name device is none, commands will be echoed only";
  73. $hash->{TYPE} = 'JeeLink';
  74. $hash->{STATE} = TimeNow() . " Dummy-Device";
  75. $attr{$name}{dummy} = 1;
  76. return undef;
  77. }
  78. JEE_CloseDev($hash);
  79. if($NodeID == 0 || $NodeID > 26 ) {return "JeeLink: NodeID between 1 and 26";}
  80. Log 0, "JEE-Define: Name = $name dev=$dev";
  81. $hash->{DeviceName} = $dev;
  82. $hash->{TYPE} = 'JeeLink';
  83. my $ret = JEE_OpenDev($hash, 0);
  84. my $msg = $NodeID . "i";
  85. $ret = &JEE_IOWrite($hash, $msg);
  86. return undef;
  87. }
  88. ################################################################################
  89. sub JEE_Set($){
  90. my ($hash, @a) = @_;
  91. # Log 0, ("JEE-SET: " . Dumper(@_));
  92. my $fields .= "NodeID NetGRP Frequenz LED CollectMode SendMSG BroadcastMSG RAW";
  93. return "Unknown argument $a[1], choose one of ". $fields if($a[1] eq "?");
  94. # a[0] = DeviceName
  95. # a[1] = Command
  96. # Command
  97. # nodeID: <n>i set node ID (standard node ids are 1..26 or 'A'..'Z')
  98. # netGRP: <n>g set network group (RFM12 only allows 212)
  99. # freq -> <n>b set MHz band (4 = 433, 8 = 868, 9 = 915)
  100. # cMode -> <n>c - set collect mode (advanced 1, normally 0)
  101. # bCast -> t - broadcast max-size test packet, with ack
  102. # sendA -> ...,<nn> a - send data packet to node <nn>, with ack
  103. # sendS -> ...,<nn> s - send data packet to node <nn>, no ack
  104. # led -> <n> l - turn activity LED on PB1 on or off
  105. # Remote control commands:
  106. # <hchi>,<hclo>,<addr>,<cmd> f - FS20 command (868 MHz)
  107. # <addr>,<dev>,<on> k - KAKU command (433 MHz)
  108. # Flash storage (JeeLink v2 only):
  109. # d - dump all log markers
  110. # <sh>,<sl>,<t3>,<t2>,<t1>,<t0> r - replay from specified marker
  111. # 123,<bhi>,<blo> e - erase 4K block
  112. # 12,34 w - wipe entire flash memory
  113. my($name, $msg);
  114. $name = $a[0];
  115. # LogLevel
  116. my $ll = 0;
  117. if(defined($attr{$name}{loglevel})) {$ll = $attr{$name}{loglevel};}
  118. Log $ll,"$name/JEE-SET: " . $a[1] . " : " . $a[2];
  119. # @a ge 2
  120. # if(int(@a) ne 3) {return "JeeLink wrong Argument Count";}
  121. $msg = "";
  122. if($a[1] eq "NodeID") {
  123. if($a[2] == 0 || $a[2] > 26 ) {return "JeeLink: NodeID between 1 and 26";}
  124. $msg = $a[2] . "i";}
  125. if($a[1] eq "NetGRP") {
  126. if($a[2] == 0 || $a[2] > 255 ) {return "JeeLink: NetGroup between 1 and 255";}
  127. $msg = $a[2] . "g";}
  128. if($a[1] eq "Frequenz") {
  129. if($a[2] !~ m/433|868|933/) {return "JeeLink: Frquenz setting use 433, 868 or 933";}
  130. my $mhz;
  131. if($a[2] eq "433") {$msg = "4b";}
  132. if($a[2] eq "868") {$msg = "8b";}
  133. if($a[2] eq "915") {$msg = "9b";}
  134. # 4 = 433, 8 = 868, 9 = 915
  135. }
  136. # LED
  137. if($a[1] eq "LED" && lc($a[2]) eq "on") {
  138. $hash->{LED} = "ON";
  139. $msg = "1l";}
  140. if($a[1] eq "LED" && lc($a[2]) eq "off") {
  141. $hash->{LED} = "OFF";
  142. $msg = "0l";}
  143. # CollectMode On or Off
  144. if($a[1] eq "CollectMode" && lc($a[2]) eq "on") {
  145. $hash->{CollectMode} = "ON";
  146. $msg = "1c";}
  147. if($a[2] eq "CollectMode" && lc($a[2]) eq "off") {
  148. $hash->{CollectMode} = "OFF";
  149. $msg = "0c";}
  150. # RF12_MSG to Remote Node with NO ack
  151. # set <NAME> SendMSG NodeID Data
  152. if($a[1] eq "SendMSG") {$msg = $a[2] . "," . $a[3] . "s"};
  153. # RF12- BroadcastMSG
  154. if($a[1] eq "BroadcastMSG") {$msg = "t";}
  155. # RAW
  156. if($a[1] eq "RAW") {$msg = $a[2];}
  157. # Send MSG
  158. Log 0,"JEE-SET->WRITE: $msg";
  159. my $ret = &JEE_IOWrite($hash, $msg);
  160. return undef;
  161. }
  162. ################################################################################
  163. sub JEE_CloseDev($)
  164. {
  165. my ($hash) = @_;
  166. my $name = $hash->{NAME};
  167. my $dev = $hash->{DeviceName};
  168. return if(!$dev);
  169. $hash->{USBDev}->close() ;
  170. delete($hash->{USBDev});
  171. delete($selectlist{"$name.$dev"});
  172. delete($readyfnlist{"$name.$dev"});
  173. delete($hash->{FD});
  174. }
  175. ################################################################################
  176. sub JEE_OpenDev($$)
  177. {
  178. my ($hash, $reopen) = @_;
  179. my $dev = $hash->{DeviceName};
  180. my $name = $hash->{NAME};
  181. my $po;
  182. $hash->{PARTIAL} = "";
  183. Log 3, "JeeLink opening $name device $dev"
  184. if(!$reopen);
  185. my $baudrate;
  186. ($dev, $baudrate) = split("@", $dev);
  187. $baudrate = 57600;
  188. if ($^O=~/Win/) {
  189. require Win32::SerialPort;
  190. $po = new Win32::SerialPort ($dev);
  191. } else {
  192. require Device::SerialPort;
  193. $po = new Device::SerialPort ($dev);
  194. }
  195. if(!$po) {
  196. return undef if($reopen);
  197. Log(3, "Can't open $dev: $!");
  198. $readyfnlist{"$name.$dev"} = $hash;
  199. $hash->{STATE} = "disconnected";
  200. return "";
  201. }
  202. $hash->{USBDev} = $po;
  203. if( $^O =~ /Win/ ) {
  204. $readyfnlist{"$name.$dev"} = $hash;
  205. } else {
  206. $hash->{FD} = $po->FILENO;
  207. delete($readyfnlist{"$name.$dev"});
  208. $selectlist{"$name.$dev"} = $hash;
  209. }
  210. if($baudrate) {
  211. $po->reset_error();
  212. Log 3, "$name: Setting baudrate to $baudrate";
  213. $po->baudrate($baudrate);
  214. $po->databits(8);
  215. $po->parity('none');
  216. $po->stopbits(1);
  217. $po->handshake('none');
  218. }
  219. if($reopen) {
  220. Log 1, "JeeLink $dev reappeared ($name)";
  221. } else {
  222. Log 3, "JeeLink device opened";
  223. }
  224. # Set Defaults
  225. # CollectMode on
  226. my $ret = &JEE_IOWrite($hash, "1c");
  227. # QuietMode on
  228. $ret = &JEE_IOWrite($hash, "1q");
  229. # LED On
  230. $ret = &JEE_IOWrite($hash, "1l");
  231. # Set Frequenz to 868MHz
  232. $ret = &JEE_IOWrite($hash, "8b");
  233. $hash->{STATE}="connected"; # Allow InitDev to set the state
  234. DoTrigger($name, "CONNECTED") if($reopen);
  235. return "Initialized";
  236. }
  237. ################################################################################
  238. sub JEE_Ready($)
  239. {
  240. my ($hash) = @_;
  241. return JEE_OpenDev($hash, 1)
  242. if($hash->{STATE} eq "disconnected");
  243. # This is relevant for windows/USB only
  244. my $po = $hash->{USBDev};
  245. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  246. # Get Config
  247. my $ret = &JEE_Write($hash, "i", undef);
  248. return ($InBytes>0);
  249. }
  250. ################################################################################
  251. sub JEE_Read($)
  252. {
  253. my ($hash) = @_;
  254. my $name = $hash->{NAME};
  255. # LogLevel
  256. # Default 4
  257. my $ll = 4;
  258. if(defined($attr{$name}{loglevel})) {
  259. $ll = $attr{$name}{loglevel};
  260. }
  261. my $buf = $hash->{USBDev}->input();
  262. #
  263. # Lets' try again: Some drivers return len(0) on the first read...
  264. if(defined($buf) && length($buf) == 0) {
  265. $buf = $hash->{USBDev}->input();
  266. }
  267. my $jeedata = $hash->{PARTIAL};
  268. $jeedata .= $buf;
  269. ##############################################################################
  270. # Arduino/JeeLink
  271. # Prints data to the serial port as human-readable ASCII text followed by
  272. # a carriage return character (ASCII 13, or '\r') and
  273. # a newline character (ASCII 10, or '\n').
  274. # HEX 0D AD \xf0
  275. if($jeedata =~ m/\n$/){
  276. chomp($jeedata);
  277. chop($jeedata);
  278. my $status = substr($jeedata, 0, 2);
  279. Log $ll,("$name/JeeLink RAW:$status -> $jeedata");
  280. if($status =~/^OK/){
  281. Log $ll,("$name/JeeLink Dispatch RAW:$jeedata");
  282. &JEE_DispatchData($jeedata,$name,$ll);
  283. }
  284. elsif($jeedata =~ m/(^.*i)([0-9]{1,2})(\*.g)([0-9]{1,3})(.@.)([0-9]{1,3})(.MHz.*)/) {
  285. JEE_RF12MSG($jeedata,$name,$ll);
  286. }
  287. elsif($jeedata =~/^DF/){JEE_RF12MSG($jeedata,$name,$ll);}
  288. $jeedata = "";
  289. }
  290. if($jeedata =~ m/^\x0A/) {
  291. Log $ll,("$name/JeeLink RAW DEL HEX-0A:$jeedata -> $jeedata");
  292. $jeedata =~ s/\x0A//;
  293. }
  294. $hash->{PARTIAL} = $jeedata;
  295. }
  296. ################################################################################
  297. sub JEE_DispatchData($){
  298. my ($rawdata,$name,$ll) = @_;
  299. my @data = split(/\s+/,$rawdata);
  300. my $status = shift(@data);
  301. my $NodeID = shift(@data);
  302. # see http://talk.jeelabs.net/topic/642#post-3622
  303. # The id +32 is what you see when the node requests an ACK.
  304. if($NodeID > 31) {$NodeID = $NodeID - 32;}
  305. Log $ll, "$name JEE-DISP: Status:$status NodeID:$NodeID Data:$rawdata";
  306. # normalize 0 => 00 without NodeID
  307. for(my $i=0;$i<=$#data;$i++){
  308. if(length($data[$i]) == 1) { $data[$i] = "0" . $data[$i]}
  309. }
  310. # SensorData to Dispatch
  311. my ($DispData,$SType,$SPre,@SData,$data_bytes,$slice_end);
  312. for(my $i=0;$i<=$#data;$i++){
  313. # Get Number of DataBytes
  314. $SType = $data[$i];
  315. if(defined($data{JEECONF}{$SType}{DataBytes})){
  316. $data_bytes = $data{JEECONF}{$SType}{DataBytes};
  317. ###
  318. $SPre = $data{JEECONF}{$SType}{Prefix};
  319. $i++;
  320. $slice_end = $i + $data_bytes - 1;
  321. @SData = @data[$i..$slice_end];
  322. $DispData = $SPre . " " . $NodeID . " " . $SType . " " . join(" ",@SData);
  323. }
  324. else {
  325. Log $ll, "$name JEE-DISP: -ERROR- SensorType $SType not defined";
  326. return undef;
  327. }
  328. # Dispacth-Data to FHEM-Dispatcher -----------------------------------------
  329. #foreach my $m (sort keys %{$modules{JeeLink}{MatchList}}) {
  330. # my $match = $modules{JeeLink}{MatchList}{$m};
  331. $defs{$name}{"${name}_MSGCNT"}++;
  332. $defs{$name}{"${name}_TIME"} = TimeNow();
  333. $defs{$name}{RAWMSG} = $DispData;
  334. my %addvals = (RAWMSG => $DispData);
  335. my $hash = $defs{$name};
  336. Log $ll,"$name JEE-DISP: SType=$SType -> DispData=$DispData";
  337. my $ret_disp = &Dispatch($hash, $DispData, \%addvals);
  338. #}
  339. # Dispacth-Data to FHEM-Dispatcher -----------------------------------------
  340. # Minimum 2Bytes left
  341. # if((int(@data) - $i) < 2 ) {
  342. # Log $ll,"$name JEE-DISP: 2Byte $i -> " . int(@data);
  343. # $i = int(@data);
  344. # }
  345. #else {$i = $slice_end;}
  346. $i = $slice_end;
  347. }
  348. }
  349. ################################################################################
  350. sub JEE_RF12MSG($$$){
  351. my ($rawdata,$name,$ll) = @_;
  352. # ^ i23* g212 @ 868 MHz
  353. # i -> NodeID
  354. # g -> NetGroup
  355. # @ -> 868MHz or 492MHz
  356. Log $ll,("$name/JeeLink RF12MSG: $rawdata");
  357. my($NodeID,$NetGroup,$Freq);
  358. if ( $rawdata =~ m/(^.*i)([0-9]{1,2})(\*.g)([0-9]{1,3})(.@.)([0-9]{1,3})(.MHz.*)/) {
  359. ($NodeID,$NetGroup,$Freq) = ($2,$4,$6);
  360. Log $ll,("$name/JeeLink RF12MSG-CONFIG: NodeId:$NodeID NetGroup:$NetGroup Freq:$Freq");
  361. $defs{$name}{RF12_NodeID} = $NodeID;
  362. $defs{$name}{RF12_NetGroup} = $NetGroup;
  363. $defs{$name}{RF12_Frequenz} = $Freq;
  364. }
  365. if($rawdata =~ m/\s+DF/){
  366. Log $ll,("$name/JeeLink RF12MSG-FLASH: $rawdata");
  367. }
  368. return undef;
  369. }
  370. ################################################################################
  371. sub JEE_IOWrite() {
  372. my ($hash, $msg,) = @_;
  373. return if(!$hash);
  374. # LogLevel
  375. my $name = $hash->{NAME};
  376. my $ll = 4;
  377. if(defined($attr{$name}{loglevel})) {
  378. $ll = $attr{$name}{loglevel};
  379. }
  380. if(defined($attr{$name}{dummy})){
  381. Log $ll ,"JEE-IOWRITE[DUMMY-MODE]: " . $hash->{NAME} . " $msg";
  382. }
  383. else {
  384. Log $ll ,"JEE-IOWRITE: " . $name . " $msg";
  385. $hash->{USBDev}->write($msg . "\n");
  386. select(undef, undef, undef, 0.001);
  387. }
  388. }
  389. ################################################################################
  390. sub JEE_Write() {
  391. my ($hash, $msg1, $msg2) = @_;
  392. # $hash -> Received form Device
  393. # $msg1 -> Message Type ???
  394. # $msg2 -> Data
  395. return if(!$hash);
  396. # LogLevel
  397. my $name = $hash->{NAME};
  398. my $ll = 4;
  399. if(defined($attr{$name}{loglevel})) {
  400. $ll = $attr{$name}{loglevel};
  401. }
  402. Log $ll ,"JEE-WRITE: " . $hash->{NAME} . " MSG-1-: $msg1 MSG-2-: $msg2";
  403. # Default --------------------------------------------------------------------
  404. my $msg = $msg2;
  405. # FS20 -----------------------------------------------------------------------
  406. # JEE-WRITE: JL01 MSG: 04 BTN: 010101 1234 33 00
  407. # FS20.pm
  408. # IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c);
  409. # MSG-1-: 04 MSG-2-: 01010177770311
  410. # <hchi>,<hclo>,<addr>,<cmd> f - FS20 command (868 MHz)
  411. # substr($jeedata, 0, 2);
  412. my ($hchi,$hclo,$addr,$cmd);
  413. if($msg2 =~ m/^010101/) {
  414. $msg2 =~s/010101//;
  415. Log 0, "JEE-IOWRITE-FS20: $msg2";
  416. $hchi = hex(substr($msg2,0,2));
  417. $hclo = substr($msg2,2,2);
  418. $addr = hex(substr($msg2,4,2));
  419. $cmd = hex(substr($msg2,6,2));
  420. $msg = "$hchi,$hclo,$addr,$cmd f";
  421. Log $ll, "JEE-IOWRITE-FS20: hchi:$hchi hclo:$hclo addr:$addr cmd:$cmd";
  422. $hash->{FS20_LastSend} = TimeNow() . ":" . $msg ;
  423. }
  424. # FS20 -----------------------------------------------------------------------
  425. if(defined($attr{$name}{dummy})){
  426. Log $ll, "JEE_Write[DUMMY-MODE]: >$msg<";
  427. }
  428. else {
  429. # Send Message >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  430. $hash->{USBDev}->write($msg . "\n");
  431. Log $ll, "JEE_Write >$msg<";
  432. select(undef, undef, undef, 0.001);
  433. # Send Message >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  434. }
  435. }
  436. ################################################################################
  437. 1;