PageRenderTime 62ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/php_serial.class.php

https://github.com/techieshop/BWRPi_RFIDKey
PHP | 1003 lines | 616 code | 125 blank | 262 comment | 113 complexity | 6a957b2e39d36c8712e91fe9f186ac23 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * Serial port control class
  4. *
  5. * THIS PROGRAM COMES WITH ABSOLUTELY NO WARANTIES !
  6. * USE IT AT YOUR OWN RISKS !
  7. *
  8. * Changes added by Rizwan Kassim <rizwank@geekymedia.com> for OSX functionality
  9. * default serial device for osx devices is /dev/tty.serial for machines with a built in serial device
  10. *
  11. * @author Rémy Sanchez <thenux@gmail.com>
  12. * @thanks Aurélien Derouineau for finding how to open serial ports with windows
  13. * @thanks Alec Avedisyan for help and testing with reading
  14. * @thanks Jim Wright for OSX cleanup/fixes.
  15. * @copyright under GPL 2 licence
  16. *
  17. * As of 2014/02/18, all the IO of this class has essentially been rewritten.
  18. * These changes are major and include many changes to the public interface.
  19. * Code written to take advantage of this class will likely need to be altered.
  20. * Many of the changes have not yet been tested on any platform and those that
  21. * have were tested only on a Raspbian, a Linux variant on the Raspberry PI (v2).
  22. * The changes are expected to fix several reliability issues, will likely work
  23. * well on all platforms, but push some data management to the user of this class
  24. * thus it is less simple to use (and more powerful).
  25. * @thanks and/or curses to Christiana Johnson for these changes.
  26. */
  27. class phpSerial
  28. {
  29. const SERIAL_DEVICE_NOTSET = 0;
  30. const SERIAL_DEVICE_SET = 1;
  31. const SERIAL_DEVICE_OPENED = 2;
  32. var $_device = null;
  33. var $_windevice = null;
  34. var $_dHandle = null;
  35. var $_dState = self::SERIAL_DEVICE_NOTSET;
  36. var $_dPrevState = self::SERIAL_DEVICE_NOTSET;
  37. var $_dBlocking = true;
  38. var $_buffer = "";
  39. var $_buflen = 0;
  40. var $_os = "";
  41. /**
  42. * This var says if buffer should be flushed by sendMessage (true) or manualy (false)
  43. *
  44. * @var bool
  45. */
  46. var $autoflush = true;
  47. /**
  48. * Constructor. Perform some checks about the OS and setserial
  49. *
  50. * @return phpSerial
  51. */
  52. function phpSerial ()
  53. {
  54. setlocale(LC_ALL, "en_US");
  55. $sysname = php_uname();
  56. if (substr($sysname, 0, 5) === "Linux")
  57. {
  58. $this->_os = "linux";
  59. if($this->_exec("stty --version") === 0)
  60. {
  61. register_shutdown_function(array($this, "deviceClose"));
  62. }
  63. else
  64. {
  65. trigger_error("No stty availible, unable to run.", E_USER_ERROR);
  66. }
  67. }
  68. elseif (substr($sysname, 0, 6) === "Darwin")
  69. {
  70. $this->_os = "osx";
  71. // We know stty is available in Darwin.
  72. // stty returns 1 when run from php, because "stty: stdin isn't a
  73. // terminal"
  74. // skip this check
  75. // if($this->_exec("stty") === 0)
  76. // {
  77. register_shutdown_function(array($this, "deviceClose"));
  78. // }
  79. // else
  80. // {
  81. // trigger_error("No stty availible, unable to run.", E_USER_ERROR);
  82. // }
  83. }
  84. elseif(substr($sysname, 0, 7) === "Windows")
  85. {
  86. $this->_os = "windows";
  87. register_shutdown_function(array($this, "deviceClose"));
  88. }
  89. else
  90. {
  91. trigger_error("Host OS is neither osx, linux nor windows, unable to run.", E_USER_ERROR);
  92. exit();
  93. }
  94. }
  95. //
  96. // OPEN/CLOSE DEVICE SECTION -- {START}
  97. //
  98. /**
  99. * Device set function : used to set the device name/address.
  100. * -> linux : use the device address, like /dev/ttyS0
  101. * -> osx : use the device address, like /dev/tty.serial
  102. * -> windows : use the COMxx device name, like COM1 (can also be used
  103. * with linux)
  104. *
  105. * @param string $device the name of the device to be used
  106. * @return bool
  107. */
  108. function deviceSet ($device)
  109. {
  110. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  111. {
  112. if ($this->_os === "linux")
  113. {
  114. if (preg_match("@^COM(\d+):?$@i", $device, $matches))
  115. {
  116. $device = "/dev/ttyS" . ($matches[1] - 1);
  117. }
  118. // setup some better standard serial-port parameters.
  119. // -F $defice : which device to config?
  120. // tell the tty to do less. we want fewer suprises, more raw data.
  121. // '-hup' : don't send hup when last process closes tty
  122. // 'ignbrk' : ignore break characters (pass them through)
  123. // '-icrnl -onlcr' : do not translate \r <--> \n at all.
  124. // '-opost' : don't post-process outgoing data
  125. // '-isig -icanon -iexten' : do not enable various special characters
  126. // '-echo -echoe -echok' : turn off some special echo options
  127. $sttycmd = 'stty -F '.$device;
  128. $sttycmd .= ' -hup ignbrk -icrnl -onlcr -opost -isig -icanon -iexten -echo -echoe -echok';
  129. if ($this->_exec($sttycmd) === 0)
  130. {
  131. $this->_device = $device;
  132. $this->_changeState(self::SERIAL_DEVICE_SET);
  133. return true;
  134. }
  135. }
  136. elseif ($this->_os === "osx")
  137. {
  138. if ($this->_exec("stty -f " . $device) === 0)
  139. {
  140. $this->_device = $device;
  141. $this->_changeState(self::SERIAL_DEVICE_SET);
  142. return true;
  143. }
  144. }
  145. elseif ($this->_os === "windows")
  146. {
  147. if (preg_match("@^COM(\d+):?$@i", $device, $matches) and $this->_exec(exec("mode " . $device . " xon=on BAUD=9600")) === 0)
  148. {
  149. $this->_windevice = "COM" . $matches[1];
  150. $this->_device = "\\.\com" . $matches[1];
  151. $this->_changeState(self::SERIAL_DEVICE_SET);
  152. return true;
  153. }
  154. }
  155. trigger_error("Specified serial port is not valid", E_USER_WARNING);
  156. return false;
  157. }
  158. else
  159. {
  160. trigger_error("You must close your device before to set an other one", E_USER_WARNING);
  161. return false;
  162. }
  163. }
  164. /**
  165. * Opens the device for reading and/or writing.
  166. *
  167. * @param string $mode Opening mode : same parameter as fopen()
  168. * @return bool
  169. */
  170. function deviceOpen ($mode = "r+b")
  171. {
  172. if ($this->_dState === self::SERIAL_DEVICE_OPENED)
  173. {
  174. trigger_error("The device is already opened", E_USER_NOTICE);
  175. return true;
  176. }
  177. if ($this->_dState === self::SERIAL_DEVICE_NOTSET)
  178. {
  179. trigger_error("The device must be set before to be open", E_USER_WARNING);
  180. return false;
  181. }
  182. if (!preg_match("@^[raw]\+?b?$@", $mode))
  183. {
  184. trigger_error("Invalid opening mode : ".$mode.". Use fopen() modes.", E_USER_WARNING);
  185. return false;
  186. }
  187. $this->_dHandle = @fopen($this->_device, $mode);
  188. if ($this->_dHandle !== false)
  189. {
  190. stream_set_blocking($this->_dHandle, 0);
  191. $this->_changeState(self::SERIAL_DEVICE_OPENED);
  192. return true;
  193. }
  194. $this->_dHandle = null;
  195. trigger_error("Unable to open the device", E_USER_WARNING);
  196. return false;
  197. }
  198. /**
  199. * Closes the device
  200. *
  201. * @return bool
  202. */
  203. function deviceClose ()
  204. {
  205. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  206. {
  207. return true;
  208. }
  209. if (fclose($this->_dHandle))
  210. {
  211. $this->_dHandle = null;
  212. $this->_changeState(self::SERIAL_DEVICE_SET);
  213. return true;
  214. }
  215. trigger_error("Unable to close the device", E_USER_ERROR);
  216. return false;
  217. }
  218. /**
  219. * Returns a device file handle to allow external stream_select() usages
  220. *
  221. * @return file pointer resource , the serial port filehandle
  222. */
  223. function getFilehandle ()
  224. {
  225. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  226. {
  227. trigger_error("Device must be opened to get the file handle", E_USER_WARNING);
  228. return false;
  229. }
  230. return $this->_dHandle;
  231. }
  232. //
  233. // OPEN/CLOSE DEVICE SECTION -- {STOP}
  234. //
  235. //
  236. // CONFIGURE SECTION -- {START}
  237. //
  238. /**
  239. * Configure all standard config options.
  240. * See other conf* methods for information about a specific arugment.
  241. *
  242. * @param string $device the name of the device to be used
  243. * @param int $rate the rate to set the port in
  244. * @param int $charbits length of a character (5 <= length <= 8)
  245. * @param string $parity one of the modes
  246. * @param float $stopbits the length of a stop bit.
  247. * @param string $mode Set the flow control mode.
  248. * @return bool true means all settings succeeded.
  249. */
  250. function confPort( $device, $rate=57600, $charbits=8, $parity='none', $stopbits=1, $mode='none' )
  251. {
  252. $sc = ( $this->deviceSet ($device) ? 1 : 0 ); // ex: "/dev/ttyS0"
  253. $sc += ( $this->confBaudRate ( $rate ) ? 1 : 0 ); // 9600
  254. $sc += ( $this->confCharacterLength ( $charbits ) ? 1 : 0 ); // 5,6,7,8
  255. $sc += ( $this->confParity ( $parity ) ? 1 : 0 ); // even, none, ...
  256. $sc += ( $this->confStopBits( $stopbits ) ? 1 : 0 ); // 1, 1.5, 2
  257. $sc += ( $this->confFlowControl( $mode ) ? 1 : 0 ); // none, rts/cts, xon/xoff
  258. return ( $sc == 6 ); // true if all above returned 1
  259. }
  260. /**
  261. * Configure the stream blocking parameter.
  262. *
  263. * @param bool $on true if blocking is desired
  264. * @return void;
  265. */
  266. function confBlocking( $on )
  267. {
  268. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  269. {
  270. trigger_error("Device must be opened to configure the file handle", E_USER_WARNING);
  271. return false;
  272. }
  273. $this->_dBlocking = $on;
  274. return true;
  275. }
  276. /**
  277. * Configure the Baud Rate
  278. * Possible rates : 110, 150, 300, 600, 1200, 2400, 4800, 9600, 38400,
  279. * 57600 and 115200.
  280. *
  281. * @param int $rate the rate to set the port in
  282. * @return bool
  283. */
  284. function confBaudRate ($rate)
  285. {
  286. $validBauds = array (
  287. 110 => 11,
  288. 150 => 15,
  289. 300 => 30,
  290. 600 => 60,
  291. 1200 => 12,
  292. 2400 => 24,
  293. 4800 => 48,
  294. 9600 => 96,
  295. 19200 => 19,
  296. 38400 => 38400,
  297. 57600 => 57600,
  298. 115200 => 115200
  299. );
  300. $ret = false;
  301. $havemsg = false;
  302. $errmsg = "";
  303. if ($this->_dState !== self::SERIAL_DEVICE_SET)
  304. {
  305. $havemsg = true;
  306. $errmsg = "the device is either not set or opened";
  307. }
  308. elseif (isset($validBauds[$rate]))
  309. {
  310. $havemsg = true;
  311. $out = array(0,"");
  312. switch( $this->_os )
  313. {
  314. case 'linux':
  315. $ret = (0 === $this->_exec("stty -F " . $this->_device . " " . (int) $rate, $out));
  316. break;
  317. case 'osx':
  318. $ret = (0 === $this->_exec("stty -f " . $this->_device . " " . (int) $rate, $out));
  319. break;
  320. case 'windows':
  321. $ret = (0 === $this->_exec("mode " . $this->_windevice . " BAUD=" . $validBauds[$rate], $out));
  322. break;
  323. default:
  324. $havemsg = false;
  325. break;
  326. }
  327. $errmsg = $out[1];
  328. }
  329. // the purpose of this is to report $out[1], an error message from the _exec'd command
  330. if (! $ret)
  331. {
  332. $msg = 'Unable to set baud rate';
  333. if( $havemsg )
  334. $msg .= ': '.$out[1];
  335. trigger_error( $msg, E_USER_WARNING );
  336. }
  337. return $ret;
  338. }
  339. /**
  340. * Configure parity.
  341. * Modes : odd, even, none
  342. *
  343. * @param string $parity one of the modes
  344. * @return bool
  345. */
  346. function confParity ($parity)
  347. {
  348. if ($this->_dState !== self::SERIAL_DEVICE_SET)
  349. {
  350. trigger_error("Unable to set parity : the device is either not set or opened", E_USER_WARNING);
  351. return false;
  352. }
  353. $args = array(
  354. "none" => "-parenb",
  355. "odd" => "parenb parodd",
  356. "even" => "parenb -parodd",
  357. );
  358. if (!isset($args[$parity]))
  359. {
  360. trigger_error("Parity mode not supported", E_USER_WARNING);
  361. return false;
  362. }
  363. if ($this->_os === "linux")
  364. {
  365. $ret = $this->_exec("stty -F " . $this->_device . " " . $args[$parity], $out);
  366. }
  367. elseif ($this->_os === "osx")
  368. {
  369. $ret = $this->_exec("stty -f " . $this->_device . " " . $args[$parity], $out);
  370. }
  371. else
  372. {
  373. $ret = $this->_exec("mode " . $this->_windevice . " PARITY=" . $parity{0}, $out);
  374. }
  375. if ($ret === 0)
  376. {
  377. return true;
  378. }
  379. trigger_error("Unable to set parity : " . $out[1], E_USER_WARNING);
  380. return false;
  381. }
  382. /**
  383. * Sets the length of a character.
  384. *
  385. * @param int $int length of a character (5 <= length <= 8)
  386. * @return bool
  387. */
  388. function confCharacterLength ($int)
  389. {
  390. if ($this->_dState !== self::SERIAL_DEVICE_SET)
  391. {
  392. trigger_error("Unable to set length of a character : the device is either not set or opened", E_USER_WARNING);
  393. return false;
  394. }
  395. $int = (int) $int;
  396. if ($int < 5) $int = 5;
  397. elseif ($int > 8) $int = 8;
  398. if ($this->_os === "linux")
  399. {
  400. $ret = $this->_exec("stty -F " . $this->_device . " cs" . $int, $out);
  401. }
  402. elseif ($this->_os === "osx")
  403. {
  404. $ret = $this->_exec("stty -f " . $this->_device . " cs" . $int, $out);
  405. }
  406. else
  407. {
  408. $ret = $this->_exec("mode " . $this->_windevice . " DATA=" . $int, $out);
  409. }
  410. if ($ret === 0)
  411. {
  412. return true;
  413. }
  414. trigger_error("Unable to set character length : " .$out[1], E_USER_WARNING);
  415. return false;
  416. }
  417. /**
  418. * Sets the length of stop bits.
  419. *
  420. * @param float $length the length of a stop bit. It must be either 1,
  421. * 1.5 or 2. 1.5 is not supported under linux and on some computers.
  422. * @return bool
  423. */
  424. function confStopBits ($length)
  425. {
  426. if ($this->_dState !== self::SERIAL_DEVICE_SET)
  427. {
  428. trigger_error("Unable to set the length of a stop bit : the device is either not set or opened", E_USER_WARNING);
  429. return false;
  430. }
  431. if ($length != 1 and $length != 2 and $length != 1.5 and !($length == 1.5 and $this->_os === "linux"))
  432. {
  433. trigger_error("Specified stop bit length is invalid", E_USER_WARNING);
  434. return false;
  435. }
  436. if ($this->_os === "linux")
  437. {
  438. $ret = $this->_exec("stty -F " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);
  439. }
  440. elseif ($this->_os === "osx")
  441. {
  442. $ret = $this->_exec("stty -f " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);
  443. }
  444. else
  445. {
  446. $ret = $this->_exec("mode " . $this->_windevice . " STOP=" . $length, $out);
  447. }
  448. if ($ret === 0)
  449. {
  450. return true;
  451. }
  452. trigger_error("Unable to set stop bit length : " . $out[1], E_USER_WARNING);
  453. return false;
  454. }
  455. /**
  456. * Configures the flow control
  457. *
  458. * @param string $mode Set the flow control mode. Availible modes :
  459. * -> "none" : no flow control
  460. * -> "rts/cts" : use RTS/CTS handshaking
  461. * -> "xon/xoff" : use XON/XOFF protocol
  462. * @return bool
  463. */
  464. function confFlowControl ($mode)
  465. {
  466. if ($this->_dState !== self::SERIAL_DEVICE_SET)
  467. {
  468. trigger_error("Unable to set flow control mode : the device is either not set or opened", E_USER_WARNING);
  469. return false;
  470. }
  471. $linuxModes = array(
  472. "none" => "clocal -crtscts -ixon -ixoff",
  473. "rts/cts" => "-clocal crtscts -ixon -ixoff",
  474. "xon/xoff" => "-clocal -crtscts ixon ixoff"
  475. );
  476. $windowsModes = array(
  477. "none" => "xon=off octs=off rts=on",
  478. "rts/cts" => "xon=off octs=on rts=hs",
  479. "xon/xoff" => "xon=on octs=off rts=on",
  480. );
  481. if ($mode !== "none" and $mode !== "rts/cts" and $mode !== "xon/xoff") {
  482. trigger_error("Invalid flow control mode specified", E_USER_ERROR);
  483. return false;
  484. }
  485. if ($this->_os === "linux")
  486. $ret = $this->_exec("stty -F " . $this->_device . " " . $linuxModes[$mode], $out);
  487. elseif ($this->_os === "osx")
  488. $ret = $this->_exec("stty -f " . $this->_device . " " . $linuxModes[$mode], $out);
  489. else
  490. $ret = $this->_exec("mode " . $this->_windevice . " " . $windowsModes[$mode], $out);
  491. if ($ret === 0) return true;
  492. else {
  493. trigger_error("Unable to set flow control : " . $out[1], E_USER_ERROR);
  494. return false;
  495. }
  496. }
  497. /**
  498. * Sets a setserial parameter (cf man setserial)
  499. * NO MORE USEFUL !
  500. * -> No longer supported
  501. * -> Only use it if you need it
  502. *
  503. * @param string $param parameter name
  504. * @param string $arg parameter value
  505. * @return bool
  506. */
  507. function setSetserialFlag ($param, $arg = "")
  508. {
  509. if (!$this->_ckOpened()) return false;
  510. $return = exec ("setserial " . $this->_device . " " . $param . " " . $arg . " 2>&1");
  511. if ($return{0} === "I")
  512. {
  513. trigger_error("setserial: Invalid flag", E_USER_WARNING);
  514. return false;
  515. }
  516. elseif ($return{0} === "/")
  517. {
  518. trigger_error("setserial: Error with device file", E_USER_WARNING);
  519. return false;
  520. }
  521. else
  522. {
  523. return true;
  524. }
  525. }
  526. //
  527. // CONFIGURE SECTION -- {STOP}
  528. //
  529. //
  530. // I/O SECTION -- {START}
  531. //
  532. /**
  533. * Sends a string to the device
  534. *
  535. * @param string $str string to be sent to the device
  536. * @param int $len number of bytes to write.
  537. * if($len <= 0) then attempt to write the whole buffer.
  538. * @return int indicating the number of bytes actually written.
  539. */
  540. function sendMessage ($str, $len=0)
  541. {
  542. if( ! empty($str) ) // _buffer may not be empty
  543. {
  544. $this->_buffer .= $str;
  545. $this->_buflen += strlen($str);
  546. }
  547. $written = 0;
  548. $bytes = 0;
  549. if ($this->autoflush === true || $len < 0)
  550. {
  551. // success means the whole buffer was written.
  552. $bytes = $this->_buflen;
  553. if( ! $this->serialflush() )
  554. $bytes = false;
  555. }
  556. elseif ($len != 0)
  557. {
  558. // do not try to write more than we have in the buffer.
  559. $len = ( $len <= $this->_buflen ? $len : $this->_buflen );
  560. $written = 0;
  561. do{
  562. $bytes = $this->writePort( $this->_buffer, $len - $written );
  563. if ($bytes !== false)
  564. $this->_buflen -= $bytes;
  565. } while( $written < $len && $bytes !== false );
  566. // but... what if $written !== 0 before this?
  567. if( $bytes === false )
  568. $written = false;
  569. }
  570. // else { write nothing at all right now. wait for explicit flush. }
  571. return $written;
  572. }
  573. public function appendToBuffer( $cntnt ) { $this->_buffer .= $cntnt; $this->_buflen = strlen($this->_buffer); }
  574. public function setBuffer( $cntnt ) { $this->_buffer = $cntnt; $this->_buflen = strlen($this->_buffer); }
  575. public function getBuffer() { return $this->_buffer; }
  576. public function getBufLength() { return $this->_buflen; }
  577. /**
  578. * XXX Experimental.
  579. * This should work for all platforms, including windows, but is untested.
  580. *
  581. * Writes to the port. A reaonably standard write() function.
  582. *
  583. * @pararm string &$content This the content to write. The resulting
  584. * value of this parameter, after return, will be shorter if successful.
  585. * @pararm int $len count of bytes characters write.(from write docs)
  586. * writing will stop after len bytes have been written or the end
  587. * of content is reached, whichever comes first.
  588. * @pararm float $timeout number of seconds to try before returning.
  589. * null timeout means block until finished writing or until an error
  590. * @return int the count of bytes written or false on error.
  591. */
  592. public function writePort( &$content, $count=null, $timeout=null )
  593. {
  594. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  595. {
  596. trigger_error("Device must be opened to write to it", E_USER_WARNING);
  597. return false;
  598. }
  599. // return 0 if there's nothing to write. validate $count.
  600. if ($count !== null && $count <= 0 ) return 0;
  601. if ($count === null || strlen($content) < $count ) $count = strlen($content);
  602. if ($count == 0) return 0;
  603. // negative timeout has already timed out.
  604. if ($timeout !== null && $timeout < 0 ) return 0;
  605. // but now negative timeout becomes a code for "never timeout"
  606. if ($timeout === null) $timeout = -1;
  607. $starttime = microtime( true );
  608. $totcnt = 0; // at all times, this is the total count of bytes sent
  609. $error = false; // stop loop if there's an error
  610. $et = 0; // elapsed time. if timeout==0: execute all, once.
  611. do {
  612. $ready_count = 1;
  613. // because non-blocking is set on _dHandle, we must implement a
  614. // blocking call on our own. We do this using stream_select().
  615. // _dBlocking == true by default, so this code, in the
  616. // following block of code, will probably run.
  617. if( $this->_dBlocking )
  618. {
  619. // if blocking, stop and wait for $timeout seconds
  620. // or until there's something to read
  621. $loop_count = 0;
  622. do {
  623. $loop_count++;
  624. $r = null;
  625. $w = array( $this->_dHandle );
  626. $e = null;
  627. if( $timeout < 0 ) // "no timeout" can be a very long time
  628. $ready_count = stream_select( $r, $w, $e, NULL );
  629. else
  630. {
  631. // if $et == $timeout then to_usec == 0, and select() won't block
  632. if( $et > $timeout ) // timed out.
  633. break;
  634. $to_sec = $to_usec = 0; // php-style declaration
  635. floatsec_to_secusec( $timeout - $et, $to_sec, $to_usec );
  636. $ready_count = stream_select( $r, $w, $e, $to_sec, $to_usec );
  637. }
  638. // if we're getting errors terminate this loop after
  639. // only a few tries. this should happen very rarely.
  640. if ($loop_count > 4)
  641. break;
  642. } while ( $ready_count === false );
  643. if ($ready_count === false) // needed once in a blue moon.
  644. $ready_count = 0;
  645. }
  646. if ($ready_count > 0)
  647. {
  648. $ac = fwrite($this->_dHandle, $count-$totcnt);
  649. if( $ac === false )
  650. $error = true;
  651. elseif( $ac > 0 )
  652. {
  653. $totcnt += $ac;
  654. $content = substr( $content, $ac );
  655. }
  656. }
  657. if( $timeout >= 0 )
  658. {
  659. $et = microtime( true ) - $starttime;
  660. if( $et >= $timeout ) // timed out
  661. break; // break from while() loop. stop everything.
  662. }
  663. } while ( $totcnt < $count // stop if total meets read count sought
  664. && $this->_dBlocking // !!! do not loop if non-blocking
  665. && ! $error ); // stop if error
  666. // we want to report an error if there is one. setting totcnt to
  667. // false doesn't destroy information because totcnt ==
  668. // strlen($content) and $content *is* returned.
  669. if( $error )
  670. $totcnt = false;
  671. return $totcnt; // set to false if error
  672. }
  673. /**
  674. * XXX Experimental but should fix bugs, hopefully without making new ones.
  675. * This should work for all platforms, including windows, but is untested.
  676. *
  677. * Reads the port until no new datas are availible, then return the content.
  678. *
  679. * @pararm string &$content This value is ignored, but the parameter is
  680. * used to return the data read from the port. Parameter is set to ""
  681. * if no data is read.
  682. * @pararm int $count number of characters to be read (will stop before
  683. * if less characters are in the buffer)
  684. * @pararm float $timeout number of seconds to try before returning.
  685. * null timeout means block until finished reading or until an error.
  686. * This parameter is ignored in non-blocking mode.
  687. * @return int the count of bytes read or false on error.
  688. */
  689. function readPort (&$content, $count = null, $timeout = null)
  690. {
  691. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  692. {
  693. trigger_error("Device must be opened to read it", E_USER_WARNING);
  694. return false;
  695. }
  696. if ($count !== null && $count <= 0 ) return 0;
  697. if ($count === null) $count = 0;
  698. if ($timeout !== null && $timeout < 0 ) return 0;
  699. if ($timeout === null) $timeout = -1;
  700. if ($content === null)
  701. $content = "";
  702. $starttime = microtime( true );
  703. $totcnt = 0; // at all times, this is the total count of bytes read
  704. $error = false; // stop loop if there's an error
  705. $et = 0; // elapsed time. if timeout==0: execute all, once.
  706. do {
  707. $ready_count = 1;
  708. // because non-blocking is set on _dHandle, we must implement a
  709. // blocking call on our own. We do this using stream_select().
  710. // _dBlocking == true by default, so this code, in the
  711. // following block of code, will probably run.
  712. if( $this->_dBlocking )
  713. {
  714. // if blocking, stop and wait for $timeout seconds
  715. // or until there's something to read
  716. $loop_count = 0;
  717. do {
  718. $loop_count++;
  719. $r = array( $this->_dHandle );
  720. $w = null;
  721. $e = null;
  722. if( $timeout < 0 ) // "no timeout" can be a very long time
  723. $ready_count = stream_select( $r, $w, $e, NULL );
  724. else
  725. {
  726. // if $et == $timeout then to_usec == 0, and select() won't block
  727. if( $et > $timeout ) // timed out.
  728. break;
  729. $to_sec = $to_usec = 0; // php-style declaration
  730. floatsec_to_secusec( $timeout - $et, $to_sec, $to_usec );
  731. $ready_count = stream_select( $r, $w, $e, $to_sec, $to_usec );
  732. }
  733. // if we're getting errors terminate this loop after
  734. // only a few tries. this should happen very rarely.
  735. if ($loop_count > 4)
  736. break;
  737. } while ( $ready_count === false );
  738. if ($ready_count === false) // needed once in a blue moon.
  739. $ready_count = 0;
  740. }
  741. if ($ready_count > 0)
  742. {
  743. $tc = 1024*1024; // READ!!! as much as possible.
  744. if ($count !== 0) // unless otherwise told
  745. $tc = ($count - $totcnt);
  746. $bytes = fread($this->_dHandle, $tc);
  747. if( $bytes === false )
  748. $error = true;
  749. else
  750. {
  751. $ac = strlen( $bytes ); // strlen() gives *byte* count, including nulls.
  752. if( $ac != 0 )
  753. {
  754. $totcnt += $ac;
  755. $content .= $bytes;
  756. }
  757. }
  758. }
  759. if( $timeout >= 0 )
  760. {
  761. $et = microtime( true ) - $starttime;
  762. if( $et >= $timeout ) // timed out
  763. break; // break from while() loop. stop everything.
  764. }
  765. } while ( $totcnt < $count // if($count == NULL) loop executes once
  766. && $this->_dBlocking // !!! do not loop if non-blocking
  767. && ! $error); // stop if error
  768. // we want to report an error if there is one. setting totcnt to
  769. // false doesn't destroy information because totcnt ==
  770. // strlen($content) and $content *is* returned.
  771. if( $error )
  772. $totcnt = false;
  773. return $totcnt; // set to false if error
  774. }
  775. /**
  776. * Flushes the output buffer
  777. * Renamed from flush for osx compat. issues
  778. *
  779. * @return bool
  780. */
  781. function serialflush ()
  782. {
  783. $success = false;
  784. if (!$this->_ckOpened()) return false;
  785. $init_length = $this->_buflen;
  786. $bytes = 0;
  787. $written = 0;
  788. while( $this->_buflen > 0 && $bytes !== false )
  789. {
  790. // long timeout because flush() should take all necessary time.
  791. $bytes = $this->writePort( $this->_buffer, $this->_buflen, 5.0 );
  792. if( $bytes !== false )
  793. {
  794. $written += $bytes;
  795. $this->_buflen -= $bytes;
  796. }
  797. }
  798. // success only truely depends only on if the buffer was actually
  799. // written or not, so report that value regardless of errors.
  800. $success = ( $written === $init_length );
  801. if( $bytes === false )
  802. trigger_error('Error while sending message. wrote '.$written.' of '.$init_length.' byte(s) before failing.', E_USER_WARNING);
  803. // XXX assert( $this->_buflen === 0 && strlen($this->_buffer) === 0 )
  804. if( $this->_buflen !== 0 || strlen($this->_buffer) !== 0 )
  805. trigger_error('Internal Error. Flush fell short? wrote '.$written.' of '.$init_length.' byte(s) before exit.', E_USER_WARNING);
  806. // after flush the buffer is guarenteed to be empty.
  807. $this->_buffer = "";
  808. $this->_buflen = 0;
  809. return $success;
  810. }
  811. //
  812. // I/O SECTION -- {STOP}
  813. //
  814. //
  815. // INTERNAL TOOLKIT -- {START}
  816. //
  817. /**
  818. * Protected
  819. * Change Device state. for managing state info
  820. *
  821. * @param int $newstate the state change to. one of the SERIAL_DEVICE_* constants.
  822. * @return int
  823. */
  824. protected function _changeState( $newstate )
  825. {
  826. $this->_dPrevState = $this->_dState; // to detect 'closed' state. perhaps other.
  827. $this->_dState = $newstate;
  828. return $this->_dState;
  829. }
  830. function _ckOpened()
  831. {
  832. if ($this->_dState !== self::SERIAL_DEVICE_OPENED)
  833. {
  834. trigger_error("Device must be opened", E_USER_WARNING);
  835. return false;
  836. }
  837. return true;
  838. }
  839. function _ckClosed()
  840. {
  841. // to differentiate 'closed' from 'set' we check both _dState and _dPrevState
  842. if ($this->_dState !== self::SERIAL_DEVICE_SET || $this->_dPrevState !== self::SERIAL_DEVICE_OPENED)
  843. {
  844. trigger_error("Device must be closed", E_USER_WARNING);
  845. return false;
  846. }
  847. return true;
  848. }
  849. function _exec($cmd, &$out = null)
  850. {
  851. $desc = array(
  852. 1 => array("pipe", "w"),
  853. 2 => array("pipe", "w")
  854. );
  855. $proc = proc_open($cmd, $desc, $pipes);
  856. $ret = stream_get_contents($pipes[1]);
  857. $err = stream_get_contents($pipes[2]);
  858. fclose($pipes[1]);
  859. fclose($pipes[2]);
  860. $retVal = proc_close($proc);
  861. if (func_num_args() == 2) $out = array($ret, $err);
  862. return $retVal;
  863. }
  864. //
  865. // INTERNAL TOOLKIT -- {STOP}
  866. //
  867. }
  868. // convert float to pair of ints. sec and usec
  869. function floatsec_to_secusec( $floatsec, &$sec, &$usec )
  870. {
  871. $usec = $sec = $floatsec;
  872. $sec = floor( $sec ); // get int seconds
  873. $usec -= $sec; // get fractional seconds
  874. $usec *= 1000000; // micro is million
  875. $usec = floor( $usec ); // discard nanosec
  876. }
  877. /* vim: set ai noet ts=4 sw=4: */
  878. ?>