2024 π Daylatest newsbuy art
Here we are now at the middle of the fourth large part of this talk.Pepe Deluxeget nowheremore quotes
very clickable
data + munging

The Perl Journal

Volumes 1–6 (1996–2002)

Code tarballs available for issues 1–21.

I reformatted the CD-ROM contents. Some things may still be a little wonky — oh, why hello there <FONT> tag. Syntax highlighting is iffy. Please report any glaring issues.

The Perl Journal
#13
Spring 1999
vol 4
num 1
Perl News
What's new in the Perl community.
Five Quick Hacks: Downloading Web Pages
Grabbing web page contents without a browser.
The Perl Scripts Archive
The CPAN now stores scripts as well as modules.
Web Databases the Genome Project Way
A free database system tailored for the web.
XML
The successor to HTML.
Bricolage: Memoization
Speeding up Perl functions.
Review: The O'Reilly Perl Resource Kit
Localizing Your Perl Programs
Adapting programs for languages other than English.
Building Your Own Perl for Win32
How to compile and install your own Perl from the source code.
The rezrov Infocom Game Interpreter
A maximally portable game engine, with a few back doors.
Perl and MIDI: Simple Languages, Easy Music
How a little language for generating music was created.
Perl Heresies: Building Objects Out Of Arrays
Most people build objects out of hashes. Here's why you shouldn't.
Controlling Modems with Win32::SerialPort
Let your programs talk to any serial device.
Contest: The Solitaire 500
Speed matters. How fast can your program play a simple card game?
TPJ One Liners
Bill Birthsiel (1999) Controlling Modems with Win32::SerialPort. The Perl Journal, vol 4(1), issue #13, Spring 1999.

Controlling Modems with Win32::SerialPort

Let your programs talk to any serial device.

Bill Birthsiel


Modules Mentioned

Win32::SerialPort           CPAN

Win32API::CommPort   CPAN

Win32::API    CPAN, https://www.divinf.it/DADA/PERL/

Serial ports are one of the oldest black boxes in computing: data goes in, data goes out, and few people understand how, even though they've been around much longer than PCs and haven't changed much over the years. They're still the most common way to connect computers to their peripherals, or even computers to one another. This article describes how to use the Win32::SerialPort module to talk to devices like modems from your Win32 Perl programs.

Before communicating, serial ports have to agree about a few things. Sometimes the agreement is pre-arranged; for instance, operating systems typically know how to talk to many types of mice. But you've probably seen something like 9600,8N1 describing a modem configuration. Those are the serial characteristics that must be agreed upon: baud rate, data bits, parity, and stop bits.

Let's look at what that 9600,8N1 means. At all times, a serial line either has a voltage difference across it (an "on" bit) or no voltage difference (an "off" bit). The 9600 defines the time between bits: 1/9600 second. The 8 is the number of bits per character. ASCII requires only seven bits, but most connections these days find a use for that eighth bit. An additional bit, the parity bit, is used as a simplistic checksum for the others; here, the N means that there is no parity bit. Finally, the 1 is the stop bit used to separate the end of one character from the beginning of the next.

I'll assume you've installed at least version 0.13 of the SerialPort module and the other prerequisite modules: Win32API::CommPort (currently bundled with SerialPort) and Aldo Calpini's Win32::API. SerialPort includes the high-level user interface, while CommPort provides low-level details, object creation, and other building blocks.

In this article, I'll show you eight examples that send data through a serial port. The first four will work on any serial device; the second four are modem-specific. The examples use COM2, meaning the second serial port.

Example 1

#! perl -w

use strict;
use Win32::SerialPort;

my $ob = Win32::SerialPort->new ('COM2') || die;

my $baud = $ob->baudrate;
my $parity = $ob->parity;
my $data = $ob->databits;
my $stop = $ob->stopbits;
my $hshake = $ob->handshake;

print "B = $baud, D = $data, S = $stop, P = $parity, 
            H = $hshake\n";

undef $ob; 

This creates a SerialPort object and prints the five most common parameters. We don't set the parameters, and we don't send any data. These are the same five settings you see when you select File->Parameters->Configure in HyperTerminal. This example works as long as your serial port was left in a sane state by whatever used it last; if you have a problem (occasionally a PC modem will claim that the baud rate is 0), open and close the port with HyperTerminal.

