Serial IO in PHP using the DIO extension

DIO is the Direct IO extension for PHP. I recently took over maintaining this extension and have implemented comprehensive stream support for both POSIX and Windows systems. To demonstrate the use of DIO this post will describe a PHP script that sends an SMS using a USB 3G modem.

To make control of modems easier over twenty years ago the Hayes Corporation developed a serial command protocol, the Hayes or AT command set. Many other modems adopted the protocol and now its a de-facto standard. Over the years as modem functionality expanded so did the command set. Now it supports FAX, cell phone address books, SMSes and so on. The AT command set comprises a set of text based commands terminated with CRLF. The responses are similarly text strings terminated with CRLF. Typically the modem responds with “OK”, “ERROR” or a setting value.

DIO currently provides two stream types raw and serial. Raw streams provide low level access to operating system devices. Serial streams provide access to, and abstract, serial ports. DIO stream support is accessed just like any other PHP streams. The appropriate URI is fopened. The relevant URIs are:

dio.raw://[device]

for raw streams and

dio.serial://[device]

for serials streams. In both cases [device] is the device name to open. For example this could be /dev/random for the random device on Linux; or /dev/ttyS0 for a comms port on Linux; or COM1 for a comms port on Windows. We’ll be using the later form of the URI as we’re opening a serial port.

The example code opens the serial port as follows:

$f = fopen("dio.serial:///dev/tty.ZTEUSBATPort_", "r+", false, $c);

The first argument is the device to open. In this case a serial port to the USB 3G modem connected to my MacBook. The second is the open mode, in this case read/write. The third is an optional argument that comprises a flag that indicates whether to search the include path for the file to open. We don’t want this. The final optional argument is a stream context which is a collection of stream specific additional parameters.

A stream context is created using stream_context_create(). The code in the example that creates the context, prior to opening the port, is shown below.

$c = stream_context_create(array('dio' => 
	array('data_rate' => 9600, 
		  'data_bits' => 8, 
		  'stop_bits' => 1, 
		  'parity' => 0, 
		  'flow_control' => 0,
		  'is_blocking' => 0,
		  'canonical' => 1)));

The first four context options are pretty obvious. The serial port is being configured for 9600 bits per second, 8 bits per byte with 1 additional stop bit per byte and no parity checking.

The next option disables hardware flow control. Serial ports are relatively slow so they often have hardware control lines which indicate when there is data ready to be sent and space in the buffer ready to receive data. My USB modem does not have these so the hardware flow control is disabled.

Next we turn off blocking. With blocking function calls the operating system waits for data and only returns when it is ready to return the data that was requested i.e. the operating system blocks on the data. Non-blocking function calls return whether there is data or not. The POSIX open call also blocks on the state of the Data Carrier Detect (“DCD”) hardware control line. Some modems have this control line and use it to indicate there’s a carrier signal on the telephone line. Again since the USB modem does not have this we disable it.

Finally we enable canonical mode. Canonical IO on POSIX means that the serial port driver will wait for a line of text ending in a newline (or the internal buffer being full) before returning. If less characters than were entered on the line were requested the driver buffers the data for subsequent reads until the entire line is returned. In canonical mode you can actually connect a terminal to the serial port as line editing is supported. Since Windows does not have a canonical mode DIO provides an implementation on top of the Windows driver. The alternative to canonical mode is byte mode where by DIO waits until the requested number of bytes have been read and returns regardless of whether there are more bytes available.

If all that sounds a little complicated think of it like this. If you’re reading strings terminated with a new line use canonical mode otherwise use byte mode.

Remember that blocking was disabled so that opening the serial device did not block. This isn’t desirable when actually reading data. Blocking mode can be re-enabled on an open stream using stream_set_blocking as follows:

stream_set_blocking($f, true);

Once the serial port has been opened with the appropriate port settings and blocking re-enabled the port can be written and read using the standard fprintf and fgets PHP functions.

