PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/php_serial.class.php

https://bitbucket.org/swit/shootingrange
PHP | 650 lines | 425 code | 75 blank | 150 comment | 66 complexity | 67ec0cddf101b4855282bbd7b4accbd1 MD5 | raw file
  1. <?php
  2. define ("SERIAL_DEVICE_NOTSET", 0);
  3. define ("SERIAL_DEVICE_SET", 1);
  4. define ("SERIAL_DEVICE_OPENED", 2);
  5. /**
  6. * Serial port control class
  7. *
  8. * THIS PROGRAM COMES WITH ABSOLUTELY NO WARANTIES !
  9. * USE IT AT YOUR OWN RISKS !
  10. *
  11. * Changes added by Rizwan Kassim <rizwank@geekymedia.com> for OSX functionality
  12. * default serial device for osx devices is /dev/tty.serial for machines with a built in serial device
  13. *
  14. * @author R???my Sanchez <thenux@gmail.com>
  15. * @thanks Aur???lien Derouineau for finding how to open serial ports with windows
  16. * @thanks Alec Avedisyan for help and testing with reading
  17. * @thanks Jim Wright for OSX cleanup/fixes.
  18. * @copyright under GPL 2 licence
  19. */
  20. class phpSerial
  21. {
  22. var $_device = null;
  23. var $_windevice = null;
  24. var $_dHandle = null;
  25. var $_dState = SERIAL_DEVICE_NOTSET;
  26. var $_buffer = "";
  27. var $_os = "";
  28. /**
  29. * This var says if buffer should be flushed by sendMessage (true) or manualy (false)
  30. *
  31. * @var bool
  32. */
  33. var $autoflush = true;
  34. /**
  35. * Constructor. Perform some checks about the OS and setserial
  36. *
  37. * @return phpSerial
  38. */
  39. function phpSerial ()
  40. {
  41. setlocale(LC_ALL, "en_US");
  42. $sysname = php_uname();
  43. if (substr($sysname, 0, 5) === "Linux")
  44. {
  45. $this->_os = "linux";
  46. if($this->_exec("stty --version") === 0)
  47. {
  48. register_shutdown_function(array($this, "deviceClose"));
  49. }
  50. else
  51. {
  52. trigger_error("No stty availible, unable to run.", E_USER_ERROR);
  53. }
  54. }
  55. elseif (substr($sysname, 0, 6) === "Darwin")
  56. {
  57. $this->_os = "osx";
  58. // We know stty is available in Darwin.
  59. // stty returns 1 when run from php, because "stty: stdin isn't a
  60. // terminal"
  61. // skip this check
  62. // if($this->_exec("stty") === 0)
  63. // {
  64. register_shutdown_function(array($this, "deviceClose"));
  65. // }
  66. // else
  67. // {
  68. // trigger_error("No stty availible, unable to run.", E_USER_ERROR);
  69. // }
  70. }
  71. elseif(substr($sysname, 0, 7) === "Windows")
  72. {
  73. $this->_os = "windows";
  74. register_shutdown_function(array($this, "deviceClose"));
  75. }
  76. else
  77. {
  78. trigger_error("Host OS is neither osx, linux nor windows, unable to run.", E_USER_ERROR);
  79. exit();
  80. }
  81. }
  82. //
  83. // OPEN/CLOSE DEVICE SECTION -- {START}
  84. //
  85. /**
  86. * Device set function : used to set the device name/address.
  87. * -> linux : use the device address, like /dev/ttyS0
  88. * -> osx : use the device address, like /dev/tty.serial
  89. * -> windows : use the COMxx device name, like COM1 (can also be used
  90. * with linux)
  91. *
  92. * @param string $device the name of the device to be used
  93. * @return bool
  94. */
  95. function deviceSet ($device)
  96. {
  97. if ($this->_dState !== SERIAL_DEVICE_OPENED)
  98. {
  99. if ($this->_os === "linux")
  100. {
  101. if (preg_match("@^COM(\d+):?$@i", $device, $matches))
  102. {
  103. $device = "/dev/ttyS" . ($matches[1] - 1);
  104. }
  105. if ($this->_exec("stty -F " . $device) === 0)
  106. {
  107. $this->_device = $device;
  108. $this->_dState = SERIAL_DEVICE_SET;
  109. return true;
  110. }
  111. }
  112. elseif ($this->_os === "osx")
  113. {
  114. if ($this->_exec("stty -f " . $device) === 0)
  115. {
  116. $this->_device = $device;
  117. $this->_dState = SERIAL_DEVICE_SET;
  118. return true;
  119. }
  120. }
  121. elseif ($this->_os === "windows")
  122. {
  123. if (preg_match("@^COM(\d+):?$@i", $device, $matches) and $this->_exec(exec("mode " . $device . " xon=on BAUD=9600")) === 0)
  124. {
  125. $this->_windevice = "COM" . $matches[1];
  126. $this->_device = "\\.\com" . $matches[1];
  127. $this->_dState = SERIAL_DEVICE_SET;
  128. return true;
  129. }
  130. }
  131. trigger_error("Specified serial port is not valid", E_USER_WARNING);
  132. return false;
  133. }
  134. else
  135. {
  136. trigger_error("You must close your device before to set an other one", E_USER_WARNING);
  137. return false;
  138. }
  139. }
  140. /**
  141. * Opens the device for reading and/or writing.
  142. *
  143. * @param string $mode Opening mode : same parameter as fopen()
  144. * @return bool
  145. */
  146. function deviceOpen ($mode = "r+b")
  147. {
  148. if ($this->_dState === SERIAL_DEVICE_OPENED)
  149. {
  150. trigger_error("The device is already opened", E_USER_NOTICE);
  151. return true;
  152. }
  153. if ($this->_dState === SERIAL_DEVICE_NOTSET)
  154. {
  155. trigger_error("The device must be set before to be open", E_USER_WARNING);
  156. return false;
  157. }
  158. if (!preg_match("@^[raw]\+?b?$@", $mode))
  159. {
  160. trigger_error("Invalid opening mode : ".$mode.". Use fopen() modes.", E_USER_WARNING);
  161. return false;
  162. }
  163. $this->_dHandle = fopen($this->_device, $mode);
  164. if ($this->_dHandle !== false)
  165. {
  166. stream_set_blocking($this->_dHandle, 0);
  167. $this->_dState = SERIAL_DEVICE_OPENED;
  168. return true;
  169. }
  170. $this->_dHandle = null;
  171. trigger_error("Unable to open the device", E_USER_WARNING);
  172. return false;
  173. }
  174. /**
  175. * Closes the device
  176. *
  177. * @return bool
  178. */
  179. function deviceClose ()
  180. {
  181. if ($this->_dState !== SERIAL_DEVICE_OPENED)
  182. {
  183. return true;
  184. }
  185. if (fclose($this->_dHandle))
  186. {
  187. $this->_dHandle = null;
  188. $this->_dState = SERIAL_DEVICE_SET;
  189. return true;
  190. }
  191. trigger_error("Unable to close the device", E_USER_ERROR);
  192. return false;
  193. }
  194. //
  195. // OPEN/CLOSE DEVICE SECTION -- {STOP}
  196. //
  197. //
  198. // CONFIGURE SECTION -- {START}
  199. //
  200. /**
  201. * Configure the Baud Rate
  202. * Possible rates : 110, 150, 300, 600, 1200, 2400, 4800, 9600, 38400,
  203. * 57600 and 115200.
  204. *
  205. * @param int $rate the rate to set the port in
  206. * @return bool
  207. */
  208. function confBaudRate ($rate)
  209. {
  210. if ($this->_dState !== SERIAL_DEVICE_SET)
  211. {
  212. trigger_error("Unable to set the baud rate : the device is either not set or opened", E_USER_WARNING);
  213. return false;
  214. }
  215. $validBauds = array (
  216. 110 => 11,
  217. 150 => 15,
  218. 300 => 30,
  219. 600 => 60,
  220. 1200 => 12,
  221. 2400 => 24,
  222. 4800 => 48,
  223. 9600 => 96,
  224. 19200 => 19,
  225. 38400 => 38400,
  226. 57600 => 57600,
  227. 115200 => 115200
  228. );
  229. if (isset($validBauds[$rate]))
  230. {
  231. if ($this->_os === "linux")
  232. {
  233. $ret = $this->_exec("stty -F " . $this->_device . " " . (int) $rate, $out);
  234. }
  235. if ($this->_os === "osx")
  236. {
  237. $ret = $this->_exec("stty -f " . $this->_device . " " . (int) $rate, $out);
  238. }
  239. elseif ($this->_os === "windows")
  240. {
  241. $ret = $this->_exec("mode " . $this->_windevice . " BAUD=" . $validBauds[$rate], $out);
  242. }
  243. else return false;
  244. if ($ret !== 0)
  245. {
  246. trigger_error ("Unable to set baud rate: " . $out[1], E_USER_WARNING);
  247. return false;
  248. }
  249. }
  250. }
  251. /**
  252. * Configure parity.
  253. * Modes : odd, even, none
  254. *
  255. * @param string $parity one of the modes
  256. * @return bool
  257. */
  258. function confParity ($parity)
  259. {
  260. if ($this->_dState !== SERIAL_DEVICE_SET)
  261. {
  262. trigger_error("Unable to set parity : the device is either not set or opened", E_USER_WARNING);
  263. return false;
  264. }
  265. $args = array(
  266. "none" => "-parenb",
  267. "odd" => "parenb parodd",
  268. "even" => "parenb -parodd",
  269. );
  270. if (!isset($args[$parity]))
  271. {
  272. trigger_error("Parity mode not supported", E_USER_WARNING);
  273. return false;
  274. }
  275. if ($this->_os === "linux")
  276. {
  277. $ret = $this->_exec("stty -F " . $this->_device . " " . $args[$parity], $out);
  278. }
  279. elseif ($this->_os === "osx")
  280. {
  281. $ret = $this->_exec("stty -f " . $this->_device . " " . $args[$parity], $out);
  282. }
  283. else
  284. {
  285. $ret = $this->_exec("mode " . $this->_windevice . " PARITY=" . $parity{0}, $out);
  286. }
  287. if ($ret === 0)
  288. {
  289. return true;
  290. }
  291. trigger_error("Unable to set parity : " . $out[1], E_USER_WARNING);
  292. return false;
  293. }
  294. /**
  295. * Sets the length of a character.
  296. *
  297. * @param int $int length of a character (5 <= length <= 8)
  298. * @return bool
  299. */
  300. function confCharacterLength ($int)
  301. {
  302. if ($this->_dState !== SERIAL_DEVICE_SET)
  303. {
  304. trigger_error("Unable to set length of a character : the device is either not set or opened", E_USER_WARNING);
  305. return false;
  306. }
  307. $int = (int) $int;
  308. if ($int < 5) $int = 5;
  309. elseif ($int > 8) $int = 8;
  310. if ($this->_os === "linux")
  311. {
  312. $ret = $this->_exec("stty -F " . $this->_device . " cs" . $int, $out);
  313. }
  314. elseif ($this->_os === "osx")
  315. {
  316. $ret = $this->_exec("stty -f " . $this->_device . " cs" . $int, $out);
  317. }
  318. else
  319. {
  320. $ret = $this->_exec("mode " . $this->_windevice . " DATA=" . $int, $out);
  321. }
  322. if ($ret === 0)
  323. {
  324. return true;
  325. }
  326. trigger_error("Unable to set character length : " .$out[1], E_USER_WARNING);
  327. return false;
  328. }
  329. /**
  330. * Sets the length of stop bits.
  331. *
  332. * @param float $length the length of a stop bit. It must be either 1,
  333. * 1.5 or 2. 1.5 is not supported under linux and on some computers.
  334. * @return bool
  335. */
  336. function confStopBits ($length)
  337. {
  338. if ($this->_dState !== SERIAL_DEVICE_SET)
  339. {
  340. trigger_error("Unable to set the length of a stop bit : the device is either not set or opened", E_USER_WARNING);
  341. return false;
  342. }
  343. if ($length != 1 and $length != 2 and $length != 1.5 and !($length == 1.5 and $this->_os === "linux"))
  344. {
  345. trigger_error("Specified stop bit length is invalid", E_USER_WARNING);
  346. return false;
  347. }
  348. if ($this->_os === "linux")
  349. {
  350. $ret = $this->_exec("stty -F " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);
  351. }
  352. elseif ($this->_os === "osx")
  353. {
  354. $ret = $this->_exec("stty -f " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);
  355. }
  356. else
  357. {
  358. $ret = $this->_exec("mode " . $this->_windevice . " STOP=" . $length, $out);
  359. }
  360. if ($ret === 0)
  361. {
  362. return true;
  363. }
  364. trigger_error("Unable to set stop bit length : " . $out[1], E_USER_WARNING);
  365. return false;
  366. }
  367. /**
  368. * Configures the flow control
  369. *
  370. * @param string $mode Set the flow control mode. Availible modes :
  371. * -> "none" : no flow control
  372. * -> "rts/cts" : use RTS/CTS handshaking
  373. * -> "xon/xoff" : use XON/XOFF protocol
  374. * @return bool
  375. */
  376. function confFlowControl ($mode)
  377. {
  378. if ($this->_dState !== SERIAL_DEVICE_SET)
  379. {
  380. trigger_error("Unable to set flow control mode : the device is either not set or opened", E_USER_WARNING);
  381. return false;
  382. }
  383. $linuxModes = array(
  384. "none" => "clocal -crtscts -ixon -ixoff",
  385. "rts/cts" => "-clocal crtscts -ixon -ixoff",
  386. "xon/xoff" => "-clocal -crtscts ixon ixoff"
  387. );
  388. $windowsModes = array(
  389. "none" => "xon=off octs=off rts=on",
  390. "rts/cts" => "xon=off octs=on rts=hs",
  391. "xon/xoff" => "xon=on octs=off rts=on",
  392. );
  393. if ($mode !== "none" and $mode !== "rts/cts" and $mode !== "xon/xoff") {
  394. trigger_error("Invalid flow control mode specified", E_USER_ERROR);
  395. return false;
  396. }
  397. if ($this->_os === "linux")
  398. $ret = $this->_exec("stty -F " . $this->_device . " " . $linuxModes[$mode], $out);
  399. elseif ($this->_os === "osx")
  400. $ret = $this->_exec("stty -f " . $this->_device . " " . $linuxModes[$mode], $out);
  401. else
  402. $ret = $this->_exec("mode " . $this->_windevice . " " . $windowsModes[$mode], $out);
  403. if ($ret === 0) return true;
  404. else {
  405. trigger_error("Unable to set flow control : " . $out[1], E_USER_ERROR);
  406. return false;
  407. }
  408. }
  409. /**
  410. * Sets a setserial parameter (cf man setserial)
  411. * NO MORE USEFUL !
  412. * -> No longer supported
  413. * -> Only use it if you need it
  414. *
  415. * @param string $param parameter name
  416. * @param string $arg parameter value
  417. * @return bool
  418. */
  419. function setSetserialFlag ($param, $arg = "")
  420. {
  421. if (!$this->_ckOpened()) return false;
  422. $return = exec ("setserial " . $this->_device . " " . $param . " " . $arg . " 2>&1");
  423. if ($return{0} === "I")
  424. {
  425. trigger_error("setserial: Invalid flag", E_USER_WARNING);
  426. return false;
  427. }
  428. elseif ($return{0} === "/")
  429. {
  430. trigger_error("setserial: Error with device file", E_USER_WARNING);
  431. return false;
  432. }
  433. else
  434. {
  435. return true;
  436. }
  437. }
  438. //
  439. // CONFIGURE SECTION -- {STOP}
  440. //
  441. //
  442. // I/O SECTION -- {START}
  443. //
  444. /**
  445. * Sends a string to the device
  446. *
  447. * @param string $str string to be sent to the device
  448. * @param float $waitForReply time to wait for the reply (in seconds)
  449. */
  450. function sendMessage ($str, $waitForReply = 0.1)
  451. {
  452. $this->_buffer .= $str;
  453. if ($this->autoflush === true) $this->serialflush();
  454. usleep((int) ($waitForReply * 1000000));
  455. }
  456. /**
  457. * Reads the port until no new datas are availible, then return the content.
  458. *
  459. * @pararm int $count number of characters to be read (will stop before
  460. * if less characters are in the buffer)
  461. * @return string
  462. */
  463. function readPort ($count = 0)
  464. {
  465. if ($this->_dState !== SERIAL_DEVICE_OPENED)
  466. {
  467. trigger_error("Device must be opened to read it", E_USER_WARNING);
  468. return false;
  469. }
  470. if ($this->_os === "linux" || $this->_os === "osx")
  471. {
  472. // Behavior in OSX isn't to wait for new data to recover, but just grabs what's there!
  473. // Doesn't always work perfectly for me in OSX
  474. $content = ""; $i = 0;
  475. if ($count !== 0)
  476. {
  477. do {
  478. if ($i > $count) $content .= fread($this->_dHandle, ($count - $i));
  479. else $content .= fread($this->_dHandle, 128);
  480. } while (($i += 128) === strlen($content));
  481. }
  482. else
  483. {
  484. do {
  485. $content .= fread($this->_dHandle, 128);
  486. } while (($i += 128) === strlen($content));
  487. }
  488. return $content;
  489. }
  490. elseif ($this->_os === "windows")
  491. {
  492. // Windows port reading procedures still buggy
  493. $content = ""; $i = 0;
  494. if ($count !== 0)
  495. {
  496. do {
  497. if ($i > $count) $content .= fread($this->_dHandle, ($count - $i));
  498. else $content .= fread($this->_dHandle, 128);
  499. } while (($i += 128) === strlen($content));
  500. }
  501. else
  502. {
  503. do {
  504. $content .= fread($this->_dHandle, 128);
  505. } while (($i += 128) === strlen($content));
  506. }
  507. return $content;
  508. }
  509. return false;
  510. }
  511. /**
  512. * Flushes the output buffer
  513. * Renamed from flush for osx compat. issues
  514. *
  515. * @return bool
  516. */
  517. function serialflush ()
  518. {
  519. if (!$this->_ckOpened()) return false;
  520. if (fwrite($this->_dHandle, $this->_buffer) !== false)
  521. {
  522. $this->_buffer = "";
  523. return true;
  524. }
  525. else
  526. {
  527. $this->_buffer = "";
  528. trigger_error("Error while sending message", E_USER_WARNING);
  529. return false;
  530. }
  531. }
  532. //
  533. // I/O SECTION -- {STOP}
  534. //
  535. //
  536. // INTERNAL TOOLKIT -- {START}
  537. //
  538. function _ckOpened()
  539. {
  540. if ($this->_dState !== SERIAL_DEVICE_OPENED)
  541. {
  542. trigger_error("Device must be opened", E_USER_WARNING);
  543. return false;
  544. }
  545. return true;
  546. }
  547. function _ckClosed()
  548. {
  549. if ($this->_dState !== SERIAL_DEVICE_CLOSED)
  550. {
  551. trigger_error("Device must be closed", E_USER_WARNING);
  552. return false;
  553. }
  554. return true;
  555. }
  556. function _exec($cmd, &$out = null)
  557. {
  558. $desc = array(
  559. 1 => array("pipe", "w"),
  560. 2 => array("pipe", "w")
  561. );
  562. $proc = proc_open($cmd, $desc, $pipes);
  563. $ret = stream_get_contents($pipes[1]);
  564. $err = stream_get_contents($pipes[2]);
  565. fclose($pipes[1]);
  566. fclose($pipes[2]);
  567. $retVal = proc_close($proc);
  568. if (func_num_args() == 2) $out = array($ret, $err);
  569. return $retVal;
  570. }
  571. //
  572. // INTERNAL TOOLKIT -- {STOP}
  573. //
  574. }
  575. ?>