Note that my examples turn on all of Perl's sanity checking (-w and use strict). This is strongly recommended for all SerialPort programs, because little bugs that would be mere irritations in other programs can confuse your hardware. Nothing will crash your system or cause any sort of permanent damage, but debugging will be tough, so it's best to take these precautions.

If you look at the Parameters display in HyperTerminal, you'll see that each setting has a menu of permitted values. You can get the same information in Perl by calling these methods in list context. The output below was generated by adding lines like these to the first example:

Example 2

my @data_opt = $ob->parity; # list context

print "\nData Bit Options: ";
foreach $a (@data_opt) { print " $a"; } 

The complete Example 2 is available on the TPJ website; it's a subset of the options.plx program included in the distribution. The resulting output will look like this:

Data Bit Options:   8 5 6 7

Stop Bit Options:   1 2 1.5

Handshake Options:  none rts dtr xoff

Parity Options:     none mark space even odd

Baudrate Options:   110 1200 300 2400 600 14400
                    56000 19200 4800 38400 9600

Current Settings:   B = 9600, D = 8, S = 1, 
                    P = none, H = rts 

Baud rates like 28800 and 33600 are actually synthesized by the modem hardware and so don't appear in the list. Unfortunately, SerialPort only supports speeds in the list at present.

Initializing Your Modem

Creating a SerialPort object opens the port, but doesn't initialize it for data transfer. We're ready to initialize it now.

SerialPort defers changes during initialization until the write_settings() method is invoked. Then it validates your selections, and verifies that baudrate, data bits, parity, and stop bits have been set. Setting the handshake is strongly recommended, but not required. We can complete the initialization by adding the following:

Example 3

$ob->baudrate($baud) 	|| die "fail setting baud";
$ob->parity($parity) 	|| die "fail setting parity";
$ob->databits($data) 	|| die "fail setting databits";
$ob->stopbits($stop) 	|| die "fail setting stopbits";
$ob->handshake($hshake)	|| die "fail setting handshake";

$ob->write_settings 	|| die "no settings"; 

The complete Example 3 does even more checking than what you see above. Handily, you don't have to go through all that checking every time you want to initialize a serial port. Once you have a functioning setup, the save() method generates a configuration file that specifies the parameters and their values. You can use this configuration file to duplicate the settings in a subsequent script. So on to Example 4, which creates our configuration for the rest of this article.

Example 4

#! perl -w

use strict;
use Win32::SerialPort;

my $ob = Win32::SerialPort->new ('COM2') || die;

$ob->user_msg(1); 	# misc. warnings
$ob->error_msg(1); 	# hardware and data errors

$ob->baudrate(38400);
$ob->parity("none");
$ob->parity_enable(1); 	# for any parity except "none"
$ob->databits(8);
$ob->stopbits(1);
$ob->handshake('rts');

$ob->write_settings;
$ob->save("tpj4.cfg");

print "wrote configuration file tpj4.cfg\n";

undef $ob; 

The user_msg() method enables messages intended for the user, like "Waiting for CTS", and the error_msg() method enables error messages like "Framing Error."

An error during write_settings() closes the port but leaves your program alive; the module is designed so that error recovery is preferred over failure.

The save() method checks whether write_settings() succeeded, and then generates a file with a complete collection of settings--many more than the five parameters you see here.

Once we have tpj4.cfg, the configuration file created by Example 4, we can use it with a method named start() that we call instead of the new() in the examples above. start() reads the file, validates the file format, sets all the parameters, and performs the write_settings() for you. Bingo, instant serial port!

There is a similar restart() method that re-initializes an already open port, a bit like stty sane in Unix. Example 5 below requires a modem that understands the Hayes command set--most do. Try running it twice, using HyperTerminal to change the baud rate between trials. The start() will change it back to 38400.

Example 5

#! perl -w

use strict;
use Win32::SerialPort 0.11;

my $ob = Win32::SerialPort->start ("tpj4.cfg") || die;

my $baud = $ob->baudrate;
print "baud from configuration: $baud\n";

$ob->write("ATE0X4\r");
sleep 1;
my $result = $ob->input; 
print "result = $result\n";