There are two AT commands related to sending an SMS. The first is “AT+CMGF=1″ which tells the modem to operate in SMS text mode. This should respond with “OK”. The second is “AT+CMGS=” which is followed by a string comprising a telephone number. This accepts the text of an SMS which is sent when CTRL-Z is entered.

There’s a little bit of additional work in the example as the modem echos data sent to it back to the sender so the PHP code consumes data after a command is sent until it sees the sent command echoed back. The full example follows.

<?php 
 
define('PHONE_NUMBER', '5551234');
define('SMS_TEXT', 'Hello world!');
 
/**
 * Sends data to the modem
 */
function send_data($f, $string) {
	echo "< $string\n";
	fprintf($f, "$string\r\n");
}
 
/**
 * Reads lines from the serial port.  It reads until a non blank line is
 * received then returns it.
 */
function get_string($f) {
	do {
		// remove carriage returns, line feeds and excess white space
		$response = trim(str_replace(array("\r\n","\r","\n"), '', fgets($f)));
	} while ($response == '');
 
	return $response;
}
 
/**
 * Waits for OK to be sent back by the modem.
 */
function wait_for_ok($f) {
	do {
		$response = get_string($f);
		echo "> $response\n";
	} while ($response != "OK");
}
 
// MAIN CODE
 
// Create the context
$c = stream_context_create(array('dio' => 
	array('data_rate' => 9600, 
		  'data_bits' => 8, 
		  'stop_bits' => 1, 
		  'parity' => 0, 
		  'flow_control' => 0,
		  'is_blocking' => 0,
		  'canonical' => 1)));
 
// Open the port
$f = fopen("dio.serial:///dev/tty.ZTEUSBATPort_", "r+", false, $c);
 
if ($f) {
	// Re-enable blocking
	stream_set_blocking($f, true);
 
	// Set SMS text entry mode
	send_data($f, "AT+CMGF=1");
	wait_for_ok($f);
 
	// Send an SMS
	send_data($f, "AT+CMGS=\"" . PHONE_NUMBER . "\"");
	send_data($f, SMS_TEXT);
	// Send CTRL-Z to end SMS text entry
	fprintf($f, chr(26));	
	wait_for_ok($f);
 
	// Close the port
	fclose($f);
}
?>

Tags: , ,

