DevIo.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. ##############################################
  2. # $Id: DevIo.pm 12716 2016-12-05 09:11:31Z rudolfkoenig $
  3. package main;
  4. sub DevIo_CloseDev($@);
  5. sub DevIo_Disconnected($);
  6. sub DevIo_Expect($$$);
  7. sub DevIo_OpenDev($$$;$);
  8. sub DevIo_SetHwHandshake($);
  9. sub DevIo_SimpleRead($);
  10. sub DevIo_SimpleReadWithTimeout($$);
  11. sub DevIo_SimpleWrite($$$);
  12. sub DevIo_TimeoutRead($$);
  13. sub
  14. DevIo_setStates($$)
  15. {
  16. my ($hash, $val) = @_;
  17. $hash->{STATE} = $val;
  18. setReadingsVal($hash, "state", $val, TimeNow());
  19. }
  20. ########################
  21. sub
  22. DevIo_DoSimpleRead($)
  23. {
  24. my ($hash) = @_;
  25. my ($buf, $res);
  26. if($hash->{USBDev}) {
  27. $buf = $hash->{USBDev}->input();
  28. } elsif($hash->{DIODev}) {
  29. $res = sysread($hash->{DIODev}, $buf, 4096);
  30. $buf = undef if(!defined($res));
  31. } elsif($hash->{TCPDev}) {
  32. $res = sysread($hash->{TCPDev}, $buf, 4096);
  33. $buf = "" if(!defined($res));
  34. } elsif($hash->{IODev}) {
  35. if($hash->{IOReadFn}) {
  36. $buf = CallFn($hash->{IODev}{NAME},"IOReadFn",$hash);
  37. } else {
  38. $buf = $hash->{IODevRxBuffer};
  39. $hash->{IODevRxBuffer} = "";
  40. $buf = "" if(!defined($buf));
  41. }
  42. }
  43. return $buf;
  44. }
  45. ########################
  46. # If called directly after a select, it should not block.
  47. sub
  48. DevIo_SimpleRead($)
  49. {
  50. my ($hash) = @_;
  51. my $buf = DevIo_DoSimpleRead($hash);
  52. ###########
  53. # Lets' try again: Some drivers return len(0) on the first read...
  54. if(defined($buf) && length($buf) == 0) {
  55. $buf = DevIo_SimpleReadWithTimeout($hash, 1);
  56. }
  57. if(!defined($buf) || length($buf) == 0) {
  58. DevIo_Disconnected($hash);
  59. return undef;
  60. }
  61. return $buf;
  62. }
  63. ########################
  64. # wait at most timeout seconds until the file handle gets ready
  65. # for reading; returns undef on timeout
  66. # NOTE1: FHEM can be blocked for $timeout seconds!
  67. # NOTE2: This works on Windows only for TCP connections
  68. sub
  69. DevIo_SimpleReadWithTimeout($$)
  70. {
  71. my ($hash, $timeout) = @_;
  72. my $rin = "";
  73. vec($rin, $hash->{FD}, 1) = 1;
  74. my $nfound = select($rin, undef, undef, $timeout);
  75. return DevIo_DoSimpleRead($hash) if($nfound> 0);
  76. return undef;
  77. }
  78. ########################
  79. # Read until you get the timeout. Use it with care since it waits _at least_
  80. # timeout seconds, and it works on Windows only for TCP/IP connections
  81. sub
  82. DevIo_TimeoutRead($$)
  83. {
  84. my ($hash, $timeout) = @_;
  85. my $answer = "";
  86. for(;;) {
  87. my $rin = "";
  88. vec($rin, $hash->{FD}, 1) = 1;
  89. my $nfound = select($rin, undef, undef, $timeout);
  90. last if($nfound <= 0);
  91. my $r = DevIo_DoSimpleRead($hash);
  92. last if(!defined($r) || ($r == "" && $hash->{TCPDev}));
  93. $answer .= $r;
  94. }
  95. return $answer;
  96. }
  97. ########################
  98. # Input is HEX, with header and CRC
  99. sub
  100. DevIo_SimpleWrite($$$)
  101. {
  102. my ($hash, $msg, $type) = @_; # Type: 0:binary, 1:hex, 2:ASCII
  103. return if(!$hash);
  104. my $name = $hash->{NAME};
  105. Log3 ($name, 5, $type ? "SW: $msg" : "SW: ".unpack("H*",$msg));
  106. $msg = pack('H*', $msg) if($type && $type == 1);
  107. if($hash->{USBDev}){
  108. $hash->{USBDev}->write($msg);
  109. } elsif($hash->{TCPDev}) {
  110. syswrite($hash->{TCPDev}, $msg);
  111. } elsif($hash->{DIODev}) {
  112. syswrite($hash->{DIODev}, $msg);
  113. } elsif($hash->{IODev}) {
  114. CallFn($hash->{IODev}{NAME},"IOWriteFn",$hash,$msg);
  115. }
  116. select(undef, undef, undef, 0.001);
  117. }
  118. ########################
  119. # Write something, then read something
  120. # reopen device if timeout occurs and write again, then read again
  121. sub
  122. DevIo_Expect($$$)
  123. {
  124. my ($hash, $msg, $timeout) = @_;
  125. my $name= $hash->{NAME};
  126. my $state= $hash->{STATE};
  127. if($state ne "opened") {
  128. Log3 $name, 2, "Attempt to write to $state device.";
  129. return undef;
  130. }
  131. # write something
  132. return undef unless defined(DevIo_SimpleWrite($hash, $msg, 0));
  133. # read answer
  134. my $answer= DevIo_SimpleReadWithTimeout($hash, $timeout);
  135. return $answer unless($answer eq "");
  136. # the device has failed to deliver a result
  137. DevIo_setStates($hash, "failed");
  138. DoTrigger($name, "FAILED");
  139. # reopen device
  140. # unclear how to know whether the following succeeded
  141. Log3 $name, 2, "$name: first attempt to read timed out, ".
  142. "trying to close and open the device.";
  143. # The next two lines are required to avoid a deadlock when the remote end
  144. # closes the connection upon DevIo_OpenDev, as e.g. netcat -l <port> does.
  145. DevIo_CloseDev($hash);
  146. DevIo_OpenDev($hash, 0, undef); # where to get the initfn from?
  147. # write something again
  148. return undef unless defined(DevIo_SimpleWrite($hash, $msg, 0));
  149. # read answer again
  150. $answer= DevIo_SimpleReadWithTimeout($hash, $timeout);
  151. # success
  152. if($answer ne "") {
  153. DevIo_setStates($hash, "opened");
  154. DoTrigger($name, "CONNECTED");
  155. return $answer;
  156. }
  157. # ultimate failure
  158. Log3 $name, 2,
  159. "$name: second attempt to read timed out, this is an unrecoverable error.";
  160. DoTrigger($name, "DISCONNECTED");
  161. return undef; # undef means ultimate failure
  162. }
  163. ########################
  164. # callback is only meaningful for TCP/IP (Nonblocking connect), but can used in
  165. # every cases. It will be called with $hash and a (potential) error message
  166. sub
  167. DevIo_OpenDev($$$;$)
  168. {
  169. my ($hash, $reopen, $initfn, $callback) = @_;
  170. my $dev = $hash->{DeviceName};
  171. my $name = $hash->{NAME};
  172. my $po;
  173. my $baudrate;
  174. ($dev, $baudrate) = split("@", $dev);
  175. my ($databits, $parity, $stopbits) = (8, 'none', 1);
  176. my $nextOpenDelay = ($hash->{nextOpenDelay} ? $hash->{nextOpenDelay} : 60);
  177. my $doCb = sub ($) {
  178. my ($r) = @_;
  179. Log3 $name, 3, "Can't connect to $dev: $r" if(!$reopen && $r);
  180. $callback->($hash,$r) if($callback);
  181. return $r;
  182. };
  183. my $doTailWork = sub {
  184. DevIo_setStates($hash, "opened");
  185. my $ret;
  186. if($initfn) {
  187. my $hadFD = defined($hash->{FD});
  188. $ret = &$initfn($hash);
  189. if($ret) {
  190. if($hadFD && !defined($hash->{FD})) { # Forum #54732 / ser2net
  191. DevIo_Disconnected($hash);
  192. $hash->{NEXT_OPEN} = time() + $nextOpenDelay;
  193. } else {
  194. DevIo_CloseDev($hash);
  195. Log3 $name, 1, "Cannot init $dev, ignoring it ($name)";
  196. }
  197. }
  198. }
  199. if(!$ret) {
  200. my $l = $hash->{devioLoglevel}; # Forum #61970
  201. if($reopen) {
  202. Log3 $name, ($l ? $l:1), "$dev reappeared ($name)";
  203. } else {
  204. Log3 $name, ($l ? $l:3), "$name device opened" if(!$hash->{DevioText});
  205. }
  206. }
  207. DoTrigger($name, "CONNECTED") if($reopen && !$ret);
  208. return undef;
  209. };
  210. if($baudrate =~ m/(\d+)(,([78])(,([NEO])(,([012]))?)?)?/) {
  211. $baudrate = $1 if(defined($1));
  212. $databits = $3 if(defined($3));
  213. $parity = 'odd' if(defined($5) && $5 eq 'O');
  214. $parity = 'even' if(defined($5) && $5 eq 'E');
  215. $stopbits = $7 if(defined($7));
  216. }
  217. if($hash->{DevIoJustClosed}) {
  218. delete $hash->{DevIoJustClosed};
  219. return &$doCb(undef);
  220. }
  221. $hash->{PARTIAL} = "";
  222. Log3 $name, 3, ($hash->{DevioText} ? $hash->{DevioText} : "Opening").
  223. " $name device $dev" if(!$reopen);
  224. if($dev =~ m/^UNIX:(SEQPACKET|STREAM):(.*)$/) { # FBAHA
  225. my ($type, $fname) = ($1, $2);
  226. my $conn;
  227. eval {
  228. require IO::Socket::UNIX;
  229. $conn = IO::Socket::UNIX->new(
  230. Type=>($type eq "STREAM" ? SOCK_STREAM:SOCK_SEQPACKET), Peer=>$fname);
  231. };
  232. if($@) {
  233. Log3 $name, 1, $@;
  234. return &$doCb($@);
  235. }
  236. if(!$conn) {
  237. Log3 $name, 3, "Can't connect to $dev: $!" if(!$reopen);
  238. $readyfnlist{"$name.$dev"} = $hash;
  239. DevIo_setStates($hash, "disconnected");
  240. return &$doCb("");
  241. }
  242. $hash->{TCPDev} = $conn;
  243. $hash->{FD} = $conn->fileno();
  244. delete($readyfnlist{"$name.$dev"});
  245. $selectlist{"$name.$dev"} = $hash;
  246. } elsif($dev =~ m/^FHEM:DEVIO:(.*)(:(.*))/) { # Forum #46276
  247. my ($devName, $devPort) = ($1, $3);
  248. AssignIoPort($hash, $devName);
  249. if (defined($hash->{IODev})) {
  250. ($dev, $baudrate) = split("@", $hash->{DeviceName});
  251. $hash->{IODevPort} = $devPort if (defined($devPort));
  252. $hash->{IODevParameters} = $baudrate if (defined($baudrate));
  253. if (!CallFn($devName, "IOOpenFn", $hash)) {
  254. Log3 $name, 3, "Can't open $dev!";
  255. DevIo_setStates($hash, "disconnected");
  256. return &$doCb("");
  257. }
  258. } else {
  259. DevIo_setStates($hash, "disconnected");
  260. return &$doCb("");
  261. }
  262. } elsif($dev =~ m/^(.+):([0-9]+)$/) { # host:port
  263. # This part is called every time the timeout (5sec) is expired _OR_
  264. # somebody is communicating over another TCP connection. As the connect
  265. # for non-existent devices has a delay of 3 sec, we are sitting all the
  266. # time in this connect. NEXT_OPEN tries to avoid this problem.
  267. if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN}) {
  268. return &$doCb(undef); # Forum 53309
  269. }
  270. delete($readyfnlist{"$name.$dev"});
  271. my $timeout = $hash->{TIMEOUT} ? $hash->{TIMEOUT} : 3;
  272. my $doTcpTail = sub($) {
  273. my ($conn) = @_;
  274. if($conn) {
  275. delete($hash->{NEXT_OPEN});
  276. $conn->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if(defined($conn));
  277. } else {
  278. Log3 $name, 3, "Can't connect to $dev: $!" if(!$reopen && $!);
  279. $readyfnlist{"$name.$dev"} = $hash;
  280. DevIo_setStates($hash, "disconnected");
  281. $hash->{NEXT_OPEN} = time() + $nextOpenDelay;
  282. return 0;
  283. }
  284. $hash->{TCPDev} = $conn;
  285. $hash->{FD} = $conn->fileno();
  286. $selectlist{"$name.$dev"} = $hash;
  287. return 1;
  288. };
  289. if($callback) {
  290. use HttpUtils;
  291. my $err = HttpUtils_Connect({ # Nonblocking
  292. timeout => $timeout,
  293. url => $hash->{SSL} ? "https://$dev/" : "http://$dev/",
  294. NAME => $hash->{NAME},
  295. noConn2 => 1,
  296. callback=> sub() {
  297. my ($h, $err, undef) = @_;
  298. &$doTcpTail($err ? undef : $h->{conn});
  299. return &$doCb($err ? $err : &$doTailWork());
  300. }
  301. });
  302. return &$doCb($err) if($err);
  303. return undef; # no double callback: connect is running in bg now
  304. } else {
  305. my $conn = IO::Socket::INET->new(PeerAddr => $dev, Timeout => $timeout);
  306. return "" if(!&$doTcpTail($conn)); # no callback: no doCb
  307. }
  308. } elsif($baudrate && lc($baudrate) eq "directio") { # w/o Device::SerialPort
  309. if(!open($po, "+<$dev")) {
  310. return &$doCb(undef) if($reopen);
  311. Log3 $name, 3, "Can't open $dev: $!";
  312. $readyfnlist{"$name.$dev"} = $hash;
  313. DevIo_setStates($hash, "disconnected");
  314. return &$doCb("");
  315. }
  316. $hash->{DIODev} = $po;
  317. if( $^O =~ /Win/ ) {
  318. $readyfnlist{"$name.$dev"} = $hash;
  319. } else {
  320. $hash->{FD} = fileno($po);
  321. delete($readyfnlist{"$name.$dev"});
  322. $selectlist{"$name.$dev"} = $hash;
  323. }
  324. } else { # USB/Serial device
  325. if ($^O=~/Win/) {
  326. eval {
  327. require Win32::SerialPort;
  328. $po = new Win32::SerialPort ($dev);
  329. }
  330. } else {
  331. eval {
  332. require Device::SerialPort;
  333. $po = new Device::SerialPort ($dev);
  334. }
  335. }
  336. if($@) {
  337. Log3 $name, 1, $@;
  338. return &$doCb($@);
  339. }
  340. if(!$po) {
  341. return &$doCb(undef) if($reopen);
  342. Log3 $name, 3, "Can't open $dev: $!";
  343. $readyfnlist{"$name.$dev"} = $hash;
  344. DevIo_setStates($hash, "disconnected");
  345. return &$doCb("");
  346. }
  347. $hash->{USBDev} = $po;
  348. if( $^O =~ /Win/ ) {
  349. $readyfnlist{"$name.$dev"} = $hash;
  350. } else {
  351. $hash->{FD} = $po->FILENO;
  352. delete($readyfnlist{"$name.$dev"});
  353. $selectlist{"$name.$dev"} = $hash;
  354. }
  355. if($baudrate) {
  356. $po->reset_error();
  357. my $p = ($parity eq "none" ? "N" : ($parity eq "odd" ? "O" : "E"));
  358. Log3 $name, 3, "Setting $name serial parameters to ".
  359. "$baudrate,$databits,$p,$stopbits" if(!$hash->{DevioText});
  360. $po->baudrate($baudrate);
  361. $po->databits($databits);
  362. $po->parity($parity);
  363. $po->stopbits($stopbits);
  364. $po->handshake('none');
  365. # This part is for some Linux kernel versions whih has strange default
  366. # settings. Device::SerialPort is nice: if the flag is not defined for
  367. # your OS then it will be ignored.
  368. $po->stty_icanon(0);
  369. #$po->stty_parmrk(0); # The debian standard install does not have it
  370. $po->stty_icrnl(0);
  371. $po->stty_echoe(0);
  372. $po->stty_echok(0);
  373. $po->stty_echoctl(0);
  374. # Needed for some strange distros
  375. $po->stty_echo(0);
  376. $po->stty_icanon(0);
  377. $po->stty_isig(0);
  378. $po->stty_opost(0);
  379. $po->stty_icrnl(0);
  380. }
  381. $po->write_settings;
  382. }
  383. return &$doCb(&$doTailWork());
  384. }
  385. sub
  386. DevIo_SetHwHandshake($)
  387. {
  388. my ($hash) = @_;
  389. $hash->{USBDev}->can_dtrdsr();
  390. $hash->{USBDev}->can_rtscts();
  391. }
  392. ########################
  393. sub
  394. DevIo_CloseDev($@)
  395. {
  396. my ($hash,$isFork) = @_;
  397. my $name = $hash->{NAME};
  398. my $dev = $hash->{DeviceName};
  399. return if(!$dev);
  400. if($hash->{TCPDev}) {
  401. $hash->{TCPDev}->close();
  402. delete($hash->{TCPDev});
  403. } elsif($hash->{USBDev}) {
  404. if($isFork) { # SerialPort close resets the serial parameters.
  405. POSIX::close($hash->{USBDev}{FD});
  406. } else {
  407. $hash->{USBDev}->close() ;
  408. }
  409. delete($hash->{USBDev});
  410. } elsif($hash->{DIODev}) {
  411. close($hash->{DIODev});
  412. delete($hash->{DIODev});
  413. } elsif($hash->{IODev}) {
  414. eval {
  415. CallFn($hash->{IODev}{NAME}, "IOCloseFn", $hash);
  416. }; # ignore closing errors (e.g. caused by fork)
  417. delete($hash->{IODevParameters});
  418. delete($hash->{IODevPort});
  419. delete($hash->{IODevRxBuffer});
  420. delete($hash->{IODev});
  421. }
  422. ($dev, undef) = split("@", $dev); # Remove the baudrate
  423. delete($selectlist{"$name.$dev"});
  424. delete($readyfnlist{"$name.$dev"});
  425. delete($hash->{FD});
  426. delete($hash->{EXCEPT_FD});
  427. }
  428. sub
  429. DevIo_IsOpen($)
  430. {
  431. my ($hash) = @_;
  432. return ($hash->{TCPDev} ||
  433. $hash->{USBDev} ||
  434. $hash->{DIODev} ||
  435. $hash->{IODevPort});
  436. }
  437. sub
  438. DevIo_Disconnected($)
  439. {
  440. my $hash = shift;
  441. my $dev = $hash->{DeviceName};
  442. my $name = $hash->{NAME};
  443. my $baudrate;
  444. ($dev, $baudrate) = split("@", $dev);
  445. return if(!defined($hash->{FD})); # Already deleted or RFR
  446. my $l = $hash->{devioLoglevel}; # Forum #61970
  447. Log3 $name, ($l ? $l:1), "$dev disconnected, waiting to reappear ($name)";
  448. DevIo_CloseDev($hash);
  449. $readyfnlist{"$name.$dev"} = $hash; # Start polling
  450. DevIo_setStates($hash, "disconnected");
  451. $hash->{DevIoJustClosed} = 1; # Avoid a direct reopen
  452. DoTrigger($name, "DISCONNECTED");
  453. }
  454. 1;