AlphaSignXMLApp.java
package dfranklin.bbxml;

// See http://java.sun.com/products/javacomm/reference/api/index.html
// for gnu.io API. It is the same as javax.comm.
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

/**
 * Command line application for controlling an Alpha LED sign.
 
 * See the BBXML User's Guide at http://pobox.com/~dfranklin/bbxml for
 * for more information about creating the XML command files.
 */
public class AlphaSignXMLApp
{
   private static final int EOT = 0x04;
   private static boolean readFromPort = false;
   private static boolean useProto1 = false;
   private static String serialPortName;
   private static String xslFileName;
   private static String xmlFileName;
   private SerialPort serialPort;
   private int ioTimeoutMs = 5000;


   /**
    * Command line args: -port port_name -xsl alphasign.xsl -xml cmds.xml [
    * -read ] [ -proto1 ]
    */
   public static void main(String[] args)
      throws Exception
   {
      AlphaSignXMLApp app = new AlphaSignXMLApp();
      int i = 0;
      try
      {
         for (i = 0; i < args.length; i++)
         {
            if ("-port".equals(args[i]))
            {
               serialPortName = args[++i];
            }
            else if ("-xsl".equals(args[i]))
            {
               xslFileName = args[++i];
            }
            else if ("-xml".equals(args[i]))
            {
               xmlFileName = args[++i];
            }
            else if ("-read".equals(args[i]))
            {
               readFromPort = true;
            }
            else if ("-proto1".equals(args[i]))
            {
               useProto1 = true;
            }
         }
      }
      catch (ArrayIndexOutOfBoundsException e)
      {
         System.err.println("Arg requires an additional parameter: "
            + args[--i]);
         System.exit(1);
      }
      app.run();
   }


   public void run()
      throws Exception
   {
      openPort();
      try
      {
         configurePort();
         // create a buffer for the XSLT output
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         transform(out);
         byte[] buf = out.toByteArray();
         if (useProto1buf = convert3to1(buf);
         System.out.println("writing...");
         // write the buffer to the serial port
         writeToSerialPort(new ByteArrayInputStream(buf));
         if (readFromPort)
         {
            System.out.println("reading...");
            buf = readSerialPort();
            System.out.println("output (" + buf.length + " bytes):");
            if (useProto1buf = convert1to3(buf);
            String reply = new String(buf);
            System.out.println(reply);
         }
      }
      finally
      {
         closePort();
      }
   }


   /**
    * Transform the XML file using the XSL file, writing result to the specified
    * OutputStream.
    */
   public void transform(OutputStream out)
      throws IOException, TransformerException
   {
      Source xslSource =
         new StreamSource(new FileInputStream(xslFileName), xslFileName);
      TransformerFactory f = TransformerFactory.newInstance();
      Templates xsl = f.newTemplates(xslSource);
      Source xmlSource =
         new StreamSource(new FileInputStream(xmlFileName), xmlFileName);
      Result result = new StreamResult(out);
      xsl.newTransformer().transform(xmlSource, result);
   }


   private void openPort()
      throws NoSuchPortException, PortInUseException,
      UnsupportedCommOperationException
   {
      CommPortIdentifier portID =
         CommPortIdentifier.getPortIdentifier(serialPortName);
      serialPort = (SerialPortportID.open(getClass().getName(), ioTimeoutMs);
   }


   private void configurePort()
      throws UnsupportedCommOperationException
   {
      serialPort.setSerialPortParams(9600,
                                     SerialPort.DATABITS_7,
                                     SerialPort.STOPBITS_2,
                                     SerialPort.PARITY_EVEN);
      serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
   }


   private void closePort()
   {
      serialPort.close();
   }


   /**
    * Read from InputStream and write to the serial port.
    */
   public void writeToSerialPort(InputStream in)
      throws IOException
   {
      OutputStream out = serialPort.getOutputStream();
      int b;
      while (-!= (b = in.read()))
      {
         out.write(b);
      }
   }


   public byte[] readSerialPort()
      throws IOException, UnsupportedCommOperationException
   {

      serialPort.enableReceiveTimeout(ioTimeoutMs);
      InputStream in = serialPort.getInputStream();
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int b;
      while (-!= (b = in.read()))
      {
         b &= 0x7F// strip hi bit
         if (b == 0continue// skip the leading 0x00's
         out.write((byteb)// (there are never 0x00's in data)
         if (b == EOTbreak// EOT. We're done.
      }
      return out.toByteArray();
   }


   /**
    * Convert 3-byte protocol message to 1-byte.
    */
   public static byte[] convert3to1(byte[] msg)
   {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      for (int i = 0; i < msg.length; i++)
      {
         if (msg[i== '_')
         {
            out.write(Byte.parseByte(new String(msg, ++i, 2)16));
            i++;
         }
         else
         {
            out.write(msg[i]);
         }
      }
      return out.toByteArray();
   }


   /**
    * Convert 1-byte protocol to 3-byte format.
    */
   public static byte[] convert1to3(byte[] msg)
   {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      for (int i = 0; i < msg.length; i++)
      {
         byte b = msg[i];
         if (b < 0x20)
         {
            out.write(("_" + hexString(b)).getBytes()03);
         }
         else
         {
            out.write(b);
         }
      }
      return out.toByteArray();
   }


   private static String hexString(byte b)
   {
      String s = Integer.toHexString(b).toUpperCase();
      if (b < 0x10s = "0" + s;
      return s;
   }
}