Software and People

I have recently had the nostalgic pleasure of writing some software in C, running under DOS) to read from a serial port. After the first stages of tinkering with it I began to really miss the power of Test Driven Development. Now I'm sure I'm addicted!

I thought for a while of trying to write some sort of test harness to run my code on the target machine, bu this has several problems. The major problem is simply the time it would take. I can't afford to spend a week or so building/testing a test harness when the code really needs to be delivered by then. I have looked for JUnit equivalents in C/C++ before, and never found any that were at all portable, so that chance of getting someone else's test framework running on this slice of history seems very slim. Other problems include my general unfamiliarity with the (very neat and quick, but different) OpenWatcom development environment and compiler I'm using, and the non-OO nature of C (which would imply quite a different architecture to JUnit and chums.)

However, a few days ago I had a flash of inspiration. It happened because I suddenly realized that when I run thid DOS .COM executable on my Windows 2000 machine, it actually runs it in a resource constrained "dos box", not a real take-over-the-whole-machine DOS installation at all. So I'm able to run my software on this machine, and run other, "full fat" software at the same time. I'd already bought a "nullmodem" cross-connected serial cable and tried the software on a remote machine using HyperTerminal. The next step was to connect the nullmodem lead between the two com ports on the back of the development PC and try running the DOS box and HyperTerminal on the same machine at the same time. Flushed with this success, I then did a quick hack to the DOS software to recognize a command-line parameter to switch from the default "receive" mode to a more active "transmit mode. I set them both running and happily watched my sequence of characters wander along the serial lead at a stately 1200 baud from one DOS box to another. Now, I was really making progress.

Comfortable that I could drive my software from the same machine over a cable, I now set out to set things up using a powerful test framework that I am very familiar with - JUnit. Java (and thus JUnit) already has the features to test things lke the existence and content of files, so that part of the project will be easy. What Java does not have "out of the box" is the ability to write and read serial ports. I vaguely recalled hearing of a Java "comms" API, and few minutes searching at Sun found it. I downloaded the Windows version, unzipped it and got started.

Or at least, I tried to get started. Maybe I've had a sheltered life, but I found the javax.comm API one of the strangest Java installs I've had to do in recent years:

  • The installation documents assume you are using Java 1.1 (yikes!). Admittedly, there is an addendum about using with the "new" beta version of Java 1.2, but it's not immediately obvious that that is the place to look for the real installation instructions.

  • As well as adding comm.jar to the class path, you also need to put a .dll file in JAVA_HOME/jre/bin (not JAVA_HOME/bin, as suggested in the main install docs), and add a supplied properties file to JAVA_HOME/jre/lib. If you don't do the .dll and properties files right (for example, by following the obvious installation instructions) the code compiles and appears to work, but just thinks the machine has no ports.

  • The supplied example code seems to have been written by someone just learning Java. It's very poor, and doesn't even take advantage of the features of the comm API it's supposed to be demonstrating. For example, the API provides a neat callback mechanism to notify your code when events happen (such as when an output buffer is empty, indicating that the supplied data has been sent). The serial writing example enables this feature, but then never registers a listener and instead hard-codes a two second wait to "be sure data is xferred before closing". Yuck. The code also commits the cardinal sin of catching exceptions and just "eating" them in an empty catch block. This is inexcusible in a code example which people will use to check if the system has been installed correctly.

  • I could find no API documentation on the web site, just a confusing maze of links which all eventually end up back at the same, (largely useless) page. I did find some API docs in the download, but they were generated using the old JavaDoc, without frames and font/layout tidyups, so they are very clumsy to navigate.

Despite all this, I did eventually get things going. To aid my unit testing I put together a simple serial port writing helper class. It only does the things I need right now, and I can see that it might hit problems If I try to run too many tests in too short a time (because of the asynchronous nature of port open and close), but it does allow me to drive my DOS software from JUnit.

package tests;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.TooManyListenersException;

import javax.comm.CommPortIdentifier;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
import javax.comm.UnsupportedCommOperationException;

class CloseWhenEmptyListener implements SerialPortEventListener
{
  private SerialPort port;

  public CloseWhenEmptyListener(SerialPort port)
  {
    this.port = port;
  }
  
  public void serialEvent(SerialPortEvent event)
  {
    if (event.getEventType() == SerialPortEvent.OUTPUT_BUFFER_EMPTY)
    {
      port.close();
    }
  }
}