$ob->write("AT&V\r");
sleep 2;
$result = $ob->input;
print "result = $result\n";

undef $ob; 

The exact results will vary from modem to modem; this example is intended only to show data transfer. The input() method gets all of the characters currently pending (which in real applications may be limited by the size of internal buffers--see the module documentation for details). The write() method does just what you expect. We will revisit this example later at another speed.

Veteran Win32 Perl users will see the sleep statements and mumble about the lack of alarm on their platform. A pleasant surprise: SerialPort supports time limits much like alarm. Although most Win32 user code offers no easy "abort-and-do-this-on-timeout" function, the serial driver supported by the Serial API runs as a kernel service, and can therefore interrupt system calls. It also supports both blocking and non-blocking I/O with asynchronous calls--background I/O based on a start/done_yet?/complete model that appears almost as through the operation is running in a separate thread. (This mechanism is at the heart of Win32 multitasking. Since most applications are based on an "event loop" model, it was essential that a slow or failed call not disrupt the loop. The Windows 3.x serial driver had notorious defects and was commonly replaced in its entirety by application vendors, often creating additional bugs and incompatibilities. The Win32 serial drivers and API, by contrast, are full-featured, high-performance entities that permit applications access to a wider range of OS services than any substitute could easily provide.)

Now an example to clarify the usage. After we send an ATE0X4 to our modem, we expect to see an OK\r in return, but we might get more than that if E1 was previously set. So we ask for more characters than we need (20) and use read_interval()to return 100 milliseconds after the final character. We don't want that timeout for the AT&V command, so we issue a read_interval(0)to disable it. We'll read 40 characters, and then 4000 more, having set a timeout of 5 seconds for both reads. The second read should fail--I haven't seen any modems that verbose.

Example 6

#! perl -w

use strict;
use Win32::SerialPort;

my $ob = Win32::SerialPort->start ("tpj4.cfg") || die;

$ob->write("ATE0X4\r");
$ob->read_interval(100);
my ($count, $result) = $ob->read(20);
print "count = $count, result = $result\n";

$ob->read_interval(0);
$ob->read_const_time(5000);
$ob->write("AT&V\r");
($count, $result) = $ob->read(40);
print "count = $count, result = $result\n";
($count, $result) = $ob->read(4000);
print "count = $count, result = $result\n";

undef $ob; 

Getting Your Modem To Dial

Let's get our modem to dial a telephone number. There is no one telephone number that everyone can use; replace 555-1234 with whatever number you like.

Example 7 is long, so we'll just hit the high points. The important work is in the waitfor() subroutine, which accepts a time (in seconds) and calls the built-in lookfor() method. waitfor() then returns all of the input through whatever string matched so you can see the results.

The are_match() method lets you specify an array of strings that will satisfy lookfor(). Here, we stuff it with all the responses we might receive from our modem: CONNECT, OK, and so on.

Run Example 7 with the phone line disconnected, and if you can, try it again with a BUSY number. You could dial an actual number and CONNECT, but the example does not include any follow-up dialog.

Example 7

#! perl -w

use strict;
use Win32::SerialPort 0.13;

my $ob;
my $file = "tpj4.cfg";

sub waitfor {
    my $timeout = Win32::GetTickCount() + (1000 * shift);
    $ob->lookclear; # clear buffers
    my $gotit = "";

    for (;;) {
        return unless (defined ($gotit = $ob->lookfor));
        if ($gotit ne "") {
            my ($found, $end) = $ob->lastlook;
            return $gotit.$found;
        }
        return if ($ob->reset_error);
        return if (Win32::GetTickCount() > $timeout);
    }
}

$ob = Win32::SerialPort->start ($file) or die 
      "Can't start $file\n";

$ob->error_msg(1); # use built-in error messages
$ob->user_msg(1);

$ob->are_match("BUSY","CONNECT",
                  "OK","NO DIALTONE",
                  "ERROR","RING",
                  "NO CARRIER","NO ANSWER");

$ob->write("ATE0X4\r");
# Wait one second for a response
printf "%s\n", waitfor(1); 

print "\nStarting Dial\n";
# Use a different number!
$ob->write("ATDT5551234\r"); 
printf "%s\n", waitfor(20);

