/src/PhpSerial.php

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