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.

 $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: , ,

19 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 0x04 ? 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)) == 0x04)
    {
    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 🙂

  13. Javier Perez Says:

    Hi:
    In the example given you block the serial port but the function get_string, have a loop waiting for a not null string. Is necessary to block given that function?
    Another question: I’m using Linux Mint distribution and compiled the last version of dio. When I use fopen, always get false. There are some permissions to modify that you know?

  14. cyberspice Says:

    You may need to add your user to serial users group using adduser or you can change the permissions on the /dev/ttyS* (or /dev/ttyUSB*) node to 666.

  15. LREARTH Says:

    Hi, Thank you in advance.
    I am trying to connect weighing machine with PHP code. I have tested with hyperterminal it is working fine.
    But when I tried with PHP code till fopen or dio_open it executed after that when run fgets or dio_read, it hanged not display anything.

    Please check the below code –
    exec(‘mode com2: BAUD=2400 PARITY=N data=8 stop=1 xon=on’);
    $fp = fopen(“COM2:”, “r”);
    if (!$fp) {
    echo “Uh-oh. Port not opened.”;
    } else {
    echo fgets($fp);
    fclose($fp);
    }

    DIO Code –
    exec(‘mode COM2: baud=2400 data=8 stop=1 parity=n xon=off to=on’);
    $fd = dio_open(‘COM2:’, O_RDONLY | O_NONBLOCK, 0644);
    echo dio_read($fd, 256);

    I tried both ways, but not able to get output.

    I have few doubts –
    Do I require inpout32.dll file to connect?
    And what is role of php_iol.dll, this file I also required?

    I tried all the options, Please help me out to resolve this issue.

  16. cyberspice Says:

    Let’s address some of the issues here.

    1) fopen() in php takes a string comprising a protocol and a device. If you omit the protocol it thinks its just a file. I.e. fopen(“:COM1”, “r”) will try and open a file called “:COM1” and not know its a serial device.

    You must prepend “dio.serial://” to any path to a device. So for windows this will be “dio.serial://COM1” for Linux it will typically be something like “dio.serial::///dev/ttyS0” or “dio.serial::///dev/ttyUSB0” and on my Mac its similar as “dio.serial:///dev/tty.ZTEUSBATPort_” (the //dev/tty.ZTEUSBATPort_ bit is the device).

    Basically the PHP file system infra-structure has to know its a serial device so that the correct handling occurs within PHP.

    2) It is not a good idea to configure the serial port external to DIO for the same reasons as above. So do not exec ‘mode’ or a tty utility. You should use the file context as I have above. E.g.

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

    3) The old school C like dio functionality is being deprecated. It is highly advisable it is not used for serial ports. It can be used for linux devices which are not currently supported within PHP.

  17. Kai Holthaus Says:

    Hi –

    First, thanks for your work on this extension. I’m using it to communicate with a whole house audio controller with a serial interface. For that, I found out after a long trial and error that I definitely do not want canonical mode – more specifically, I do need “-icanon” set on the device (in my case /dev/ttyUSB0 – using Ubuntu).
    When I use these settings to open the device in PHP:
    $c = stream_context_create(array(‘dio’ => array(
    ‘data_rate’ => 38400,
    ‘data_bits’ => 8,
    ‘stop_bits’ => 1,
    ‘parity’ => 0,
    ‘flow_control’ => 0,
    ‘is_blocking’ => 0,
    ‘canonical’ => 0))
    );
    I see some strange behavior with data showing up in “clumps” – never all the data returned by the audio controller at once, but typically, I have to submit another query to the controller to get the remaining data.
    On a hunch, I called “stty -F /dev/ttyUSB0 -a” on the device, and found this:
    speed 38400 baud; rows 0; columns 0; line = 0;
    intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S;
    susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
    -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
    -ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
    -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    -isig icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

    Notice the “icanon” (I’m somewhat naive about serial device programming – but I expected this to be “-icanon” if I specify “canonical” => 0). So, against your advice in the previous post, I did submit a “stty -F /dev/ttyUSB0 -icanon” to change the icanon setting on the device, and now the communication works as expected – a query to the controller produces the response I’m looking for.

    Two questions…
    1) Should I have expected “-icanon” on the device after specifying “canonical” => 0?
    and if not,
    2) Is there a way within PHP/DIO to force the “-icanon” setting without having to resort to using stty? Using the stty separately didn’t seem to produce any ill-effects, so that will be my fallback…

    Thanks for any info!

  18. cyberspice Says:

    Hi,

    I’ll check there’s not a bug. Setting it to 0 or false should turn off canonical mode. This is odd.

    Mel.

  19. Kai Holthaus Says:

    Thanks! – In the meantime, I found that my device I’m trying to talk to also needs “-icrnl” set. Again, not knowing the intricacies of serial communication programming, I’m wondering if there should be a better way of setting this (besides calling stty after DIO has configured the device)?

Leave a Reply