12 Responses to “Serial IO in PHP using the DIO extension”

  1. dongzhigang Says:

    dio-0.0.4RC4 compiled with vc6 failed, unable to find zend_config.h …… asked about solutions,
    Or send a php_dio.dll for php5.3.1, thank you.
    Configure the environment:
    windows xp
    Apache2.2.14
    dio-0.0.4RC4
    PEAR-1.9.0
    php-5.3.1

  2. cyberspice Says:

    Hi dongzhigang,

    I’ll look in to it. I currently don’t have a copy of VC6 but I’ll see what I can do.

  3. Cliff Lan Says:

    Dear Sir,

    can you advise me why I can not read byte 0×04 ? when this byte arrives, the reading stopped. I was really puzzled.

    Cliff

    19200,
    ‘bits’ => 8,
    ‘stop’ => 1,
    ‘parity’ => 0
    ));

    $cnt=0;
    $f = null;
    while (1) {

    //if($f == null)$f = fopen(‘t’.time(),”w”);

    $data = dio_read($fd,1);
    print $data;
    if(ord(substr($data,0,1)) == 0×04)
    {
    print “EOT received\n”;
    dio_write($fd,chr(1).chr(2).chr(3).chr(4));
    }

    }

    dio_close($fd);
    ?>

  4. cyberspice Says:

    Hi Cliff, I think the code snippet you pasted has gotten truncated.

    However if you are using DIO 0.0.4 I think this may be because I changed the default behaviour (which I shouldn’t have had so I will fix it). You need to provide ‘is_canonical’ => 0 in your array of serial port settings. Character 4, i.e. ctrl-D, is end of file in canonical mode and so no further data will be received. You need to disable canonical mode in the settings.

    Melanie

  5. siddesh Says:

    hi

    i wanted to get the video stream from digital webcam attached to the usb port.
    i have the activXcomport but it not full version. so i m decided to use the dio_open but i m get error by this method………….

    please help me regarding this …..
    thank you in advance……

  6. Marcel Says:

    XAMMP 1.7.3 (WinXP)
    PHP 5.3.1 MSVC6 (Visual C++ 6.0)
    PHP EXTENSION BUILD: API20090626,TS,VC6

    Trying to install via PEAR, which fails and tells me to install via PECL.

    C:\xampp\php>pear install dio
    No releases available for package “pear.php.net/dio” – package pecl/dio can be installed with “pecl install dio”
    install failed

    Trying PECL install and fails as well:

    C:\xampp\php>pecl install dio
    WARNING: channel “pecl.php.net” has updated its protocols, use “pecl channel-update pecl.php.net” to update
    downloading dio-0.0.4RC4.tgz …
    Starting to download dio-0.0.4RC4.tgz (18,334 bytes)
    ……done: 18,334 bytes
    12 source files, building
    WARNING: php_bin \xampp\php\php.exe appears to have a suffix \php.exe, but config variable php_suffix does not match
    ERROR: The DSP dio.dsp does not exist.

    Tried all php_dio.dll that I could find, but all failed.

    Any idea how I can get DIO to work? Is there anybody with a compiled DLL that does work?

  7. Daniel Says:

    Hi!

    I’m trying to use DIO to send SMS using a GSM modem – this is what your test script outputs:

    AT+CMGF=1
    > OK
    < AT+CMGS="+49160xyz"
    AT+CMGS=”+49160xyz”
    > ERROR
    > Hello world!
    ^C

    Using minicom and issuing the AT-commands manually works as expected.

    I’m using ubuntu x64, php 5.3.3 and DIO 0.0.4RC4 (beta) from PECL repositories.

  8. Daniel Says:

    I think I solved the issue above – modem expects “\r\r” instead of “\r\n” after AT+CMGS command. Works fine now!

  9. manish Says:

    Hi All,
    I am trying to open a com port8/port10 using fopen() of PHP but i am getting an error “Warning: fopen(COM8:) [function.fopen]: failed to open stream” .
    can you please tell what is wrong in my code,

    $fp = fopen (“COM8:”, “w+”);
    if (!$fp) {
    echo ‘not open’;
    }
    else{
    echo ‘port is open for write’;
    $string .= ‘C30C10178C10100C103110606C103081000C10100C10101C100′;
    fputs ($fp, $string );
    echo $string;
    fclose ($fp);
    }
    $fp = fopen (“COM8:”, “r+”);
    if (!$fp) {
    echo ‘not open for read’;
    }
    else{
    echo ‘ port is open for read’;
    $buffer = fread($fp, 128 );
    echo $buffer;
    fclose ($fp);
    }

  10. Krisno W Utomo Says:

    i have compiled dll (0.0.4RC4) with PHP 5.3.x source and it works fine. you can download it at http://www.4shared.com/file/Fp7b4-2I/php_dio.html.

  11. naresh Says:

    OK < AT+CMGS="9851129982" ERROR
    why is this error??can uhelp me.Fatal error: Maximum execution time of 60 seconds exceeded in D:\xampp\htdocs\sms\gonzalo123-gam-sms-cb3cce4\SMS1.PHP on line 21

  12. DrSeMSeM Says:

    Hiii

    thanks for your code looking interested and direct using AT commands

    will i have a problem with this line

    $f = fopen(“dio.serial:///dev/tty.ZTEUSBATPort_”, “r+”, false, $c);

    i change this part
    “dio.serial:///dev/tty.ZTEUSBATPort_”
    to be
    “dio.serial:COM25.ZTEUSBATPort_”
    as my USB connected to COM25

    and i got this error

    Warning: fopen(dio.serial:COM25.ZTEUSBATPort_) [function.fopen]: failed to open stream: No such file or directory in C:\xampp\htdocs\sms\1\test.php on line 50

    so please tell me what is the suitable code to replace
    i’m using XAMPP apache server on windows 7
    my USB Modem Huawei K3565

    thanks for your help :)

Leave a Reply