print "\n5 seconds to failure..\n";
waitfor(5) || print "Timed Out\n";

undef $ob; 

Example 7 could be extended to handle any device with well-defined responses--say, a pager. But managing completely interactive dialogs is tough; don't expect to have your programs dial up your ISP and chat with your friends. However, if you're intrigued, consult the demo5.plx example bundled with the module. Starting with Version 0.13, the module provides many of the functions of both stty and Expect, including echo control, line-ending conversion, backspace/backspace-echo, separate pre_match, match, and post_match strings, and multiple match patterns. Expanding this into a "mini-shell" is on my TODO list.

To Block Or Not To Block?

The examples so far are written as if the serial I/O operations were blocking--that is, as though you couldn't do anything else until the operation finished. Actually, input() is non-blocking. It returns an empty string when nothing has been received. read() and write()block, but each has a non-blocking alternative. These I/O operations are constructed from low-level primitive methods in Win32API::CommPort, which can be set to block or not.

Function

Primitives

read($count)

write($out)

input

status

read_bg($count), read_done($block)

write_bg($out), write_done($block)

status, read_bg($waiting_count)

is_status

Unless you want to perform your own bitmask processing and error handling, use status() instead of is_status(). It handles user_msg() and error_msg() output, returning an array containing the number of characters in the input and output buffers of the serial driver. Hence, input() issues only a read_bg() when something is there to read. Example 8 is a slow version of Example 5 using non-blocking I/O.

Example 8

#! perl -w

use strict;
use Win32::SerialPort 0.11;

my $ob = SerialPort->start ("tpj4.cfg") || die;

my $baud = $ob->baudrate(1200);
print "baud for background demo: $baud\n";

$ob->read_interval(0);
$ob->read_const_time(10000);

$ob->write("ATE0X4\r");
sleep 1;
my $result = $ob->input;
print "result = $result\n";

$ob->write("AT&V\r");
print "Starting 500 character background read\n";
my $in = $ob->read_bg(500);
my $done = 0;
my $blk;
my $err;
my $out;

for (;;) {
    ($blk, $in, $out, $err) = $ob->status;
    print "got $in characters so far..\n";
    sleep 1;
    ($done, $in, $result) = $ob->read_done(0);
    last if $done;
}

print "got = $in\nresult = $result\n";

$baud = $ob->baudrate(38400);
sleep 2;
$result = $ob->input;
print "\n\n....And now the rest = \n$result\n";

undef $ob;

This example doesn't do much, but if you want to implement some serial protocol, you could prepare the next transmission at the same time as you send the current buffer. You can read and write at the same time, because read_done() and write_done() need not block; they can be used in a continuous loop construct such as those found in control systems (or in Perl/Tk).

What's Next?

Many modems include fax capabilities, and a basic fax capability could be built on top of a CommPort object. It might not use much of SerialPort, since fax characteristics are more rigidly defined. Win32 includes an extensive interface called TAPI for managing telephony issues such as incoming calls, line selection, voice interface, modem-specific extensions, and so on.

I haven't tested SerialPort on ports within an NT Service, but I don't foresee any problems. I also plan to add a simple file-transfer demo (xmodem) to the distribution. And the readline() method will be expanded to allow a virtual login from the console that can be accessed as a limited command line from an external serial device. A mini-HyperTerminal (interactive dialout) with scripting capability is possible as well. Contributions of ideas, examples, and code are welcome. Any TAPI wizards out there?

With methods like read() and write(), SerialPort objects are similar to Perl filehandles. But they're not--you can't use <>, or print, for instance. A tied filehandle interface is included in Version 0.14, now available on the CPAN.


Bill Birthsiel has been making computers work in manufacturing for over 25 years. He holds all the jobs from owner/ to Chief Engineer to janitor for Birthsiel Engineering of Clinton, Wisconsin.


References

Systems Programming for Windows 95, Walter Oney, Microsoft Press, 1996.

Communications Programming for Windows 95, Charles A. Mirho and Andre Terrisse, Microsoft Press, 1996.

Martin Krzywinski | contact | Canada's Michael Smith Genome Sciences CentreBC Cancer Research CenterBC CancerPHSA
Google whack “vicissitudinal corporealization”
{ 10.9.234.152 }