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