public class CommPort
{
  private int baud;
  private CommPortIdentifier port;

  private static List findSerialPorts()
  {
    List ports = null;
    
    if (ports == null)
    {
      ports = new ArrayList();

      Enumeration portList = CommPortIdentifier.getPortIdentifiers();

      while (portList.hasMoreElements()) 
      {
        CommPortIdentifier port = (CommPortIdentifier) portList.nextElement();

        if (port.getPortType() == CommPortIdentifier.PORT_SERIAL) 
        {
          ports.add(port);
        }
      }
    }
    
    return ports;
  }
  
  public static CommPortIdentifier findPort(String name)
  {
    CommPortIdentifier ret = null;
     
    List ports = findSerialPorts();
    
    Iterator it = ports.iterator();
    while (it.hasNext())
    {
      CommPortIdentifier port = (CommPortIdentifier)it.next();
      if (name.equalsIgnoreCase(port.getName()))
      {
        ret = port;
        break;
      }
    }
    
    return ret;
  }
  
  public CommPort(String name, int baud)
  {
    this.port = findPort(name);
    this.baud = baud;
  }
  
  public void write(String text)
    throws IOException, PortInUseException,
    UnsupportedCommOperationException, TooManyListenersException
  {
    SerialPort serialPort = (SerialPort) port.open("CommPort", 2000);
    serialPort.setSerialPortParams(baud, 
               SerialPort.DATABITS_8, 
               SerialPort.STOPBITS_1, 
               SerialPort.PARITY_NONE);
    OutputStream outputStream = serialPort.getOutputStream();

    serialPort.addEventListener(new CloseWhenEmptyListener(serialPort));
    serialPort.notifyOnOutputEmpty(true);
        
    outputStream.write(text.getBytes());
  }
}

It's really a shame, isn't it? javax.comm seems to be abandonware. Plenty of people have use for it, but it's not available on all platforms, nor it is documented or maintained.

I hear you, Frank. The wonders of crappy quality...

I just realized that exploring C-JDBC, which is supposed to be "1.0 release candidate 4", is practically a worthless effort as the darn thing is throwing NullPointerExceptions at me more often than SCO sues competitors... Oh, and did I mention that I also found one of those nifty little exception-swallowers from the class responsible for parsing the configuration file which politely prevented me from knowing that the P.O.S. has a "null recovery log" because it can't find a suitable JDBC driver.

I already sent the development team a friendly note about the empty catch-block but after some two hours of dodging those NPE's and staring at cryptic error messages, I'm not in the mood of helping out even that much...

...sorry about using your blog for ranting :)

Sun's is a toy implementation. See www.SerialIo.com for a real one ($50). I've been a satisfied user for years.
"If you don't do the .dll and properties files right (for example, by following the obvious installation instructions)..." That was inspired. Great writing. Thanks for the page, I finally got javax.comm running today because of your notes. Progress. I could have written half a dozen apps using c and outb in the time I wasted trying to get javax.comm running. And the sample code is an impossibly bad joke. Were they kidding?
i m a beginner in this field and after reading the above comments i don't feel confident enough to use javax.comm.SerialPort in my application . If there's any alternative to this , please place it on ur website bye............. rdk
Sun's javax.comm could certainly be better supported, but is nonetheless quite useable.

Here are some options for different platforms:

Sun javax.comm Windows/Solaris
IBM javax.comm Linux x86, Alpha, etc
RXTX ( www.rxtx.org ) free serial extension under the GPL. This is the only one I know to work with Java 1.5 ( "5.0" ) Supports Windows, Linux, Unix, and Mac OS X.

Docs for Sun and IBM javax.comm:

http://java.sun.com/products/javacomm/downloads/index.html

BTW
I followed your instructions and I still can't get it to work. Can you assist me. I copied the comm.jar file into c:\java\jdk1.5.0\jre\lib and set the classpath to c:\java\jdk1.5.0\jre\lib\comm.jar. This isn't mention in your notes but it is mentioned in the readme file. Do I need to install JUnit to get it to work. It complies, but the sample codes don't work.
Where can I download the Windows version? Sun seems to have discontinued it.
TrackBack to http://radio.javaranch.com/frank/addTrackBack.action?entry=1079951422000