00_FHZ.pm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. ##############################################
  2. # $Id: 00_FHZ.pm 14888 2017-08-13 12:07:12Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Time::HiRes qw(gettimeofday);
  7. sub FHZ_Write($$$);
  8. sub FHZ_Read($);
  9. sub FHZ_ReadAnswer($$$);
  10. sub FHZ_Crc(@);
  11. sub FHZ_CheckCrc($);
  12. sub FHZ_XmitLimitCheck($$);
  13. sub FHZ_DoInit($$$);
  14. my $msgstart = pack('H*', "81");# Every msg starts with this
  15. # See also "FHZ1000 Protocol" http://fhz4linux.info/tiki-index.php?page=FHZ1000%20Protocol
  16. # NOTE: for protocol analysis, especially the "serial" vs. "FHTcode" case
  17. # is interestingly different yet similar:
  18. # - code 0x84 (FHZ area) vs. 0x83 (FHT area),
  19. # - register 0x57, _read_ vs. 0x9e, _write_ (hmm, or is this "house code" 0x9e01?)
  20. # - _read_ 8 nibbles (4 bytes serial), _write_ 1 (1 byte FHTcode - align-corrected to two nibbles, right?)
  21. # I did some few tests already (also scripted tests), no interesting findings so far,
  22. # but despite that torture my 1300PC still works fine ;)
  23. my %gets = (
  24. "init1" => "c9 02011f64",
  25. "init2" => "c9 02011f60",
  26. "init3" => "c9 02011f0a",
  27. "serial" => "04 c90184570208",
  28. "fhtbuf" => "04 c90185", # get free FHZ memory (e.g. 23 bytes free)
  29. # NOTE: there probably is another command to return the number of pending
  30. # FHT msg submissions in FHZ (including last one), IOW: 1 == "empty";
  31. # see thread "Kommunikation FHZ1000PC zum FHT80b" for clues;
  32. # TODO: please analyze in case you use homeputer!!
  33. );
  34. my %sets = (
  35. "time" => "c9 020161",
  36. "initHMS" => "04 c90186",
  37. "stopHMS" => "04 c90197",
  38. "initFS20" => "04 c90196",
  39. "initFS20_02" => "04 c9019602", # some alternate variant
  40. "FHTcode" => "04 c901839e0101", # (parameter range 1-99, "Zentralencode" in contronics speak; randomly chosen - and forgotten!! - by FHZ, thus better manually hardcode it in fhem.cfg)
  41. "raw" => "xx xx",
  42. "initfull" => "xx xx",
  43. "reopen" => "xx xx",
  44. "close" => "xx xx",
  45. "open" => "xx xx",
  46. );
  47. my %setnrparam = (
  48. "time" => 0,
  49. "initHMS" => 0,
  50. "stopHMS" => 0,
  51. "initFS20" => 0,
  52. "initFS20_02" => 0,
  53. "FHTcode" => 1,
  54. "raw" => 2,
  55. "initfull" => 0,
  56. "reopen" => 0,
  57. "close" => 0,
  58. "open" => 0,
  59. );
  60. my %codes = (
  61. "^8501..\$" => "fhtbuf",
  62. );
  63. #####################################
  64. # Note: we are a data provider _and_ a consumer at the same time
  65. sub
  66. FHZ_Initialize($)
  67. {
  68. my ($hash) = @_;
  69. # Provider
  70. $hash->{ReadFn} = "FHZ_Read";
  71. $hash->{WriteFn} = "FHZ_Write";
  72. $hash->{Clients} = ":FHZ:FS20:FHT:HMS:KS300:USF1000:BS:";
  73. my %mc = (
  74. "1:USF1000" => "^81..(04|0c)..0101a001a5ceaa00....",
  75. "2:BS" => "^81..(04|0c)..0101a001a5cf",
  76. "3:FS20" => "^81..(04|0c)..0101a001",
  77. "4:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
  78. "5:HMS" => "^810e04....(1|5|9).a001",
  79. "6:KS300" => "^810d04..4027a001",
  80. );
  81. $hash->{MatchList} = \%mc;
  82. $hash->{ReadyFn} = "FHZ_Ready";
  83. # Consumer
  84. $hash->{Match} = "^81..C9..0102";
  85. $hash->{ParseFn} = "FHZ_Parse";
  86. # Normal devices
  87. $hash->{DefFn} = "FHZ_Define";
  88. $hash->{FingerprintFn} = "FHZ_FingerprintFn";
  89. $hash->{UndefFn} = "FHZ_Undef";
  90. $hash->{GetFn} = "FHZ_Get";
  91. $hash->{SetFn} = "FHZ_Set";
  92. $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
  93. "showtime:1,0 model:fhz1000,fhz1300 ".
  94. "fhtsoftbuffer:1,0 addvaltrigger";
  95. }
  96. sub
  97. FHZ_FingerprintFn($$)
  98. {
  99. my ($name, $msg) = @_;
  100. # Store only the "relevant" part, as the CUL won't compute the checksum
  101. $msg = substr($msg, 8) if($msg =~ m/^81/ && length($msg) > 8);
  102. return ($name, $msg);
  103. }
  104. #####################################
  105. sub
  106. FHZ_Ready($)
  107. {
  108. my ($hash) = @_;
  109. my $po=$hash->{PortObj};
  110. if(!$po) { # Looking for the device
  111. my $dev = $hash->{DeviceName};
  112. my $name = $hash->{NAME};
  113. $hash->{PARTIAL} = "";
  114. if($^O =~ m/Win/) {
  115. $po = new Win32::SerialPort ($dev);
  116. } else {
  117. $po = new Device::SerialPort ($dev);
  118. }
  119. return undef if(!$po);
  120. Log3 $name, 1, "USB device $dev reappeared";
  121. $hash->{PortObj} = $po;
  122. if($^O !~ m/Win/) {
  123. $hash->{FD} = $po->FILENO;
  124. delete($readyfnlist{"$name.$dev"});
  125. $selectlist{"$name.$dev"} = $hash;
  126. } else {
  127. $readyfnlist{"$name.$dev"} = $hash;
  128. }
  129. FHZ_DoInit($name, $hash->{ttytype}, $po);
  130. DoTrigger($name, "CONNECTED");
  131. return undef;
  132. }
  133. # This is relevant for windows only
  134. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
  135. return ($InBytes>0);
  136. }
  137. #####################################
  138. sub
  139. CommandChain($$)
  140. {
  141. my ($retry, $list) = @_;
  142. my $ov = $attr{global}{verbose};
  143. my $oid = $init_done;
  144. $init_done = 0;
  145. $attr{global}{verbose} = 1;
  146. foreach my $cmd (@{$list}) {
  147. for(my $n = 0; $n < $retry; $n++) {
  148. Log 1, sprintf("Trying again $cmd (%d out of %d)", $n+1,$retry) if($n>0);
  149. my $ret = AnalyzeCommand(undef, $cmd);
  150. last if(!defined($ret) || $ret !~ m/Timeout/);
  151. }
  152. }
  153. $attr{global}{verbose} = $ov;
  154. $init_done = $oid;
  155. }
  156. #####################################
  157. sub
  158. FHZ_Set($@)
  159. {
  160. my ($hash, @a) = @_;
  161. return "Need one to three parameter" if(@a < 2);
  162. return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
  163. if(!defined($sets{$a[1]}));
  164. return "Need one to three parameter" if(@a > 4);
  165. return "Wrong number of parameters for $a[1], need " . ($setnrparam{$a[1]}+2)
  166. if(@a != ($setnrparam{$a[1]} + 2));
  167. my ($fn, $arg) = split(" ", $sets{$a[1]});
  168. my $v = join(" ", @a);
  169. my $name = $hash->{NAME};
  170. Log3 $name, 2, "FHZ set $v";
  171. if($a[1] eq "initfull") {
  172. my @init;
  173. push(@init, "get $name init2");
  174. push(@init, "get $name serial");
  175. push(@init, "set $name initHMS");
  176. push(@init, "set $name initFS20");
  177. push(@init, "set $name time");
  178. push(@init, "set $name raw 04 01010100010000");
  179. CommandChain(3, \@init);
  180. return undef;
  181. } elsif($a[1] eq "reopen") {
  182. FHZ_Reopen($hash);
  183. return undef;
  184. } elsif($a[1] eq "close") {
  185. FHZ_Close($hash);
  186. return undef;
  187. } elsif($a[1] eq "open") {
  188. FHZ_Open($hash);
  189. return undef;
  190. } elsif($a[1] eq "raw") {
  191. $fn = $a[2];
  192. $arg = $a[3];
  193. } elsif($a[1] eq "time") {
  194. my @t = localtime;
  195. $arg .= sprintf("%02x%02x%02x%02x%02x",
  196. $t[5]%100, $t[4]+1, $t[3], $t[2], $t[1]);
  197. } elsif($a[1] eq "FHTcode") {
  198. return "invalid argument, must be hex" if(!$a[2] ||
  199. $a[2] !~ m/^[A-F0-9]{2}$/);
  200. $arg .= $a[2];
  201. }
  202. FHZ_Write($hash, $fn, $arg) if(!IsDummy($hash->{NAME}));
  203. return undef;
  204. }
  205. #####################################
  206. sub
  207. FHZ_Get($@)
  208. {
  209. my ($hash, @a) = @_;
  210. return "\"get FHZ\" needs only one parameter" if(@a != 2);
  211. return "Unknown argument $a[1], choose one of " . join(",", sort keys %gets)
  212. if(!defined($gets{$a[1]}));
  213. my ($fn, $arg) = split(" ", $gets{$a[1]});
  214. my $v = join(" ", @a);
  215. my $name = $hash->{NAME};
  216. Log3 $name, 2, "FHZ get $v";
  217. FHZ_ReadAnswer($hash, "Flush", 0);
  218. FHZ_Write($hash, $fn, $arg) if(!IsDummy($hash->{NAME}));
  219. my $msg = FHZ_ReadAnswer($hash, $a[1], 1.0);
  220. Log3 $name, 5, "GET Got: $msg" if(defined($msg));
  221. return $msg if(!$msg || $msg !~ /^81..c9..0102/);
  222. if($a[1] eq "serial") {
  223. $v = substr($msg, 22, 8)
  224. } elsif($a[1] eq "fhtbuf") {
  225. $v = substr($msg, 16, 2);
  226. } else {
  227. $v = substr($msg, 12);
  228. }
  229. $hash->{READINGS}{$a[1]}{VAL} = $v;
  230. $hash->{READINGS}{$a[1]}{TIME} = TimeNow();
  231. return "$a[0] $a[1] => $v";
  232. }
  233. #####################################
  234. sub
  235. FHZ_DoInit($$$)
  236. {
  237. my ($name,$type,$po) = @_;
  238. my @init;
  239. $po->reset_error();
  240. $po->baudrate(9600);
  241. $po->databits(8);
  242. $po->parity('none');
  243. $po->stopbits(1);
  244. $po->handshake('none');
  245. if($type && $type eq "strangetty") {
  246. # This part is for some Linux kernel versions whih has strange default
  247. # settings. Device::SerialPort is nice: if the flag is not defined for your
  248. # OS then it will be ignored.
  249. $po->stty_icanon(0);
  250. #$po->stty_parmrk(0); # The debian standard install does not have it
  251. $po->stty_icrnl(0);
  252. $po->stty_echoe(0);
  253. $po->stty_echok(0);
  254. $po->stty_echoctl(0);
  255. # Needed for some strange distros
  256. $po->stty_echo(0);
  257. $po->stty_icanon(0);
  258. $po->stty_isig(0);
  259. $po->stty_opost(0);
  260. $po->stty_icrnl(0);
  261. }
  262. $po->write_settings;
  263. push(@init, "get $name init2");
  264. push(@init, "get $name serial");
  265. push(@init, "set $name initHMS");
  266. push(@init, "set $name initFS20");
  267. push(@init, "set $name time");
  268. # Workaround: Sending "set 0001 00 off" after initialization to enable
  269. # the fhz1000 receiver, else we won't get anything reported.
  270. push(@init, "set $name raw 04 01010100010000");
  271. CommandChain(3, \@init);
  272. # Reset the counter
  273. my $hash = $defs{$name};
  274. delete($hash->{XMIT_TIME});
  275. delete($hash->{NR_CMD_LAST_H});
  276. $hash->{STATE} = "Initialized";
  277. return undef;
  278. }
  279. #####################################
  280. sub
  281. FHZ_Define($$)
  282. {
  283. my ($hash, $def) = @_;
  284. my @a = split("[ \t][ \t]*", $def);
  285. my $po;
  286. return "wrong syntax: define <name> FHZ devicename ".
  287. "[normal|strangetty] [mobile]" if(@a < 3 || @a > 5);
  288. delete $hash->{PortObj};
  289. delete $hash->{FD};
  290. my $name = $a[0];
  291. my $dev = $a[2];
  292. $hash->{ttytype} = $a[3] if($a[3]);
  293. $hash->{MOBILE} = 1 if($a[4] && $a[4] eq "mobile");
  294. $hash->{STATE} = "defined";
  295. $attr{$name}{fhtsoftbuffer} = 0;
  296. if($dev eq "none") {
  297. Log3 $name, 1, "FHZ device is none, commands will be echoed only";
  298. $attr{$name}{dummy} = 1;
  299. return undef;
  300. }
  301. $hash->{DeviceName} = $dev;
  302. $hash->{PARTIAL} = "";
  303. Log3 $name, 3, "FHZ opening FHZ device $dev";
  304. if($^O =~ m/Win/) {
  305. require Win32::SerialPort;
  306. $po = new Win32::SerialPort ($dev);
  307. } else {
  308. require Device::SerialPort;
  309. $po = new Device::SerialPort ($dev);
  310. }
  311. if(!$po) {
  312. my $msg = "Can't open $dev: $!";
  313. Log3($name, 3, $msg) if($hash->{MOBILE});
  314. return $msg if(!$hash->{MOBILE});
  315. $readyfnlist{"$name.$dev"} = $hash;
  316. return "";
  317. }
  318. Log3 $name, 3, "FHZ opened FHZ device $dev";
  319. $hash->{PortObj} = $po;
  320. if($^O !~ m/Win/) {
  321. $hash->{FD} = $po->FILENO;
  322. $selectlist{"$name.$dev"} = $hash;
  323. } else {
  324. $readyfnlist{"$name.$dev"} = $hash;
  325. }
  326. FHZ_DoInit($name, $hash->{ttytype}, $po);
  327. return undef;
  328. }
  329. #####################################
  330. sub
  331. FHZ_Undef($$)
  332. {
  333. my ($hash, $arg) = @_;
  334. my $name = $hash->{NAME};
  335. foreach my $d (sort keys %defs) {
  336. if(defined($defs{$d}) &&
  337. defined($defs{$d}{IODev}) &&
  338. $defs{$d}{IODev} == $hash)
  339. {
  340. my $lev = ($reread_active ? 4 : 2);
  341. Log3 $name, $lev, "deleting port for $d";
  342. delete $defs{$d}{IODev};
  343. }
  344. }
  345. $hash->{PortObj}->close() if($hash->{PortObj});
  346. delete($hash->{PortObj});
  347. delete($hash->{FD});
  348. return undef;
  349. }
  350. #####################################
  351. sub
  352. FHZ_Parse($$)
  353. {
  354. my ($hash,$msg) = @_;
  355. my $omsg = $msg;
  356. $msg = substr($msg, 12); # The first 12 bytes are not really interesting
  357. my $type = "";
  358. my $name = $hash->{NAME};
  359. foreach my $c (keys %codes) {
  360. if($msg =~ m/$c/) {
  361. $type = $codes{$c};
  362. last;
  363. }
  364. }
  365. if(!$type) {
  366. Log3 $name, 4, "FHZ $name unknown: $omsg";
  367. $hash->{CHANGED}[0] = "$msg";
  368. return $hash->{NAME};
  369. }
  370. if($type eq "fhtbuf") {
  371. $msg = substr($msg, 4, 2);
  372. }
  373. Log3 $name, 4, "FHZ $name $type: $msg";
  374. $hash->{CHANGED}[0] = "$type: $msg";
  375. return $hash->{NAME};
  376. }
  377. #####################################
  378. sub
  379. FHZ_Crc(@)
  380. {
  381. my $sum = 0;
  382. map { $sum += $_; } @_;
  383. return $sum & 0xFF;
  384. }
  385. #####################################
  386. sub
  387. FHZ_CheckCrc($)
  388. {
  389. my $msg = shift;
  390. return 0 if(length($msg) < 8);
  391. my @data;
  392. for(my $i = 8; $i < length($msg); $i += 2) {
  393. push(@data, ord(pack('H*', substr($msg, $i, 2))));
  394. }
  395. my $crc = hex(substr($msg, 6, 2));
  396. # FS20 Repeater generate a CRC which is one or two greater then the computed
  397. # one. The FHZ1000 filters such pakets, so we do not see them
  398. return (($crc eq FHZ_Crc(@data)) ? 1 : 0);
  399. }
  400. #####################################
  401. # This is a direct read for commands like get
  402. sub
  403. FHZ_ReadAnswer($$$)
  404. {
  405. my ($hash,$arg, $to) = @_;
  406. return undef if(!$hash || ($^O!~/Win/ && !defined($hash->{FD})));
  407. my ($mfhzdata, $rin) = ("", '');
  408. my $buf;
  409. for(;;) {
  410. if($^O =~ m/Win/) {
  411. $hash->{PortObj}->read_const_time($to*1000); # set timeout (ms)
  412. # Read anstatt input sonst funzt read_const_time nicht.
  413. $buf = $hash->{PortObj}->read(999);
  414. return "Timeout reading answer for get $arg"
  415. if(length($buf) == 0);
  416. } else {
  417. vec($rin, $hash->{FD}, 1) = 1;
  418. my $nfound = select($rin, undef, undef, $to);
  419. if($nfound < 0) {
  420. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  421. die("Select error $nfound / $!\n");
  422. }
  423. return "Timeout reading answer for get $arg"
  424. if($nfound == 0);
  425. $buf = $hash->{PortObj}->input();
  426. }
  427. Log3 $hash, 4, "FHZ/RAW: " . unpack('H*',$buf);
  428. $mfhzdata .= $buf;
  429. next if(length($mfhzdata) < 2);
  430. my $len = ord(substr($mfhzdata,1,1)) + 2;
  431. if($len>20) {
  432. Log3 $hash, 4, "Oversized message (" . unpack('H*',$mfhzdata) .
  433. "), dropping it ...";
  434. return undef;
  435. }
  436. return unpack('H*', $mfhzdata) if(length($mfhzdata) == $len);
  437. }
  438. }
  439. ##############
  440. # Compute CRC, add header, glue fn and messages
  441. sub
  442. FHZ_CompleteMsg($$)
  443. {
  444. my ($fn,$msg) = @_;
  445. my $len = length($msg);
  446. my @data;
  447. for(my $i = 0; $i < $len; $i += 2) {
  448. push(@data, ord(pack('H*', substr($msg, $i, 2))));
  449. }
  450. return pack('C*', 0x81, $len/2+2, ord(pack('H*',$fn)), FHZ_Crc(@data), @data);
  451. }
  452. #####################################
  453. # Check if the 1% limit is reached and trigger notifies
  454. sub
  455. FHZ_XmitLimitCheck($$)
  456. {
  457. my ($hash,$bstring) = @_;
  458. my $now = time();
  459. $bstring = unpack('H*', $bstring);
  460. return if($bstring =~ m/c90185$/); # fhtbuf
  461. if(!$hash->{XMIT_TIME}) {
  462. $hash->{XMIT_TIME}[0] = $now;
  463. $hash->{NR_CMD_LAST_H} = 1;
  464. return;
  465. }
  466. my $nowM1h = $now-3600;
  467. my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
  468. if(@b > 163) { # Maximum nr of transmissions per hour (unconfirmed).
  469. my $me = $hash->{NAME};
  470. Log3 $me, 2, "FHZ TRANSMIT LIMIT EXCEEDED";
  471. DoTrigger($me, "TRANSMIT LIMIT EXCEEDED");
  472. } else {
  473. push(@b, $now);
  474. }
  475. $hash->{XMIT_TIME} = \@b;
  476. $hash->{NR_CMD_LAST_H} = int(@b);
  477. }
  478. #####################################
  479. sub
  480. FHZ_Write($$$)
  481. {
  482. my ($hash,$fn,$msg) = @_;
  483. if(!$hash || !defined($hash->{PortObj})) {
  484. Log3 $hash, 5, "FHZ device $hash->{NAME} is not active, cannot send";
  485. return;
  486. }
  487. ###############
  488. # insert value into the msghist. At the moment this only makes sense for FS20
  489. # devices. As the transmitted value differs from the received one, we have to
  490. # recompute.
  491. if($fn eq "04" && substr($msg,0,6) eq "010101") {
  492. AddDuplicate($hash->{NAME},
  493. "0101a001" . substr($msg, 6, 6) . "00" . substr($msg, 12));
  494. }
  495. my $bstring = FHZ_CompleteMsg($fn, $msg);
  496. Log3 $hash, 5, "Sending " . unpack('H*', $bstring);
  497. if(!$hash->{QUEUE}) {
  498. FHZ_XmitLimitCheck($hash,$bstring);
  499. $hash->{QUEUE} = [ $bstring ];
  500. $hash->{PortObj}->write($bstring) if($hash->{PortObj});
  501. ##############
  502. # Write the next buffer not earlier than 0.22 seconds (= 65.6ms + 10ms +
  503. # 65.6ms + 10ms + 65.6ms), else it will be discarded by the FHZ1X00 PC
  504. InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1);
  505. } else {
  506. push(@{$hash->{QUEUE}}, $bstring);
  507. }
  508. }
  509. #####################################
  510. sub
  511. FHZ_HandleWriteQueue($)
  512. {
  513. my $hash = shift;
  514. my $arr = $hash->{QUEUE};
  515. if(defined($arr) && @{$arr} > 0) {
  516. shift(@{$arr});
  517. if(@{$arr} == 0) {
  518. delete($hash->{QUEUE});
  519. return;
  520. }
  521. my $bstring = $arr->[0];
  522. FHZ_XmitLimitCheck($hash,$bstring);
  523. $hash->{PortObj}->write($bstring) if($hash->{PortObj});
  524. InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1);
  525. }
  526. }
  527. #####################################
  528. sub
  529. FHZ_Reopen($)
  530. {
  531. my ($hash) = @_;
  532. my $dev = $hash->{DeviceName};
  533. $hash->{PortObj}->close();
  534. Log3 $hash, 1, "USB device $dev closed";
  535. for(;;) {
  536. sleep(5);
  537. if($^O =~ m/Win/) {
  538. $hash->{PortObj} = new Win32::SerialPort($dev);
  539. }else{
  540. $hash->{PortObj} = new Device::SerialPort($dev);
  541. }
  542. if($hash->{PortObj}) {
  543. Log3 $hash, 1, "USB device $dev reopened";
  544. $hash->{FD} = $hash->{PortObj}->FILENO if($^O !~ m/Win/);
  545. FHZ_DoInit($hash->{NAME}, $hash->{ttytype}, $hash->{PortObj});
  546. return;
  547. }
  548. }
  549. }
  550. #####################################
  551. sub
  552. FHZ_Close($)
  553. {
  554. my ($hash) = @_;
  555. my $dev = $hash->{DeviceName};
  556. return if(!$dev);
  557. my $name = $hash->{NAME};
  558. $hash->{PortObj}->close();
  559. Log3 $name, 1, "USB device $dev closed";
  560. delete($hash->{PortObj});
  561. delete($hash->{FD});
  562. delete($selectlist{"$name.$dev"});
  563. #$readyfnlist{"$name.$dev"} = $hash; # Start polling
  564. $hash->{STATE} = "disconnected";
  565. # Without the following sleep the open of the device causes a SIGSEGV,
  566. # and following opens block infinitely. Only a reboot helps.
  567. sleep(5);
  568. DoTrigger($name, "DISCONNECTED");
  569. }
  570. #####################################
  571. sub
  572. FHZ_Open($)
  573. {
  574. my ($hash) = @_;
  575. my $dev = $hash->{DeviceName};
  576. return if(!$dev);
  577. my $name = $hash->{NAME};
  578. $readyfnlist{"$name.$dev"} = $hash; # Start polling
  579. $hash->{STATE} = "disconnected";
  580. # Without the following sleep the open of the device causes a SIGSEGV,
  581. # and following opens block infinitely. Only a reboot helps.
  582. sleep(5);
  583. DoTrigger($name, "DISCONNECTED");
  584. }
  585. #####################################
  586. sub
  587. FHZ_Read($)
  588. {
  589. my ($hash) = @_;
  590. my $buf = $hash->{PortObj}->input();
  591. my $iohash = $modules{$hash->{TYPE}}; # Our (FHZ) module pointer
  592. my $name = $hash->{NAME};
  593. ###########
  594. # Lets' try again: Some drivers return len(0) on the first read...
  595. if(defined($buf) && length($buf) == 0) {
  596. $buf = $hash->{PortObj}->input();
  597. }
  598. if(!defined($buf) || length($buf) == 0) {
  599. my $dev = $hash->{DeviceName};
  600. Log3 $name, 1, "USB device $dev disconnected, waiting to reappear";
  601. delete($hash->{FD});
  602. $hash->{PortObj}->close();
  603. delete($hash->{PortObj});
  604. delete($hash->{FD});
  605. delete($selectlist{"$name.$dev"});
  606. $readyfnlist{"$name.$dev"} = $hash; # Start polling
  607. $hash->{STATE} = "disconnected";
  608. # Without the following sleep the open of the device causes a SIGSEGV,
  609. # and following opens block infinitely. Only a reboot helps.
  610. sleep(5);
  611. DoTrigger($name, "DISCONNECTED");
  612. }
  613. my $fhzdata = $hash->{PARTIAL};
  614. Log3 $name, 4, "FHZ/RAW: " . unpack('H*',$buf) .
  615. " (Unparsed: " . unpack('H*', $fhzdata) . ")";
  616. $fhzdata .= $buf;
  617. while(length($fhzdata) > 2) {
  618. ###################################
  619. # Skip trash.
  620. my $si = index($fhzdata, $msgstart);
  621. if($si) {
  622. if($si == -1) {
  623. Log3 $name, 5, "Bogus message received, no start character found";
  624. $fhzdata = "";
  625. last;
  626. } else {
  627. Log3 $name, 5, "Bogus message received, skipping to start character";
  628. $fhzdata = substr($fhzdata, $si);
  629. }
  630. }
  631. my $len = ord(substr($fhzdata,1,1)) + 2;
  632. if($len>20) {
  633. Log3 $name, 4,
  634. "Oversized message (" . unpack('H*',$fhzdata) . "), dropping it ...";
  635. $fhzdata = "";
  636. next;
  637. }
  638. last if(length($fhzdata) < $len);
  639. my $dmsg = unpack('H*', substr($fhzdata, 0, $len));
  640. if(FHZ_CheckCrc($dmsg)) {
  641. if(substr($fhzdata,2,1) eq $msgstart) { # Skip function 0x81
  642. $fhzdata = substr($fhzdata, 2);
  643. next;
  644. }
  645. $hash->{"${name}_MSGCNT"}++;
  646. $hash->{"${name}_TIME"} = TimeNow();
  647. $hash->{RAWMSG} = $dmsg;
  648. my %addvals = (RAWMSG => $dmsg);
  649. my $foundp = Dispatch($hash, $dmsg, \%addvals);
  650. $fhzdata = substr($fhzdata, $len);
  651. } else {
  652. Log3 $name, 4, "Bad CRC message, skipping it (Bogus message follows)";
  653. $fhzdata = substr($fhzdata, 2);
  654. }
  655. }
  656. $hash->{PARTIAL} = $fhzdata;
  657. }
  658. 1;
  659. =pod
  660. =item summary connection to the ELV FHZ1000/FHZ1300 USB devices
  661. =item summary_DE Anbindung der ELV FHZ1000/FHZ1300 USB Ger&auml;te
  662. =begin html
  663. <a name="FHZ"></a>
  664. <h3>FHZ</h3>
  665. <ul>
  666. Note: this module requires the Device::SerialPort or Win32::SerialPort module
  667. if the devices is connected via USB or a serial port.
  668. <br><br>
  669. <a name="FHZdefine"></a>
  670. <b>Define</b>
  671. <ul>
  672. <code>define &lt;name&gt; FHZ &lt;serial-device&gt;</code> <br>
  673. <br>
  674. Specifies the serial port to communicate with the FHZ1000PC or FHZ1300PC.
  675. The name(s) of the serial-device(s) depends on your distribution. <br>
  676. If the serial-device is called none, then no device will be opened, so you
  677. can experiment without hardware attached.<br>
  678. The program can service multiple devices, FS20 and FHT device commands will
  679. be sent out through the last FHZ device defined before the definition of
  680. the FS20/FHT device. To change the association, use the IODev attribute.<br>
  681. <br>
  682. For GNU/Linux you may want to read our <a href="linux.html">hints for
  683. GNU/Linux</a> about <a href="linux.html#multipledevices">multiple USB
  684. devices</a>.<br>
  685. <b>Note:</b>The firmware of the FHZ1x00 will drop commands if the airtime
  686. for the last hour would exceed 1% (which corresponds roughly to 163
  687. commands). For this purpose there is a command counter for the last hour
  688. (see list FHZDEVICE), which triggers with "TRANSMIT LIMIT EXCEEDED" if
  689. there were more than 163 commands in the last hour.<br><br>
  690. If you experience problems (for verbose 4 you get a lot of "Bad CRC
  691. message" in the log), then try to define your device as <br> <code>define
  692. &lt;name&gt; FHZ &lt;serial-device&gt; strangetty</code><br>
  693. </ul>
  694. <br>
  695. <a name="FHZset"></a>
  696. <b>Set </b>
  697. <ul>
  698. <code>set FHZ &lt;variable&gt; [&lt;value&gt;]</code>
  699. <br><br>
  700. where <code>value</code> is one of:<br>
  701. <ul>
  702. FHTcode<br>
  703. initFS20<br>
  704. initHMS<br>
  705. stopHMS<br>
  706. initfull<br>
  707. raw<br>
  708. open<br>
  709. reopen<br>
  710. close<br>
  711. time<br>
  712. </ul>
  713. Notes:
  714. <ul>
  715. <li>raw is used to send out "raw" FS20/FHT messages (&quot;setters&quot; only - no query messages!).
  716. See message byte streams in FHEM/00_FHZ.pm and the doc directory for some examples.</li>
  717. <li>In order to set the time of your FHT's, schedule this command every
  718. minute:<br>
  719. <code>define fhz_timer at +*00:01:00 set FHZ time</code><br>
  720. See the <a href="#verbose">verbose</a> to prevent logging of
  721. this command.
  722. </li>
  723. <li>FHTcode is a two digit hex number (from 00 to 63?) and sets the
  724. central FHT code, which is used by the FHT devices. After changing
  725. it, you <b>must</b> reprogram each FHT80b with: PROG (until Sond
  726. appears), then select CEnt, Prog, Select nA.</li>
  727. <li>If the FHT ceases to work for FHT devices whereas other devices
  728. (e.g. HMS, KS300) continue to work, a<ul>
  729. <code>set FHZ initfull</code></ul> command could help. Try<ul>
  730. <code>set FHZ reopen</code></ul> if the FHZ
  731. ceases to work completely. If all else fails, shutdown fhem, unplug
  732. and replug the FHZ device. Problems with FHZ may also be related to
  733. long USB cables or insufficient power on the USB - use a powered hub
  734. to improve this particular part of such issues.
  735. See <a href="http://fhem.de/USB.html">our USB page</a>
  736. for detailed USB / electromag. interference troubleshooting.</li>
  737. <li><code>initfull</code> issues the initialization sequence for the FHZ
  738. device:<br>
  739. <ul><code>
  740. get FHZ init2<br>
  741. get FHZ serial<br>
  742. set FHZ initHMS<br>
  743. set FHZ initFS20<br>
  744. set FHZ time<br>
  745. set FHZ raw 04 01010100010000<br>
  746. </code></ul></li>
  747. <li><code>reopen</code> closes and reopens the serial device port. This
  748. implicitly initializes the FHZ and issues the
  749. <code>initfull</code> command sequence.</li>
  750. <li><code>stopHMS</code> probably is the inverse of <code>initHMS</code>
  751. (I don't have authoritative info on what exactly it does).</li>
  752. <li><code>close</code> closes and frees the serial device port until you open
  753. it again with <code>open</code>, e.g. useful if you need to temporarily
  754. unload the ftdi_sio kernel module to use the <a href="http://www.ftdichip.com/Support/Documents/AppNotes/AN232B-01_BitBang.pdf" target="_blank">bit-bang mode</a>.</li>
  755. </ul>
  756. </ul>
  757. <br>
  758. <a name="FHZget"></a>
  759. <b>Get</b>
  760. <ul>
  761. <code>get FHZ &lt;value&gt;</code>
  762. <br><br>
  763. where <code>value</code> is one of:<br>
  764. <ul>
  765. init1<br>
  766. init2<br>
  767. init3<br>
  768. serial<br>
  769. fhtbuf<br>
  770. </ul>
  771. Notes:
  772. <ul>
  773. <li>The mentioned codes are needed for initializing the FHZ1X00</li>
  774. <li>The answer for a command is also displayed by <code>list FHZ</code>
  775. </li>
  776. <li>
  777. The FHZ1x00PC has a message buffer for the FHT (see the FHT entry in
  778. the <a href="#set">set</a> section). If the buffer is full, then newly
  779. issued commands will be dropped, if the attribute <a
  780. href="#fhtsoftbuffer">fhtsoftbuffer</a> is not set.
  781. <code>fhtbuf</code> returns the free memory in this buffer (in hex),
  782. an empty buffer in the FHZ1000 is 2c (42 bytes), in the FHZ1300 is 4a
  783. (74 bytes). A message occupies 3 + 2x(number of FHT commands) bytes,
  784. this is the second reason why sending multiple FHT commands with one
  785. <a href="#set"> set</a> is a good idea. The first reason is, that
  786. these FHT commands are sent at once to the FHT.
  787. </li>
  788. </ul>
  789. </ul>
  790. <br>
  791. <a name="FHZattr"></a>
  792. <b>Attributes</b>
  793. <ul>
  794. <a name="do_not_notify"></a>
  795. <li>do_not_notify<br>
  796. Disable FileLog/notify/inform notification for a device. This affects
  797. the received signal, the set and trigger commands.</li><br>
  798. <li><a href="#attrdummy">dummy</a></li><br>
  799. <li><a href="#showtime">showtime</a></li><br>
  800. <a name="loglevel"></a>
  801. <li>loglevel<br>
  802. <b>Note:</b>Deprecated! The module maintainer is encouraged to replace it
  803. with verbose.<br><br>
  804. Set the device loglevel to e.g. 6 if you do not wish messages from a
  805. given device to appear in the global logfile (FHZ/FS20/FHT). E.g. to
  806. set the FHT time, you should schedule "set FHZ time" every minute, but
  807. this in turn makes your logfile unreadable. These messages will not be
  808. generated if the FHZ attribute loglevel is set to 6.<br>
  809. On the other hand, if you have to debug a given device, setting its
  810. loglevel to a smaller value than the value of the global verbose attribute,
  811. it will output its messages normally seen only with higher global verbose
  812. levels.
  813. </li> <br>
  814. <li><a href="#model">model</a> (fhz1000,fhz1300)</li><br>
  815. <a name="fhtsoftbuffer"></a>
  816. <li>fhtsoftbuffer<br>
  817. As the FHZ command buffer for FHT devices is limited (see fhtbuf),
  818. and commands are only sent to the FHT device every 120 seconds,
  819. the hardware buffer may overflow and FHT commands get lost.
  820. Setting this attribute implements an "unlimited" software buffer.<br>
  821. Default is disabled (i.e. not set or set to 0).</li><br>
  822. </ul>
  823. <br>
  824. </ul>
  825. =end html
  826. =cut